--- a/.hgtags Fri Dec 10 12:17:18 2010 +0100
+++ b/.hgtags Fri Mar 11 09:46:45 2011 +0100
@@ -155,7 +155,29 @@
1c01f9dffd64d507863c9f8f68e3585b7aa24374 cubicweb-debian-version-3.9.7-1
eed788018b595d46a55805bd8d2054c401812b2b cubicweb-version-3.9.8
e4dba8ae963701a36be94ae58c790bc97ba029bb cubicweb-debian-version-3.9.8-1
+0793fe84651be36f8de9b4faba3781436dc07be0 cubicweb-version-3.10.0
+9ef1347f8d99e7daad290738ef93aa894a2c03ce cubicweb-debian-version-3.10.0-1
+6c6859a676732c845af69f92e74d4aafae12f83a cubicweb-version-3.10.1
+3abb41c47925f8fc6e327164d0ceca3773503ef9 cubicweb-debian-version-3.10.1-1
+3fc6b4aaaff301e482a92c61e39789621bd7ed3b cubicweb-version-3.10.2
+4a87c8af6f3ffe59c6048ebbdc1b6b204d0b9c7f cubicweb-debian-version-3.10.2-1
+8eb58d00a0cedcf7b275b1c7f43b08e2165f655c cubicweb-version-3.10.3
+303b150ebb7a92b2904efd52b446457999cab370 cubicweb-debian-version-3.10.3-1
+3829498510a754b1b8a40582cb8dcbca9145fc9d cubicweb-version-3.10.4
+49f1226f2fab6d9ff17eb27d5a66732a4e5b5add cubicweb-debian-version-3.10.4-1
df0b2de62cec10c84a2fff5233db05852cbffe93 cubicweb-version-3.9.9
1ba51b00fc44faa0d6d57448000aaa1fd5c6ab57 cubicweb-debian-version-3.9.9-1
b7db1f59355832a409d2032e19c84cfffdb3b265 cubicweb-debian-version-3.9.9-2
09c98763ae9d43616d047c1b25d82b4e41a4362f cubicweb-debian-version-3.9.9-3
+3829498510a754b1b8a40582cb8dcbca9145fc9d cubicweb-version-3.10.4
+d73733479a3af453f06b849ed88d120784ce9224 cubicweb-version-3.10.4
+49f1226f2fab6d9ff17eb27d5a66732a4e5b5add cubicweb-debian-version-3.10.4-1
+7b41930e1d32fea3989a85f6ea7281983300adb1 cubicweb-debian-version-3.10.4-1
+159d0dbe07d9eb1c6ace4c5e160d1ec6e6762086 cubicweb-version-3.10.5
+e2e7410e994777589aec218d31eef9ff8d893f92 cubicweb-debian-version-3.10.5-1
+3c81dbb58ac4d4a6f61b74eef4b943a8316c2f42 cubicweb-version-3.10.6
+1484257fe9aeb29d0210e635c12ae5b3d6118cfb cubicweb-debian-version-3.10.6-1
+1959d97ebf2e6a0f7cd05d4cc48bb955c4351da5 cubicweb-version-3.10.7
+bf5d9a1415e3c9abe6b68ba3b24a8ad741f9de3c cubicweb-debian-version-3.10.7-1
+e581a86a68f089946a98c966ebca7aee58a5718f cubicweb-version-3.10.8
+132b525de25bc75ed6389c45aee77e847cb3a437 cubicweb-debian-version-3.10.8-1
--- a/MANIFEST.in Fri Dec 10 12:17:18 2010 +0100
+++ b/MANIFEST.in Fri Mar 11 09:46:45 2011 +0100
@@ -21,7 +21,7 @@
recursive-include entities/test/data bootstrap_cubes *.py
recursive-include sobjects/test/data bootstrap_cubes *.py
recursive-include hooks/test/data bootstrap_cubes *.py
-recursive-include server/test/data bootstrap_cubes *.py source*
+recursive-include server/test/data bootstrap_cubes *.py source* *.conf.in *.ldif
recursive-include devtools/test/data bootstrap_cubes *.py *.txt *.js
recursive-include web/test/data bootstrap_cubes pouet.css *.py
--- a/__pkginfo__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/__pkginfo__.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,5 +1,5 @@
-# pylint: disable-msg=W0622,C0103
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# pylint: disable=W0622,C0103
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -22,7 +22,7 @@
modname = distname = "cubicweb"
-numversion = (3, 9, 9)
+numversion = (3, 10, 9)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
@@ -40,9 +40,9 @@
]
__depends__ = {
- 'logilab-common': '>= 0.51.0',
+ 'logilab-common': '>= 0.54.0',
'logilab-mtconverter': '>= 0.8.0',
- 'rql': '>= 0.26.2',
+ 'rql': '>= 0.28.0',
'yams': '>= 0.30.1',
'docutils': '>= 0.6',
#gettext # for xgettext, msgcat, etc...
@@ -52,7 +52,7 @@
'Twisted': '',
# XXX graphviz
# server dependencies
- 'logilab-database': '>= 1.3.2',
+ 'logilab-database': '>= 1.3.3',
'pysqlite': '>= 2.5.5', # XXX install pysqlite2
}
--- a/_exceptions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/_exceptions.py Fri Mar 11 09:46:45 2011 +0100
@@ -159,5 +159,5 @@
class ExecutionError(Exception):
"""server execution control error (already started, not running...)"""
-# pylint: disable-msg=W0611
+# pylint: disable=W0611
from logilab.common.clcommands import BadCommandUsage
--- a/appobject.py Fri Dec 10 12:17:18 2010 +0100
+++ b/appobject.py Fri Mar 11 09:46:45 2011 +0100
@@ -214,6 +214,9 @@
return NotImplementedError("selector %s must implement its logic "
"in its __call__ method" % self.__class__)
+ def __repr__(self):
+ return u'<Selector %s at %x>' % (self.__class__.__name__, id(self))
+
class MultiSelector(Selector):
"""base class for compound selector classes"""
--- a/common/mail.py Fri Dec 10 12:17:18 2010 +0100
+++ b/common/mail.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""pre 3.6 bw compat"""
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
from warnings import warn
warn('moved to cubicweb.mail', DeprecationWarning, stacklevel=2)
from cubicweb.mail import *
--- a/common/mixins.py Fri Dec 10 12:17:18 2010 +0100
+++ b/common/mixins.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""pre 3.6 bw compat"""
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
from warnings import warn
warn('moved to cubicweb.mixins', DeprecationWarning, stacklevel=2)
from cubicweb.mixins import *
--- a/common/mttransforms.py Fri Dec 10 12:17:18 2010 +0100
+++ b/common/mttransforms.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""pre 3.6 bw compat"""
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
from warnings import warn
warn('moved to cubicweb.mttransforms', DeprecationWarning, stacklevel=2)
from cubicweb.mttransforms import *
--- a/common/tags.py Fri Dec 10 12:17:18 2010 +0100
+++ b/common/tags.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""pre 3.6 bw compat"""
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
from warnings import warn
warn('moved to cubicweb.tags', DeprecationWarning, stacklevel=2)
from cubicweb.tags import *
--- a/common/uilib.py Fri Dec 10 12:17:18 2010 +0100
+++ b/common/uilib.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""pre 3.6 bw compat"""
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
from warnings import warn
warn('moved to cubicweb.uilib', DeprecationWarning, stacklevel=2)
from cubicweb.uilib import *
--- a/cwconfig.py Fri Dec 10 12:17:18 2010 +0100
+++ b/cwconfig.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,16 +22,11 @@
Resource mode
-------------
-A resource *mode* is a predifined set of settings for various resources
+A resource *mode* is a predefined set of settings for various resources
directories, such as cubes, instances, etc. to ease development with the
framework. There are two running modes with *CubicWeb*:
-* 'user', resources are searched / created in the user home directory:
-
- - instances are stored in :file:`~/etc/cubicweb.d`
- - temporary files (such as pid file) in :file:`/tmp`
-
-* 'system', resources are searched / created in the system directories (eg
+* **system**: resources are searched / created in the system directories (eg
usually requiring root access):
- instances are stored in :file:`<INSTALL_PREFIX>/etc/cubicweb.d`
@@ -40,28 +35,34 @@
where `<INSTALL_PREFIX>` is the detected installation prefix ('/usr/local' for
instance).
+* **user**: resources are searched / created in the user home directory:
+
+ - instances are stored in :file:`~/etc/cubicweb.d`
+ - temporary files (such as pid file) in :file:`/tmp`
+
+
Notice that each resource path may be explicitly set using an environment
variable if the default doesn't suit your needs. Here are the default resource
directories that are affected according to mode:
-* 'system': ::
+* **system**: ::
CW_INSTANCES_DIR = <INSTALL_PREFIX>/etc/cubicweb.d/
CW_INSTANCES_DATA_DIR = /var/lib/cubicweb/instances/
CW_RUNTIME_DIR = /var/run/cubicweb/
-* 'user': ::
+* **user**: ::
CW_INSTANCES_DIR = ~/etc/cubicweb.d/
CW_INSTANCES_DATA_DIR = ~/etc/cubicweb.d/
CW_RUNTIME_DIR = /tmp
-Cubes search path is also affected, see the :ref:Cube section.
+Cubes search path is also affected, see the :ref:`Cube` section.
-By default, the mode automatically set to 'user' if a :file:`.hg` directory is found
-in the cubicweb package, else it's set to 'system'. You can force this by setting
-the :envvar:`CW_MODE` environment variable to either 'user' or 'system' so you can
+By default, the mode automatically set to `user` if a :file:`.hg` directory is found
+in the cubicweb package, else it's set to `system`. You can force this by setting
+the :envvar:`CW_MODE` environment variable to either `user` or `system` so you can
easily:
* use system wide installation but user specific instances and all, without root
@@ -158,14 +159,6 @@
SMTP_LOCK = Lock()
-class metaconfiguration(type):
- """metaclass to automaticaly register configuration"""
- def __new__(mcs, name, bases, classdict):
- cls = super(metaconfiguration, mcs).__new__(mcs, name, bases, classdict)
- if classdict.get('name'):
- CONFIGURATIONS.append(cls)
- return cls
-
def configuration_cls(name):
"""return the configuration class registered with the given name"""
try:
@@ -289,7 +282,6 @@
class CubicWebNoAppConfiguration(ConfigurationMixIn):
"""base class for cubicweb configuration without a specific instance directory
"""
- __metaclass__ = metaconfiguration
# to set in concrete configuration
name = None
# log messages format (see logging module documentation for available keys)
@@ -316,6 +308,12 @@
'help': 'server\'s log level',
'group': 'main', 'level': 1,
}),
+ ('umask',
+ {'type' : 'int',
+ 'default': 077,
+ 'help': 'permission umask for files created by the server',
+ 'group': 'main', 'level': 2,
+ }),
# pyro options
('pyro-instance-id',
{'type' : 'string',
@@ -443,14 +441,15 @@
@classmethod
def cube_dir(cls, cube):
- """return the cube directory for the given cube id,
- raise `ConfigurationError` if it doesn't exists
+ """return the cube directory for the given cube id, raise
+ `ConfigurationError` if it doesn't exist
"""
for directory in cls.cubes_search_path():
cubedir = join(directory, cube)
if exists(cubedir):
return cubedir
- raise ConfigurationError('no cube %s in %s' % (cube, cls.cubes_search_path()))
+ raise ConfigurationError('no cube %r in %s' % (
+ cube, cls.cubes_search_path()))
@classmethod
def cube_migration_scripts_dir(cls, cube):
@@ -588,6 +587,14 @@
return # cubes dir doesn't exists
@classmethod
+ def load_available_configs(cls):
+ from logilab.common.modutils import load_module_from_file
+ for conffile in ('web/webconfig.py', 'etwist/twconfig.py',
+ 'server/serverconfig.py',):
+ if exists(join(CW_SOFTWARE_ROOT, conffile)):
+ load_module_from_file(join(CW_SOFTWARE_ROOT, conffile))
+
+ @classmethod
def load_cwctl_plugins(cls):
from logilab.common.modutils import load_module_from_file
cls.cls_adjust_sys_path()
@@ -598,8 +605,8 @@
try:
load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile))
except ImportError, err:
- cls.info('could not import the command provider %s (cause : %s)' %
- (ctlfile, err))
+ cls.error('could not import the command provider %s: %s',
+ ctlfile, err)
cls.info('loaded cubicweb-ctl plugin %s', ctlfile)
for cube in cls.available_cubes():
oldpluginfile = join(cls.cube_dir(cube), 'ecplugin.py')
@@ -688,6 +695,7 @@
def __init__(self, debugmode=False):
register_stored_procedures()
+ self._cubes = None
super(CubicWebNoAppConfiguration, self).__init__()
self.debugmode = debugmode
self.adjust_sys_path()
@@ -763,7 +771,7 @@
self.debug('%s loaded', sitefile)
return module
- def eproperty_definitions(self):
+ def cwproperty_definitions(self):
cfg = self.persistent_options_configuration()
for section, options in cfg.options_by_section():
section = section.lower()
@@ -791,6 +799,31 @@
"""
return None
+ _cubes = None
+
+ def init_cubes(self, cubes):
+ assert self._cubes is None, self._cubes
+ self._cubes = self.reorder_cubes(cubes)
+ # load cubes'__init__.py file first
+ for cube in cubes:
+ __import__('cubes.%s' % cube)
+ self.load_site_cubicweb()
+
+ def cubes(self):
+ """return the list of cubes used by this instance
+
+ result is ordered from the top level cubes to inner dependencies
+ cubes
+ """
+ assert self._cubes is not None, 'cubes not initialized'
+ return self._cubes
+
+ def cubes_path(self):
+ """return the list of path to cubes used by this instance, from outer
+ most to inner most cubes
+ """
+ return [self.cube_dir(p) for p in self.cubes()]
+
class CubicWebConfiguration(CubicWebNoAppConfiguration):
"""base class for cubicweb server and web configurations"""
@@ -870,6 +903,7 @@
def config_for(cls, appid, config=None, debugmode=False):
"""return a configuration instance for the given instance identifier
"""
+ cls.load_available_configs()
config = config or guess_configuration(cls.instance_home(appid))
configcls = configuration_cls(config)
return configcls(appid, debugmode)
@@ -984,33 +1018,13 @@
return join(iddir, self.appid)
def init_cubes(self, cubes):
- assert self._cubes is None, self._cubes
- self._cubes = self.reorder_cubes(cubes)
- # load cubes'__init__.py file first
- for cube in cubes:
- __import__('cubes.%s' % cube)
- self.load_site_cubicweb()
+ super(CubicWebConfiguration, self).init_cubes(cubes)
# reload config file in cases options are defined in cubes __init__
# or site_cubicweb files
self.load_file_configuration(self.main_config_file())
# configuration initialization hook
self.load_configuration()
- def cubes(self):
- """return the list of cubes used by this instance
-
- result is ordered from the top level cubes to inner dependencies
- cubes
- """
- assert self._cubes is not None
- return self._cubes
-
- def cubes_path(self):
- """return the list of path to cubes used by this instance, from outer
- most to inner most cubes
- """
- return [self.cube_dir(p) for p in self.cubes()]
-
def add_cubes(self, cubes):
"""add given cubes to the list of used cubes"""
if not isinstance(cubes, list):
@@ -1265,7 +1279,9 @@
stack[0] = self.source_execute
def as_sql(self, backend, args):
- raise NotImplementedError('source only callback')
+ raise NotImplementedError(
+ 'This callback is only available for BytesFileSystemStorage '
+ 'managed attribute. Is FSPATH() argument BFSS managed?')
def source_execute(self, source, session, value):
fpath = source.binary_to_str(value)
--- a/cwctl.py Fri Dec 10 12:17:18 2010 +0100
+++ b/cwctl.py Fri Mar 11 09:46:45 2011 +0100
@@ -42,11 +42,18 @@
from logilab.common.shellutils import ASK
from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
+from cubicweb.utils import support_args
from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
from cubicweb.toolsutils import Command, rm, create_dir, underline_title
from cubicweb.__pkginfo__ import version
-CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.', version=version)
+if support_args(CommandLine, 'check_duplicated_command'):
+ # don't check duplicated commands, it occurs when reloading site_cubicweb
+ CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.',
+ version=version, check_duplicated_command=False)
+else:
+ CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.',
+ version=version)
def wait_process_end(pid, maxtry=10, waittime=1):
"""wait for a process to actually die"""
@@ -235,9 +242,9 @@
tinfo = cwcfg.cube_pkginfo(cube)
tversion = tinfo.version
cfgpb.add_cube(cube, tversion)
- except ConfigurationError:
+ except (ConfigurationError, AttributeError), ex:
tinfo = None
- tversion = '[missing cube information]'
+ tversion = '[missing cube information: %s]' % ex
print '* %s %s' % (cube.ljust(namesize), tversion)
if self.config.verbose:
if tinfo:
@@ -494,7 +501,8 @@
msg = "%s seems to be running. Remove %s by hand if necessary or use \
the --force option."
raise ExecutionError(msg % (appid, pidf))
- helper.start_server(config)
+ if helper.start_server(config) == 1:
+ print 'instance %s started' % appid
def init_cmdline_log_threshold(config, loglevel):
@@ -656,10 +664,11 @@
name = 'upgrade'
actionverb = 'upgraded'
options = InstanceCommand.options + (
- ('force-componant-version',
- {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z',
+ ('force-cube-version',
+ {'short': 't', 'type' : 'named', 'metavar': 'cube1:X.Y.Z,cube2:X.Y.Z',
'default': None,
- 'help': 'force migration from the indicated version for the specified cube.'}),
+ 'help': 'force migration from the indicated version for the specified cube(s).'}),
+
('force-cubicweb-version',
{'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
'default': None,
@@ -713,12 +722,9 @@
mih = config.migration_handler()
repo = mih.repo_connect()
vcconf = repo.get_versions()
- if self.config.force_componant_version:
- packversions = {}
- for vdef in self.config.force_componant_version:
- componant, version = vdef.split('=')
- packversions[componant] = Version(version)
- vcconf.update(packversions)
+ if self.config.force_cube_version:
+ for cube, version in self.config.force_cube_version.iteritems():
+ vcconf[cube] = Version(version)
toupgrade = []
for cube in config.cubes():
installedversion = config.cube_version(cube)
--- a/cwvreg.py Fri Dec 10 12:17:18 2010 +0100
+++ b/cwvreg.py Fri Mar 11 09:46:45 2011 +0100
@@ -196,7 +196,7 @@
from warnings import warn
from logilab.common.decorators import cached, clear_cache
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import deprecated, class_deprecated
from logilab.common.modutils import cleanup_sys_modules
from rql import RQLHelper
@@ -290,13 +290,18 @@
class ETypeRegistry(CWRegistry):
+ def clear_caches(self):
+ clear_cache(self, 'etype_class')
+ clear_cache(self, 'parent_classes')
+ from cubicweb import selectors
+ selectors._reset_is_instance_cache(self.vreg)
+
def initialization_completed(self):
"""on registration completed, clear etype_class internal cache
"""
super(ETypeRegistry, self).initialization_completed()
# clear etype cache if you don't want to run into deep weirdness
- clear_cache(self, 'etype_class')
- clear_cache(self, 'parent_classes')
+ self.clear_caches()
def register(self, obj, **kwargs):
oid = kwargs.get('oid') or class_regid(obj)
@@ -389,6 +394,8 @@
for vid, views in self.items():
if vid[0] == '_':
continue
+ views = [view for view in views
+ if not isinstance(view, class_deprecated)]
try:
view = self._select_best(views, req, rset=rset, **kwargs)
if view.linkable():
@@ -421,6 +428,56 @@
VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry
+class CtxComponentsRegistry(CWRegistry):
+ def poss_visible_objects(self, *args, **kwargs):
+ """return an ordered list of possible components"""
+ context = kwargs.pop('context')
+ if '__cache' in kwargs:
+ cache = kwargs.pop('__cache')
+ elif kwargs.get('rset') is None:
+ cache = args[0]
+ else:
+ cache = kwargs['rset']
+ try:
+ cached = cache.__components_cache
+ except AttributeError:
+ ctxcomps = super(CtxComponentsRegistry, self).poss_visible_objects(
+ *args, **kwargs)
+ if cache is None:
+ components = []
+ for component in ctxcomps:
+ cctx = component.cw_propval('context')
+ if cctx == context:
+ component.cw_extra_kwargs['context'] = cctx
+ components.append(component)
+ return components
+ cached = cache.__components_cache = {}
+ for component in ctxcomps:
+ cctx = component.cw_propval('context')
+ component.cw_extra_kwargs['context'] = cctx
+ cached.setdefault(cctx, []).append(component)
+ thisctxcomps = cached.get(context, ())
+ # XXX set context for bw compat (should now be taken by comp.render())
+ for component in thisctxcomps:
+ component.cw_extra_kwargs['context'] = context
+ return thisctxcomps
+
+VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry
+
+
+class BwCompatCWRegistry(object):
+ def __init__(self, vreg, oldreg, redirecttoreg):
+ self.vreg = vreg
+ self.oldreg = oldreg
+ self.redirecto = redirecttoreg
+
+ def __getattr__(self, attr):
+ warn('[3.10] you should now use the %s registry instead of the %s registry'
+ % (self.redirecto, self.oldreg), DeprecationWarning, stacklevel=2)
+ return getattr(self.vreg[self.redirecto], attr)
+
+ def clear(self): pass
+ def initialization_completed(self): pass
class CubicWebVRegistry(VRegistry):
"""Central registry for the cubicweb instance, extending the generic
@@ -433,15 +490,23 @@
stored objects. Currently we have the following registries of objects known
by the web instance (library may use some others additional registries):
- * etypes
- * views
- * components
- * actions
- * forms
- * formrenderers
- * controllers, which are directly plugged into the application
- object to handle request publishing XXX to merge with views
- * contentnavigation XXX to merge with components? to kill?
+ * 'etypes', entity type classes
+
+ * 'views', views and templates (e.g. layout views)
+
+ * 'components', non contextual components, like magic search, url evaluators
+
+ * 'ctxcomponents', contextual components like boxes and dynamic section
+
+ * 'actions', contextual actions, eg links to display in predefined places in
+ the ui
+
+ * 'forms', describing logic of HTML form
+
+ * 'formrenderers', rendering forms to html
+
+ * 'controllers', primary objects to handle request publishing, directly
+ plugged into the application
"""
def __init__(self, config, initlog=True):
@@ -456,6 +521,8 @@
# don't clear rtags during test, this may cause breakage with
# manually imported appobject modules
CW_EVENT_MANAGER.bind('before-registry-reload', clear_rtag_objects)
+ self['boxes'] = BwCompatCWRegistry(self, 'boxes', 'ctxcomponents')
+ self['contentnavigation'] = BwCompatCWRegistry(self, 'contentnavigation', 'ctxcomponents')
def setdefault(self, regid):
try:
@@ -487,7 +554,7 @@
if not self.initialized:
self['propertydefs'] = {}
self['propertyvalues'] = self.eprop_values = {}
- for key, propdef in self.config.eproperty_definitions():
+ for key, propdef in self.config.cwproperty_definitions():
self.register_property(key, **propdef)
CW_EVENT_MANAGER.emit('after-registry-reset', self)
@@ -713,7 +780,7 @@
vocab = pdef['vocabulary']
if vocab is not None:
if callable(vocab):
- vocab = vocab(key, None) # XXX need a req object
+ vocab = vocab(None) # XXX need a req object
if not value in vocab:
raise ValueError(_('unauthorized value'))
return value
@@ -751,7 +818,7 @@
def possible_actions(self, req, rset=None, **kwargs):
return self["actions"].possible_actions(req, rest=rset, **kwargs)
- @deprecated('[3.4] use vreg["boxes"].select_object(...)')
+ @deprecated('[3.4] use vreg["ctxcomponents"].select_object(...)')
def select_box(self, oid, *args, **kwargs):
return self['boxes'].select_object(oid, *args, **kwargs)
--- a/dataimport.py Fri Dec 10 12:17:18 2010 +0100
+++ b/dataimport.py Fri Mar 11 09:46:45 2011 +0100
@@ -81,10 +81,11 @@
from logilab.common.deprecation import deprecated
from cubicweb.server.utils import eschema_eid
+from cubicweb.server.ssplanner import EditedEntity
def count_lines(stream_or_filename):
if isinstance(stream_or_filename, basestring):
- f = open(filename)
+ f = open(stream_or_filename)
else:
f = stream_or_filename
f.seek(0)
@@ -97,8 +98,8 @@
skipfirst=False, withpb=True):
"""same as ucsvreader but a progress bar is displayed as we iter on rows"""
if isinstance(stream_or_path, basestring):
- if not osp.exists(filepath):
- raise Exception("file doesn't exists: %s" % filepath)
+ if not osp.exists(stream_or_path):
+ raise Exception("file doesn't exists: %s" % stream_or_path)
stream = open(stream_or_path)
else:
stream = stream_or_path
@@ -305,11 +306,18 @@
self.items.append(item)
return len(self.items) - 1
- def add(self, type, item):
+ def create_entity(self, etype, **data):
+ data['eid'] = eid = self._put(etype, data)
+ self.eids[eid] = data
+ self.types.setdefault(etype, []).append(eid)
+ return data
+
+ @deprecated("[3.11] add is deprecated, use create_entity instead")
+ def add(self, etype, item):
assert isinstance(item, dict), 'item is not a dict but a %s' % type(item)
- eid = item['eid'] = self._put(type, item)
- self.eids[eid] = item
- self.types.setdefault(type, []).append(eid)
+ data = self.create_entity(etype, **item)
+ item['eid'] = data['eid']
+ return item
def relate(self, eid_from, rtype, eid_to, inlined=False):
"""Add new relation"""
@@ -331,6 +339,7 @@
def rql(self, *args):
if self._rql is not None:
return self._rql(*args)
+ return []
@property
def nb_inserted_entities(self):
@@ -420,7 +429,6 @@
ObjectStore.__init__(self)
if session is None:
sys.exit('please provide a session of run this script with cubicweb-ctl shell and pass cnx as session')
- session = cnx
if not hasattr(session, 'set_pool'):
# connection
cnx = session
@@ -453,15 +461,17 @@
return entity
def _put(self, type, item):
- query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
- for k in item)
+ query = 'INSERT %s X' % type
+ if item:
+ query += ': ' + ', '.join('X %s %%(%s)s' % (k, k)
+ for k in item)
return self.rql(query, item)[0][0]
def relate(self, eid_from, rtype, eid_to, inlined=False):
eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
eid_from, rtype, eid_to)
self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
- {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
+ {'x': int(eid_from), 'y': int(eid_to)})
# the import controller ########################################################
@@ -504,7 +514,6 @@
traceback.print_exc(file=tmp)
else:
traceback.print_exception(type, value, tb, file=tmp)
- print tmp.getvalue()
# use a list to avoid counting a <nb lines> errors instead of one
errorlog = self.errors.setdefault(key, [])
if msg is None:
@@ -612,8 +621,7 @@
entity = copy(entity)
entity.cw_clear_relation_cache()
self.metagen.init_entity(entity)
- entity.update(kwargs)
- entity.edited_attributes = set(entity)
+ entity.cw_edited.update(kwargs, skipsec=False)
session = self.session
self.source.add_entity(session, entity)
self.source.add_info(session, entity, self.source, None, complete=False)
@@ -651,6 +659,11 @@
class MetaGenerator(object):
+ META_RELATIONS = (META_RTYPES
+ - VIRTUAL_RTYPES
+ - set(('eid', 'cwuri',
+ 'is', 'is_instance_of', 'cw_source')))
+
def __init__(self, session, baseurl=None):
self.session = session
self.source = session.repo.system_source
@@ -669,25 +682,20 @@
#self.entity_rels = [] XXX not handled (YAGNI?)
schema = session.vreg.schema
rschema = schema.rschema
- for rtype in META_RTYPES:
- if rtype in ('eid', 'cwuri') or rtype in VIRTUAL_RTYPES:
- continue
+ for rtype in self.META_RELATIONS:
if rschema(rtype).final:
self.etype_attrs.append(rtype)
else:
self.etype_rels.append(rtype)
- if not schema._eid_index:
- # test schema loaded from the fs
- self.gen_is = self.test_gen_is
- self.gen_is_instance_of = self.test_gen_is_instanceof
@cached
def base_etype_dicts(self, etype):
entity = self.session.vreg['etypes'].etype_class(etype)(self.session)
# entity are "surface" copied, avoid shared dict between copies
del entity.cw_extra_kwargs
+ entity.cw_edited = EditedEntity(entity)
for attr in self.etype_attrs:
- entity[attr] = self.generate(entity, attr)
+ entity.cw_edited.edited_attribute(attr, self.generate(entity, attr))
rels = {}
for rel in self.etype_rels:
rels[rel] = self.generate(entity, rel)
@@ -696,7 +704,7 @@
def init_entity(self, entity):
entity.eid = self.source.create_eid(self.session)
for attr in self.entity_attrs:
- entity[attr] = self.generate(entity, attr)
+ entity.cw_edited.edited_attribute(attr, self.generate(entity, attr))
def generate(self, entity, rtype):
return getattr(self, 'gen_%s' % rtype)(entity)
@@ -709,26 +717,7 @@
def gen_modification_date(self, entity):
return self.time
- def gen_is(self, entity):
- return entity.e_schema.eid
- def gen_is_instance_of(self, entity):
- eids = []
- for etype in entity.e_schema.ancestors() + [entity.e_schema]:
- eids.append(entity.e_schema.eid)
- return eids
-
def gen_created_by(self, entity):
return self.session.user.eid
def gen_owned_by(self, entity):
return self.session.user.eid
-
- # implementations of gen_is / gen_is_instance_of to use during test where
- # schema has been loaded from the fs (hence entity type schema eids are not
- # known)
- def test_gen_is(self, entity):
- return eschema_eid(self.session, entity.e_schema)
- def test_gen_is_instanceof(self, entity):
- eids = []
- for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
- eids.append(eschema_eid(self.session, eschema))
- return eids
--- a/dbapi.py Fri Dec 10 12:17:18 2010 +0100
+++ b/dbapi.py Fri Mar 11 09:46:45 2011 +0100
@@ -48,6 +48,9 @@
except KeyError:
return ''
+def fake(*args, **kwargs):
+ return None
+
def multiple_connections_fix():
"""some monkey patching necessary when an application has to deal with
several connections to different repositories. It tries to hide buggy class
@@ -165,16 +168,20 @@
where it's already initialized.
:kwargs:
- there goes authentication tokens. You usually have to specify for
- instance a password for the given user, using a named 'password' argument.
+ there goes authentication tokens. You usually have to specify a password
+ for the given user, using a named 'password' argument.
"""
- config = cwconfig.CubicWebNoAppConfiguration()
- if host:
- config.global_set_option('pyro-ns-host', host)
- if group:
- config.global_set_option('pyro-ns-group', group)
cnxprops = cnxprops or ConnectionProperties()
method = cnxprops.cnxtype
+ if method == 'pyro':
+ config = cwconfig.CubicWebNoAppConfiguration()
+ if host:
+ config.global_set_option('pyro-ns-host', host)
+ if group:
+ config.global_set_option('pyro-ns-group', group)
+ else:
+ assert database
+ config = cwconfig.instance_configuration(database)
repo = get_repository(method, database, config=config)
if method == 'inmemory':
vreg = repo.vreg
@@ -194,21 +201,30 @@
cnx.vreg = vreg
return cnx
-def in_memory_cnx(config, login, **kwargs):
- """usefull method for testing and scripting to get a dbapi.Connection
- object connected to an in-memory repository instance
- """
+def in_memory_repo(config):
+ """Return and in_memory Repository object from a config (or vreg)"""
if isinstance(config, cwvreg.CubicWebVRegistry):
vreg = config
config = None
else:
vreg = None
# get local access to the repository
- repo = get_repository('inmemory', config=config, vreg=vreg)
- # connection to the CubicWeb repository
+ return get_repository('inmemory', config=config, vreg=vreg)
+
+def in_memory_cnx(repo, login, **kwargs):
+ """Establish a In memory connection to a <repo> for the user with <login>
+
+ additionel credential might be required"""
cnxprops = ConnectionProperties('inmemory')
- cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
- return repo, cnx
+ return repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
+
+def in_memory_repo_cnx(config, login, **kwargs):
+ """usefull method for testing and scripting to get a dbapi.Connection
+ object connected to an in-memory repository instance
+ """
+ # connection to the CubicWeb repository
+ repo = in_memory_repo(config)
+ return repo, in_memory_cnx(repo, login, **kwargs)
class _NeedAuthAccessMock(object):
def __getattribute__(self, attr):
@@ -313,19 +329,17 @@
# low level session data management #######################################
- def get_shared_data(self, key, default=None, pop=False):
- """return value associated to `key` in shared data"""
- return self.cnx.get_shared_data(key, default, pop)
-
- def set_shared_data(self, key, value, querydata=False):
- """set value associated to `key` in shared data
+ def get_shared_data(self, key, default=None, pop=False, txdata=False):
+ """see :meth:`Connection.get_shared_data`"""
+ return self.cnx.get_shared_data(key, default, pop, txdata)
- if `querydata` is true, the value will be added to the repository
- session's query data which are cleared on commit/rollback of the current
- transaction, and won't be available through the connexion, only on the
- repository side.
- """
- return self.cnx.set_shared_data(key, value, querydata)
+ def set_shared_data(self, key, value, txdata=False, querydata=None):
+ """see :meth:`Connection.set_shared_data`"""
+ if querydata is not None:
+ txdata = querydata
+ warn('[3.10] querydata argument has been renamed to txdata',
+ DeprecationWarning, stacklevel=2)
+ return self.cnx.set_shared_data(key, value, txdata)
# server session compat layer #############################################
@@ -482,6 +496,7 @@
self.sessionid = cnxid
self._close_on_del = getattr(cnxprops, 'close_on_del', True)
self._cnxtype = getattr(cnxprops, 'cnxtype', 'pyro')
+ self._web_request = False
if cnxprops and cnxprops.log_queries:
self.executed_queries = []
self.cursor_class = LogCursor
@@ -534,9 +549,8 @@
esubpath = list(subpath)
esubpath.remove('views')
esubpath.append(join('web', 'views'))
- cubespath = [config.cube_dir(p) for p in cubes]
- config.load_site_cubicweb(cubespath)
- vpath = config.build_vregistry_path(reversed(cubespath),
+ config.init_cubes(cubes)
+ vpath = config.build_vregistry_path(reversed(config.cubes_path()),
evobjpath=esubpath,
tvobjpath=subpath)
self.vreg.register_objects(vpath)
@@ -547,35 +561,33 @@
You should call `load_appobjects` at some point to register those views.
"""
- from cubicweb.web.request import CubicWebRequestBase as cwrb
- DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func
- DBAPIRequest.ajax_replace_url = cwrb.ajax_replace_url.im_func
- DBAPIRequest.list_form_param = cwrb.list_form_param.im_func
DBAPIRequest.property_value = _fake_property_value
DBAPIRequest.next_tabindex = count().next
- DBAPIRequest.form = {}
- DBAPIRequest.data = {}
- fake = lambda *args, **kwargs: None
DBAPIRequest.relative_path = fake
DBAPIRequest.url = fake
- DBAPIRequest.next_tabindex = fake
DBAPIRequest.get_page_data = fake
DBAPIRequest.set_page_data = fake
- DBAPIRequest.add_js = fake #cwrb.add_js.im_func
- DBAPIRequest.add_css = fake #cwrb.add_css.im_func
# XXX could ask the repo for it's base-url configuration
self.vreg.config.set_option('base-url', baseurl)
+ self.vreg.config.uiprops = {}
+ self.vreg.config.datadir_url = baseurl + '/data'
# XXX why is this needed? if really needed, could be fetched by a query
if sitetitle is not None:
self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle}
+ self._web_request = True
- @check_not_closed
- def source_defs(self):
- """Return the definition of sources used by the repository.
-
- This is NOT part of the DB-API.
- """
- return self._repo.source_defs()
+ def request(self):
+ if self._web_request:
+ from cubicweb.web.request import CubicWebRequestBase
+ req = CubicWebRequestBase(self.vreg, False)
+ req.get_header = lambda x, default=None: default
+ req.set_session = lambda session, user=None: DBAPIRequest.set_session(
+ req, session, user)
+ req.relative_path = lambda includeparams=True: ''
+ else:
+ req = DBAPIRequest(self.vreg)
+ req.set_session(DBAPISession(self))
+ return req
@check_not_closed
def user(self, req=None, props=None):
@@ -593,21 +605,20 @@
else:
from cubicweb.entity import Entity
user = Entity(req, rset, row=0)
- user['login'] = login # cache login
+ user.cw_attr_cache['login'] = login # cache login
return user
@check_not_closed
def check(self):
- """raise `BadConnectionId` if the connection is no more valid"""
- self._repo.check_session(self.sessionid)
+ """raise `BadConnectionId` if the connection is no more valid, else
+ return its latest activity timestamp.
+ """
+ return self._repo.check_session(self.sessionid)
def _txid(self, cursor=None): # XXX could now handle various isolation level!
# return a dict as bw compat trick
return {'txid': currentThread().getName()}
- def request(self):
- return DBAPIRequest(self.vreg, DBAPISession(self))
-
# session data methods #####################################################
@check_not_closed
@@ -616,24 +627,35 @@
self._repo.set_session_props(self.sessionid, props)
@check_not_closed
- def get_shared_data(self, key, default=None, pop=False):
- """return value associated to `key` in shared data"""
- return self._repo.get_shared_data(self.sessionid, key, default, pop)
+ def get_shared_data(self, key, default=None, pop=False, txdata=False):
+ """return value associated to key in the session's data dictionary or
+ session's transaction's data if `txdata` is true.
+
+ If pop is True, value will be removed from the dictionnary.
+
+ If key isn't defined in the dictionnary, value specified by the
+ `default` argument will be returned.
+ """
+ return self._repo.get_shared_data(self.sessionid, key, default, pop, txdata)
@check_not_closed
- def set_shared_data(self, key, value, querydata=False):
+ def set_shared_data(self, key, value, txdata=False):
"""set value associated to `key` in shared data
- if `querydata` is true, the value will be added to the repository
+ if `txdata` is true, the value will be added to the repository
session's query data which are cleared on commit/rollback of the current
- transaction, and won't be available through the connexion, only on the
- repository side.
+ transaction.
"""
- return self._repo.set_shared_data(self.sessionid, key, value, querydata)
+ return self._repo.set_shared_data(self.sessionid, key, value, txdata)
# meta-data accessors ######################################################
@check_not_closed
+ def source_defs(self):
+ """Return the definition of sources used by the repository."""
+ return self._repo.source_defs()
+
+ @check_not_closed
def get_schema(self):
"""Return the schema currently used by the repository."""
return self._repo.get_schema()
--- a/debian/changelog Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/changelog Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,57 @@
+cubicweb (3.10.8-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 02 Feb 2011 11:09:22 +0100
+
+cubicweb (3.10.7-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 12 Jan 2011 08:50:29 +0100
+
+cubicweb (3.10.6-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr> Tue, 30 Nov 2010 22:25:41 +0100
+
+cubicweb (3.10.5-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 25 Oct 2010 18:22:20 +0200
+
+cubicweb (3.10.4-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 22 Oct 2010 17:41:00 +0200
+
+cubicweb (3.10.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 20 Oct 2010 16:00:33 +0200
+
+cubicweb (3.10.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 18 Oct 2010 11:47:37 +0200
+
+cubicweb (3.10.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 15 Oct 2010 12:08:58 +0200
+
+cubicweb (3.10.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 13 Oct 2010 22:18:39 +0200
+
cubicweb (3.9.9-3) unstable; urgency=low
* cubicweb-common must actually include shared/i18n folder
--- a/debian/control Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/control Fri Mar 11 09:46:45 2011 +0100
@@ -7,15 +7,15 @@
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>,
Aurélien Campéas <aurelien.campeas@logilab.fr>,
Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 5), python-dev (>=2.5), python-central (>= 0.5)
-Standards-Version: 3.8.0
+Build-Depends: debhelper (>= 7), python (>= 2.5), python-central (>= 0.5)
+Standards-Version: 3.9.1
Homepage: http://www.cubicweb.org
XS-Python-Version: >= 2.5, << 2.7
Package: cubicweb
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version})
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version})
XB-Recommends: (postgresql, postgresql-plpython) | mysql | sqlite3
Recommends: postgresql | mysql | sqlite3
Description: the complete CubicWeb framework
@@ -33,8 +33,8 @@
Conflicts: cubicweb-multisources
Replaces: cubicweb-multisources
Provides: cubicweb-multisources
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.2), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
-Recommends: pyro (< 4.0.0), cubicweb-documentation (= ${source:Version})
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.3), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
Description: server part of the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -46,7 +46,7 @@
Package: cubicweb-postgresql-support
Architecture: all
# postgresql-client packages for backup/restore of non local database
-Depends: python-psycopg2, postgresql-client
+Depends: ${misc:Depends}, python-psycopg2, postgresql-client
Description: postgres support for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -56,7 +56,7 @@
Package: cubicweb-mysql-support
Architecture: all
# mysql-client packages for backup/restore of non local database
-Depends: python-mysqldb, mysql-client
+Depends: ${misc:Depends}, python-mysqldb, mysql-client
Description: mysql support for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -68,8 +68,8 @@
Architecture: all
XB-Python-Version: ${python:Versions}
Provides: cubicweb-web-frontend
-Depends: ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web
-Recommends: pyro (< 4.0.0), cubicweb-documentation (= ${source:Version})
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web
+Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
Description: twisted-based web interface for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -82,7 +82,7 @@
Package: cubicweb-web
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3)
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3)
Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-imaging
Description: web interface library for the CubicWeb framework
CubicWeb is a semantic web application framework.
@@ -97,7 +97,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.51.0), python-yams (>= 0.30.1), python-rql (>= 0.26.3), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.54.0), python-yams (>= 0.30.1), python-rql (>= 0.28.0), python-lxml
Recommends: python-simpletal (>= 4.0), python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
@@ -111,7 +111,7 @@
Package: cubicweb-ctl
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version})
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version})
Description: tool to manage the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -123,7 +123,7 @@
Package: cubicweb-dev
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-web (= ${source:Version}), python-pysqlite2
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-web (= ${source:Version}), python-pysqlite2
Suggests: w3c-dtd-xhtml
Description: tests suite and development tools for the CubicWeb framework
CubicWeb is a semantic web application framework.
@@ -133,7 +133,6 @@
Package: cubicweb-documentation
-Architecture: all
Recommends: doc-base
Description: documentation for the CubicWeb framework
CubicWeb is a semantic web application framework.
--- a/debian/cubicweb-ctl.cubicweb.init Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/cubicweb-ctl.cubicweb.init Fri Mar 11 09:46:45 2011 +0100
@@ -2,8 +2,8 @@
### BEGIN INIT INFO
# Provides: cubicweb
-# Required-Start: $syslog $local_fs $network
-# Required-Stop: $syslog $local_fs $network
+# Required-Start: $remote_fs $syslog $local_fs $network
+# Required-Stop: $remote_fs $syslog $local_fs $network
# Should-Start: $postgresql $pyro-nsd
# Should-Stop: $postgresql $pyro-nsd
# Default-Start: 2 3 4 5
@@ -24,12 +24,12 @@
case $1 in
force-reload)
- /usr/bin/cubicweb-ctl reload --force
+ python -W ignore /usr/bin/cubicweb-ctl reload --force
;;
status)
- /usr/bin/cubicweb-ctl status
+ python -W ignore /usr/bin/cubicweb-ctl status
;;
- *)
- /usr/bin/cubicweb-ctl $1 --force
+ start|stop|restart|*)
+ python -W ignore /usr/bin/cubicweb-ctl $1 --force
;;
esac
--- a/debian/cubicweb-ctl.dirs Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/cubicweb-ctl.dirs Fri Mar 11 09:46:45 2011 +0100
@@ -4,7 +4,6 @@
etc/bash_completion.d
usr/bin
usr/share/doc/cubicweb-ctl
-var/run/cubicweb
var/log/cubicweb
var/lib/cubicweb/backup
var/lib/cubicweb/instances
--- a/debian/cubicweb-ctl.prerm Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/cubicweb-ctl.prerm Fri Mar 11 09:46:45 2011 +0100
@@ -2,8 +2,7 @@
case "$1" in
purge)
- rm -rf /etc/cubicweb.d/
- rm -rf /var/run/cubicweb/
+ rm -rf /etc/cubicweb.d/
rm -rf /var/log/cubicweb/
rm -rf /var/lib/cubicweb/
;;
--- a/debian/cubicweb-documentation.install.in Fri Dec 10 12:17:18 2010 +0100
+++ b/debian/cubicweb-documentation.install.in Fri Mar 11 09:46:45 2011 +0100
@@ -1,2 +1,3 @@
doc/book usr/share/doc/cubicweb-documentation
+doc/html usr/share/doc/cubicweb-documentation
debian/cubicweb-doc usr/share/doc-base/cubicweb-doc
--- a/devtools/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -35,30 +35,19 @@
# db auto-population configuration #############################################
-SYSTEM_ENTITIES = schema.SCHEMA_TYPES | set((
- 'CWGroup', 'CWUser', 'CWProperty',
- 'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition',
- 'TrInfo', 'SubWorkflowExitPoint',
- ))
-
-SYSTEM_RELATIONS = schema.META_RTYPES | set((
- # workflow related
- 'workflow_of', 'state_of', 'transition_of', 'initial_state', 'default_workflow',
- 'allowed_transition', 'destination_state', 'from_state', 'to_state',
- 'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
- 'custom_workflow', 'in_state', 'wf_info_for',
- # cwproperty
- 'for_user',
- # schema definition
- 'specializes',
- 'relation_type', 'from_entity', 'to_entity',
- 'constrained_by', 'cstrtype', 'widget',
- 'read_permission', 'update_permission', 'delete_permission', 'add_permission',
- # permission
- 'in_group', 'require_group', 'require_permission',
- # deducted from other relations
- 'primary_email',
- ))
+SYSTEM_ENTITIES = (schema.SCHEMA_TYPES
+ | schema.INTERNAL_TYPES
+ | schema.WORKFLOW_TYPES
+ | set(('CWGroup', 'CWUser',))
+ )
+SYSTEM_RELATIONS = (schema.META_RTYPES
+ | schema.WORKFLOW_RTYPES
+ | schema.WORKFLOW_DEF_RTYPES
+ | schema.SYSTEM_RTYPES
+ | schema.SCHEMA_TYPES
+ | set(('primary_email', # deducted from other relations
+ ))
+ )
# content validation configuration #############################################
@@ -96,22 +85,8 @@
read_instance_schema = False
init_repository = True
db_require_setup = True
- options = cwconfig.merge_options(ServerConfiguration.options + (
- ('anonymous-user',
- {'type' : 'string',
- 'default': None,
- 'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
- 'group': 'main', 'level': 1,
- }),
- ('anonymous-password',
- {'type' : 'string',
- 'default': None,
- 'help': 'password of the CubicWeb user account matching login',
- 'group': 'main', 'level': 1,
- }),
- ))
- def __init__(self, appid, apphome=None, log_threshold=logging.CRITICAL+10):
+ def __init__(self, appid='data', apphome=None, log_threshold=logging.CRITICAL+10):
# must be set before calling parent __init__
if apphome is None:
if exists(appid):
@@ -124,7 +99,20 @@
# need this, usually triggered by cubicweb-ctl
self.load_cwctl_plugins()
- anonymous_user = TwistedConfiguration.anonymous_user.im_func
+ # By default anonymous login are allow but some test need to deny of to
+ # change the default user. Set it to None to prevent anonymous login.
+ anonymous_credential = ('anon', 'anon')
+
+ def anonymous_user(self):
+ if not self.anonymous_credential:
+ return None, None
+ return self.anonymous_credential
+
+ def set_anonymous_allowed(self, allowed, anonuser='anon'):
+ if allowed:
+ self.anonymous_credential = (anonuser, anonuser)
+ else:
+ self.anonymous_credential = None
@property
def apphome(self):
@@ -133,8 +121,6 @@
def load_configuration(self):
super(TestServerConfiguration, self).load_configuration()
- self.global_set_option('anonymous-user', 'anon')
- self.global_set_option('anonymous-password', 'anon')
# no undo support in tests
self.global_set_option('undo-support', '')
@@ -170,6 +156,8 @@
sources = super(TestServerConfiguration, self).sources()
if not sources:
sources = DEFAULT_SOURCES
+ if 'admin' not in sources:
+ sources['admin'] = DEFAULT_SOURCES['admin']
return sources
# web config methods needed here for cases when we use this config as a web
@@ -184,6 +172,7 @@
class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
repo_method = 'inmemory'
+ name = 'all-in-one' # so it search for all-in-one.conf, not repository.conf
options = cwconfig.merge_options(TestServerConfiguration.options
+ TwistedConfiguration.options)
cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path
@@ -233,10 +222,10 @@
# test database handling #######################################################
-def init_test_database(config=None, configdir='data'):
+def init_test_database(config=None, appid='data', apphome=None):
"""init a test database for a specific driver"""
- from cubicweb.dbapi import in_memory_cnx
- config = config or TestServerConfiguration(configdir)
+ from cubicweb.dbapi import in_memory_repo_cnx
+ config = config or TestServerConfiguration(appid, apphome=apphome)
sources = config.sources()
driver = sources['system']['db-driver']
if config.db_require_setup:
@@ -247,7 +236,7 @@
else:
raise ValueError('no initialization function for driver %r' % driver)
config._cubes = None # avoid assertion error
- repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
+ repo, cnx = in_memory_repo_cnx(config, unicode(sources['admin']['login']),
password=sources['admin']['password'] or 'xxx')
if driver == 'sqlite':
install_sqlite_patch(repo.querier)
@@ -344,12 +333,13 @@
def init_test_database_sqlite(config):
"""initialize a fresh sqlite databse used for testing purpose"""
# remove database file if it exists
+ dbfile = join(config.apphome, config.sources()['system']['db-name'])
+ config.sources()['system']['db-name'] = dbfile
if not reset_test_database_sqlite(config):
# initialize the database
import shutil
from cubicweb.server import init_repository
init_repository(config, interactive=False)
- dbfile = config.sources()['system']['db-name']
shutil.copy(dbfile, '%s-template' % dbfile)
def install_sqlite_patch(querier):
--- a/devtools/cwwindmill.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/cwwindmill.py Fri Mar 11 09:46:45 2011 +0100
@@ -29,120 +29,131 @@
import sys
# imported by default to simplify further import statements
-from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.testlib import TestCase, unittest_main, Tags
-import windmill
-from windmill.dep import functest
-from windmill.bin.admin_lib import configure_global_settings, setup, teardown
+try:
+ import windmill
+ from windmill.dep import functest
+ from windmill.bin.admin_lib import configure_global_settings, setup, teardown
+except ImportError, ex:
+ windmill = None
from cubicweb.devtools.httptest import CubicWebServerTC, CubicWebServerConfig
+if windmill is None:
+ class CubicWebWindmillUseCase(CubicWebServerTC):
+ tags = CubicWebServerTC.tags & Tags(('windmill',))
-# Excerpt from :ref:`windmill.authoring.unit`
-class UnitTestReporter(functest.reports.FunctestReportInterface):
- def summary(self, test_list, totals_dict, stdout_capture):
- self.test_list = test_list
-
-unittestreporter = UnitTestReporter()
-functest.reports.register_reporter(unittestreporter)
+ def testWindmill(self):
+ self.skipTest("can't import windmill %s" % ex)
+else:
+ # Excerpt from :ref:`windmill.authoring.unit`
+ class UnitTestReporter(functest.reports.FunctestReportInterface):
+ def summary(self, test_list, totals_dict, stdout_capture):
+ self.test_list = test_list
-class CubicWebWindmillUseCase(CubicWebServerTC):
- """basic class for Windmill use case tests
+ unittestreporter = UnitTestReporter()
+ functest.reports.register_reporter(unittestreporter)
+
+ class CubicWebWindmillUseCase(CubicWebServerTC):
+ """basic class for Windmill use case tests
- If you want to change cubicweb test server parameters, define a new
- :class:`CubicWebServerConfig` and override the :var:`configcls`
- attribute:
+ If you want to change cubicweb test server parameters, define a new
+ :class:`CubicWebServerConfig` and override the :var:`configcls`
+ attribute:
- configcls = CubicWebServerConfig
+ configcls = CubicWebServerConfig
- From Windmill configuration:
+ From Windmill configuration:
- .. attribute:: browser
- identification string (firefox|ie|safari|chrome) (firefox by default)
- .. attribute :: edit_test
- load and edit test for debugging (False by default)
- .. attribute:: test_dir (optional)
- testing file path or directory (windmill directory under your unit case
- file by default)
+ .. attribute:: browser
+ identification string (firefox|ie|safari|chrome) (firefox by default)
+ .. attribute :: edit_test
+ load and edit test for debugging (False by default)
+ .. attribute:: test_dir (optional)
+ testing file path or directory (windmill directory under your unit case
+ file by default)
+
+ Examples:
- Examples:
+ browser = 'firefox'
+ test_dir = osp.join(__file__, 'windmill')
+ edit_test = False
+ If you prefer, you can put here the use cases recorded by windmill GUI
+ (services transformer) instead of the windmill sub-directory
+ You can change `test_dir` as following:
+
+ test_dir = __file__
+
+ Instead of toggle `edit_test` value, try `pytest -i`
+ """
browser = 'firefox'
- test_dir = osp.join(__file__, 'windmill')
- edit_test = False
-
- If you prefer, you can put here the use cases recorded by windmill GUI
- (services transformer) instead of the windmill sub-directory
- You can change `test_dir` as following:
+ edit_test = "-i" in sys.argv # detection for pytest invocation
+ # Windmill use case are written with no anonymous user
+ anonymous_allowed = False
- test_dir = __file__
-
- Instead of toggle `edit_test` value, try `pytest -i`
- """
- browser = 'firefox'
- edit_test = "-i" in sys.argv # detection for pytest invocation
- # Windmill use case are written with no anonymous user
- anonymous_logged = False
+ tags = CubicWebServerTC.tags & Tags(('windmill',))
- def _test_dir(self):
- """access to class attribute if possible or make assumption
- of expected directory"""
- try:
- return getattr(self, 'test_dir')
- except AttributeError:
- if os.path.basename(sys.argv[0]) == "pytest":
- test_dir = os.getcwd()
- else:
- import inspect
- test_dir = os.path.dirname(inspect.stack()[-1][1])
- return osp.join(test_dir, 'windmill')
+ def _test_dir(self):
+ """access to class attribute if possible or make assumption
+ of expected directory"""
+ try:
+ return getattr(self, 'test_dir')
+ except AttributeError:
+ if os.path.basename(sys.argv[0]) == "pytest":
+ test_dir = os.getcwd()
+ else:
+ import inspect
+ test_dir = os.path.dirname(inspect.stack()[-1][1])
+ return osp.join(test_dir, 'windmill')
- def setUp(self):
- # Start CubicWeb session before running the server to populate self.vreg
- CubicWebServerTC.setUp(self)
- # XXX reduce log output (should be done in a cleaner way)
- # windmill fu** up our logging configuration
- for logkey in ('windmill', 'logilab', 'cubicweb'):
- getLogger(logkey).setLevel(ERROR)
- self.test_dir = self._test_dir()
- msg = "provide a valid 'test_dir' as the given test file/dir (current: %s)"
- assert os.path.exists(self.test_dir), (msg % self.test_dir)
- # windmill setup
- windmill.stdout, windmill.stdin = sys.stdout, sys.stdin
- configure_global_settings()
- windmill.settings['TEST_URL'] = self.config['base-url']
- if hasattr(self,"windmill_settings"):
- for (setting,value) in self.windmill_settings.iteritems():
- windmill.settings[setting] = value
- self.windmill_shell_objects = setup()
+ def setUp(self):
+ # Start CubicWeb session before running the server to populate self.vreg
+ CubicWebServerTC.setUp(self)
+ # XXX reduce log output (should be done in a cleaner way)
+ # windmill fu** up our logging configuration
+ for logkey in ('windmill', 'logilab', 'cubicweb'):
+ getLogger(logkey).setLevel(ERROR)
+ self.test_dir = self._test_dir()
+ msg = "provide a valid 'test_dir' as the given test file/dir (current: %s)"
+ assert os.path.exists(self.test_dir), (msg % self.test_dir)
+ # windmill setup
+ windmill.stdout, windmill.stdin = sys.stdout, sys.stdin
+ configure_global_settings()
+ windmill.settings['TEST_URL'] = self.config['base-url']
+ if hasattr(self,"windmill_settings"):
+ for (setting,value) in self.windmill_settings.iteritems():
+ windmill.settings[setting] = value
+ self.windmill_shell_objects = setup()
- def tearDown(self):
- teardown(self.windmill_shell_objects)
- CubicWebServerTC.tearDown(self)
+ def tearDown(self):
+ teardown(self.windmill_shell_objects)
+ CubicWebServerTC.tearDown(self)
- def testWindmill(self):
- if self.edit_test:
- # see windmill.bin.admin_options.Firebug
- windmill.settings['INSTALL_FIREBUG'] = 'firebug'
- windmill.settings.setdefault('MOZILLA_PLUGINS', []).extend(
- ['/usr/share/mozilla-extensions/',
- '/usr/share/xul-ext/'])
- controller = self.windmill_shell_objects['start_' + self.browser]()
- self.windmill_shell_objects['do_test'](self.test_dir,
- load=self.edit_test,
- threaded=False)
- # set a breakpoint to be able to debug windmill test
- if self.edit_test:
- import pdb; pdb.set_trace()
- return
+ def testWindmill(self):
+ if self.edit_test:
+ # see windmill.bin.admin_options.Firebug
+ windmill.settings['INSTALL_FIREBUG'] = 'firebug'
+ windmill.settings.setdefault('MOZILLA_PLUGINS', []).extend(
+ ['/usr/share/mozilla-extensions/',
+ '/usr/share/xul-ext/'])
+ controller = self.windmill_shell_objects['start_' + self.browser]()
+ self.windmill_shell_objects['do_test'](self.test_dir,
+ load=self.edit_test,
+ threaded=False)
+ # set a breakpoint to be able to debug windmill test
+ if self.edit_test:
+ import pdb; pdb.set_trace()
+ return
- # reporter
- for test in unittestreporter.test_list:
- msg = ""
- self._testMethodDoc = getattr(test, "__doc__", None)
- self._testMethodName = test.__name__
- # try to display a better message in case of failure
- if hasattr(test, "tb"):
- msg = '\n'.join(test.tb)
- self.assertEqual(test.result, True, msg=msg)
+ # reporter
+ for test in unittestreporter.test_list:
+ msg = ""
+ self._testMethodDoc = getattr(test, "__doc__", None)
+ self._testMethodName = test.__name__
+ # try to display a better message in case of failure
+ if hasattr(test, "tb"):
+ msg = '\n'.join(test.tb)
+ self.assertEqual(test.result, True, msg=msg)
--- a/devtools/dataimport.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/dataimport.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0614,W0401
+# pylint: disable=W0614,W0401
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
--- a/devtools/devctl.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/devctl.py Fri Mar 11 09:46:45 2011 +0100
@@ -127,8 +127,7 @@
from copy import deepcopy
from cubicweb.i18n import add_msg
from cubicweb.web import uicfg
- from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES, CONSTRAINTS
- no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
+ from cubicweb.schema import NO_I18NCONTEXT, CONSTRAINTS
w('# schema pot file, generated on %s\n'
% datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
w('# \n')
@@ -217,13 +216,13 @@
else:
librschema = libschema.rschema(rtype)
# add context information only for non-metadata rtypes
- if rschema not in no_context_rtypes:
+ if rschema not in NO_I18NCONTEXT:
libsubjects = librschema and librschema.subjects() or ()
for subjschema in rschema.subjects():
if not subjschema in libsubjects:
add_msg(w, rtype, subjschema.type)
if not (schema.rschema(rtype).final or rschema.symmetric):
- if rschema not in no_context_rtypes:
+ if rschema not in NO_I18NCONTEXT:
libobjects = librschema and librschema.objects() or ()
for objschema in rschema.objects():
if not objschema in libobjects:
@@ -239,6 +238,8 @@
def _iter_vreg_objids(vreg, done):
for reg, objdict in vreg.items():
+ if reg in ('boxes', 'contentnavigation'):
+ continue
for objects in objdict.values():
for obj in objects:
objid = '%s_%s' % (reg, obj.__regid__)
@@ -345,7 +346,7 @@
print 'when you are done, run "cubicweb-ctl i18ncube yourcube".'
-class UpdateTemplateCatalogCommand(Command):
+class UpdateCubeCatalogCommand(Command):
"""Update i18n catalogs for cubes. If no cube is specified, update
catalogs of all registered cubes.
"""
@@ -782,7 +783,7 @@
print make_qunit_html(args[0], args[1:])
for cmdcls in (UpdateCubicWebCatalogCommand,
- UpdateTemplateCatalogCommand,
+ UpdateCubeCatalogCommand,
#LiveServerCommand,
NewCubeCommand,
ExamineLogCommand,
--- a/devtools/fake.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/fake.py Fri Mar 11 09:46:45 2011 +0100
@@ -127,6 +127,16 @@
def validate_cache(self):
pass
+ def build_url_params(self, **kwargs):
+ # overriden to get predictable resultts
+ args = []
+ for param, values in sorted(kwargs.iteritems()):
+ if not isinstance(values, (list, tuple)):
+ values = (values,)
+ for value in values:
+ assert value is not None
+ args.append(u'%s=%s' % (param, self.url_quote(value)))
+ return '&'.join(args)
class FakeUser(object):
login = 'toto'
@@ -170,6 +180,7 @@
self.config = config or FakeConfig()
self.vreg = vreg or CubicWebVRegistry(self.config, initlog=False)
self.vreg.schema = schema
+ self.sources = []
def internal_session(self):
return FakeSession(self)
--- a/devtools/fill.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/fill.py Fri Mar 11 09:46:45 2011 +0100
@@ -27,7 +27,7 @@
from logilab.common import attrdict
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
- IntervalBoundConstraint, BoundConstraint,
+ IntervalBoundConstraint, BoundaryConstraint,
Attribute, actual_value)
from rql.utils import decompose_b26 as base_decompose_b26
@@ -185,10 +185,12 @@
minvalue = maxvalue - (index * step) # i.e. randint(-index, 0)
return choice(list(custom_range(minvalue, maxvalue, step)))
- def _actual_boundary(self, entity, boundary):
+ def _actual_boundary(self, entity, attrname, boundary):
if isinstance(boundary, Attribute):
# ensure we've a value for this attribute
- self.generate_attribute_value(entity, boundary.attr)
+ entity[attrname] = None # infinite loop safety belt
+ if not boundary.attr in entity:
+ self.generate_attribute_value(entity, boundary.attr)
boundary = actual_value(boundary, entity)
return boundary
@@ -196,13 +198,13 @@
minvalue = maxvalue = None
for cst in self.eschema.rdef(attrname).constraints:
if isinstance(cst, IntervalBoundConstraint):
- minvalue = self._actual_boundary(entity, cst.minvalue)
- maxvalue = self._actual_boundary(entity, cst.maxvalue)
- elif isinstance(cst, BoundConstraint):
+ minvalue = self._actual_boundary(entity, attrname, cst.minvalue)
+ maxvalue = self._actual_boundary(entity, attrname, cst.maxvalue)
+ elif isinstance(cst, BoundaryConstraint):
if cst.operator[0] == '<':
- maxvalue = self._actual_boundary(entity, cst.boundary)
+ maxvalue = self._actual_boundary(entity, attrname, cst.boundary)
else:
- minvalue = self._actual_boundary(entity, cst.boundary)
+ minvalue = self._actual_boundary(entity, attrname, cst.boundary)
return minvalue, maxvalue
def get_choice(self, entity, attrname):
--- a/devtools/htmlparser.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/htmlparser.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,16 +15,17 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""defines a validating HTML parser used in web application tests
-
-"""
+"""defines a validating HTML parser used in web application tests"""
import re
import sys
from lxml import etree
+from logilab.common.deprecation import class_deprecated
+
from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE
+
STRICT_DOCTYPE = str(STRICT_DOCTYPE)
TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE)
@@ -51,10 +52,7 @@
def __init__(self):
Validator.__init__(self)
# XXX understand what's happening under windows
- validate = True
- if sys.platform == 'win32':
- validate = False
- self.parser = etree.XMLParser(dtd_validation=validate)
+ self.parser = etree.XMLParser(dtd_validation=sys.platform != 'win32')
def preprocess_data(self, data):
"""used to fix potential blockquote mess generated by docutils"""
@@ -87,12 +85,14 @@
Validator.__init__(self)
self.parser = etree.XMLParser()
+
class XMLDemotingValidator(SaxOnlyValidator):
""" some views produce html instead of xhtml, using demote_to_html
this is typically related to the use of external dependencies
which do not produce valid xhtml (google maps, ...)
"""
+ __metaclass__ = class_deprecated
def preprocess_data(self, data):
if data.startswith('<?xml'):
@@ -127,15 +127,46 @@
self.input_tags = self.find_tag('input')
self.title_tags = [self.h1_tags, self.h2_tags, self.h3_tags, self.h4_tags]
+ def _iterstr(self, tag):
+ if self.default_ns is None:
+ return ".//%s" % tag
+ else:
+ return ".//{%s}%s" % (self.default_ns, tag)
+
+ def matching_nodes(self, tag, **attrs):
+ for elt in self.etree.iterfind(self._iterstr(tag)):
+ eltattrs = elt.attrib
+ for attr, value in attrs.iteritems():
+ try:
+ if eltattrs[attr] != value:
+ break
+ except KeyError:
+ break
+ else: # all attributes match
+ yield elt
+
+ def has_tag(self, tag, nboccurs=1, **attrs):
+ """returns True if tag with given attributes appears in the page
+ `nbtimes` (any if None)
+ """
+ for elt in self.matching_nodes(tag, **attrs):
+ if nboccurs is None: # no need to check number of occurences
+ return True
+ if not nboccurs: # too much occurences
+ return False
+ nboccurs -= 1
+ if nboccurs == 0: # correct number of occurences
+ return True
+ return False # no matching tag/attrs
+
def find_tag(self, tag, gettext=True):
"""return a list which contains text of all "tag" elements """
- if self.default_ns is None:
- iterstr = ".//%s" % tag
- else:
- iterstr = ".//{%s}%s" % (self.default_ns, tag)
+ iterstr = self._iterstr(tag)
if not gettext or tag in ('a', 'input'):
- return [(elt.text, elt.attrib) for elt in self.etree.iterfind(iterstr)]
- return [u''.join(elt.xpath('.//text()')) for elt in self.etree.iterfind(iterstr)]
+ return [(elt.text, elt.attrib)
+ for elt in self.etree.iterfind(iterstr)]
+ return [u''.join(elt.xpath('.//text()'))
+ for elt in self.etree.iterfind(iterstr)]
def appears(self, text):
"""returns True if <text> appears in the page"""
--- a/devtools/httptest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/httptest.py Fri Mar 11 09:46:45 2011 +0100
@@ -89,12 +89,11 @@
"""Class for running test web server. See :class:`CubicWebServerConfig`.
Class attributes:
- * ` anonymous_logged`: flag telling ifs anonymous user should be log logged
- by default (True by default)
+ * `anonymous_allowed`: flag telling if anonymous browsing should be allowed
"""
configcls = CubicWebServerConfig
# anonymous is logged by default in cubicweb test cases
- anonymous_logged = True
+ anonymous_allowed = True
def start_server(self):
# use a semaphore to avoid starting test while the http server isn't
@@ -176,7 +175,7 @@
return response
def setUp(self):
- CubicWebTC.setUp(self)
+ super(CubicWebServerTC, self).setUp()
self.start_server()
def tearDown(self):
@@ -185,13 +184,9 @@
except error.ReactorNotRunning, err:
# Server could be launched manually
print err
- CubicWebTC.tearDown(self)
+ super(CubicWebServerTC, self).tearDown()
@classmethod
def init_config(cls, config):
+ config.set_anonymous_allowed(cls.anonymous_allowed)
super(CubicWebServerTC, cls).init_config(config)
- if not cls.anonymous_logged:
- config.global_set_option('anonymous-user', None)
- else:
- config.global_set_option('anonymous-user', 'anon')
- config.global_set_option('anonymous-password', 'anon')
--- a/devtools/livetest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/livetest.py Fri Mar 11 09:46:45 2011 +0100
@@ -35,7 +35,7 @@
from logilab.common.testlib import TestCase
-from cubicweb.dbapi import in_memory_cnx
+from cubicweb.dbapi import in_memory_repo_cnx
from cubicweb.etwist.server import CubicWebRootResource
from cubicweb.devtools import BaseApptestConfiguration, init_test_database
@@ -164,7 +164,7 @@
# build a config, and get a connection
self.config = LivetestConfiguration(self.cube, self.sourcefile)
_, user, passwd, _ = loadconf()
- self.repo, self.cnx = in_memory_cnx(self.config, user, password=passwd)
+ self.repo, self.cnx = in_memory_repo_cnx(self.config, user, password=passwd)
self.setup_db(self.cnx)
def tearDown(self):
--- a/devtools/qunit.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/qunit.py Fri Mar 11 09:46:45 2011 +0100
@@ -8,7 +8,7 @@
from uuid import uuid4
# imported by default to simplify further import statements
-from logilab.common.testlib import unittest_main, with_tempdir, InnerTest
+from logilab.common.testlib import unittest_main, with_tempdir, InnerTest, Tags
from logilab.common.shellutils import getlogin
import cubicweb
@@ -86,6 +86,8 @@
class QUnitTestCase(CubicWebServerTC):
+ tags = CubicWebServerTC.tags | Tags(('qunit',))
+
# testfile, (dep_a, dep_b)
all_js_tests = ()
--- a/devtools/repotest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/repotest.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,9 +22,11 @@
__docformat__ = "restructuredtext en"
+from copy import deepcopy
from pprint import pprint
from logilab.common.decorators import clear_cache
+from logilab.common.testlib import SkipTest
def tuplify(list):
for i in range(len(list)):
@@ -140,7 +142,7 @@
from rql import RQLHelper
from cubicweb.devtools.fake import FakeRepo, FakeSession
-from cubicweb.server import set_debug
+from cubicweb.server import set_debug, debugged
from cubicweb.server.querier import QuerierHelper
from cubicweb.server.session import Session
from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions
@@ -148,6 +150,15 @@
class RQLGeneratorTC(TestCase):
schema = backend = None # set this in concret test
+
+ @classmethod
+ def setUpClass(cls):
+ if cls.backend is not None:
+ try:
+ cls.dbhelper = get_db_helper(cls.backend)
+ except ImportError, ex:
+ raise SkipTest(str(ex))
+
def setUp(self):
self.repo = FakeRepo(self.schema)
self.repo.system_source = mock_object(dbdriver=self.backend)
@@ -158,11 +169,7 @@
ExecutionPlan._check_permissions = _dummy_check_permissions
rqlannotation._select_principal = _select_principal
if self.backend is not None:
- try:
- dbhelper = get_db_helper(self.backend)
- except ImportError, ex:
- self.skipTest(str(ex))
- self.o = SQLGenerator(self.schema, dbhelper)
+ self.o = SQLGenerator(self.schema, self.dbhelper)
def tearDown(self):
ExecutionPlan._check_permissions = _orig_check_permissions
@@ -170,6 +177,8 @@
def set_debug(self, debug):
set_debug(debug)
+ def debugged(self, debug):
+ return debugged(debug)
def _prepare(self, rql):
#print '******************** prepare', rql
@@ -221,6 +230,8 @@
def set_debug(self, debug):
set_debug(debug)
+ def debugged(self, debug):
+ return debugged(debug)
def _rqlhelper(self):
rqlhelper = self.repo.vreg.rqlhelper
@@ -284,8 +295,7 @@
self.repo.vreg.rqlhelper.backend = 'postgres' # so FTIRANK is considered
def add_source(self, sourcecls, uri):
- self.sources.append(sourcecls(self.repo, self.o.schema,
- {'uri': uri}))
+ self.sources.append(sourcecls(self.repo, {'uri': uri}))
self.repo.sources_by_uri[uri] = self.sources[-1]
setattr(self, uri, self.sources[-1])
self.newsources += 1
@@ -364,17 +374,17 @@
from cubicweb.server.msplanner import PartPlanInformation
except ImportError:
class PartPlanInformation(object):
- def merge_input_maps(self, *args):
+ def merge_input_maps(self, *args, **kwargs):
pass
def _choose_term(self, sourceterms):
pass
_orig_merge_input_maps = PartPlanInformation.merge_input_maps
_orig_choose_term = PartPlanInformation._choose_term
-def _merge_input_maps(*args):
- return sorted(_orig_merge_input_maps(*args))
+def _merge_input_maps(*args, **kwargs):
+ return sorted(_orig_merge_input_maps(*args, **kwargs))
-def _choose_term(self, sourceterms):
+def _choose_term(self, source, sourceterms):
# predictable order for test purpose
def get_key(x):
try:
@@ -387,8 +397,13 @@
except AttributeError:
# const
return x.value
- return _orig_choose_term(self, DumbOrderedDict2(sourceterms, get_key))
+ return _orig_choose_term(self, source, DumbOrderedDict2(sourceterms, get_key))
+from cubicweb.server.sources.pyrorql import PyroRQLSource
+_orig_syntax_tree_search = PyroRQLSource.syntax_tree_search
+
+def _syntax_tree_search(*args, **kwargs):
+ return deepcopy(_orig_syntax_tree_search(*args, **kwargs))
def do_monkey_patch():
RQLRewriter.insert_snippets = _insert_snippets
@@ -398,6 +413,7 @@
ExecutionPlan.init_temp_table = _init_temp_table
PartPlanInformation.merge_input_maps = _merge_input_maps
PartPlanInformation._choose_term = _choose_term
+ PyroRQLSource.syntax_tree_search = _syntax_tree_search
def undo_monkey_patch():
RQLRewriter.insert_snippets = _orig_insert_snippets
@@ -406,3 +422,4 @@
ExecutionPlan.init_temp_table = _orig_init_temp_table
PartPlanInformation.merge_input_maps = _orig_merge_input_maps
PartPlanInformation._choose_term = _orig_choose_term
+ PyroRQLSource.syntax_tree_search = _orig_syntax_tree_search
--- a/devtools/test/unittest_httptest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/test/unittest_httptest.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,6 +19,7 @@
import httplib
+from logilab.common.testlib import Tags
from cubicweb.devtools.httptest import CubicWebServerTC, CubicWebServerConfig
@@ -40,7 +41,9 @@
class TwistedCWIdentTC(CubicWebServerTC):
- anonymous_logged = False
+
+ anonymous_allowed = False
+ tags = CubicWebServerTC.tags | Tags(('auth',))
def test_response_denied(self):
response = self.web_get()
@@ -49,7 +52,7 @@
def test_login(self):
response = self.web_get()
if response.status != httplib.FORBIDDEN:
- self.skipTest('Already authenticated')
+ self.skipTest('Already authenticated, "test_response_denied" must have failed')
# login
self.web_login(self.admlogin, self.admpassword)
response = self.web_get()
--- a/devtools/test/unittest_testlib.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/test/unittest_testlib.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,24 +15,23 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unittests for gct.apptest module
-
-"""
+"""unittests for cw.devtools.testlib module"""
from cStringIO import StringIO
-from logilab.common.testlib import (TestCase, unittest_main, TestSuite,
- SkipAwareTextTestRunner)
+from unittest import TextTestRunner
+from logilab.common.testlib import TestSuite, TestCase, unittest_main
from cubicweb.devtools import htmlparser
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.pytestconf import clean_repo_test_cls
+
class WebTestTC(TestCase):
def setUp(self):
output = StringIO()
- self.runner = SkipAwareTextTestRunner(stream=output)
+ self.runner = TextTestRunner(stream=output)
def test_error_raised(self):
class MyWebTest(CubicWebTC):
--- a/devtools/testlib.py Fri Dec 10 12:17:18 2010 +0100
+++ b/devtools/testlib.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -25,7 +25,7 @@
import sys
import re
import urlparse
-from os.path import dirname, join
+from os.path import dirname, join, abspath
from urllib import unquote
from math import log
from contextlib import contextmanager
@@ -38,7 +38,7 @@
from logilab.common.debugger import Debugger
from logilab.common.umessage import message_from_string
from logilab.common.decorators import cached, classproperty, clear_cache
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import deprecated, class_deprecated
from logilab.common.shellutils import getlogin
from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
@@ -185,6 +185,7 @@
* `repo`, the repository object
* `admlogin`, login of the admin user
* `admpassword`, password of the admin user
+ * `shell`, create and use shell environment
"""
appid = 'data'
configcls = devtools.ApptestConfiguration
@@ -200,7 +201,7 @@
try:
return cls.__dict__['_config']
except KeyError:
- home = join(dirname(sys.modules[cls.__module__].__file__), cls.appid)
+ home = abspath(join(dirname(sys.modules[cls.__module__].__file__), cls.appid))
config = cls._config = cls.configcls(cls.appid, apphome=home)
config.mode = 'test'
return config
@@ -286,18 +287,29 @@
"""return current server side session (using default manager account)"""
return self.repo._sessions[self._orig_cnx[0].sessionid]
+ def shell(self):
+ """return a shell session object"""
+ from cubicweb.server.migractions import ServerMigrationHelper
+ return ServerMigrationHelper(None, repo=self.repo, cnx=self.cnx,
+ interactive=False,
+ # hack so it don't try to load fs schema
+ schema=1)
+
def set_option(self, optname, value):
self.config.global_set_option(optname, value)
def set_debug(self, debugmode):
server.set_debug(debugmode)
+ def debugged(self, debugmode):
+ return server.debugged(debugmode)
+
# default test setup and teardown #########################################
def setUp(self):
# monkey patch send mail operation so emails are sent synchronously
- self._old_mail_commit_event = SendMailOp.commit_event
- SendMailOp.commit_event = SendMailOp.sendmails
+ self._old_mail_postcommit_event = SendMailOp.postcommit_event
+ SendMailOp.postcommit_event = SendMailOp.sendmails
pause_tracing()
previous_failure = self.__class__.__dict__.get('_repo_init_failed')
if previous_failure is not None:
@@ -319,7 +331,7 @@
for cnx in self._cnxs:
if not cnx._closed:
cnx.close()
- SendMailOp.commit_event = self._old_mail_commit_event
+ SendMailOp.postcommit_event = self._old_mail_postcommit_event
def setup_database(self):
"""add your database setup code by overriding this method"""
@@ -344,7 +356,7 @@
user = req.create_entity('CWUser', login=unicode(login),
upassword=password, **kwargs)
req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
- % ','.join(repr(g) for g in groups),
+ % ','.join(repr(str(g)) for g in groups),
{'x': user.eid})
user.cw_clear_relation_cache('in_group', 'subject')
if commit:
@@ -423,6 +435,21 @@
# other utilities #########################################################
+ def grant_permission(self, entity, group, pname, plabel=None):
+ """insert a permission on an entity. Will have to commit the main
+ connection to be considered
+ """
+ pname = unicode(pname)
+ plabel = plabel and unicode(plabel) or unicode(group)
+ e = entity.eid
+ with security_enabled(self.session, False, False):
+ peid = self.execute(
+ 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,'
+ 'X require_group G, E require_permission X '
+ 'WHERE G name %(group)s, E eid %(e)s',
+ locals())[0][0]
+ return peid
+
@contextmanager
def temporary_appobjects(self, *appobjects):
self.vreg._loadedmods.setdefault(self.__module__, {})
@@ -434,7 +461,20 @@
for obj in appobjects:
self.vreg.unregister(obj)
- # vregistry inspection utilities ###########################################
+ def assertModificationDateGreater(self, entity, olddate):
+ entity.cw_attr_cache.pop('modification_date', None)
+ self.failUnless(entity.modification_date > olddate)
+
+
+ # workflow utilities #######################################################
+
+ def assertPossibleTransitions(self, entity, expected):
+ transitions = entity.cw_adapt_to('IWorkflowable').possible_transitions()
+ self.assertListEqual(sorted(tr.name for tr in transitions),
+ sorted(expected))
+
+
+ # views and actions registries inspection ##################################
def pviews(self, req, rset):
return sorted((a.__regid__, a.__class__)
@@ -468,9 +508,7 @@
def items(self):
return self
class fake_box(object):
- def mk_action(self, label, url, **kwargs):
- return (label, url)
- def box_action(self, action, **kwargs):
+ def action_link(self, action, **kwargs):
return (action.title, action.url())
submenu = fake_menu()
action.fill_menu(fake_box(), submenu)
@@ -489,7 +527,8 @@
continue
views = [view for view in views
if view.category != 'startupview'
- and not issubclass(view, notification.NotificationView)]
+ and not issubclass(view, notification.NotificationView)
+ and not isinstance(view, class_deprecated)]
if views:
try:
view = viewsvreg._select_best(views, req, rset=rset)
@@ -511,7 +550,7 @@
def list_boxes_for(self, rset):
"""returns the list of boxes that can be applied on `rset`"""
req = rset.req
- for box in self.vreg['boxes'].possible_objects(req, rset=rset):
+ for box in self.vreg['ctxcomponents'].possible_objects(req, rset=rset):
yield box
def list_startup_views(self):
@@ -620,6 +659,10 @@
def init_authentication(self, authmode, anonuser=None):
self.set_option('auth-mode', authmode)
self.set_option('anonymous-user', anonuser)
+ if anonuser is None:
+ self.config.anonymous_credential = None
+ else:
+ self.config.anonymous_credential = (anonuser, anonuser)
req = self.request()
origsession = req.session
req.session = req.cnx = None
@@ -721,10 +764,8 @@
:returns: an instance of `cubicweb.devtools.htmlparser.PageInfo`
encapsulation the generated HTML
"""
- output = None
try:
output = viewfunc(**kwargs)
- return self._check_html(output, view, template)
except (SystemExit, KeyboardInterrupt):
raise
except:
@@ -735,44 +776,107 @@
msg = '[%s in %s] %s' % (klass, view.__regid__, exc)
except:
msg = '[%s in %s] undisplayable exception' % (klass, view.__regid__)
- if output is not None:
- position = getattr(exc, "position", (0,))[0]
- if position:
- # define filter
- output = output.splitlines()
- width = int(log(len(output), 10)) + 1
- line_template = " %" + ("%i" % width) + "i: %s"
- # XXX no need to iterate the whole file except to get
- # the line number
- output = '\n'.join(line_template % (idx + 1, line)
- for idx, line in enumerate(output)
- if line_context_filter(idx+1, position))
- msg += '\nfor output:\n%s' % output
raise AssertionError, msg, tcbk
+ return self._check_html(output, view, template)
+ def get_validator(self, view=None, content_type=None, output=None):
+ if view is not None:
+ try:
+ return self.vid_validators[view.__regid__]()
+ except KeyError:
+ if content_type is None:
+ content_type = view.content_type
+ if content_type is None:
+ content_type = 'text/html'
+ if content_type in ('text/html', 'application/xhtml+xml'):
+ if output and output.startswith('<?xml'):
+ default_validator = htmlparser.DTDValidator
+ else:
+ default_validator = htmlparser.HTMLValidator
+ else:
+ default_validator = None
+ validatorclass = self.content_type_validators.get(content_type,
+ default_validator)
+ if validatorclass is None:
+ return
+ return validatorclass()
@nocoverage
def _check_html(self, output, view, template='main-template'):
"""raises an exception if the HTML is invalid"""
- try:
- validatorclass = self.vid_validators[view.__regid__]
- except KeyError:
- if view.content_type in ('text/html', 'application/xhtml+xml'):
- if template is None:
- default_validator = htmlparser.HTMLValidator
- else:
- default_validator = htmlparser.DTDValidator
- else:
- default_validator = None
- validatorclass = self.content_type_validators.get(view.content_type,
- default_validator)
- if validatorclass is None:
- return output.strip()
- validator = validatorclass()
+ output = output.strip()
+ validator = self.get_validator(view, output=output)
+ if validator is None:
+ return
if isinstance(validator, htmlparser.DTDValidator):
# XXX remove <canvas> used in progress widget, unknown in html dtd
output = re.sub('<canvas.*?></canvas>', '', output)
- return validator.parse_string(output.strip())
+ return self.assertWellFormed(validator, output.strip(), context= view.__regid__)
+
+ def assertWellFormed(self, validator, content, context=None):
+ try:
+ return validator.parse_string(content)
+ except (SystemExit, KeyboardInterrupt):
+ raise
+ except:
+ # hijack exception: generative tests stop when the exception
+ # is not an AssertionError
+ klass, exc, tcbk = sys.exc_info()
+ if context is None:
+ msg = u'[%s]' % (klass,)
+ else:
+ msg = u'[%s in %s]' % (klass, context)
+ msg = msg.encode(sys.getdefaultencoding(), 'replace')
+
+ try:
+ str_exc = str(exc)
+ except:
+ str_exc = 'undisplayable exception'
+ msg += str_exc
+ if content is not None:
+ position = getattr(exc, "position", (0,))[0]
+ if position:
+ # define filter
+ if isinstance(content, str):
+ content = unicode(content, sys.getdefaultencoding(), 'replace')
+ content = content.splitlines()
+ width = int(log(len(content), 10)) + 1
+ line_template = " %" + ("%i" % width) + "i: %s"
+ # XXX no need to iterate the whole file except to get
+ # the line number
+ content = u'\n'.join(line_template % (idx + 1, line)
+ for idx, line in enumerate(content)
+ if line_context_filter(idx+1, position))
+ msg += u'\nfor content:\n%s' % content
+ raise AssertionError, msg, tcbk
+
+ def assertDocTestFile(self, testfile):
+ # doctest returns tuple (failure_count, test_count)
+ result = self.shell().process_script(testfile)
+ if result[0] and result[1]:
+ raise self.failureException("doctest file '%s' failed"
+ % testfile)
+
+ # notifications ############################################################
+
+ def assertSentEmail(self, subject, recipients=None, nb_msgs=None):
+ """test recipients in system mailbox for given email subject
+
+ :param subject: email subject to find in mailbox
+ :param recipients: list of email recipients
+ :param nb_msgs: expected number of entries
+ :returns: list of matched emails
+ """
+ messages = [email for email in MAILBOX
+ if email.message.get('Subject') == subject]
+ if recipients is not None:
+ sent_to = set()
+ for msg in messages:
+ sent_to.update(msg.recipients)
+ self.assertSetEqual(set(recipients), sent_to)
+ if nb_msgs is not None:
+ self.assertEqual(len(MAILBOX), nb_msgs)
+ return messages
# deprecated ###############################################################
@@ -966,7 +1070,8 @@
for action in self.list_actions_for(rset):
yield InnerTest(self._testname(rset, action.__regid__, 'action'), self._test_action, action)
for box in self.list_boxes_for(rset):
- yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render)
+ w = [].append
+ yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render, w)
@staticmethod
def _testname(rset, objid, objtype):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/cubicweb-ctl.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,122 @@
+.. -*- coding: utf-8 -*-
+
+.. _cubicweb-ctl:
+
+``cubicweb-ctl`` tool
+=====================
+
+`cubicweb-ctl` is the swiss knife to manage *CubicWeb* instances.
+The general syntax is ::
+
+ cubicweb-ctl <command> [options command] <arguments commands>
+
+To view available commands ::
+
+ cubicweb-ctl
+ cubicweb-ctl --help
+
+Please note that the commands available depends on the *CubicWeb* packages
+and cubes that have been installed.
+
+To view the help menu on specific command ::
+
+ cubicweb-ctl <command> --help
+
+Listing available cubes and instance
+-------------------------------------
+
+* ``list``, provides a list of the available configuration, cubes
+ and instances.
+
+
+Creation of a new cube
+-----------------------
+
+Create your new cube cube ::
+
+ cubicweb-ctl newcube
+
+This will create a new cube in
+``/path/to/forest/cubicweb/cubes/<mycube>`` for a Mercurial forest
+installation, or in ``/usr/share/cubicweb/cubes`` for a debian
+packages installation.
+
+Create an instance
+-------------------
+
+You must ensure `~/cubicweb.d/` exists prior to this. On windows, the
+'~' part will probably expand to 'Documents and Settings/user'.
+
+To create an instance from an existing cube, execute the following
+command ::
+
+ cubicweb-ctl create <cube_name> <instance_name>
+
+This command will create the configuration files of an instance in
+``~/etc/cubicweb.d/<instance_name>``.
+
+The tool ``cubicweb-ctl`` executes the command ``db-create`` and
+``db-init`` when you run ``create`` so that you can complete an
+instance creation in a single command. But of course it is possible
+to issue these separate commands separately, at a later stage.
+
+Command to create/initialize an instance database
+-------------------------------------------------
+
+* ``db-create``, creates the system database of an instance (tables and
+ extensions only)
+* ``db-init``, initializes the system database of an instance
+ (schema, groups, users, workflows...)
+
+Commands to control instances
+-----------------------------
+
+* ``start``, starts one or more or all instances
+
+of special interest::
+
+ start -D
+
+will start in debug mode (under windows, starting without -D will not
+work; you need instead to setup your instance as a service).
+
+* ``stop``, stops one or more or all instances
+* ``restart``, restarts one or more or all instances
+* ``status``, returns the status of the instance(s)
+
+Commands to maintain instances
+------------------------------
+
+* ``upgrade``, launches the existing instances migration when a new version
+ of *CubicWeb* or the cubes installed is available
+* ``shell``, opens a (Python based) migration shell for manual maintenance of the instance
+* ``db-dump``, creates a dump of the system database
+* ``db-restore``, restores a dump of the system database
+* ``db-check``, checks data integrity of an instance. If the automatic correction
+ is activated, it is recommanded to create a dump before this operation.
+* ``schema-sync``, synchronizes the persistent schema of an instance with
+ the instance schema. It is recommanded to create a dump before this operation.
+
+Commands to maintain i18n catalogs
+----------------------------------
+* ``i18ncubicweb``, regenerates messages catalogs of the *CubicWeb* library
+* ``i18ncube``, regenerates the messages catalogs of a cube
+* ``i18ninstance``, recompiles the messages catalogs of an instance.
+ This is automatically done while upgrading.
+
+See also chapter :ref:`internationalization`.
+
+Other commands
+--------------
+* ``delete``, deletes an instance (configuration files and database)
+
+Command to create an instance for Google AppEngine datastore source
+-------------------------------------------------------------------
+* ``newgapp``, creates the configuration files for an instance
+
+This command needs to be followed by the commands responsible for
+the database initialization. As those are specific to the `datastore`,
+specific Google AppEgine database, they are not available for now
+in cubicweb-ctl, but they are available in the instance created.
+
+For more details, please see :ref:`GoogleAppEngineSource` .
--- a/doc/book/en/admin/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/admin/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -14,6 +14,7 @@
:numbered:
setup
+ cubicweb-ctl
create-instance
instance-config
site-config
@@ -23,16 +24,5 @@
gae
migration
additional-tips
-
-RQL logs
---------
+ rql-logs
-You can configure the *CubicWeb* instance to keep a log
-of the queries executed against your database. To do so,
-edit the configuration file of your instance
-``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the
-variable ``query-log-file``::
-
- # web instance query log file
- query-log-file=/tmp/rql-myapp.log
-
--- a/doc/book/en/admin/instance-config.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/admin/instance-config.rst Fri Mar 11 09:46:45 2011 +0100
@@ -4,6 +4,11 @@
Configure an instance
=====================
+On a Unix system, the instances are usually stored in the directory
+:file:`/etc/cubicweb.d/`. During development, the
+:file:`~/etc/cubicweb.d/` directory is looked up, as well as the paths
+in :envvar:`CW_INSTANCES_DIR` environment variable.
+
While creating an instance, a configuration file is generated in::
$ (CW_INSTANCES_DIR) / <instance> / <configuration name>.conf
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/rql-logs.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,14 @@
+.. -*- coding: utf-8 -*-
+
+RQL logs
+========
+
+You can configure the *CubicWeb* instance to keep a log
+of the queries executed against your database. To do so,
+edit the configuration file of your instance
+``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the
+variable ``query-log-file``::
+
+ # web instance query log file
+ query-log-file=/tmp/rql-myapp.log
+
--- a/doc/book/en/admin/setup.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/admin/setup.rst Fri Mar 11 09:46:45 2011 +0100
@@ -2,7 +2,7 @@
.. _SetUpEnv:
-Installation and set-up of a *CubicWeb* environment
+Installation and set-up of a |cubicweb| environment
===================================================
Installation of `Cubicweb` and its dependencies
@@ -68,8 +68,8 @@
`cubicweb with postgresql datatabase`_ and `cubicweb-mysql-support` contains
necessary dependency for using `cubicweb with mysql database`_ .
-There is also a wide variety of :ref:`cubes <Cubes>` listed on the `CubicWeb.org Forge`_
-available as debian packages and tarball.
+There is also a wide variety of :ref:`cubes <AvailableCubes>` listed on the
+`CubicWeb.org Forge`_ available as debian packages and tarball.
The repositories are signed with `Logilab's gnupg key`_. To avoid warning on
"apt-get update":
--- a/doc/book/en/annexes/cubicweb-ctl.rst Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubicweb-ctl:
-
-``cubicweb-ctl`` tool
-=====================
-
-`cubicweb-ctl` is the swiss knife to manage *CubicWeb* instances.
-The general syntax is ::
-
- cubicweb-ctl <command> [options command] <arguments commands>
-
-To view available commands ::
-
- cubicweb-ctl
- cubicweb-ctl --help
-
-Please note that the commands available depends on the *CubicWeb* packages
-and cubes that have been installed.
-
-To view the help menu on specific command ::
-
- cubicweb-ctl <command> --help
-
-Listing available cubes and instance
--------------------------------------
-
-* ``list``, provides a list of the available configuration, cubes
- and instances.
-
-
-Creation of a new cube
------------------------
-
-Create your new cube cube ::
-
- cubicweb-ctl newcube
-
-This will create a new cube in
-``/path/to/forest/cubicweb/cubes/<mycube>`` for a Mercurial forest
-installation, or in ``/usr/share/cubicweb/cubes`` for a debian
-packages installation.
-
-Create an instance
--------------------
-
-You must ensure `~/cubicweb.d/` exists prior to this. On windows, the
-'~' part will probably expand to 'Documents and Settings/user'.
-
-To create an instance from an existing cube, execute the following
-command ::
-
- cubicweb-ctl create <cube_name> <instance_name>
-
-This command will create the configuration files of an instance in
-``~/etc/cubicweb.d/<instance_name>``.
-
-The tool ``cubicweb-ctl`` executes the command ``db-create`` and
-``db-init`` when you run ``create`` so that you can complete an
-instance creation in a single command. But of course it is possible
-to issue these separate commands separately, at a later stage.
-
-Command to create/initialize an instance database
--------------------------------------------------
-
-* ``db-create``, creates the system database of an instance (tables and
- extensions only)
-* ``db-init``, initializes the system database of an instance
- (schema, groups, users, workflows...)
-
-Commands to control instances
------------------------------
-
-* ``start``, starts one or more or all instances
-
-of special interest::
-
- start -D
-
-will start in debug mode (under windows, starting without -D will not
-work; you need instead to setup your instance as a service).
-
-* ``stop``, stops one or more or all instances
-* ``restart``, restarts one or more or all instances
-* ``status``, returns the status of the instance(s)
-
-Commands to maintain instances
-------------------------------
-
-* ``upgrade``, launches the existing instances migration when a new version
- of *CubicWeb* or the cubes installed is available
-* ``shell``, opens a migration shell for manual maintenance of the instance
-* ``db-dump``, creates a dump of the system database
-* ``db-restore``, restores a dump of the system database
-* ``db-check``, checks data integrity of an instance. If the automatic correction
- is activated, it is recommanded to create a dump before this operation.
-* ``schema-sync``, synchronizes the persistent schema of an instance with
- the instance schema. It is recommanded to create a dump before this operation.
-
-Commands to maintain i18n catalogs
-----------------------------------
-* ``i18ncubicweb``, regenerates messages catalogs of the *CubicWeb* library
-* ``i18ncube``, regenerates the messages catalogs of a cube
-* ``i18ninstance``, recompiles the messages catalogs of an instance.
- This is automatically done while upgrading.
-
-See also chapter :ref:`internationalization`.
-
-Other commands
---------------
-* ``delete``, deletes an instance (configuration files and database)
-
-Command to create an instance for Google AppEngine datastore source
--------------------------------------------------------------------
-* ``newgapp``, creates the configuration files for an instance
-
-This command needs to be followed by the commands responsible for
-the database initialization. As those are specific to the `datastore`,
-specific Google AppEgine database, they are not available for now
-in cubicweb-ctl, but they are available in the instance created.
-
-For more details, please see :ref:`GoogleAppEngineSource` .
--- a/doc/book/en/annexes/faq.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/annexes/faq.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,7 +1,7 @@
.. -*- coding: utf-8 -*-
-Frequently Asked Questions
-==========================
+Frequently Asked Questions (FAQ)
+================================
[XXX 'copy answer from forum' means reusing text from
http://groups.google.com/group/google-appengine/browse_frm/thread/c9476925f5f66ec6
@@ -73,7 +73,7 @@
Why is the RQL query language looking similar to X ?
------------------------------------------------------
+----------------------------------------------------
It may remind you of SQL but it is higher level than SQL, more like
SPARQL. Except that SPARQL did not exist when we started the project.
@@ -97,13 +97,57 @@
Which ajax library is CubicWeb using ?
--------------------------------------
-CubicWeb uses jQuery and provides a few helpers on top of
-that. Additionally, some jQuery plugins are provided (some are
-provided in specific cubes).
+CubicWeb uses jQuery_ and provides a few helpers on top of that. Additionally,
+some jQuery plugins are provided (some are provided in specific cubes).
+
+.. _jQuery: http://jquery.com
+
Development
```````````
+How to change the instance logo ?
+---------------------------------
+
+There are two ways of changing the logo.
+
+1. The easiest way to use a different logo is to replace the existing
+ ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
+ By default all instance will look for a ``logo.png`` to be
+ rendered in the logo section.
+
+ .. image:: ../images/lax-book_06-main-template-logo_en.png
+
+2. In your cube directory, you can specify which file to use for the logo.
+ This is configurable in ``mycube/uiprops.py``: ::
+
+ LOGO = data('mylogo.gif')
+
+ ``mylogo.gif`` is in ``mycube/data`` directory.
+
+How to create an anonymous user ?
+---------------------------------
+
+This allows to browse the site without being authenticated. In the
+``all-in-one.conf`` file of your instance, define the anonymous user
+as follows ::
+
+ # login of the CubicWeb user account to use for anonymous user (if you want to
+ # allow anonymous)
+ anonymous-user=anon
+
+ # password of the CubicWeb user account matching login
+ anonymous-password=anon
+
+You also must ensure that this `anon` user is a registered user of
+the DB backend. If not, you can create through the administation
+interface of your instance by adding a user with in the group `guests`.
+
+.. note::
+ While creating a new instance, you can decide to allow access
+ to anonymous user, which will automatically execute what is
+ decribed above.
+
How to load data from a script ?
--------------------------------
@@ -117,42 +161,29 @@
cnx = dbapi.connect(database='instance-id', user='admin', password='admin')
cur = cnx.cursor()
- for name in ('Personal', 'Professional', 'Computers'):
- cur.execute('INSERT Blog B: B name %s', name)
+ for name in (u'Personal', u'Professional', u'Computers'):
+ cur.execute('INSERT Tag T: T name %(n)s', {'n': name})
cnx.commit()
+Wether your instance as pyro activated or not, you can still acheive this by
+using cubicweb-ctl shell scripts.
How to format an entity date attribute ?
----------------------------------------
-If your schema has an attribute of type Date or Datetime, you might
-want to format it. First, you should define your preferred format using
-the site configuration panel ``http://appurl/view?vid=systempropertiesform``
-and then set ``ui.date`` and/or ``ui.datetime``.
-Then in the view code, use:
+If your schema has an attribute of type `Date` or `Datetime`, you usually want to
+format it when displaying it. First, you should define your preferred format
+using the site configuration panel
+``http://appurl/view?vid=systempropertiesform`` and then set ``ui.date`` and/or
+``ui.datetime``. Then in the view code, use:
.. sourcecode:: python
- self.format_date(entity.date_attribute)
-
-What is the CubicWeb datatype corresponding to GAE datastore's UserProperty ?
------------------------------------------------------------------------------
+ entity.printable_value(date_attribute)
-If you take a look at your instance schema and
-click on "display detailed view of metadata" you will see that there
-is a Euser entity in there. That's the one that is modeling users. The
-thing that corresponds to a UserProperty is a relationship between
-your entity and the Euser entity. As in:
-
-.. sourcecode:: python
-
- class TodoItem(EntityType):
- text = String()
- todo_by = SubjectRelation('Euser')
-
-[XXX check that cw handle users better by mapping Google Accounts to local Euser
-entities automatically]
-
+which will always return a string whatever the attribute's type (so it's
+recommended also for other attribute types). By default it expects to generate
+HTML, so it deals with rich text formating, xml escaping...
How do I translate an msg id defined (and translated) in another cube ?
-----------------------------------------------------------------------
@@ -160,46 +191,6 @@
You should put these translations in the `i18n/static-messages.pot`
file of your own cube.
-
-What is `Error while publishing rest text ...` ?
-------------------------------------------------
-
-While modifying the description of an entity, you get an error message in
-the instance `Error while publishing ...` for Rest text and plain text.
-The server returns a traceback like as follows ::
-
- 2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text
- Traceback (most recent call last):
- File "/home/user/src/blogdemo/cubicweb/common/rest.py", line 217, in rest_publish
- File "/usr/lib/python2.5/codecs.py", line 817, in open
- file = __builtin__.open(filename, mode, buffering)
- TypeError: __init__() takes at most 3 arguments (4 given)
-
-This can be fixed by applying the patch described in :
-http://code.google.com/p/googleappengine/issues/detail?id=48
-
-What are hooks used for ?
--------------------------
-
-Hooks are executed around (actually before or after) events. The
-most common events are data creation, update and deletion. They
-permit additional constraint checking (those not expressible at the
-schema level), pre and post computations depending on data
-movements.
-
-As such, they are a vital part of the framework.
-
-Other kinds of hooks, called Operations, are available
-for execution just before commit.
-
-When should you define an HTML template rather than define a graphical component ?
-----------------------------------------------------------------------------------
-
-An HTML template cannot contain code, hence it is only about static
-content. A component is made of code and operations that apply on a
-well defined context (request, result set). It enables much more
-dynamic views.
-
How to update a database after a schema modification ?
------------------------------------------------------
@@ -212,51 +203,76 @@
* add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
+I get `NoSelectableObject` exceptions, how do I debug selectors ?
+-----------------------------------------------------------------
-How to create an anonymous user ?
----------------------------------
+You just need to put the appropriate context manager around view/component
+selection. One standard place for components is in cubicweb/vregistry.py:
+
+.. sourcecode:: python
-This allows to bypass authentication for your site. In the
-``all-in-one.conf`` file of your instance, define the anonymous user
-as follows ::
+ def possible_objects(self, *args, **kwargs):
+ """return an iterator on possible objects in this registry for the given
+ context
+ """
+ from cubicweb.selectors import traced_selection
+ with traced_selection():
+ for appobjects in self.itervalues():
+ try:
+ yield self._select_best(appobjects, *args, **kwargs)
+ except NoSelectableObject:
+ continue
- # login of the CubicWeb user account to use for anonymous user (if you want to
- # allow anonymous)
- anonymous-user=anon
+Don't forget the 'from __future__ import with_statement' at the module
+top-level if you're using python 2.5.
+
+This will yield additional WARNINGs, like this::
+
+ 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
- # password of the CubicWeb user account matching login
- anonymous-password=anon
+For views, you can put this context in `cubicweb/web/views/basecontrollers.py` in
+the `ViewController`:
+
+.. sourcecode:: python
-You also must ensure that this `anon` user is a registered user of
-the DB backend. If not, you can create through the administation
-interface of your instance by adding a user with the role `guests`.
-This could be the admin account (for development
-purposes, of course).
+ def _select_view_and_rset(self, rset):
+ ...
+ try:
+ from cubicweb.selectors import traced_selection
+ with traced_selection():
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
+ except ObjectNotFound:
+ self.warning("the view %s could not be found", vid)
+ req.set_message(req._("The view %s could not be found") % vid)
+ vid = vid_from_rset(req, rset, self._cw.vreg.schema)
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
+ ...
-.. note::
- While creating a new instance, you can decide to allow access
- to anonymous user, which will automatically execute what is
- decribed above.
+I get "database is locked" when executing tests
+-----------------------------------------------
+
+If you have "database is locked" as error when you are executing security tests,
+it is usually because commit or rollback are missing before login() calls.
+
+You can also use a context manager, to avoid such errors, as described
+here: :ref:`securitytest`.
-How to change the instance logo ?
-------------------------------------
+What are hooks used for ?
+-------------------------
-There are two ways of changing the logo.
+Hooks are executed around (actually before or after) events. The most common
+events are data creation, update and deletion. They permit additional constraint
+checking (those not expressible at the schema level), pre and post computations
+depending on data movements.
-1. The easiest way to use a different logo is to replace the existing
- ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
- By default all instance will look for a ``logo.png`` to be
- rendered in the logo section.
-
- .. image:: ../images/lax-book_06-main-template-logo_en.png
+As such, they are a vital part of the framework.
-2. In your cube directory, you can specify which file to use for the logo.
- This is configurable in ``mycube/data/external_resources``: ::
+Other kinds of hooks, called Operations, are available
+for execution just before commit.
- LOGO = DATADIR/path/to/mylogo.gif
+For more information, read :ref:`hooks` section.
- where DATADIR is ``mycube/data``.
Configuration
`````````````
@@ -264,31 +280,7 @@
How to configure a LDAP source ?
--------------------------------
-Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
-Configuring an LDAP source is about declaring that source in your
-instance configuration file such as: ::
-
- [ldapuser]
- adapter=ldapuser
- # ldap host
- host=myhost
- # base DN to lookup for usres
- user-base-dn=ou=People,dc=mydomain,dc=fr
- # user search scope
- user-scope=ONELEVEL
- # classes of user
- user-classes=top,posixAccount
- # attribute used as login on authentication
- user-login-attr=uid
- # name of a group in which ldap users will be by default
- user-default-group=users
- # map from ldap user attributes to cubicweb attributes
- user-attrs-map=gecos:email,uid:login
-
-Any change applied to configuration file requires to restart your
-instance.
-
-You can find additional information in the section :ref:`LDAP`.
+See :ref:`LDAP`.
How to import LDAP users in |cubicweb| ?
----------------------------------------
@@ -350,34 +342,6 @@
cnx.close()
-I get NoSelectableObject exceptions, how do I debug selectors ?
----------------------------------------------------------------
-
-You just need to put the appropriate context manager around view/component
-selection (one standard place in in vreg.py):
-
-.. sourcecode:: python
-
- def possible_objects(self, registry, *args, **kwargs):
- """return an iterator on possible objects in a registry for this result set
-
- actions returned are classes, not instances
- """
- from cubicweb.selectors import traced_selection
- with traced_selection():
- for vobjects in self.registry(registry).values():
- try:
- yield self.select(vobjects, *args, **kwargs)
- except NoSelectableObject:
- continue
-
-Don't forget the 'from __future__ import with_statement' at the module
-top-level.
-
-This will yield additional WARNINGs, like this::
-
- 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
-
Security
````````
@@ -410,7 +374,7 @@
Be careful, the encryption algorithm is different on Windows and on
Unix. You cannot therefore use a hash generated on Unix to fill in a
-Windows database, nor the other way round.
+Windows database, nor the other way round.
You can prefer use a migration script similar to this shell invocation instead::
@@ -434,7 +398,8 @@
remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
-This is because you have to put your user in the "users" group. The user has to be in both groups.
+This is because you have to put your user in the "users" group. The user has to
+be in both groups.
How is security implemented ?
------------------------------
@@ -491,9 +456,7 @@
Is it possible to bypass security from the UI (web front) part ?
----------------------------------------------------------------
-No.
-
-Only Hooks/Operations can do that.
+No. Only Hooks/Operations can do that.
Can PostgreSQL and CubicWeb authentication work with kerberos ?
----------------------------------------------------------------
--- a/doc/book/en/annexes/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/annexes/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -13,9 +13,7 @@
:numbered:
faq
- cubicweb-ctl
rql/index
mercurial
depends
- javascript-api
docstrings-conventions
--- a/doc/book/en/annexes/rql/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/annexes/rql/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-.. _RQLChapter
+.. _RQLChapter:
Relation Query Language (RQL)
=============================
--- a/doc/book/en/devrepo/cubes/available-cubes.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/cubes/available-cubes.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,4 @@
+.. _AvailableCubes:
Available cubes
---------------
--- a/doc/book/en/devrepo/cubes/cc-newcube.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/cubes/cc-newcube.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,5 +1,5 @@
-Creating a new cube from scratch using :command:`cubicweb-ctl newcube`
-----------------------------------------------------------------------
+Creating a new cube from scratch
+--------------------------------
Let's start by creating the cube environment in which we will develop ::
@@ -14,7 +14,7 @@
hg ci
If all went well, you should see the cube you just created in the list
-returned by ``cubicweb-ctl list`` in the *Available cubes* section.
+returned by ``cubicweb-ctl list`` in the *Available cubes* section.
If not, please refer to :ref:`ConfigurationEnv`.
To reuse an existing cube, add it to the list named
@@ -24,6 +24,14 @@
database for the instance is created (import_erschema('MyCube') will
not properly work otherwise).
+On a Unix system, the available cubes are usually stored in the
+directory :file:`/usr/share/cubicweb/cubes`. If you are using the
+cubicweb mercurial repository (:ref:`SourceInstallation`), the cubes
+are searched in the directory
+:file:`/path/to/cubicweb_toplevel/cubes`. In this configuration
+cubicweb itself ought to be located at
+:file:`/path/to/cubicweb_toplevel/cubicweb`.
+
.. note::
Please note that if you do not wish to use default directory for your cubes
--- a/doc/book/en/devrepo/datamodel/define-workflows.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/datamodel/define-workflows.rst Fri Mar 11 09:46:45 2011 +0100
@@ -8,14 +8,13 @@
General
-------
-A workflow describes how certain entities have to evolve between
-different states. Hence we have a set of states, and a "transition
-graph", i.e. a set of possible transitions from one state to another
-state.
+A workflow describes how certain entities have to evolve between different
+states. Hence we have a set of states, and a "transition graph", i.e. a set of
+possible transitions from one state to another state.
-We will define a simple workflow for a blog, with only the following
-two states: `submitted` and `published`. So first, we create a simple
-|cubicweb| instance in five minutes (see :ref:`BlogFiveMinutes`).
+We will define a simple workflow for a blog, with only the following two states:
+`submitted` and `published`. You may want to take a look at :ref:`_TutosBase` if
+you want to quickly setup an instance running a blog.
Setting up a workflow
---------------------
--- a/doc/book/en/devrepo/datamodel/definition.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/datamodel/definition.rst Fri Mar 11 09:46:45 2011 +0100
@@ -304,7 +304,7 @@
* we associate rights at the entities/relations schema level
-* the default groups are: `administrators`, `users` and `guests`
+* the default groups are: `managers`, `users` and `guests`
* users belong to the `users` group
@@ -334,6 +334,34 @@
provided if the user is in one of the listed groups or if one of the RQL condition
is satisfied.
+Default permissions
+```````````````````
+
+The default permissions for ``EntityType`` are:
+
+.. sourcecode:: python
+
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ 'add': ('managers', 'users',)
+ }
+
+The default permissions for relations are:
+
+.. sourcecode:: python
+
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
+ 'delete': ('managers', 'users'),
+ 'add': ('managers', 'users',)}
+
+The default permissions for attributes are:
+
+.. sourcecode:: python
+
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
+ 'update': ('managers', ERQLExpression('U has_update_permission X')),}
The standard user groups
````````````````````````
--- a/doc/book/en/devrepo/devcore/cwconfig.rst Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-Configuration
--------------
-
-.. automodule:: cubicweb.cwconfig
- :members:
--- a/doc/book/en/devrepo/devcore/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/devcore/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -6,5 +6,4 @@
dbapi.rst
reqbase.rst
- cwconfig.rst
--- a/doc/book/en/devrepo/repo/hooks.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/repo/hooks.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,162 +1,27 @@
.. -*- coding: utf-8 -*-
-
.. _hooks:
Hooks and Operations
====================
-Generalities
-------------
-
-Paraphrasing the `emacs`_ documentation, let us say that hooks are an
-important mechanism for customizing an application. A hook is
-basically a list of functions to be called on some well-defined
-occasion (this is called `running the hook`).
-
-.. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
-
-In CubicWeb, hooks are subclasses of the Hook class in
-`server/hook.py`, implementing their own `call` method, and selected
-over a set of pre-defined `events` (and possibly more conditions,
-hooks being selectable AppObjects like views and components).
-
-There are two families of events: data events and server events. In a
-typical application, most of the Hooks are defined over data
-events.
-
-The purpose of data hooks is to complement the data model as defined
-in the schema.py, which is static by nature, with dynamic or value
-driven behaviours. It is functionally equivalent to a `database
-trigger`_, except that database triggers definition languages are not
-standardized, hence not portable (for instance, PL/SQL works with
-Oracle and PostgreSQL but not SqlServer nor Sqlite).
-
-.. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
-
-Data hooks can serve the following purposes:
-
-* enforcing constraints that the static schema cannot express
- (spanning several entities/relations, exotic value ranges and
- cardinalities, etc.)
-
-* implement computed attributes
-
-Operations are Hook-like objects that may be created by Hooks and
-scheduled to happen just before (or after) the `commit` event. Hooks
-being fired immediately on data operations, it is sometime necessary
-to delay the actual work down to a time where all other Hooks have
-run, for instance a validation check which needs that all relations be
-already set on an entity. Also while the order of execution of Hooks
-is data dependant (and thus hard to predict), it is possible to force
-an order on Operations.
-
-Operations also may be used to process various side effects associated
-with a transaction such as filesystem udpates, mail notifications,
-etc.
-
-Operations are subclasses of the Operation class in `server/hook.py`,
-implementing `precommit_event` and other standard methods (wholly
-described in :ref:`operations_api`).
-
-.. hint::
-
- It is a good practice, to write unit tests for each hook. See an example in :ref:`hook_test`
-
-Events
-------
-
-Hooks are mostly defined and used to handle `dataflow`_ operations. It
-means as data gets in (entities added, updated, relations set or
-unset), specific events are issued and the Hooks matching these events
-are called.
-
-.. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
-
-Below comes a list of the dataflow events related to entities operations:
-
-* before_add_entity
-
-* before_update_entity
-
-* before_delete_entity
-
-* after_add_entity
-
-* after_update_entity
-
-* after_delete_entity
-
-These define ENTTIES HOOKS. RELATIONS HOOKS are defined
-over the following events:
-
-* after_add_relation
-
-* after_delete_relation
-
-* before_add_relation
-
-* before_delete_relation
-
-This is an occasion to remind us that relations support the add/delete
-operation, but no update.
-
-Non data events also exist. These are called SYSTEM HOOKS.
-
-* server_startup
-
-* server_shutdown
-
-* server_maintenance
-
-* server_backup
-
-* server_restore
-
-* session_open
-
-* session_close
+.. autodocstring:: cubicweb.server.hook
-Using dataflow Hooks
---------------------
-
-Dataflow hooks either automate data operations or maintain the
-consistency of the data model. In the later case, we must use a
-specific exception named ValidationError
-
-Validation Errors
-~~~~~~~~~~~~~~~~~
-
-When a condition is not met in a Hook/Operation, it must raise a
-`ValidationError`. Raising anything but a (subclass of)
-ValidationError is a programming error. Raising a ValidationError
-entails aborting the current transaction.
+Example using dataflow hooks
+----------------------------
-The ValidationError exception is used to convey enough information up
-to the user interface. Hence its constructor is different from the
-default Exception constructor. It accepts, positionally:
-
-* an entity eid,
-
-* a dict whose keys represent attribute (or relation) names and values
- an end-user facing message (hence properly translated) relating the
- problem.
-
-An entity hook
-~~~~~~~~~~~~~~
-
-We will use a very simple example to show hooks usage. Let us start
-with the following schema.
+We will use a very simple example to show hooks usage. Let us start with the
+following schema.
.. sourcecode:: python
class Person(EntityType):
age = Int(required=True)
-We would like to add a range constraint over a person's age. Let's
-write an hook. It shall be placed into mycube/hooks.py. If this file
-were to grow too much, we can easily have a mycube/hooks/... package
-containing hooks in various modules.
+We would like to add a range constraint over a person's age. Let's write an hook
+(supposing yams can not handle this nativly, which is wrong). It shall be placed
+into `mycube/hooks.py`. If this file were to grow too much, we can easily have a
+`mycube/hooks/... package` containing hooks in various modules.
.. sourcecode:: python
@@ -166,68 +31,30 @@
class PersonAgeRange(Hook):
__regid__ = 'person_age_range'
+ __select__ = Hook.__select__ & is_instance('Person')
events = ('before_add_entity', 'before_update_entity')
- __select__ = Hook.__select__ & is_instance('Person')
def __call__(self):
- if 0 >= self.entity.age <= 120:
- return
- msg = self._cw._('age must be between 0 and 120')
- raise ValidationError(self.entity.eid, {'age': msg})
-
-Hooks being AppObjects like views, they have a __regid__ and a
-__select__ class attribute. The base __select__ is augmented with an
-`is_instance` selector matching the desired entity type. The `events`
-tuple is used by the Hook.__select__ base selector to dispatch the
-hook on the right events. In an entity hook, it is possible to
-dispatch on any entity event (e.g. 'before_add_entity',
-'before_update_entity') at once if needed.
+ if 'age' in self.entity.cw_edited:
+ if 0 <= self.entity.age <= 120:
+ return
+ msg = self._cw._('age must be between 0 and 120')
+ raise ValidationError(self.entity.eid, {'age': msg})
-Like all appobjects, hooks have the `self._cw` attribute which
-represents the current session. In entity hooks, a `self.entity`
-attribute is also present.
-
-
-A relation hook
-~~~~~~~~~~~~~~~
-
-Let us add another entity type with a relation to person (in
-mycube/schema.py).
-
-.. sourcecode:: python
+In our example the base `__select__` is augmented with an `is_instance` selector
+matching the desired entity type.
- class Company(EntityType):
- name = String(required=True)
- boss = SubjectRelation('Person', cardinality='1*')
+The `events` tuple is used specify that our hook should be called before the
+entity is added or updated.
-We would like to constrain the company's bosses to have a minimum
-(legal) age. Let's write an hook for this, which will be fired when
-the `boss` relation is established.
-
-.. sourcecode:: python
-
- class CompanyBossLegalAge(Hook):
- __regid__ = 'company_boss_legal_age'
- events = ('before_add_relation',)
- __select__ = Hook.__select__ & match_rtype('boss')
+Then in the hook's `__call__` method, we:
- def __call__(self):
- boss = self._cw.entity_from_eid(self.eidto)
- if boss.age < 18:
- msg = self._cw._('the minimum age for a boss is 18')
- raise ValidationError(self.eidfrom, {'boss': msg})
-
-We use the `match_rtype` selector to select the proper relation type.
+* check if the 'age' attribute is edited
+* if so, check the value is in the range
+* if not, raise a validation error properly
-The essential difference with respect to an entity hook is that there
-is no self.entity, but `self.eidfrom` and `self.eidto` hook attributes
-which represent the subject and object eid of the relation.
-
-
-Using Operations
-----------------
-
-Let's augment our example with a new `subsidiary_of` relation on Company.
+Now Let's augment our schema with new `Company` entity type with some relation to
+`Person` (in 'mycube/schema.py').
.. sourcecode:: python
@@ -236,16 +63,41 @@
boss = SubjectRelation('Person', cardinality='1*')
subsidiary_of = SubjectRelation('Company', cardinality='*?')
-Base example
-~~~~~~~~~~~~
-We would like to check that there is no cycle by the `subsidiary_of`
-relation. This is best achieved in an Operation since all relations
-are likely to be set at commit time.
+We would like to constrain the company's bosses to have a minimum (legal)
+age. Let's write an hook for this, which will be fired when the `boss` relation
+is established (still supposing we could not specify that kind of thing in the
+schema).
.. sourcecode:: python
- from cubicweb.server.hook import Hook, Operation, match_rtype
+ class CompanyBossLegalAge(Hook):
+ __regid__ = 'company_boss_legal_age'
+ __select__ = Hook.__select__ & match_rtype('boss')
+ events = ('before_add_relation',)
+
+ def __call__(self):
+ boss = self._cw.entity_from_eid(self.eidto)
+ if boss.age < 18:
+ msg = self._cw._('the minimum age for a boss is 18')
+ raise ValidationError(self.eidfrom, {'boss': msg})
+
+.. Note::
+
+ We use the :class:`~cubicweb.server.hook.match_rtype` selector to select the
+ proper relation type.
+
+ The essential difference with respect to an entity hook is that there is no
+ self.entity, but `self.eidfrom` and `self.eidto` hook attributes which
+ represent the subject and object **eid** of the relation.
+
+Suppose we want to check that there is no cycle by the `subsidiary_of`
+relation. This is best achieved in an operation since all relations are likely to
+be set at commit time.
+
+.. sourcecode:: python
+
+ from cubicweb.server.hook import Hook, DataOperationMixIn, Operation, match_rtype
def check_cycle(self, session, eid, rtype, role='subject'):
parents = set([eid])
@@ -257,7 +109,8 @@
raise ValidationError(eid, {rtype: msg})
parents.add(parent.eid)
- class CheckSubsidiaryCycleOp(Operation):
+
+ class CheckSubsidiaryCycleOp(DataOperationMixIn, Operation):
def precommit_event(self):
check_cycle(self.session, self.eidto, 'subsidiary_of')
@@ -265,30 +118,20 @@
class CheckSubsidiaryCycleHook(Hook):
__regid__ = 'check_no_subsidiary_cycle'
+ __select__ = Hook.__select__ & match_rtype('subsidiary_of')
events = ('after_add_relation',)
- __select__ = Hook.__select__ & match_rtype('subsidiary_of')
def __call__(self):
CheckSubsidiaryCycleOp(self._cw, eidto=self.eidto)
-The operation is instantiated in the Hook.__call__ method.
-An operation always takes a session object as first argument
-(accessible as `.session` from the operation instance), and optionally
-all keyword arguments needed by the operation. These keyword arguments
-will be accessible as attributes from the operation instance.
+Like in hooks, :exc:`~cubicweb.ValidationError` can be raised in operations. Other
+exceptions are usually programming errors.
-Like in Hooks, ValidationError can be raised in Operations. Other
-exceptions are programming errors.
-
-Notice how our hook will instantiate an operation each time the Hook
-is called, i.e. each time the `subsidiary_of` relation is set.
-
-Using set_operation
-~~~~~~~~~~~~~~~~~~~
-
-There is an alternative method to schedule an Operation from a Hook,
-using the `set_operation` function.
+In the above example, our hook will instantiate an operation each time the hook
+is called, i.e. each time the `subsidiary_of` relation is set. There is an
+alternative method to schedule an operation from a hook, using the
+:func:`get_instance` class method.
.. sourcecode:: python
@@ -300,143 +143,98 @@
__select__ = Hook.__select__ & match_rtype('subsidiary_of')
def __call__(self):
- set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto,
- CheckSubsidiaryCycleOp, rtype=self.rtype)
+ CheckSubsidiaryCycleOp.get_instance(self._cw).add_data(self.eidto)
class CheckSubsidiaryCycleOp(Operation):
def precommit_event(self):
- for eid in self.session.transaction_data['subsidiary_cycle_detection']:
+ for eid in self.get_data():
check_cycle(self.session, eid, self.rtype)
-Here, we call set_operation with a session object, a specially forged
-key, a value that is the actual payload of an individual operation (in
-our case, the object of the subsidiary_of relation) , the class of the
-Operation, and more optional parameters to give to the operation (here
-the rtype which do not vary accross operations).
-
-The body of the operation must then iterate over the values that have
-been mapped in the transaction_data dictionary to the forged key.
-This mechanism is especially useful on two occasions (not shown in our
-example):
+Here, we call :func:`set_operation` so that we will simply accumulate eids of
+entities to check at the end in a single `CheckSubsidiaryCycleOp`
+operation. Value are stored in a set associated to the
+'subsidiary_cycle_detection' transaction data key. The set initialization and
+operation creation are handled nicely by :func:`set_operation`.
-* massive data import (reduced memory consumption within a large
- transaction)
+A more realistic example can be found in the advanced tutorial chapter
+:ref:`adv_tuto_security_propagation`.
-* when several hooks need to instantiate the same operation (e.g. an
- entity and a relation hook).
-
-.. note::
- A more realistic example can be found in the advanced tutorial
- chapter :ref:`adv_tuto_security_propagation`.
+Hooks writing tips
+------------------
-.. _operations_api:
-
-Operation: a small API overview
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Reminder
+~~~~~~~~
-.. autoclass:: cubicweb.server.hook.Operation
-.. autoclass:: cubicweb.server.hook.LateOperation
-.. autofunction:: cubicweb.server.hook.set_operation
+You should never use the `entity.foo = 42` notation to update an
+entity. It will not do what you expect (updating the
+database). Instead, use the :meth:`set_attributes` and
+:meth:`set_relations` methods.
-Hooks writing rules
--------------------
-
-Remainder
-~~~~~~~~~
-
-Never, ever use the `entity.foo = 42` notation to update an entity. It
-will not work.
How to choose between a before and an after event ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Before hooks give you access to the old attribute (or relation)
-values. By definition the database is not yet updated in a before
-hook.
+`before_*` hooks give you access to the old attribute (or relation)
+values. You can also intercept and update edited values in the case of
+entity modification before they reach the database.
+
+Else the question is: should I need to do things before or after the actual
+modification ? If the answer is "it doesn't matter", use an 'after' event.
+
+
+Validation Errors
+~~~~~~~~~~~~~~~~~
-To access old and new values in an before_update_entity hook, one can
-use the `server.hook.entity_oldnewvalue` function which returns a
-tuple of the old and new values. This function takes an entity and an
-attribute name as parameters.
+When a hook which is responsible to maintain the consistency of the
+data model detects an error, it must use a specific exception named
+:exc:`~cubicweb.ValidationError`. Raising anything but a (subclass of)
+:exc:`~cubicweb.ValidationError` is a programming error. Raising it
+entails aborting the current transaction.
-In a 'before_add|update_entity' hook the self.entity contains the new
-values. One is allowed to further modify them before database
-operations, using the dictionary notation.
+This exception is used to convey enough information up to the user
+interface. Hence its constructor is different from the default Exception
+constructor. It accepts, positionally:
+
+* an entity eid,
+
+* a dict whose keys represent attribute (or relation) names and values
+ an end-user facing message (hence properly translated) relating the
+ problem.
.. sourcecode:: python
- self.entity['age'] = 42
-
-This is because using self.entity.set_attributes(age=42) will
-immediately update the database (which does not make sense in a
-pre-database hook), and will trigger any existing
-before_add|update_entity hook, thus leading to infinite hook loops or
-such awkward situations.
+ raise ValidationError(earth.eid, {'sea_level': self._cw._('too high'),
+ 'temperature': self._cw._('too hot')})
-Beyond these specific cases, updating an entity attribute or relation
-must *always* be done using `set_attributes` and `set_relations`
-methods.
-(Of course, ValidationError will always abort the current transaction,
-whetever the event).
-
-Peculiarities of inlined relations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Checking for object created/deleted in the current transaction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Some relations are defined in the schema as `inlined` (see
-:ref:`RelationType` for details). In this case, they are inserted in
-the database at the same time as entity attributes.
-
-Hence in the case of before_add_relation, such relations already exist
-in the database.
-
-Edited attributes
-~~~~~~~~~~~~~~~~~
+In hooks, you can use the
+:meth:`~cubicweb.server.session.Session.added_in_transaction` or
+:meth:`~cubicweb.server.session.Session.deleted_in_transaction` of the session
+object to check if an eid has been created or deleted during the hook's
+transaction.
-On udpates, it is possible to ask the `entity.edited_attributes`
-variable whether one attribute has been updated.
-
-.. sourcecode:: python
-
- if 'age' not in entity.edited_attribute:
- return
-
-Deleted in transaction
-~~~~~~~~~~~~~~~~~~~~~~
-
-The session object has a deleted_in_transaction method, which can help
-writing deletion Hooks.
+This is useful to enable or disable some stuff if some entity is being added or
+deleted.
.. sourcecode:: python
if self._cw.deleted_in_transaction(self.eidto):
return
-Given this predicate, we can avoid scheduling an operation.
-Disabling hooks
-~~~~~~~~~~~~~~~
-
-It is sometimes convenient to disable some hooks. For instance to
-avoid infinite Hook loops. One uses the `hooks_control` context
-manager.
-
-This can be controlled more finely through the `category` Hook class
-attribute, which is a string.
+Peculiarities of inlined relations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. sourcecode:: python
-
- with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, <category>):
- # ... do stuff
-
-.. autoclass:: cubicweb.server.session.hooks_control
+Relations which are defined in the schema as `inlined` (see :ref:`RelationType`
+for details) are inserted in the database at the same time as entity attributes.
-The existing categories are: ``email``, ``syncsession``,
-``syncschema``, ``bookmark``, ``security``, ``worfklow``,
-``metadata``, ``notification``, ``integrity``, ``activeintegrity``.
-
-Nothing precludes one to invent new categories and use the
-hooks_control context manager to filter them (in or out).
+This may have some side effect, for instance when creating an entity
+and setting an inlined relation in the same rql query, then at
+`before_add_relation` time, the relation will already exist in the
+database (it is otherwise not the case).
--- a/doc/book/en/devrepo/testing.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/testing.rst Fri Mar 11 09:46:45 2011 +0100
@@ -109,6 +109,8 @@
.. _apycot: http://www.logilab.org/project/apycot
+.. _securitytest:
+
Managing connections or users
+++++++++++++++++++++++++++++
@@ -194,13 +196,13 @@
blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
content=u'cubicweb yes')
blog_entry_2.set_relations(entry_of=cubicweb_blog)
- self.assertEquals(len(MAILBOX), 0)
+ self.assertEqual(len(MAILBOX), 0)
self.commit()
- self.assertEquals(len(MAILBOX), 2)
+ self.assertEqual(len(MAILBOX), 2)
mail = MAILBOX[0]
- self.assertEquals(mail.subject, '[data] hop')
+ self.assertEqual(mail.subject, '[data] hop')
mail = MAILBOX[1]
- self.assertEquals(mail.subject, '[data] yes')
+ self.assertEqual(mail.subject, '[data] yes')
Visible actions tests
`````````````````````
@@ -227,7 +229,7 @@
def test_admin(self):
req = self.request()
rset = req.execute('Any C WHERE C is Conference')
- self.assertListEquals(self.pactions(req, rset),
+ self.assertListEqual(self.pactions(req, rset),
[('workflow', workflow.WorkflowActions),
('edit', confactions.ModifyAction),
('managepermission', actions.ManagePermissionsAction),
@@ -236,7 +238,7 @@
('generate_badge_action', badges.GenerateBadgeAction),
('addtalkinconf', confactions.AddTalkInConferenceAction)
])
- self.assertListEquals(self.action_submenu(req, rset, 'addrelated'),
+ self.assertListEqual(self.action_submenu(req, rset, 'addrelated'),
[(u'add Track in_conf Conference object',
u'http://testing.fr/cubicweb/add/Track'
u'?__linkto=in_conf%%3A%(conf)s%%3Asubject&'
@@ -341,6 +343,60 @@
therefore making new entity types and relations available to the
tests.
+Literate programming
+--------------------
+
+CubicWeb provides some literate programming capabilities. The :ref:`cubicweb-ctl`
+`shell` command accepts differents format files. If your file ends with `.txt`
+or `.rst`, the file will be parsed by :mod:`doctest.testfile` with CubicWeb
+:ref:`migration` API enabled in it.
+
+Create a `scenario.txt` file into `test/` directory and fill with some content.
+Please refer the :mod:`doctest.testfile` `documentation`_.
+
+.. _documentation: http://docs.python.org/library/doctest.html
+
+Then, you can run it directly by::
+
+ $ cubicweb-ctl shell <cube_instance> test/scenario.txt
+
+When your scenario file is ready, put it in a new test case to be able to run
+it automatically.
+
+.. sourcecode:: python
+
+ from os.path import dirname, join
+ from logilab.common.testlib import unittest_main
+ from cubicweb.devtools.testlib import CubicWebTC
+
+ class AcceptanceTC(CubicWebTC):
+
+ def test_scenario(self):
+ self.assertDocTestFile(join(dirname(__file__), 'scenario.txt'))
+
+ if __name__ == '__main__':
+ unittest_main()
+
+Skipping a scenario
+```````````````````
+
+If you want to set up initial conditions that you can't put in your unit test
+case, you have to use a :exc:`KeyboardInterrupt` exception only because of the
+way :mod:`doctest` module will catch all the exceptions internally.
+
+ >>> if condition_not_met:
+ ... raise KeyboardInterrupt('please, check your fixture.')
+
+Passing paramaters
+``````````````````
+Using extra arguments to parametrize your scenario is possible by prepend them
+by double dashes.
+
+Please refer to the `cubicweb-ctl shell --help` usage.
+
+.. important::
+ Your scenario file must be utf-8 encoded.
+
Test APIS
---------
--- a/doc/book/en/devrepo/vreg.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devrepo/vreg.rst Fri Mar 11 09:46:45 2011 +0100
@@ -38,6 +38,7 @@
.. autoclass:: cubicweb.selectors.match_kwargs
.. autoclass:: cubicweb.selectors.appobject_selectable
.. autoclass:: cubicweb.selectors.adaptable
+.. autoclass:: cubicweb.selectors.configuration_values
Result set selectors
@@ -77,6 +78,8 @@
.. autoclass:: cubicweb.selectors.has_permission
.. autoclass:: cubicweb.selectors.has_add_permission
.. autoclass:: cubicweb.selectors.has_mimetype
+.. autoclass:: cubicweb.selectors.is_in_state
+.. autoclass:: cubicweb.selectors.on_transition
.. autoclass:: cubicweb.selectors.implements
@@ -100,11 +103,13 @@
.. autoclass:: cubicweb.selectors.match_view
.. autoclass:: cubicweb.selectors.primary_view
.. autoclass:: cubicweb.selectors.specified_etype_implements
+.. autoclass:: cubicweb.selectors.attribute_edited
Other selectors
~~~~~~~~~~~~~~~
.. autoclass:: cubicweb.selectors.match_transition
+.. autoclass:: cubicweb.selectors.debug_mode
You'll also find some other (very) specific selectors hidden in other modules
than :mod:`cubicweb.selectors`.
--- a/doc/book/en/devweb/edition/examples.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devweb/edition/examples.rst Fri Mar 11 09:46:45 2011 +0100
@@ -117,7 +117,7 @@
set to 'sendmail', which is our form DOM id as specified by its `domid`
attribute), another to cancel the form which will go back to the previous page
using another javascript call. Also we specify an image to use as button icon as a
-resource identifier (see :ref:`external_resources`) given as last argument to
+resource identifier (see :ref:`uiprops`) given as last argument to
:class:`cubicweb.web.formwidgets.ImgButton`.
To see this form, we still have to wrap it in a view. This is pretty simple:
@@ -131,12 +131,13 @@
def call(self):
form = self._cw.vreg['forms'].select('massmailing', self._cw,
rset=self.cw_rset)
- self.w(form.render())
+ form.render(w=self.w)
As you see, we simply define a view with proper selector so it only apply to a
result set containing :class:`IEmailable` entities, and so that only users in the
managers or users group can use it. Then in the `call()` method for this view we
-simply select the above form and write what its `.render()` method returns.
+simply select the above form and call its `.render()` method with our output
+stream as argument.
When this form is submitted, a controller with id 'sendmail' will be called (as
specified using `action`). This controller will be responsible to actually send
--- a/doc/book/en/devweb/request.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devweb/request.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,5 +1,5 @@
-The `Request` class (`cubicweb.web`)
-------------------------------------
+The `Request` class (`cubicweb.web.request`)
+--------------------------------------------
Overview
````````
@@ -7,7 +7,8 @@
A request instance is created when an HTTP request is sent to the web
server. It contains informations such as form parameters,
authenticated user, etc. It is a very prevalent object and is used
-throughout all of the framework and applications.
+throughout all of the framework and applications, as you'll access to
+almost every resources through it.
**A request represents a user query, either through HTTP or not (we
also talk about RQL queries on the server side for example).**
@@ -24,8 +25,8 @@
* `User and identification`:
- * `user`, instance of `cubicweb.common.utils.User` corresponding to
- the authenticated user
+ * `user`, instance of `cubicweb.entities.authobjs.CWUser` corresponding to the
+ authenticated user
* `Session data handling`
--- a/doc/book/en/devweb/views/basetemplates.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devweb/views/basetemplates.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,7 +1,5 @@
.. -*- coding: utf-8 -*-
-.. |cubicweb| replace:: *CubicWeb*
-
.. _templates:
Templates
--- a/doc/book/en/devweb/views/primary.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devweb/views/primary.rst Fri Mar 11 09:46:45 2011 +0100
@@ -226,8 +226,6 @@
We'll show you now an example of a ``primary`` view and how to customize it.
-We continue along the basic tutorial :ref:`tuto_blog`.
-
If you want to change the way a ``BlogEntry`` is displayed, just
override the method ``cell_call()`` of the view ``primary`` in
``BlogDemo/views.py``.
@@ -247,7 +245,7 @@
The above source code defines a new primary view for
-``BlogEntry``. The `id` class attribute is not repeated there since it
+``BlogEntry``. The `__reid__` class attribute is not repeated there since it
is inherited through the `primary.PrimaryView` class.
The selector for this view chains the selector of the inherited class
--- a/doc/book/en/devweb/views/views.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/devweb/views/views.rst Fri Mar 11 09:46:45 2011 +0100
@@ -88,7 +88,7 @@
Other basic view classes
````````````````````````
-Here are some of the subclasses of `View` defined in `cubicweb.common.view`
+Here are some of the subclasses of `View` defined in `cubicweb.view`
that are more concrete as they relate to data rendering within the application:
* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
Binary file doc/book/en/images/blog-demo-first-page.png has changed
Binary file doc/book/en/images/cbw-add-relation-entryof_en.png has changed
Binary file doc/book/en/images/cbw-create-blog_en.png has changed
Binary file doc/book/en/images/cbw-detail-one-blogentry_en.png has changed
Binary file doc/book/en/images/cbw-list-one-blog_en.png has changed
Binary file doc/book/en/images/cbw-list-two-blog_en.png has changed
Binary file doc/book/en/images/cbw-schema_en.png has changed
Binary file doc/book/en/images/cbw-update-primary-view_en.png has changed
Binary file doc/book/en/images/lax-book_06-header-no-login_en.png has changed
Binary file doc/book/en/images/lax-book_06-main-template-layout_en.png has changed
Binary file doc/book/en/images/lax-book_06-simple-main-template_en.png has changed
Binary file doc/book/en/images/login-form.png has changed
Binary file doc/book/en/images/tutos-base_blog-form_en.png has changed
Binary file doc/book/en/images/tutos-base_blog-primary-after-post-creation_en.png has changed
Binary file doc/book/en/images/tutos-base_blog-primary_en.png has changed
Binary file doc/book/en/images/tutos-base_blogs-list_en.png has changed
Binary file doc/book/en/images/tutos-base_form-generic-relations_en.png has changed
Binary file doc/book/en/images/tutos-base_index_en.png has changed
Binary file doc/book/en/images/tutos-base_login-form_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-blogentry-taggable-commentable-primary_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-community-custom-primary_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-community-default-primary_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-community-taggable-primary_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-custom-footer_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-schema_en.png has changed
Binary file doc/book/en/images/tutos-base_myblog-siteinfo_en.png has changed
Binary file doc/book/en/images/tutos-base_schema_en.png has changed
Binary file doc/book/en/images/tutos-base_siteconfig_en.png has changed
Binary file doc/book/en/images/tutos-base_user-menu_en.png has changed
Binary file doc/book/en/images/tutos-photowebsite_background-image.png has changed
Binary file doc/book/en/images/tutos-photowebsite_boxes.png has changed
Binary file doc/book/en/images/tutos-photowebsite_breadcrumbs.png has changed
Binary file doc/book/en/images/tutos-photowebsite_facets.png has changed
Binary file doc/book/en/images/tutos-photowebsite_grey-box.png has changed
Binary file doc/book/en/images/tutos-photowebsite_index-after.png has changed
Binary file doc/book/en/images/tutos-photowebsite_index-before.png has changed
Binary file doc/book/en/images/tutos-photowebsite_login-box.png has changed
Binary file doc/book/en/images/tutos-photowebsite_prevnext.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui1.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui2.png has changed
Binary file doc/book/en/images/tutos-photowebsite_ui3.png has changed
--- a/doc/book/en/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -13,19 +13,28 @@
Its main features are:
-* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application,
+* an engine driven by the explicit :ref:`data model
+ <TutosBaseCustomizingTheApplicationDataModel>` of the application,
+
* a query language named :ref:`RQL <RQL>` similar to W3C's SPARQL,
-* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation,
-* a library of reusable :ref:`components <cubes>` (data model and views) that fulfill common needs,
+
+* a :ref:`selection+view <TutosBaseCustomizingTheApplicationCustomViews>`
+ mechanism for semi-automatic XHTML/XML/JSON/text generation,
+
+* a library of reusable :ref:`components <Cube>` (data model and views) that
+ fulfill common needs,
+
* the power and flexibility of the Python_ programming language,
-* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
+
+* the reliability of SQL databases, LDAP directories, Subversion and Mercurial
+ for storage backends.
Built since 2000 from an R&D effort still continued, supporting 100,000s of
daily visits at some production sites, |cubicweb| is a proven end to end solution
for semantic web application development that promotes quality, reusability and
efficiency.
-The unbeliever will read the :ref:`Tutorial`.
+The unbeliever will read the :ref:`Tutorials`.
The hacker will join development at the forge_.
--- a/doc/book/en/intro/concepts.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/intro/concepts.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,4 @@
+
.. -*- coding: utf-8 -*-
.. _Concepts:
@@ -27,14 +28,10 @@
The `CubicWeb.org Forge`_ offers a large number of cubes developed by the community
and available under a free software license.
-The command :command:`cubicweb-ctl list` displays the list of cubes installed on
-your system.
+.. note::
-On a Unix system, the available cubes are usually stored in the directory
-:file:`/usr/share/cubicweb/cubes`. If you're using the cubicweb forest
-(:ref:SourceInstallation), the cubes are searched in the directory
-:file:`/path/to/cubicweb_forest/cubes`. The environment variable
-:envvar:`CW_CUBES_PATH` gives additionnal locations where to search for cubes.
+ The command :command:`cubicweb-ctl list` displays the list of cubes
+installed on your system.
.. _`CubicWeb.org Forge`: http://www.cubicweb.org/project/
.. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
@@ -64,12 +61,6 @@
The command :command:`cubicweb-ctl list` also displays the list of instances
installed on your system.
-On a Unix system, the instances are usually stored in the directory
-:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/`
-directory is looked up, as well as the paths in :envvar:`CW_INSTANCES_DIR`
-environment variable.
-
-
.. note::
The term application is used to refer to "something that should do something as
@@ -83,28 +74,20 @@
Data Repository
---------------
-The data repository [1]_ provides access to one or more data sources (including
-SQL databases, LDAP repositories, other |cubicweb| instance repositories, GAE's
+The data repository [1]_ encapsulates and groups an access to one or
+more data sources (including SQL databases, LDAP repositories, other
+|cubicweb| instance repositories, filesystems, Google AppEngine's
DataStore, etc).
-All interactions with the repository are done using the Relation Query Language
+All interactions with the repository are done using the `Relation Query Language`
(:ref:`RQL`). The repository federates the data sources and hides them from the
-querier, which does not realize when a query spans accross several data sources
+querier, which does not realize when a query spans several data sources
and requires running sub-queries and merges to complete.
-It is common to run the web engine and the repository in the same process (see
-instances of type all-in-one above), but this is not a requirement. A repository
-can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and
-act as a server. However, it's important to know if code you're writing is
-executed on the repository side, on our client side (the web engine being a
-client for instance): you don't have the same abilities on both side. On the
-repository side, you can for instance by-pass security checks, which isn't
-possible from client code.
-
-Some logic can be attached to events that happen in the repository,
-like creation of entities, deletion of relations, etc. This is used
-for example to send email notifications when the state of an object
-changes. See :ref:`HookIntro` below.
+Application logic can be mapped to data events happenning within the
+repository, like creation of entities, deletion of relations,
+etc. This is used for example to send email notifications when the
+state of an object changes. See :ref:`HookIntro` below.
.. [1] not to be confused with a Mercurial repository or a Debian repository.
.. _`Python Remote Objects`: http://pyro.sourceforge.net/
@@ -114,14 +97,19 @@
Web Engine
----------
-The web engine replies to http requests and runs the user interface
-and most of the application logic.
+The web engine replies to http requests and runs the user interface.
By default the web engine provides a `CRUD`_ user interface based on
the data model of the instance. Entities can be created, displayed,
updated and deleted. As the default user interface is not very fancy,
it is usually necessary to develop your own.
+It is common to run the web engine and the repository in the same
+process (see instances of type all-in-one above), but this is not a
+requirement. A repository can be set up to be accessed remotely using
+Pyro (`Python Remote Objects`_) and act as a standalone server, which
+can be directly accessed or also through a standalone web engine.
+
.. _`CRUD`: http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
.. _SchemaIntro:
@@ -134,24 +122,24 @@
.. _yams: http://www.logilab.org/project/yams/
-An `entity type` defines a set of attributes and is used in some relations.
-Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`,
-`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`.
+An `entity type` defines a sequence of attributes. Attributes may be
+of the following types: `String`, `Int`, `Float`, `Boolean`, `Date`,
+`Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`.
-A `relation type` is used to define an oriented binary relation between two
-entity types. The left-hand part of a relation is named the `subject` and the
-right-hand part is named the `object`.
+A `relation type` is used to define an oriented binary relation
+between entity types. The left-hand part of a relation is named the
+`subject` and the right-hand part is named the `object`.
A `relation definition` is a triple (*subject entity type*, *relation type*, *object
entity type*) associated with a set of properties such as cardinality,
constraints, etc.
-Permissions can be set on entity types and relation definition to control who
+Permissions can be set on entity types or relation definition to control who
will be able to create, read, update or delete entities and relations. Permissions
-are granted to groups (to which users may belong) or using rql expression (if the
+are granted to groups (to which users may belong) or using rql expressions (if the
rql expression returns some results, the permission is granted).
-Some meta-data necessary to the system is added to the data model. That includes
+Some meta-data necessary to the system are added to the data model. That includes
entities like users and groups, the entities used to store the data model
itself and attributes like unique identifier, creation date, creator, etc.
@@ -169,19 +157,15 @@
Application objects
~~~~~~~~~~~~~~~~~~~
-Beside a few core functionalities, almost every feature of the framework is
+Besides a few core functionalities, almost every feature of the framework is
achieved by dynamic objects (`application objects` or `appobjects`) stored in a
-two-levels registry (the `vregistry`). Each object is affected to a registry with
+two-levels registry. Each object is affected to a registry with
an identifier in this registry. You may have more than one object sharing an
-identifier in the same registry. At runtime, appobjects are selected in a
-registry according to the context. Selection is done by comparing the *score*
-returned by each appobject's *selector*.
-
-Application objects are stored in the vregistry using a two-level hierarchy :
+identifier in the same registry:
object's `__registry__` : object's `__regid__` : [list of app objects]
-In other words, the `vregistry` contains several (sub-)registries which hold a
+In other words, the `registry` contains several (sub-)registries which hold a
list of appobjects associated to an identifier.
The base class of appobjects is :class:`cubicweb.appobject.AppObject`.
@@ -189,10 +173,14 @@
Selectors
~~~~~~~~~
-Each appobject has a selector that is used to compute how well the object fits a
-given context. The better the object fits the context, the higher the score. Scores
-are the glue that ties appobjects to the data model. Using them appropriately is
-an essential part of the construction of well behaved cubes.
+At runtime, appobjects can be selected in a registry according to some
+contextual information. Selection is done by comparing the *score*
+returned by each appobject's *selector*.
+
+The better the object fits the context, the higher the score. Scores
+are the glue that ties appobjects to the data model. Using them
+appropriately is an essential part of the construction of well behaved
+cubes.
|cubicweb| provides a set of basic selectors that may be parametrized. Also,
selectors can be combined with the `~` unary operator (negation) and the binary
@@ -200,35 +188,36 @@
selectors. Of course complex selectors may be combined too. Last but not least, you
can write your own selectors.
-The `vregistry`
+The `registry`
~~~~~~~~~~~~~~~
-At startup, the `vregistry` inspects a number of directories looking for
-compatible classes definition. After a recording process, the objects are
-assigned to registries so that they can be selected dynamically while the
-instance is running.
+At startup, the `registry` inspects a number of directories looking
+for compatible class definitions. After a recording process, the
+objects are assigned to registries and become available through the
+selection process.
In a cube, application object classes are looked in the following modules or
packages:
- `entities`
- `views`
+- `hooks`
- `sobjects`
-
-Once initialized, there are three common ways to retrieve some application object
-from a registry:
+There are three common ways to look up some application object from a
+registry:
-* get the most appropriate object by specifying an identifier. In that case, the
- object with the greatest score is selected. There should always be a single
- appobject with a greater score than others for a particular context.
+* get the most appropriate object by specifying an identifier and
+ context objects. The object with the greatest score is
+ selected. There should always be a single appobject with a greater
+ score than others for a particular context.
-* get all objects applying to a context by specifying a registry. In that case, a
- list of objects will be returned containing the object with the highest score
- (> 0) for each identifier in that registry.
+* get all objects applying to a context by specifying a registry. A
+ list of objects will be returned containing the object with the
+ highest score (> 0) for each identifier in that registry.
-* get the object within a particular registry/identifier. In that case no
- selection process is involved, the vregistry will expect to find a single
+* get the object within a particular registry/identifier. No selection
+ process is involved: the registry will expect to find a single
object in that cell.
@@ -247,21 +236,6 @@
emphasize browsing relations.
-DB-API
-~~~~~~
-
-The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
-
-.. _`db-api`: http://www.python.org/dev/peps/pep-0249/
-
-You basically get a connection using :func:`cubicweb.dbapi.connect` , then
-get a cursor to call its `execute` method which will return result set for the
-given rql query.
-
-You can also get additional information through the connection, such as the
-repository'schema, version configuration, etc.
-
-
Result set
~~~~~~~~~~
@@ -285,18 +259,10 @@
something, eg producing some html, text, xml, pdf, or whatsover that can be
displayed to a user.
-The two main entry points of a view are:
-
-* `call()`, used to render a view on a context with no result set, or on a whole
- result set
-
-* `cell_call(row, col)`, used to render a view on a the cell with index `row` and
- `col` of the context's result set (remember result set may be seen as a two
- dimensions array).
-
-Then view may gets refined into different kind of objects such as `template`,
-`boxes`, `components`, which are more high-level abstraction useful to build
-the user interface in an object oriented way.
+Views actually are partitioned into different kind of objects such as
+`templates`, `boxes`, `components` and proper `views`, which are more
+high-level abstraction useful to build the user interface in an object
+oriented way.
.. _HookIntro:
@@ -312,7 +278,7 @@
* managing computed attributes
-* enforcing complicated structural invariants
+* enforcing complicated business rules
* real-world side-effects linked to data events (email notification
being a prime example)
@@ -326,10 +292,9 @@
* it is well-coupled to the rest of the framework
-Hooks are also application objects registered on events such as after/before
-add/update/delete on entities/relations, server startup or shutdown, etc. As all
-application objects, they have a selector defining when they should be called or
-not.
+Hooks are also application objects (in the `hooks` registry) and
+selected on events such as after/before add/update/delete on
+entities/relations, server startup or shutdown, etc.
`Operations` may be instantiated by hooks to do further processing at different
steps of the transaction's commit / rollback, which usually can not be done
--- a/doc/book/en/makefile Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/makefile Fri Mar 11 09:46:45 2011 +0100
@@ -1,16 +1,11 @@
-MKHTML=mkdoc
-MKHTMLOPTS=--doctype article --target html --stylesheet standard
SRC=.
-TXTFILES:= $(wildcard *.txt)
-TARGET := $(TXTFILES:.txt=.html)
-
# You can set these sphinx variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
#BUILDDIR = build
-BUILDDIR = ~/tmp/cwdoc
+BUILDDIR = ../..
CWDIR = ../../..
JSDIR = ${CWDIR}/web/data
JSTORST = ${CWDIR}/doc/tools/pyjsrest.py
@@ -28,7 +23,6 @@
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " all to make standalone HTML files, developer manual and API doc"
- @echo " apidoc to make API doc"
@echo " html to make standalone HTML files"
@echo "--- "
@echo " pickle to make pickle files (usable by e.g. sphinx-web)"
@@ -38,20 +32,11 @@
@echo " linkcheck to check all external links for integrity"
clean:
- rm -rf apidoc/
rm -f *.html
- -rm -rf ${BUILDDIR}/*
+ -rm -rf ${BUILDDIR}/html ${BUILDDIR}/doctrees
-rm -rf ${BUILDJS}
-all: ${TARGET} apidoc html
-
-%.html: %.txt
- ${MKHTML} ${MKHTMLOPTS} $<
-
-#apydoc:
-# epydoc --html -o epydoc/ -n ../server/*.py ../core/*.py ../common/*.py ../server/*/*.py ../modpython/*/*.py ../common/*/*.py
-apidoc:
- epydoc --html -o apidoc -n "cubicweb" --exclude=setup --exclude=__pkginfo__ ../../../
+all: html
# run sphinx ###
html: js
--- a/doc/book/en/tutorials/advanced/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/tutorials/advanced/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,7 +1,8 @@
-.. _advanced_tutorial:
+
+.. _TutosPhotoWebSite:
-Building a photo gallery with CubicWeb
-======================================
+Building a photo gallery with |cubicweb|
+========================================
Desired features
----------------
@@ -16,574 +17,13 @@
* advanced security (not everyone can see everything). More on this later.
-Cube creation and schema definition
------------------------------------
-
-.. _adv_tuto_create_new_cube:
-
-Step 1: creating a new cube for my web site
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One note about my development environment: I wanted to use the packaged
-version of CubicWeb and cubes while keeping my cube in my user
-directory, let's say `~src/cubes`. I achieve this by setting the
-following environment variables::
-
- CW_CUBES_PATH=~/src/cubes
- CW_MODE=user
-
-I can now create the cube which will hold custom code for this web
-site using::
-
- cubicweb-ctl newcube --directory=~/src/cubes sytweb
-
-
-.. _adv_tuto_assemble_cubes:
-
-Step 2: pick building blocks into existing cubes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Almost everything I want to handle in my web-site is somehow already modelized in
-existing cubes that I'll extend for my need. So I'll pick the following cubes:
-
-* `folder`, containing the `Folder` entity type, which will be used as
- both 'album' and a way to map file system folders. Entities are
- added to a given folder using the `filed_under` relation.
-
-* `file`, containing `File` and `Image` entity types, gallery view,
- and a file system import utility.
-
-* `zone`, containing the `Zone` entity type for hierarchical geographical
- zones. Entities (including sub-zones) are added to a given zone using the
- `situated_in` relation.
-
-* `person`, containing the `Person` entity type plus some basic views.
-
-* `comment`, providing a full commenting system allowing one to comment entity types
- supporting the `comments` relation by adding a `Comment` entity.
-
-* `tag`, providing a full tagging system as an easy and powerful way to classify
- entities supporting the `tags` relation by linking the to `Tag` entities. This
- will allows navigation into a large number of picture.
-
-Ok, now I'll tell my cube requires all this by editing cubes/sytweb/__pkginfo__.py:
-
- .. sourcecode:: python
-
- __depends_cubes__ = {'file': '>= 1.2.0',
- 'folder': '>= 1.1.0',
- 'person': '>= 1.2.0',
- 'comment': '>= 1.2.0',
- 'tag': '>= 1.2.0',
- 'zone': None,
- }
- __depends__ = {'cubicweb': '>= 3.5.10',
- }
- for key,value in __depends_cubes__.items():
- __depends__['cubicweb-'+key] = value
- __use__ = tuple(__depends_cubes__)
-
-Notice that you can express minimal version of the cube that should be used,
-`None` meaning whatever version available.
+.. toctree::
+ :maxdepth: 2
-Step 3: glue everything together in my cube's schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. sourcecode:: python
-
- from yams.buildobjs import RelationDefinition
-
- class comments(RelationDefinition):
- subject = 'Comment'
- object = ('File', 'Image')
- cardinality = '1*'
- composite = 'object'
-
- class tags(RelationDefinition):
- subject = 'Tag'
- object = ('File', 'Image')
-
- class filed_under(RelationDefinition):
- subject = ('File', 'Image')
- object = 'Folder'
-
- class situated_in(RelationDefinition):
- subject = 'Image'
- object = 'Zone'
-
- class displayed_on(RelationDefinition):
- subject = 'Person'
- object = 'Image'
-
-
-This schema:
-
-* allows to comment and tag on `File` and `Image` entity types by adding the
- `comments` and `tags` relations. This should be all we've to do for this
- feature since the related cubes provide 'pluggable section' which are
- automatically displayed on the primary view of entity types supporting the
- relation.
-
-* adds a `situated_in` relation definition so that image entities can be
- geolocalized.
-
-* add a new relation `displayed_on` relation telling who can be seen on a
- picture.
-
-This schema will probably have to evolve as time goes (for security handling at
-least), but since the possibility to let a schema evolve is one of CubicWeb's
-features (and goals), we won't worry about it for now and see that later when needed.
-
-
-Step 4: creating the instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now that I have a schema, I want to create an instance. To
-do so using this new 'sytweb' cube, I run::
-
- cubicweb-ctl create sytweb sytweb_instance
-
-Hint: if you get an error while the database is initialized, you can
-avoid having to answer the questions again by running::
-
- cubicweb-ctl db-create sytweb_instance
-
-This will use your already configured instance and start directly from the create
-database step, thus skipping questions asked by the 'create' command.
-
-Once the instance and database are fully initialized, run ::
-
- cubicweb-ctl start sytweb_instance
-
-to start the instance, check you can connect on it, etc...
+ part01_create-cube
+ part02_security
+ part03_bfss
+ part04_ui-base
+ part05_ui-advanced
-Security, testing and migration
--------------------------------
-
-This part will cover various topics:
-
-* configuring security
-* migrating existing instance
-* writing some unit tests
-
-Here is the ``read`` security model I want:
-
-* folders, files, images and comments should have one of the following visibility:
- - ``public``, everyone can see it
- - ``authenticated``, only authenticated users can see it
- - ``restricted``, only a subset of authenticated users can see it
-* managers (e.g. me) can see everything
-* only authenticated users can see people
-* everyone can see classifier entities, such as tag and zone
-
-Also, unless explicitly specified, the visibility of an image should be the same as
-its parent folder, as well as visibility of a comment should be the same as the
-commented entity. If there is no parent entity, the default visibility is
-``authenticated``.
-
-Regarding write security, that's much easier:
-* anonymous can't write anything
-* authenticated users can only add comment
-* managers will add the remaining stuff
-
-Now, let's implement that!
-
-Proper security in CubicWeb is done at the schema level, so you don't have to
-bother with it in views: users will only see what they can see automatically.
-
-.. _adv_tuto_security:
-
-Step 1: configuring security into the schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In schema, you can grant access according to groups, or to some RQL expressions:
-users get access if the expression returns some results. To implement the read
-security defined earlier, groups are not enough, we'll need some RQL expression. Here
-is the idea:
-
-* add a `visibility` attribute on Folder, Image and Comment, which may be one of
- the value explained above
-
-* add a `may_be_read_by` relation from Folder, Image and Comment to users,
- which will define who can see the entity
-
-* security propagation will be done in hook.
-
-So the first thing to do is to modify my cube's schema.py to define those
-relations:
-
-.. sourcecode:: python
-
- from yams.constraints import StaticVocabularyConstraint
-
- class visibility(RelationDefinition):
- subject = ('Folder', 'File', 'Image', 'Comment')
- object = 'String'
- constraints = [StaticVocabularyConstraint(('public', 'authenticated',
- 'restricted', 'parent'))]
- default = 'parent'
- cardinality = '11' # required
-
- class may_be_read_by(RelationDefinition):
- subject = ('Folder', 'File', 'Image', 'Comment',)
- object = 'CWUser'
-
-We can note the following points:
-
-* we've added a new `visibility` attribute to folder, file, image and comment
- using a `RelationDefinition`
-
-* `cardinality = '11'` means this attribute is required. This is usually hidden
- under the `required` argument given to the `String` constructor, but we can
- rely on this here (same thing for StaticVocabularyConstraint, which is usually
- hidden by the `vocabulary` argument)
-
-* the `parent` possible value will be used for visibility propagation
-
-Now, we should be able to define security rules in the schema, based on these new
-attribute and relation. Here is the code to add to *schema.py*:
-
-.. sourcecode:: python
-
- from cubicweb.schema import ERQLExpression
-
- VISIBILITY_PERMISSIONS = {
- 'read': ('managers',
- ERQLExpression('X visibility "public"'),
- ERQLExpression('X may_be_read_by U')),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
- AUTH_ONLY_PERMISSIONS = {
- 'read': ('managers', 'users'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
- CLASSIFIERS_PERMISSIONS = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
-
- from cubes.folder.schema import Folder
- from cubes.file.schema import File, Image
- from cubes.comment.schema import Comment
- from cubes.person.schema import Person
- from cubes.zone.schema import Zone
- from cubes.tag.schema import Tag
-
- Folder.__permissions__ = VISIBILITY_PERMISSIONS
- File.__permissions__ = VISIBILITY_PERMISSIONS
- Image.__permissions__ = VISIBILITY_PERMISSIONS
- Comment.__permissions__ = VISIBILITY_PERMISSIONS.copy()
- Comment.__permissions__['add'] = ('managers', 'users',)
- Person.__permissions__ = AUTH_ONLY_PERMISSIONS
- Zone.__permissions__ = CLASSIFIERS_PERMISSIONS
- Tag.__permissions__ = CLASSIFIERS_PERMISSIONS
-
-What's important in there:
-
-* `VISIBILITY_PERMISSIONS` provides read access to managers group, if
- `visibility` attribute's value is 'public', or if user (designed by the 'U'
- variable in the expression) is linked to the entity (the 'X' variable) through
- the `may_read` permission
-
-* we modify permissions of the entity types we use by importing them and
- modifying their `__permissions__` attribute
-
-* notice the `.copy()`: we only want to modify 'add' permission for `Comment`,
- not for all entity types using `VISIBILITY_PERMISSIONS`!
-
-* the remaining part of the security model is done using regular groups:
-
- - `users` is the group to which all authenticated users will belong
- - `guests` is the group of anonymous users
-
-
-.. _adv_tuto_security_propagation:
-
-Step 2: security propagation in hooks
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To fullfill the requirements, we have to implement::
-
- Also, unless explicity specified, visibility of an image should be the same as
- its parent folder, as well as visibility of a comment should be the same as the
- commented entity.
-
-This kind of `active` rule will be done using CubicWeb's hook
-system. Hooks are triggered on database event such as addition of new
-entity or relation.
-
-The tricky part of the requirement is in *unless explicitly specified*, notably
-because when the entity is added, we don't know yet its 'parent'
-entity (e.g. Folder of an Image, Image commented by a Comment). To handle such things,
-CubicWeb provides `Operation`, which allow to schedule things to do at commit time.
-
-In our case we will:
-
-* on entity creation, schedule an operation that will set default visibility
-
-* when a "parent" relation is added, propagate parent's visibility unless the
- child already has a visibility set
-
-Here is the code in cube's *hooks.py*:
-
-.. sourcecode:: python
-
- from cubicweb.selectors import is_instance
- from cubicweb.server import hook
-
- class SetVisibilityOp(hook.Operation):
- def precommit_event(self):
- for eid in self.session.transaction_data.pop('pending_visibility'):
- entity = self.session.entity_from_eid(eid)
- if entity.visibility == 'parent':
- entity.set_attributes(visibility=u'authenticated')
-
- class SetVisibilityHook(hook.Hook):
- __regid__ = 'sytweb.setvisibility'
- __select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Image', 'Comment')
- events = ('after_add_entity',)
- def __call__(self):
- hook.set_operation(self._cw, 'pending_visibility', self.entity.eid,
- SetVisibilityOp)
-
- class SetParentVisibilityHook(hook.Hook):
- __regid__ = 'sytweb.setparentvisibility'
- __select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
- events = ('after_add_relation',)
-
- def __call__(self):
- parent = self._cw.entity_from_eid(self.eidto)
- child = self._cw.entity_from_eid(self.eidfrom)
- if child.visibility == 'parent':
- child.set_attributes(visibility=parent.visibility)
-
-Notice:
-
-* hooks are application objects, hence have selectors that should match entity or
- relation types to which the hook applies. To match a relation type, we use the
- hook specific `match_rtype` selector.
-
-* usage of `set_operation`: instead of adding an operation for each added entity,
- set_operation allows to create a single one and to store entity's eids to be
- processed in session's transaction data. This is a good pratice to avoid heavy
- operations manipulation cost when creating a lot of entities in the same
- transaction.
-
-* the `precommit_event` method of the operation will be called at transaction's
- commit time.
-
-* in a hook, `self._cw` is the repository session, not a web request as usually
- in views
-
-* according to hook's event, you have access to different attributes on the hook
- instance. Here:
-
- - `self.entity` is the newly added entity on 'after_add_entity' events
-
- - `self.eidfrom` / `self.eidto` are the eid of the subject / object entity on
- 'after_add_relatiohn' events (you may also get the relation type using
- `self.rtype`)
-
-The `parent` visibility value is used to tell "propagate using parent security"
-because we want that attribute to be required, so we can't use None value else
-we'll get an error before we get any chance to propagate...
-
-Now, we also want to propagate the `may_be_read_by` relation. Fortunately,
-CubicWeb provides some base hook classes for such things, so we only have to add
-the following code to *hooks.py*:
-
-.. sourcecode:: python
-
- # relations where the "parent" entity is the subject
- S_RELS = set()
- # relations where the "parent" entity is the object
- O_RELS = set(('filed_under', 'comments',))
-
- class AddEntitySecurityPropagationHook(hook.PropagateSubjectRelationHook):
- """propagate permissions when new entity are added"""
- __regid__ = 'sytweb.addentity_security_propagation'
- __select__ = (hook.PropagateSubjectRelationHook.__select__
- & hook.match_rtype_sets(S_RELS, O_RELS))
- main_rtype = 'may_be_read_by'
- subject_relations = S_RELS
- object_relations = O_RELS
-
- class AddPermissionSecurityPropagationHook(hook.PropagateSubjectRelationAddHook):
- """propagate permissions when new entity are added"""
- __regid__ = 'sytweb.addperm_security_propagation'
- __select__ = (hook.PropagateSubjectRelationAddHook.__select__
- & hook.match_rtype('may_be_read_by',))
- subject_relations = S_RELS
- object_relations = O_RELS
-
- class DelPermissionSecurityPropagationHook(hook.PropagateSubjectRelationDelHook):
- __regid__ = 'sytweb.delperm_security_propagation'
- __select__ = (hook.PropagateSubjectRelationDelHook.__select__
- & hook.match_rtype('may_be_read_by',))
- subject_relations = S_RELS
- object_relations = O_RELS
-
-* the `AddEntitySecurityPropagationHook` will propagate the relation
- when `filed_under` or `comments` relations are added
-
- - the `S_RELS` and `O_RELS` set as well as the `match_rtype_sets` selector are
- used here so that if my cube is used by another one, it'll be able to
- configure security propagation by simply adding relation to one of the two
- sets.
-
-* the two others will propagate permissions changes on parent entities to
- children entities
-
-
-.. _adv_tuto_tesing_security:
-
-Step 3: testing our security
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Security is tricky. Writing some tests for it is a very good idea. You should
-even write them first, as Test Driven Development recommends!
-
-Here is a small test case that will check the basis of our security
-model, in *test/unittest_sytweb.py*:
-
-.. sourcecode:: python
-
- from cubicweb.devtools.testlib import CubicWebTC
- from cubicweb import Binary
-
- class SecurityTC(CubicWebTC):
-
- def test_visibility_propagation(self):
- # create a user for later security checks
- toto = self.create_user('toto')
- # init some data using the default manager connection
- req = self.request()
- folder = req.create_entity('Folder',
- name=u'restricted',
- visibility=u'restricted')
- photo1 = req.create_entity('Image',
- data_name=u'photo1.jpg',
- data=Binary('xxx'),
- filed_under=folder)
- self.commit()
- photo1.clear_all_caches() # good practice, avoid request cache effects
- # visibility propagation
- self.assertEquals(photo1.visibility, 'restricted')
- # unless explicitly specified
- photo2 = req.create_entity('Image',
- data_name=u'photo2.jpg',
- data=Binary('xxx'),
- visibility=u'public',
- filed_under=folder)
- self.commit()
- self.assertEquals(photo2.visibility, 'public')
- # test security
- self.login('toto')
- req = self.request()
- self.assertEquals(len(req.execute('Image X')), 1) # only the public one
- self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
- # may_be_read_by propagation
- self.restore_connection()
- folder.set_relations(may_be_read_by=toto)
- self.commit()
- photo1.clear_all_caches()
- self.failUnless(photo1.may_be_read_by)
- # test security with permissions
- self.login('toto')
- req = self.request()
- self.assertEquals(len(req.execute('Image X')), 2) # now toto has access to photo2
- self.assertEquals(len(req.execute('Folder X')), 1) # and to restricted folder
-
- if __name__ == '__main__':
- from logilab.common.testlib import unittest_main
- unittest_main()
-
-It's not complete, but show most things you'll want to do in tests: adding some
-content, creating users and connecting as them in the test, etc...
-
-To run it type:
-
-.. sourcecode:: bash
-
- $ pytest unittest_sytweb.py
- ======================== unittest_sytweb.py ========================
- -> creating tables [....................]
- -> inserting default user and default groups.
- -> storing the schema in the database [....................]
- -> database for instance data initialized.
- .
- ----------------------------------------------------------------------
- Ran 1 test in 22.547s
-
- OK
-
-
-The first execution is taking time, since it creates a sqlite database for the
-test instance. The second one will be much quicker:
-
-.. sourcecode:: bash
-
- $ pytest unittest_sytweb.py
- ======================== unittest_sytweb.py ========================
- .
- ----------------------------------------------------------------------
- Ran 1 test in 2.662s
-
- OK
-
-If you do some changes in your schema, you'll have to force regeneration of that
-database. You do that by removing the tmpdb files before running the test: ::
-
- $ rm tmpdb*
-
-
-.. Note::
- pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
-
-.. _`logilab-common`: http://www.logilab.org/project/logilab-common
-
-.. _adv_tuto_migration_script:
-
-Step 4: writing the migration script and migrating the instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Prior to those changes, I created an instance, feeded it with some data, so I
-don't want to create a new one, but to migrate the existing one. Let's see how to
-do that.
-
-Migration commands should be put in the cube's *migration* directory, in a
-file named file:`<X.Y.Z>_Any.py` ('Any' being there mostly for historical reason).
-
-Here I'll create a *migration/0.2.0_Any.py* file containing the following
-instructions:
-
-.. sourcecode:: python
-
- add_relation_type('may_be_read_by')
- add_relation_type('visibility')
- sync_schema_props_perms()
-
-Then I update the version number in cube's *__pkginfo__.py* to 0.2.0. And
-that's it! Those instructions will:
-
-* update the instance's schema by adding our two new relations and update the
- underlying database tables accordingly (the two first instructions)
-
-* update schema's permissions definition (the last instruction)
-
-
-To migrate my instance I simply type::
-
- cubicweb-ctl upgrade sytweb
-
-I'll then be asked some questions to do the migration step by step. You should say
-YES when it asks if a backup of your database should be done, so you can get back
-to initial state if anything goes wrong...
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part01_create-cube.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,155 @@
+.. _TutosPhotoWebSiteCubeCreation:
+
+Cube creation and schema definition
+-----------------------------------
+
+.. _adv_tuto_create_new_cube:
+
+Step 1: creating a new cube for my web site
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One note about my development environment: I wanted to use the packaged
+version of CubicWeb and cubes while keeping my cube in my user
+directory, let's say `~src/cubes`. I achieve this by setting the
+following environment variables::
+
+ CW_CUBES_PATH=~/src/cubes
+ CW_MODE=user
+
+I can now create the cube which will hold custom code for this web
+site using::
+
+ cubicweb-ctl newcube --directory=~/src/cubes sytweb
+
+
+.. _adv_tuto_assemble_cubes:
+
+Step 2: pick building blocks into existing cubes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Almost everything I want to handle in my web-site is somehow already modelized in
+existing cubes that I'll extend for my need. So I'll pick the following cubes:
+
+* `folder`, containing the `Folder` entity type, which will be used as
+ both 'album' and a way to map file system folders. Entities are
+ added to a given folder using the `filed_under` relation.
+
+* `file`, containing `File` entity type, gallery view, and a file system import
+ utility.
+
+* `zone`, containing the `Zone` entity type for hierarchical geographical
+ zones. Entities (including sub-zones) are added to a given zone using the
+ `situated_in` relation.
+
+* `person`, containing the `Person` entity type plus some basic views.
+
+* `comment`, providing a full commenting system allowing one to comment entity types
+ supporting the `comments` relation by adding a `Comment` entity.
+
+* `tag`, providing a full tagging system as an easy and powerful way to classify
+ entities supporting the `tags` relation by linking the to `Tag` entities. This
+ will allows navigation into a large number of picture.
+
+Ok, now I'll tell my cube requires all this by editing :file:`cubes/sytweb/__pkginfo__.py`:
+
+ .. sourcecode:: python
+
+ __depends__ = {'cubicweb': '>= 3.10.0',
+ 'cubicweb-file': '>= 1.9.0',
+ 'cubicweb-folder': '>= 1.1.0',
+ 'cubicweb-person': '>= 1.2.0',
+ 'cubicweb-comment': '>= 1.2.0',
+ 'cubicweb-tag': '>= 1.2.0',
+ 'cubicweb-zone': None}
+
+Notice that you can express minimal version of the cube that should be used,
+`None` meaning whatever version available. All packages starting with 'cubicweb-'
+will be recognized as being cube, not bare python packages. You can still specify
+this explicitly using instead the `__depends_cubes__` dictionary which should
+contains cube's name without the prefix. So the example below would be written
+as:
+
+ .. sourcecode:: python
+
+ __depends__ = {'cubicweb': '>= 3.10.0'}
+ __depends_cubes__ = {'file': '>= 1.9.0',
+ 'folder': '>= 1.1.0',
+ 'person': '>= 1.2.0',
+ 'comment': '>= 1.2.0',
+ 'tag': '>= 1.2.0',
+ 'zone': None}
+
+If your cube is packaged for debian, it's a good idea to update the
+`debian/control` file at the same time, so you won't forget it.
+
+
+Step 3: glue everything together in my cube's schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. sourcecode:: python
+
+ from yams.buildobjs import RelationDefinition
+
+ class comments(RelationDefinition):
+ subject = 'Comment'
+ object = 'File'
+ cardinality = '1*'
+ composite = 'object'
+
+ class tags(RelationDefinition):
+ subject = 'Tag'
+ object = 'File'
+
+ class filed_under(RelationDefinition):
+ subject = 'File'
+ object = 'Folder'
+
+ class situated_in(RelationDefinition):
+ subject = 'File'
+ object = 'Zone'
+
+ class displayed_on(RelationDefinition):
+ subject = 'Person'
+ object = 'File'
+
+
+This schema:
+
+* allows to comment and tag on `File` entity type by adding the `comments` and
+ `tags` relations. This should be all we've to do for this feature since the
+ related cubes provide 'pluggable section' which are automatically displayed on
+ the primary view of entity types supporting the relation.
+
+* adds a `situated_in` relation definition so that image entities can be
+ geolocalized.
+
+* add a new relation `displayed_on` relation telling who can be seen on a
+ picture.
+
+This schema will probably have to evolve as time goes (for security handling at
+least), but since the possibility to let a schema evolve is one of CubicWeb's
+features (and goals), we won't worry about it for now and see that later when needed.
+
+
+Step 4: creating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Now that I have a schema, I want to create an instance. To
+do so using this new 'sytweb' cube, I run::
+
+ cubicweb-ctl create sytweb sytweb_instance
+
+Hint: if you get an error while the database is initialized, you can
+avoid having to answer the questions again by running::
+
+ cubicweb-ctl db-create sytweb_instance
+
+This will use your already configured instance and start directly from the create
+database step, thus skipping questions asked by the 'create' command.
+
+Once the instance and database are fully initialized, run ::
+
+ cubicweb-ctl start sytweb_instance
+
+to start the instance, check you can connect on it, etc...
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part02_security.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,440 @@
+.. _TutosPhotoWebSiteSecurity:
+
+Security, testing and migration
+-------------------------------
+
+This part will cover various topics:
+
+* configuring security
+* migrating existing instance
+* writing some unit tests
+
+Here is the ``read`` security model I want:
+
+* folders, files, images and comments should have one of the following visibility:
+
+ - ``public``, everyone can see it
+ - ``authenticated``, only authenticated users can see it
+ - ``restricted``, only a subset of authenticated users can see it
+
+* managers (e.g. me) can see everything
+* only authenticated users can see people
+* everyone can see classifier entities, such as tag and zone
+
+Also, unless explicitly specified, the visibility of an image should be the same as
+its parent folder, as well as visibility of a comment should be the same as the
+commented entity. If there is no parent entity, the default visibility is
+``authenticated``.
+
+Regarding write security, that's much easier:
+* anonymous can't write anything
+* authenticated users can only add comment
+* managers will add the remaining stuff
+
+Now, let's implement that!
+
+Proper security in CubicWeb is done at the schema level, so you don't have to
+bother with it in views: users will only see what they can see automatically.
+
+.. _adv_tuto_security:
+
+Step 1: configuring security into the schema
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In schema, you can grant access according to groups, or to some RQL expressions:
+users get access if the expression returns some results. To implement the read
+security defined earlier, groups are not enough, we'll need some RQL expression. Here
+is the idea:
+
+* add a `visibility` attribute on Folder, File and Comment, which may be one of
+ the value explained above
+
+* add a `may_be_read_by` relation from Folder, File and Comment to users,
+ which will define who can see the entity
+
+* security propagation will be done in hook.
+
+So the first thing to do is to modify my cube's schema.py to define those
+relations:
+
+.. sourcecode:: python
+
+ from yams.constraints import StaticVocabularyConstraint
+
+ class visibility(RelationDefinition):
+ subject = ('Folder', 'File', 'Comment')
+ object = 'String'
+ constraints = [StaticVocabularyConstraint(('public', 'authenticated',
+ 'restricted', 'parent'))]
+ default = 'parent'
+ cardinality = '11' # required
+
+ class may_be_read_by(RelationDefinition):
+ __permissions__ = {
+ 'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'delete': ('managers',),
+ }
+
+ subject = ('Folder', 'File', 'Comment',)
+ object = 'CWUser'
+
+We can note the following points:
+
+* we've added a new `visibility` attribute to folder, file, image and comment
+ using a `RelationDefinition`
+
+* `cardinality = '11'` means this attribute is required. This is usually hidden
+ under the `required` argument given to the `String` constructor, but we can
+ rely on this here (same thing for StaticVocabularyConstraint, which is usually
+ hidden by the `vocabulary` argument)
+
+* the `parent` possible value will be used for visibility propagation
+
+* think to secure the `may_be_read_by` permissions, else any user can add/delete it
+ by default, which somewhat breaks our security model...
+
+Now, we should be able to define security rules in the schema, based on these new
+attribute and relation. Here is the code to add to *schema.py*:
+
+.. sourcecode:: python
+
+ from cubicweb.schema import ERQLExpression
+
+ VISIBILITY_PERMISSIONS = {
+ 'read': ('managers',
+ ERQLExpression('X visibility "public"'),
+ ERQLExpression('X may_be_read_by U')),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
+ AUTH_ONLY_PERMISSIONS = {
+ 'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
+ CLASSIFIERS_PERMISSIONS = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
+
+ from cubes.folder.schema import Folder
+ from cubes.file.schema import File
+ from cubes.comment.schema import Comment
+ from cubes.person.schema import Person
+ from cubes.zone.schema import Zone
+ from cubes.tag.schema import Tag
+
+ Folder.__permissions__ = VISIBILITY_PERMISSIONS
+ File.__permissions__ = VISIBILITY_PERMISSIONS
+ Comment.__permissions__ = VISIBILITY_PERMISSIONS.copy()
+ Comment.__permissions__['add'] = ('managers', 'users',)
+ Person.__permissions__ = AUTH_ONLY_PERMISSIONS
+ Zone.__permissions__ = CLASSIFIERS_PERMISSIONS
+ Tag.__permissions__ = CLASSIFIERS_PERMISSIONS
+
+What's important in there:
+
+* `VISIBILITY_PERMISSIONS` provides read access to managers group, if
+ `visibility` attribute's value is 'public', or if user (designed by the 'U'
+ variable in the expression) is linked to the entity (the 'X' variable) through
+ the `may_read` permission
+
+* we modify permissions of the entity types we use by importing them and
+ modifying their `__permissions__` attribute
+
+* notice the `.copy()`: we only want to modify 'add' permission for `Comment`,
+ not for all entity types using `VISIBILITY_PERMISSIONS`!
+
+* the remaining part of the security model is done using regular groups:
+
+ - `users` is the group to which all authenticated users will belong
+ - `guests` is the group of anonymous users
+
+
+.. _adv_tuto_security_propagation:
+
+Step 2: security propagation in hooks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To fullfill the requirements, we have to implement::
+
+ Also, unless explicity specified, visibility of an image should be the same as
+ its parent folder, as well as visibility of a comment should be the same as the
+ commented entity.
+
+This kind of `active` rule will be done using CubicWeb's hook
+system. Hooks are triggered on database event such as addition of new
+entity or relation.
+
+The tricky part of the requirement is in *unless explicitly specified*, notably
+because when the entity is added, we don't know yet its 'parent'
+entity (e.g. Folder of an File, File commented by a Comment). To handle such things,
+CubicWeb provides `Operation`, which allow to schedule things to do at commit time.
+
+In our case we will:
+
+* on entity creation, schedule an operation that will set default visibility
+
+* when a "parent" relation is added, propagate parent's visibility unless the
+ child already has a visibility set
+
+Here is the code in cube's *hooks.py*:
+
+.. sourcecode:: python
+
+ from cubicweb.selectors import is_instance
+ from cubicweb.server import hook
+
+ class SetVisibilityOp(hook.Operation):
+ def precommit_event(self):
+ for eid in self.session.transaction_data.pop('pending_visibility'):
+ entity = self.session.entity_from_eid(eid)
+ if entity.visibility == 'parent':
+ entity.set_attributes(visibility=u'authenticated')
+
+ class SetVisibilityHook(hook.Hook):
+ __regid__ = 'sytweb.setvisibility'
+ __select__ = hook.Hook.__select__ & is_instance('Folder', 'File', 'Comment')
+ events = ('after_add_entity',)
+ def __call__(self):
+ hook.set_operation(self._cw, 'pending_visibility', self.entity.eid,
+ SetVisibilityOp)
+
+ class SetParentVisibilityHook(hook.Hook):
+ __regid__ = 'sytweb.setparentvisibility'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('filed_under', 'comments')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ parent = self._cw.entity_from_eid(self.eidto)
+ child = self._cw.entity_from_eid(self.eidfrom)
+ if child.visibility == 'parent':
+ child.set_attributes(visibility=parent.visibility)
+
+Notice:
+
+* hooks are application objects, hence have selectors that should match entity or
+ relation types to which the hook applies. To match a relation type, we use the
+ hook specific `match_rtype` selector.
+
+* usage of `set_operation`: instead of adding an operation for each added entity,
+ set_operation allows to create a single one and to store entity's eids to be
+ processed in session's transaction data. This is a good pratice to avoid heavy
+ operations manipulation cost when creating a lot of entities in the same
+ transaction.
+
+* the `precommit_event` method of the operation will be called at transaction's
+ commit time.
+
+* in a hook, `self._cw` is the repository session, not a web request as usually
+ in views
+
+* according to hook's event, you have access to different attributes on the hook
+ instance. Here:
+
+ - `self.entity` is the newly added entity on 'after_add_entity' events
+
+ - `self.eidfrom` / `self.eidto` are the eid of the subject / object entity on
+ 'after_add_relatiohn' events (you may also get the relation type using
+ `self.rtype`)
+
+The `parent` visibility value is used to tell "propagate using parent security"
+because we want that attribute to be required, so we can't use None value else
+we'll get an error before we get any chance to propagate...
+
+Now, we also want to propagate the `may_be_read_by` relation. Fortunately,
+CubicWeb provides some base hook classes for such things, so we only have to add
+the following code to *hooks.py*:
+
+.. sourcecode:: python
+
+ # relations where the "parent" entity is the subject
+ S_RELS = set()
+ # relations where the "parent" entity is the object
+ O_RELS = set(('filed_under', 'comments',))
+
+ class AddEntitySecurityPropagationHook(hook.PropagateSubjectRelationHook):
+ """propagate permissions when new entity are added"""
+ __regid__ = 'sytweb.addentity_security_propagation'
+ __select__ = (hook.PropagateSubjectRelationHook.__select__
+ & hook.match_rtype_sets(S_RELS, O_RELS))
+ main_rtype = 'may_be_read_by'
+ subject_relations = S_RELS
+ object_relations = O_RELS
+
+ class AddPermissionSecurityPropagationHook(hook.PropagateSubjectRelationAddHook):
+ """propagate permissions when new entity are added"""
+ __regid__ = 'sytweb.addperm_security_propagation'
+ __select__ = (hook.PropagateSubjectRelationAddHook.__select__
+ & hook.match_rtype('may_be_read_by',))
+ subject_relations = S_RELS
+ object_relations = O_RELS
+
+ class DelPermissionSecurityPropagationHook(hook.PropagateSubjectRelationDelHook):
+ __regid__ = 'sytweb.delperm_security_propagation'
+ __select__ = (hook.PropagateSubjectRelationDelHook.__select__
+ & hook.match_rtype('may_be_read_by',))
+ subject_relations = S_RELS
+ object_relations = O_RELS
+
+* the `AddEntitySecurityPropagationHook` will propagate the relation
+ when `filed_under` or `comments` relations are added
+
+ - the `S_RELS` and `O_RELS` set as well as the `match_rtype_sets` selector are
+ used here so that if my cube is used by another one, it'll be able to
+ configure security propagation by simply adding relation to one of the two
+ sets.
+
+* the two others will propagate permissions changes on parent entities to
+ children entities
+
+
+.. _adv_tuto_tesing_security:
+
+Step 3: testing our security
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Security is tricky. Writing some tests for it is a very good idea. You should
+even write them first, as Test Driven Development recommends!
+
+Here is a small test case that will check the basis of our security
+model, in *test/unittest_sytweb.py*:
+
+.. sourcecode:: python
+
+ from cubicweb.devtools.testlib import CubicWebTC
+ from cubicweb import Binary
+
+ class SecurityTC(CubicWebTC):
+
+ def test_visibility_propagation(self):
+ # create a user for later security checks
+ toto = self.create_user('toto')
+ # init some data using the default manager connection
+ req = self.request()
+ folder = req.create_entity('Folder',
+ name=u'restricted',
+ visibility=u'restricted')
+ photo1 = req.create_entity('File',
+ data_name=u'photo1.jpg',
+ data=Binary('xxx'),
+ filed_under=folder)
+ self.commit()
+ photo1.clear_all_caches() # good practice, avoid request cache effects
+ # visibility propagation
+ self.assertEquals(photo1.visibility, 'restricted')
+ # unless explicitly specified
+ photo2 = req.create_entity('File',
+ data_name=u'photo2.jpg',
+ data=Binary('xxx'),
+ visibility=u'public',
+ filed_under=folder)
+ self.commit()
+ self.assertEquals(photo2.visibility, 'public')
+ # test security
+ self.login('toto')
+ req = self.request()
+ self.assertEquals(len(req.execute('File X')), 1) # only the public one
+ self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
+ # may_be_read_by propagation
+ self.restore_connection()
+ folder.set_relations(may_be_read_by=toto)
+ self.commit()
+ photo1.clear_all_caches()
+ self.failUnless(photo1.may_be_read_by)
+ # test security with permissions
+ self.login('toto')
+ req = self.request()
+ self.assertEquals(len(req.execute('File X')), 2) # now toto has access to photo2
+ self.assertEquals(len(req.execute('Folder X')), 1) # and to restricted folder
+
+ if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
+
+It's not complete, but show most things you'll want to do in tests: adding some
+content, creating users and connecting as them in the test, etc...
+
+To run it type:
+
+.. sourcecode:: bash
+
+ $ pytest unittest_sytweb.py
+ ======================== unittest_sytweb.py ========================
+ -> creating tables [....................]
+ -> inserting default user and default groups.
+ -> storing the schema in the database [....................]
+ -> database for instance data initialized.
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 22.547s
+
+ OK
+
+
+The first execution is taking time, since it creates a sqlite database for the
+test instance. The second one will be much quicker:
+
+.. sourcecode:: bash
+
+ $ pytest unittest_sytweb.py
+ ======================== unittest_sytweb.py ========================
+ .
+ ----------------------------------------------------------------------
+ Ran 1 test in 2.662s
+
+ OK
+
+If you do some changes in your schema, you'll have to force regeneration of that
+database. You do that by removing the tmpdb files before running the test: ::
+
+ $ rm data/tmpdb*
+
+
+.. Note::
+ pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
+
+.. _`logilab-common`: http://www.logilab.org/project/logilab-common
+
+.. _adv_tuto_migration_script:
+
+Step 4: writing the migration script and migrating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Prior to those changes, I created an instance, feeded it with some data, so I
+don't want to create a new one, but to migrate the existing one. Let's see how to
+do that.
+
+Migration commands should be put in the cube's *migration* directory, in a
+file named file:`<X.Y.Z>_Any.py` ('Any' being there mostly for historical reason).
+
+Here I'll create a *migration/0.2.0_Any.py* file containing the following
+instructions:
+
+.. sourcecode:: python
+
+ add_relation_type('may_be_read_by')
+ add_relation_type('visibility')
+ sync_schema_props_perms()
+
+Then I update the version number in cube's *__pkginfo__.py* to 0.2.0. And
+that's it! Those instructions will:
+
+* update the instance's schema by adding our two new relations and update the
+ underlying database tables accordingly (the two first instructions)
+
+* update schema's permissions definition (the last instruction)
+
+
+To migrate my instance I simply type::
+
+ cubicweb-ctl upgrade sytweb
+
+You'll then be asked some questions to do the migration step by step. You should say
+YES when it asks if a backup of your database should be done, so you can get back
+to initial state if anything goes wrong...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part03_bfss.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,131 @@
+Storing images on the file-system
+---------------------------------
+
+Step 1: configuring the BytesFileSystem storage
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To avoid cluttering my database, and to ease file manipulation, I don't want them
+to be stored in the database. I want to be able create File entities for some
+files on the server file system, where those file will be accessed to get
+entities data. To do so I've to set a custom :class:`BytesFileSystemStorage`
+storage for the File 'data' attribute, which hold the actual file's content.
+
+Since the function to register a custom storage needs to have a repository
+instance as first argument, we've to call it in a server startup hook. So I added
+in `cubes/sytweb/hooks.py` :
+
+.. sourcecode:: python
+
+ from os import makedirs
+ from os.path import join, exists
+
+ from cubicweb.server import hook
+ from cubicweb.server.sources import storage
+
+ class ServerStartupHook(hook.Hook):
+ __regid__ = 'sytweb.serverstartup'
+ events = ('server_startup', 'server_maintenance')
+
+ def __call__(self):
+ bfssdir = join(self.repo.config.appdatahome, 'bfss')
+ if not exists(bfssdir):
+ makedirs(bfssdir)
+ print 'created', bfssdir
+ storage = storages.BytesFileSystemStorage(bfssdir)
+ set_attribute_storage(self.repo, 'File', 'data', storage)
+
+.. Note::
+
+ * how we built the hook's registry identifier (_`_regid__`): you can introduce
+ 'namespaces' by using there python module like naming identifiers. This is
+ especially import for hooks where you usually want a new custom hook, not
+ overriding / specializing an existant one, but the concept may be applied to
+ any application objects
+
+ * we catch two events here: "server_startup" and "server_maintenance". The first
+ is called on regular repository startup (eg, as a server), the other for
+ maintenance task such as shell or upgrade. In both cases, we need to have
+ the storage set, else we'll be in trouble...
+
+ * the path given to the storage is the place where file added through the ui
+ (or in the database before migration) will be located
+
+ * be ware that by doing this, you can't anymore write queries that will try to
+ restrict on File `data` attribute. Hopefuly we don't do that usually
+ on file's content or more generally on attributes for the Bytes type
+
+Now, if you've already added some photos through the web ui, you'll have to
+migrate existing data so file's content will be stored on the file-system instead
+of the database. There is a migration command to do so, let's run it in the
+cubicweb shell (in actual life, you'd have to put it in a migration script as we
+seen last time):
+
+::
+
+ $ cubicweb-ctl shell sytweb
+ entering the migration python shell
+ just type migration commands or arbitrary python code and type ENTER to execute it
+ type "exit" or Ctrl-D to quit the shell and resume operation
+ >>> storage_changed('File', 'data')
+ [........................]
+
+
+That's it. Now, file added through the web ui will have their content stored on
+the file-system, and you'll also be able to import files from the file-system as
+explained in the next part.
+
+Step 2: importing some data into the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Hey, we start to have some nice features, let give us a try on this new web
+site. For instance if I have a 'photos/201005WePyrenees' containing pictures for
+a particular event, I can import it to my web site by typing ::
+
+ $ cubicweb-ctl fsimport -F sytweb photos/201005WePyrenees/
+ ** importing directory /home/syt/photos/201005WePyrenees
+ importing IMG_8314.JPG
+ importing IMG_8274.JPG
+ importing IMG_8286.JPG
+ importing IMG_8308.JPG
+ importing IMG_8304.JPG
+
+.. Note::
+ The -F option tell that folders should be mapped, hence my photos will be
+ all under a Folder entity corresponding to the file-system folder.
+
+Let's take a look at the web ui:
+
+.. image:: ../../images/tutos-photowebsite_ui1.png
+
+Nothing different, I can't see the new folder... But remember our security model!
+By default, files are only accessible to authenticated users, and I'm looking at
+the site as anonymous, e.g. not authenticated. If I login, I can now see:
+
+.. image:: ../../images/tutos-photowebsite_ui2.png
+
+Yeah, it's there! You can also notice that I can see some entities as well as
+folders and images the anonymous user can't. It just works **everywhere in the
+ui** since it's handled at the repository level, thanks to our security model.
+
+Now if I click on the newly inserted folder, I can see
+
+.. image:: ../../images/tutos-photowebsite_ui3.png
+
+Great! There is even my pictures in the folder. I can know give to this folder a
+nicer name (provided I don't intend to import from it anymore, else already
+imported photos will be reimported), change permissions, title for some pictures,
+etc... Having a good content is much more difficult than having a good web site
+;)
+
+
+Conclusion
+~~~~~~~~~~
+
+We started to see here an advanced feature of our repository: the ability
+to store some parts of our data-model into a custom storage, outside the
+database. There is currently only the :class:`BytesFileSystemStorage` available,
+but you can expect to see more coming in a near future (our write your own!).
+
+Also, we can know start to feed our web-site with some nice pictures!
+The site isn't perfect (far from it actually) but it's usable, and we can
+now start using it and improve it on the way. The Incremental Cubic Way :)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,365 @@
+Let's make it more user friendly
+================================
+
+
+Step 1: let's improve site's usability for our visitors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first thing I've noticed is that people to whom I send links to photos with
+some login/password authentication get lost, because they don't grasp they have
+to login by clicking on the 'authenticate' link. That's much probably because
+they only get a 404 when trying to access an unauthorized folder, and the site
+doesn't make clear that 1. you're not authenticated, 2. you could get more
+content by authenticating yourself.
+
+So, to improve this situation, I decided that I should:
+
+* make a login box appears for anonymous, so they see at a first glance a place
+ to put the login / password information I provided
+
+* customize the 404 page, proposing to login to anonymous.
+
+Here is the code, samples from my cube's `views.py` file:
+
+.. sourcecode:: python
+
+ from cubicweb.selectors import is_instance
+ from cubicweb.web import component
+ from cubicweb.web.views import error
+
+ class FourOhFour(error.FourOhFour):
+ __select__ = error.FourOhFour.__select__ & anonymous_user()
+
+ def call(self):
+ self.w(u"<h1>%s</h1>" % self._cw._('this resource does not exist'))
+ self.w(u"<p>%s</p>" % self._cw._('have you tried to login?'))
+
+
+ class LoginBox(component.CtxComponent):
+ """display a box containing links to all startup views"""
+ __regid__ = 'sytweb.loginbox'
+ __select__ = component.CtxComponent.__select__ & anonymous_user()
+
+ title = _('Authenticate yourself')
+ order = 70
+
+ def render_body(self, w):
+ cw = self._cw
+ form = cw.vreg['forms'].select('logform', cw)
+ form.render(w=w, table_class='', display_progress_div=False)
+
+The first class provides a new specific implementation of the default page you
+get on 404 error, to display an adapted message to anonymous user.
+
+.. Note::
+
+ Thanks to the selection mecanism, it will be selected for anoymous user,
+ since the additional `anonymous_user()` selector gives it a higher score than
+ the default, and not for authenticated since this selector will return 0 in
+ such case (hence the object won't be selectable)
+
+The second class defines a simple box, that will be displayed by default with
+boxes in the left column, thanks to default :class:`component.CtxComponent`
+selector. The HTML is written to match default CubicWeb boxes style. The code
+fetch the actual login form and render it.
+
+
+.. figure:: ../../images/tutos-photowebsite_login-box.png
+ :alt: login box / 404 screenshot
+
+ The login box and the custom 404 page for an anonymous visitor (translated in french)
+
+
+Step 2: providing a custom index page
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Another thing we can easily do to improve the site is... A nicer index page
+(e.g. the first page you get when accessing the web site)! The default one is
+quite intimidating (that should change in a near future). I will provide a much
+simpler index page that simply list available folders (e.g. photo albums in that
+site).
+
+.. sourcecode:: python
+
+ from cubicweb.web.views import startup
+
+ class IndexView(startup.IndexView):
+ def call(self, **kwargs):
+ self.w(u'<div>\n')
+ if self._cw.cnx.anonymous_connection:
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Public Albums'))
+ else:
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Albums for %s') % self._cw.user.login)
+ self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
+ self.w(u'</div>\n')
+
+ def registration_callback(vreg):
+ vreg.register_all(globals().values(), __name__, (IndexView,))
+ vreg.register_and_replace(IndexView, startup.IndexView)
+
+As you can see, we override the default index view found in
+`cubicweb.web.views.startup`, geting back nothing but its identifier and selector
+since we override the top level view's `call` method.
+
+.. Note::
+
+ in that case, we want our index view to **replace** the existing one. To do so
+ we've to implements the `registration_callback` function, in which we tell to
+ register everything in the module *but* our IndexView, then we register it
+ instead of the former index view.
+
+Also, we added a title that tries to make it more evident that the visitor is
+authenticated, or not. Hopefuly people will get it now!
+
+
+.. figure:: ../../images/tutos-photowebsite_index-before.png
+ :alt: default index page screenshot
+
+ The default index page
+
+.. figure:: ../../images/tutos-photowebsite_index-after.png
+ :alt: new index page screenshot
+
+ Our simpler, less intimidating, index page (still translated in french)
+
+
+Step 3: more navigation improvments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are still a few problems I want to solve...
+
+* Images in a folder are displayed in a somewhat random order. I would like to
+ have them ordered by file's name (which will usually, inside a given folder,
+ also result ordering photo by their date and time)
+
+* When clicking a photo from an album view, you've to get back to the gallery
+ view to go to the next photo. This is pretty annoying...
+
+* Also, when viewing an image, there is no clue about the folder to which this
+ image belongs to.
+
+I will first try to explain the ordering problem. By default, when accessing related
+entities by using the ORM's API, you should get them ordered according to the target's
+class `fetch_order`. If we take a look at the file cube'schema, we can see:
+
+.. sourcecode:: python
+
+
+ class File(AnyEntity):
+ """customized class for File entities"""
+ __regid__ = 'File'
+ fetch_attrs, fetch_order = fetch_config(['data_name', 'title'])
+
+By default, `fetch_config` will return a `fetch_order` method that will order on
+the first attribute in the list. So, we could expect to get files ordered by
+their name. But we don't. What's up doc ?
+
+The problem is that files are related to folder using the `filed_under` relation.
+And that relation is ambiguous, eg it can lead to `File` entities, but also to
+`Folder` entities. In such case, since both entity types doesn't share the
+attribute on which we want to sort, we'll get linked entities sorted on a common
+attribute (usually `modification_date`).
+
+To fix this, we've to help the ORM. We'll do this in the method from the `ITree`
+folder's adapter, used in the folder's primary view to display the folder's
+content. Here's the code, that I've put in our cube's `entities.py` file, since
+it's more logical stuff than view stuff:
+
+.. sourcecode:: python
+
+ from cubes.folder import entities as folder
+
+ class FolderITreeAdapter(folder.FolderITreeAdapter):
+
+ def different_type_children(self, entities=True):
+ rql = self.entity.cw_related_rql(self.tree_relation,
+ self.parent_role, ('File',))
+ rset = self._cw.execute(rql, {'x': self.entity.eid})
+ if entities:
+ return list(rset.entities())
+ return rset
+
+ def registration_callback(vreg):
+ vreg.register_and_replace(FolderITreeAdapter, folder.FolderITreeAdapter)
+
+As you can see, we simple inherit from the adapter defined in the `folder` cube,
+then we override the `different_type_children` method to give a clue to the ORM's
+`cw_related_rql` method, that is responsible to generate the rql to get entities
+related to the folder by the `filed_under` relation (the value of the
+`tree_relation` attribute). The clue is that we only want to consider the `File`
+target entity type. By doing this, we remove the ambiguity and get back a RQL
+query that correctly order files by their `data_name` attribute.
+
+
+.. Note::
+
+ * Adapters have been introduced in CubicWeb 3.9 / cubicweb-folder 1.8.
+
+ * As seen earlier, we want to **replace** the folder's `ITree` adapter by our
+ implementation, hence the custom `registration_callback` method.
+
+
+Ouf. That one was tricky...
+
+Now the easier parts. Let's start by adding some links on the file's primary view
+to see the previous / next image in the same folder. CubicWeb's provide a
+component that do exactly that. To make it appears, one have to be adaptable to
+the `IPrevNext` interface. Here is the related code sample, extracted from our
+cube's `views.py` file:
+
+.. sourcecode:: python
+
+ from cubicweb.selectors import is_instance
+ from cubicweb.web.views import navigation
+
+
+ class FileIPrevNextAdapter(navigation.IPrevNextAdapter):
+ __select__ = is_instance('File')
+
+ def previous_entity(self):
+ rset = self._cw.execute('File F ORDERBY FDN DESC LIMIT 1 WHERE '
+ 'X filed_under FOLDER, F filed_under FOLDER, '
+ 'F data_name FDN, X data_name > FDN, X eid %(x)s',
+ {'x': self.entity.eid})
+ if rset:
+ return rset.get_entity(0, 0)
+
+ def next_entity(self):
+ rset = self._cw.execute('File F ORDERBY FDN ASC LIMIT 1 WHERE '
+ 'X filed_under FOLDER, F filed_under FOLDER, '
+ 'F data_name FDN, X data_name < FDN, X eid %(x)s',
+ {'x': self.entity.eid})
+ if rset:
+ return rset.get_entity(0, 0)
+
+
+The `IPrevNext` interface implemented by the adapter simply consist in the
+`previous_entity` / `next_entity` methods, that should respectivly return the
+previous / next entity or `None`. We make an RQL query to get files in the same
+folder, ordered similarly (eg by their `data_name` attribute). We set
+ascendant/descendant ordering and a strict comparison with current file's name
+(the "X" variable representing the current file).
+
+.. Note::
+
+ * Former `implements` selector should be replaced by one of `is_instance` /
+ `adaptable` selector with CubicWeb >= 3.9. In our case, `is_instance` to
+ tell our adapter is able to adapt `File` entities.
+
+Notice that this query supposes we wont have two files of the same name in the
+same folder, else things may go wrong. Fixing this is out of the scope of this
+blog. And as I would like to have at some point a smarter, context sensitive
+previous/next entity, I'll probably never fix this query (though if I had to, I
+would probably choosing to add a constraint in the schema so that we can't add
+two files of the same name in a folder).
+
+One more thing: by default, the component will be displayed below the content
+zone (the one with the white background). You can change this in the site's
+properties through the ui, but you can also change the default value in the code
+by modifying the `context` attribute of the component:
+
+.. sourcecode:: python
+
+ navigation.NextPrevNavigationComponent.context = 'navcontentbottom'
+
+.. Note::
+
+ `context` may be one of 'navtop', 'navbottom', 'navcontenttop' or
+ 'navcontentbottom'; the first two being outside the main content zone, the two
+ others inside it.
+
+.. figure:: ../../images/tutos-photowebsite_prevnext.png
+ :alt: screenshot of the previous/next entity component
+
+ The previous/next entity component, at the bottom of the main content zone.
+
+Now, the only remaining stuff in my todo list is to see the file's folder. I'll use
+the standard breadcrumb component to do so. Similarly as what we've seen before, this
+component is controled by the :class:`IBreadCrumbs` interface, so we'll have to provide a custom
+adapter for `File` entity, telling the a file's parent entity is its folder:
+
+.. sourcecode:: python
+
+ from cubicweb.web.views import ibreadcrumbs
+
+ class FileIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
+ __select__ = is_instance('File')
+
+ def parent_entity(self):
+ if self.entity.filed_under:
+ return self.entity.filed_under[0]
+
+In that case, we simply use attribute notation provided by the ORM to get the
+folder in which the current file (e.g. `self.entity`) is located.
+
+.. Note::
+ The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
+ :class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
+ at the value returned by its `parent_entity` method. It also provides a
+ default implementation for this method for entities adapting to the `ITree`
+ interface, but as our `File` doesn't, we've to provide a custom adapter.
+
+.. figure:: ../../images/tutos-photowebsite_breadcrumbs.png
+ :alt: screenshot of the breadcrumb component
+
+ The breadcrumb component when on a file entity, now displaying parent folder.
+
+
+Step 4: preparing the release and migrating the instance
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Now that greatly enhanced our cube, it's time to release it to upgrade production site.
+I'll probably detail that process later, but I currently simply transfer the new code
+to the server running the web site.
+
+However, I've still today some step to respect to get things done properly...
+
+First, as I've added some translatable string, I've to run: ::
+
+ $ cubicweb-ctl i18ncube sytweb
+
+To update the cube's gettext catalogs (the '.po' files under the cube's `i18n`
+directory). Once the above command is executed, I'll then update translations.
+
+To see if everything is ok on my test instance, I do: ::
+
+ $ cubicweb-ctl i18ninstance sytweb
+ $ cubicweb-ctl start -D sytweb
+
+The first command compile i18n catalogs (e.g. generates '.mo' files) for my test
+instance. The second command start it in debug mode, so I can open my browser and
+navigate through the web site to see if everything is ok...
+
+.. Note::
+ In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
+ in the two other, it refers to the **instance** (if you can't see the
+ difference, reread CubicWeb's concept chapter !).
+
+
+Once I've checked it's ok, I simply have to bump the version number in the
+`__pkginfo__` module to trigger a migration once I'll have updated the code on
+the production site. I can check then check the migration is also going fine, by
+first restoring a dump from the production site, then upgrading my test instance.
+
+To generate a dump from the production site: ::
+
+ $ cubicweb-ctl db-dump sytweb
+ pg_dump -Fc --username=syt --no-owner --file /home/syt/etc/cubicweb.d/sytweb/backup/tmpYIN0YI/system sytweb
+ -> backup file /home/syt/etc/cubicweb.d/sytweb/backup/sytweb-2010-07-13_10-22-40.tar.gz
+
+I can now get back the dump file ('sytweb-2010-07-13_10-22-40.tar.gz') to my test
+machine (using `scp` for instance) to restore it and start migration: ::
+
+ $ cubicweb-ctl db-restore sytweb sytweb-2010-07-13_10-22-40.tar.gz
+ $ cubicweb-ctl upgrade sytweb
+
+You'll have to answer some questions, as we've seen in `an earlier post`_.
+
+Now that everything is tested, I can transfer the new code to the production
+server, `apt-get upgrade` cubicweb 3.9 and its dependencies, and eventually
+upgrade the production instance.
+
+
+.. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
+.. _`3.8`: http://www.cubicweb.org/blogentry/917107
+.. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
+.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,375 @@
+Building my photos web site with |cubicweb| part V: let's make it even more user friendly
+=========================================================================================
+
+We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb
+
+Step 1: tired of the default look?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+OK... Now our site has its most desired features. But... I would like to make it look
+somewhat like *my* website. It is not www.cubicweb.org after all. Let's tackle this
+first!
+
+The first thing we can to is to change the logo. There are various way to achieve
+this. The easiest way is to put a :file:`logo.png` file into the cube's :file:`data`
+directory. As data files are looked at according to cubes order (CubicWeb
+resources coming last), that file will be selected instead of CubicWeb's one.
+
+.. Note::
+ As the location for static resources are cached, you'll have to restart
+ your instance for this to be taken into account.
+
+Though there are some cases where you don't want to use a :file:`logo.png` file.
+For instance if it's a JPEG file. You can still change the logo by defining in
+the cube's :file:`uiprops.py` file:
+
+.. sourcecode:: python
+
+ LOGO = data('logo.jpg')
+
+The uiprops machinery has been introduced in `CubicWeb 3.9`_. It is used to define
+some static file resources, such as the logo, default Javascript / CSS files, as
+well as CSS properties (we'll see that later).
+
+.. Note::
+ This file is imported specifically by |cubicweb|, with a predefined name space,
+ containing for instance the `data` function, telling the file is somewhere
+ in a cube or CubicWeb's data directory.
+
+ One side effect of this is that it can't be imported as a regular python
+ module.
+
+The nice thing is that in debug mode, change to a :file:`uiprops.py` file are detected
+and then automatically reloaded.
+
+Now, as it's a photos web-site, I would like to have a photo of mine as background...
+After some trials I won't detail here, I've found a working recipe explained `here`_.
+All I've to do is to override some stuff of the default CubicWeb user interface to
+apply it as explained.
+
+The first thing to to get the ``<img/>`` tag as first element after the
+``<body>`` tag. If you know a way to avoid this by simply specifying the image
+in the CSS, tell me! The easiest way to do so is to override the
+:class:`HTMLPageHeader` view, since that's the one that is directly called once
+the ``<body>`` has been written. How did I find this? By looking in the
+:mod:`cubiweb.web.views.basetemplates` module, since I know that global page
+layouts sits there. I could also have grep the "body" tag in
+:mod:`cubicweb.web.views`... Finding this was the hardest part. Now all I need is
+to customize it to write that ``img`` tag, as below:
+
+.. sourcecode:: python
+
+ class HTMLPageHeader(basetemplates.HTMLPageHeader):
+ # override this since it's the easier way to have our bg image
+ # as the first element following <body>
+ def call(self, **kwargs):
+ self.w(u'<img id="bg-image" src="%sbackground.jpg" alt="background image"/>'
+ % self._cw.datadir_url)
+ super(HTMLPageHeader, self).call(**kwargs)
+
+
+ def registration_callback(vreg):
+ vreg.register_all(globals().values(), __name__, (HTMLPageHeader))
+ vreg.register_and_replace(HTMLPageHeader, basetemplates.HTMLPageHeader)
+
+
+As you may have guessed, my background image is in a :file:`background.jpg` file
+in the cube's :file:`data` directory, but there are still some things to explain
+to newcomers here:
+
+* The :meth:`call` method is there the main access point of the view. It's called by
+ the view's :meth:`render` method. It is not the only access point for a view, but
+ this will be detailed later.
+
+* Calling `self.w` writes something to the output stream. Except for binary views
+ (which do not generate text), it *must* be passed an Unicode string.
+
+* The proper way to get a file in :file:`data` directory is to use the `datadir_url`
+ attribute of the incoming request (e.g. `self._cw`).
+
+I won't explain again the :func:`registration_callback` stuff, you should understand it
+now! If not, go back to previous posts in the series :)
+
+Fine. Now all I've to do is to add a bit of CSS to get it to behave nicely (which
+is not the case at all for now). I'll put all this in a :file:`cubes.sytweb.css`
+file, stored as usual in our :file:`data` directory:
+
+.. sourcecode:: css
+
+
+ /* fixed full screen background image
+ * as explained on http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
+ *
+ * syt update: set z-index=0 on the img instead of z-index=1 on div#page & co to
+ * avoid pb with the user actions menu
+ */
+ img#bg-image {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ }
+
+ div#page, table#header, div#footer {
+ background: transparent;
+ position: relative;
+ }
+
+ /* add some space around the logo
+ */
+ img#logo {
+ padding: 5px 15px 0px 15px;
+ }
+
+ /* more dark font for metadata to have a chance to see them with the background
+ * image
+ */
+ div.metadata {
+ color: black;
+ }
+
+You can see here stuff explained in the cited page, with only a slight modification
+explained in the comments, plus some additional rules to make things somewhat cleaner:
+
+* a bit of padding around the logo
+
+* darker metadata which appears by default below the content (the white frame in the page)
+
+To get this CSS file used everywhere in the site, I have to modify the :file:`uiprops.py` file
+introduced above:
+
+.. sourcecode:: python
+
+ STYLESHEETS = sheet['STYLESHEETS'] + [data('cubes.sytweb.css')]
+
+.. Note::
+ `sheet` is another predefined variable containing values defined by
+ already process `:file:`uiprops.py`` file, notably the CubicWeb's one.
+
+Here we simply want our CSS in addition to CubicWeb's base CSS files, so we
+redefine the `STYLESHEETS` variable to existing CSS (accessed through the `sheet`
+variable) with our one added. I could also have done:
+
+.. sourcecode:: python
+
+ sheet['STYLESHEETS'].append(data('cubes.sytweb.css'))
+
+But this is less interesting since we don't see the overriding mechanism...
+
+At this point, the site should start looking good, the background image being
+resized to fit the screen.
+
+.. image:: ../../images/tutos-photowebsite_background-image.png
+
+The final touch: let's customize CubicWeb's CSS to get less orange... By simply adding
+
+.. sourcecode:: python
+
+ contextualBoxTitleBg = incontextBoxTitleBg = '#AAAAAA'
+
+and reloading the page we've just seen, we know have a nice greyed box instead of
+the orange one:
+
+.. image:: ../../images/tutos-photowebsite_grey-box.png
+
+This is because CubicWeb's CSS include some variables which are
+expanded by values defined in uiprops file. In our case we controlled the
+properties of the CSS `background` property of boxes with CSS class
+`contextualBoxTitleBg` and `incontextBoxTitleBg`.
+
+
+Step 2: configuring boxes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Boxes present to the user some ways to use the application. Let's first do a few
+user interface tweaks in our :file:`views.py` file:
+
+.. sourcecode:: python
+
+ from cubicweb.selectors import none_rset
+ from cubicweb.web.views import bookmark
+ from cubes.zone import views as zone
+ from cubes.tag import views as tag
+
+ # change bookmarks box selector so it's only displayed on startup views
+ bookmark.BookmarksBox.__select__ = bookmark.BookmarksBox.__select__ & none_rset()
+ # move zone box to the left instead of in the context frame and tweak its order
+ zone.ZoneBox.context = 'left'
+ zone.ZoneBox.order = 100
+ # move tags box to the left instead of in the context frame and tweak its order
+ tag.TagsBox.context = 'left'
+ tag.TagsBox.order = 102
+ # hide similarity box, not interested
+ tag.SimilarityBox.visible = False
+
+The idea is to move all boxes in the left column, so we get more space for the
+photos. Now, serious things: I want a box similar to the tags box but to handle
+the `Person displayed_on File` relation. We can do this simply by adding a
+:class:`AjaxEditRelationCtxComponent` subclass to our views, as below:
+
+.. sourcecode:: python
+
+ from logilab.common.decorators import monkeypatch
+ from cubicweb import ValidationError
+ from cubicweb.web import uicfg, component
+ from cubicweb.web.views import basecontrollers
+
+ # hide displayed_on relation using uicfg since it will be displayed by the box below
+ uicfg.primaryview_section.tag_object_of(('*', 'displayed_on', '*'), 'hidden')
+
+ class PersonBox(component.AjaxEditRelationCtxComponent):
+ __regid__ = 'sytweb.displayed-on-box'
+ # box position
+ order = 101
+ context = 'left'
+ # define relation to be handled
+ rtype = 'displayed_on'
+ role = 'object'
+ target_etype = 'Person'
+ # messages
+ added_msg = _('person has been added')
+ removed_msg = _('person has been removed')
+ # bind to js_* methods of the json controller
+ fname_vocabulary = 'unrelated_persons'
+ fname_validate = 'link_to_person'
+ fname_remove = 'unlink_person'
+
+
+ @monkeypatch(basecontrollers.JSonController)
+ @basecontrollers.jsonize
+ def js_unrelated_persons(self, eid):
+ """return tag unrelated to an entity"""
+ rql = "Any F + ' ' + S WHERE P surname S, P firstname F, X eid %(x)s, NOT P displayed_on X"
+ return [name for (name,) in self._cw.execute(rql, {'x' : eid})]
+
+
+ @monkeypatch(basecontrollers.JSonController)
+ def js_link_to_person(self, eid, people):
+ req = self._cw
+ for name in people:
+ name = name.strip().title()
+ if not name:
+ continue
+ try:
+ firstname, surname = name.split(None, 1)
+ except:
+ raise ValidationError(eid, {('displayed_on', 'object'): 'provide <first name> <surname>'})
+ rset = req.execute('Person P WHERE '
+ 'P firstname %(firstname)s, P surname %(surname)s',
+ locals())
+ if rset:
+ person = rset.get_entity(0, 0)
+ else:
+ person = req.create_entity('Person', firstname=firstname,
+ surname=surname)
+ req.execute('SET P displayed_on X WHERE '
+ 'P eid %(p)s, X eid %(x)s, NOT P displayed_on X',
+ {'p': person.eid, 'x' : eid})
+
+ @monkeypatch(basecontrollers.JSonController)
+ def js_unlink_person(self, eid, personeid):
+ self._cw.execute('DELETE P displayed_on X WHERE P eid %(p)s, X eid %(x)s',
+ {'p': personeid, 'x': eid})
+
+
+You basically subclass to configure with some class attributes. The `fname_*`
+attributes give the name of methods that should be defined on the json control to
+make the AJAX part of the widget work: one to get the vocabulary, one to add a
+relation and another to delete a relation. These methods must start by a `js_`
+prefix and are added to the controller using the `@monkeypatch` decorator. In my
+case, the most complicated method is the one which adds a relation, since it
+tries to see if the person already exists, and else automatically create it,
+assuming the user entered "firstname surname".
+
+Let's see how it looks like on a file primary view:
+
+.. image:: ../../images/tutos-photowebsite_boxes.png
+
+Great, it's now as easy for me to link my pictures to people than to tag them.
+Also, visitors get a consistent display of these two pieces of information.
+
+.. Note::
+ The ui component system has been refactored in `CubicWeb 3.10`_, which also
+ introduced the :class:`AjaxEditRelationCtxComponent` class.
+
+
+Step 3: configuring facets
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The last feature we'll add today is facet configuration. If you access to the
+'/file' url, you'll see a set of 'facets' appearing in the left column. Facets
+provide an intuitive way to build a query incrementally, by proposing to the user
+various way to restrict the result set. For instance CubicWeb proposes a facet to
+restrict based on who created an entity; the tag cube proposes a facet to
+restrict based on tags; the zoe cube a facet to restrict based on geographical
+location, and so on. In that gist, I want to propose a facet to restrict based on
+the people displayed on the picture. To do so, there are various classes in the
+:mod:`cubicweb.web.facet` module which simply have to be configured using class
+attributes as we've done for the box. In our case, we'll define a subclass of
+:class:`RelationFacet`.
+
+.. Note::
+
+ Since that's ui stuff, we'll continue to add code below to our
+ :file:`views.py` file. Though we begin to have a lot of various code their, so
+ it's may be a good time to split our views module into submodules of a `view`
+ package. In our case of a simple application (glue) cube, we could start using
+ for instance the layout below: ::
+
+ views/__init__.py # uicfg configuration, facets
+ views/layout.py # header/footer/background stuff
+ views/components.py # boxes, adapters
+ views/pages.py # index view, 404 view
+
+.. sourcecode:: python
+
+ from cubicweb.web import facet
+
+ class DisplayedOnFacet(facet.RelationFacet):
+ __regid__ = 'displayed_on-facet'
+ # relation to be displayed
+ rtype = 'displayed_on'
+ role = 'object'
+ # view to use to display persons
+ label_vid = 'combobox'
+
+Let's say we also want to filter according to the `visibility` attribute. This is
+even simpler as we just have to derive from the :class:`AttributeFacet` class:
+
+.. sourcecode:: python
+
+ class VisibilityFacet(facet.AttributeFacet):
+ __regid__ = 'visibility-facet'
+ rtype = 'visibility'
+
+Now if I search for some pictures on my site, I get the following facets available:
+
+.. image:: ../../images/tutos-photowebsite_facets.png
+
+.. Note::
+
+ By default a facet must be applyable to every entity in the result set and
+ provide at leat two elements of vocabulary to be displayed (for instance you
+ won't see the `created_by` facet if the same user has created all
+ entities). This may explain why you don't see yours...
+
+
+Conclusion
+~~~~~~~~~~
+
+We started to see the power behind the infrastructure provided by the
+framework, both on the pure ui (CSS, Javascript) side and on the Python side
+(high level generic classes for components, including boxes and facets). We now
+have, with a few lines of code, a full-featured web site with a personalized look.
+
+Of course we'll probably want more as time goes, but we can now
+concentrate on making good pictures, publishing albums and sharing them with
+friends...
+
+
+
+.. _`CubicWeb 3.10`: http://www.cubicweb.org/blogentry/1330518
+.. _`CubicWeb 3.9`: http://www.cubicweb.org/blogentry/1179899
+.. _`here`: http://webdesign.about.com/od/css3/f/blfaqbgsize.htm
--- a/doc/book/en/tutorials/base/blog-in-five-minutes.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/tutorials/base/blog-in-five-minutes.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,42 +1,70 @@
.. -*- coding: utf-8 -*-
-.. _BlogFiveMinutes:
+.. _TutosBaseBlogFiveMinutes:
Get a blog running in five minutes!
-----------------------------------
-For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`)::
+For Debian or Ubuntu users, first install the following packages
+(:ref:`DebianInstallation`)::
cubicweb, cubicweb-dev, cubicweb-blog
-For Windows or Mac OS X users, you must install cubicweb from source (see :ref:`SourceInstallation` and :ref:`WindowsInstallation`).
+Windows or Mac OS X users must install |cubicweb| from source (see
+:ref:`SourceInstallation` and :ref:`WindowsInstallation`).
Then create and initialize your instance::
cubicweb-ctl create blog myblog
-And start it::
+You'll be asked a few questions, and you can keep the default answer for most of
+them. The one question you'll have to think about is the database you'll want to
+use for that instance. For a quick test, if you don't have `postgresql` installed
+and configured (see :ref:`PostgresqlConfiguration`), it's highly recommended to
+choose `sqlite` when asked for which database driver to use, since it has a much
+simple setup (no database server needed).
+
+One the process is completed (including database initialisation), you can start
+your instance by using: ::
cubicweb-ctl start -D myblog
-The -D option is the debugging mode of cubicweb, removing it will lauch the instance in the background.
+The `-D` option activates the debugging mode. Removing it will launch the instance
+as a daemon in the background, and ``cubicweb-ctl stop myblog`` will stop
+it in that case.
+
+
+About file system permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Permission
-~~~~~~~~~~
+Unless you installed from sources, the above commands assume that you have root
+access to the :file:`/etc/` directory. In order to initialize your instance as a
+regular user, within your home directory, you can use the :envvar:`CW_MODE`
+environment variable: ::
-This command assumes that you have root access to the /etc/ path. In order to initialize your instance as a `user` (from scratch), please check your current PYTHONPATH then create the ~/etc/cubicweb.d directory.
+ export CW_MODE=user
+
+then create a :file:`~/etc/cubicweb.d` directory that will hold your instances.
+
+More information about how to configure your own environment is
+available in :ref:`ResourceMode`.
+
Instance parameters
~~~~~~~~~~~~~~~~~~~
-If you would like to change some instance parameters, for example, the database host or the user name, edit the `source` file located in the /etc/cubicweb.d/myblog directory.
+If you would like to change database parameters such as the database host or the
+user name used to connect to the database, edit the `sources` file located in the
+:file:`/etc/cubicweb.d/myblog` directory.
Then relaunch the database creation::
cubicweb-ctl db-create myblog
-Other paramaters, like web server or emails parameters, can be modified in the `all-in-one.conf` file.
+Other parameters, like web server or emails parameters, can be modified in the
+:file:`/etc/cubicweb.d/myblog/all-in-one.conf` file.
-This is it. Your blog is running. Visit http://localhost:8080 and enjoy it! This blog is fully functionnal. The next section section will present the way to develop new cubes and customizing the look of your instance.
+You'll have to restart the instance after modification in one of those files.
+This is it. Your blog is functional and running. Visit http://localhost:8080 and enjoy it!
--- a/doc/book/en/tutorials/base/components.rst Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubes:
-
-Cubes
------
-
-Standard library
-~~~~~~~~~~~~~~~~
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities in standard cubes are:
-
-* addressbook: PhoneNumber and PostalAddress
-
-* basket: Basket (like a shopping cart)
-
-* blog: Blog (a *very* basic blog)
-
-* classfolder: Folder (to organize things but grouping them in folders)
-
-* classtags: Tag (to tag anything)
-
-* comment: Comment (to attach comment threads to entities)
-
-* file: File (to allow users to upload and store binary or text files)
-
-* link: Link (to collect links to web resources)
-
-* mailinglist: MailingList (to reference a mailing-list and the URLs
- for its archives and its admin interface)
-
-* person: Person (easily mixed with addressbook)
-
-* task: Task (something to be done between start and stop date)
-
-* zone: Zone (to define places within larger places, for example a
- city in a state in a country)
-
-.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
-
-Adding comments to BlogDemo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To import a cube in your instance just change the line in the
-``__pkginfo__.py`` file and verify that the cube you are planning
-to use is listed by the command ``cubicweb-ctl list``.
-For example::
-
- __use__ = ('comment',)
-
-will make the ``Comment`` entity available in your ``BlogDemo``
-cube.
-
-Change the schema to add a relationship between ``BlogEntry`` and
-``Comment`` and you are done. Since the comment cube defines the
-``comments`` relationship, adding the line::
-
- comments = ObjectRelation('Comment', cardinality='1*', composite='object')
-
-to the definition of a ``BlogEntry`` will be enough.
-
-Synchronize the data model
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once you modified your data model, you need to synchronize the
-database with your model. For this purpose, *CubicWeb* provides
-a very useful command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive shell where you can enter migration
-commands (see :ref:`cubicweb-ctl` for more details)).
-As you added the cube named `comment`, you need to run:
-
-::
-
- add_cube('comment')
-
-You can now start your instance and comment your blog entries.
--- a/doc/book/en/tutorials/base/conclusion.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/tutorials/base/conclusion.rst Fri Mar 11 09:46:45 2011 +0100
@@ -3,11 +3,16 @@
What's next?
------------
-In this chapter, we have seen have you can, right after the installation of *CubicWeb*, build a web application in five minutes by defining a data model. Everything is there already: views, templates, permissions, etc.
+In this tutorial, we have seen have you can, right after the installation of
+|cubicweb|, build a web application in a few minutes by defining a data model as
+assembling cubes. You get a working application that you can then customize there
+and there while keeping something that works. This is important in agile
+development practices, you can right from the start of the project show things
+to customer and so take the right decision early in the process.
-The next step is to change the design and learn about the many features available to customize and extend your application: RSS channels (:ref:`XmlAndRss`), events (:ref:`hooks`), support of sources such as
-Google App Engine (:ref:`GoogleAppEngineSource`), etc.
-
-You will find more `tutorials and howtos`_ in the blog published on the CubicWeb.org website.
+The next steps will be to discover hooks, security, data sources, digging deeper
+into view writing and interface customisation... Yet a lot of fun stuff to
+discover! You will find more `tutorials and howtos`_ in the blog published on the
+CubicWeb.org website.
.. _`tutorials and howtos`: http://www.cubicweb.org/view?rql=Any+X+ORDERBY+D+DESC+WHERE+X+is+BlogEntry%2C+T+tags+X%2C+T+name+IN+%28%22tutorial%22%2C+%22howto%22%29%2C+X+creation_date+D
--- a/doc/book/en/tutorials/base/create-cube.rst Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,437 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Steps:
-
-Steps for creating your cube
-----------------------------
-
-The following steps will help you to create and customize a new cube.
-
-1. :ref:`CreateYourCube`
-
-Create the directory to hold the code of your cube. The most important
-files that will be useful to customize your newly created cube are:
-
- * schema.py: contains the data model
- * views.py: contains your custom views
- * entities.py: contains XXX
-
-The detailed structure of the code directory is described in :ref:`cubelayout`.
-
-2. :ref:`DefineDataModel`
-
-Define the data model of your application.
-
-3. :ref:`ExploreYourInstance`
-
-Create, run, and explore an instance of your cube.
-
-4. :ref:`DefineViews`
-
-Customize the views of your data: how and which part of your data are showed.
-
-.. note:: views do not define the look'n'feel and the design of your application. For that, you will use CSS and the files located 'blog/data/'.
-
-
-5. :ref:`DefineEntities`
-
-Define your own entities to add useful functions when you manipulate your data, especially when you write view.
-
-
-.. _CreateYourCube:
-
-Create your cube
-----------------
-
-The packages ``cubicweb`` and ``cubicweb-dev`` install a command line
-tool for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide
-range of commands described in details in :ref:`cubicweb-ctl`.
-
-Once your *CubicWeb* development environment is set up, you can create
-a new cube::
-
- cubicweb-ctl newcube blog
-
-This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
-installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
-a directory named ``blog`` reflecting the structure described in :ref:`Concepts`.
-
-
-For packages installation, you can still create new cubes in your home directory using the following configuration. Let's say you want to develop your new cubes in `~src/cubes`, then set the following environment variables:
-::
-
- CW_CUBES_PATH=~/src/cubes
- CW_MODE=user
-
-and then create your new cube using:
-::
-
- cubicweb-ctl newcube --directory=~/src/cubes blog
-
-
-.. _DefineDataModel:
-
-Define your data model
-----------------------
-
-The data model or schema is the core of your *CubicWeb* application.
-It defines the type of content your application will handle.
-
-The data model of your cube ``blog`` is defined in the file ``schema.py``:
-
-.. sourcecode:: python
-
- from yams.buildobjs import EntityType, String, SubjectRelation, Date
-
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
-
- class BlogEntry(EntityType):
- title = String(required=True, fulltextindexed=True, maxsize=256)
- publish_date = Date(default='TODAY')
- content = String(required=True, fulltextindexed=True)
- entry_of = SubjectRelation('Blog', cardinality='?*')
-
-The first step is the import of the EntityType (generic class for entity and
-attributes that will be used in both Blog and BlogEntry entities.
-
-A Blog has a title and a description. The title is a string that is
-required and must be less than 50 characters. The
-description is a string that is not constrained.
-
-A BlogEntry has a title, a publish_date and a content. The title is a
-string that is required and must be less than 100 characters. The
-publish_date is a Date with a default value of TODAY, meaning that
-when a BlogEntry is created, its publish_date will be the current day
-unless it is modified. The content is a string that will be indexed in
-the database full-text index and has no constraint.
-
-A BlogEntry also has a relationship ``entry_of`` that links it to a
-Blog. The cardinality ``?*`` means that a BlogEntry can be part of
-zero or one Blog (``?`` means `zero or one`) and that a Blog can
-have any number of BlogEntry (``*`` means `any number including
-zero`). For completeness, remember that ``+`` means `one or more`.
-
-
-.. _ExploreYourInstance:
-
-Create and explore your instance
---------------------------------
-.. _CreateYourInstance:
-
-Create your instance
-~~~~~~~~~~~~~~~~~~~~
-
-To use this cube as an instance and create a new instance named ``blogdemo``, do::
-
- cubicweb-ctl create blog blogdemo
-
-This command will create the corresponding database and initialize it.
-
-
-.. _WelcomeToYourWebInstance:
-
-Welcome to your web instance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Start your instance in debug mode with the following command: ::
-
- cubicweb-ctl start -D blogdemo
-
-
-You can now access your web instance to create blogs and post messages
-by visiting the URL http://localhost:8080/.
-
-A login form will appear. By default, the instance will not allow anonymous
-users to enter the instance. To login, you need then use the admin account
-you created at the time you initialized the database with ``cubicweb-ctl
-create``.
-
-.. image:: ../../images/login-form.png
-
-
-Once authenticated, you can start playing with your instance
-and create entities.
-
-.. image:: ../../images/blog-demo-first-page.png
-
-Please notice that so far, the *CubicWeb* framework managed all aspects of
-the web application based on the schema provided at the beginning.
-
-.. _AddEntities:
-
-Add entities
-~~~~~~~~~~~~
-
-We will now add entities in our web application.
-
-Add a Blog
-**********
-
-Let us create a few of these entities. Click on the `[+]` at the left of the
-link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
-``everything about technology`` as the description, then validate the form by
-clicking on ``Validate``.
-
-.. image:: ../../images/cbw-create-blog_en.png
- :alt: from to create blog
-
-Click on the logo at top left to get back to the home page, then
-follow the Blog link that will list for you all the existing Blog.
-You should be seeing a list with a single item ``Tech-blog`` you
-just created.
-
-.. image:: ../../images/cbw-list-one-blog_en.png
- :alt: displaying a list of a single blog
-
-Clicking on this item will get you to its detailed description except
-that in this case, there is not much to display besides the name and
-the phrase ``everything about technology``.
-
-Now get back to the home page by clicking on the top-left logo, then
-create a new Blog called ``MyLife`` and get back to the home page
-again to follow the Blog link for the second time. The list now
-has two items.
-
-.. image:: ../../images/cbw-list-two-blog_en.png
- :alt: displaying a list of two blogs
-
-Add a BlogEntry
-***************
-
-Get back to the home page and click on [+] at the left of the link
-BlogEntry. Call this new entry ``Hello World`` and type in some text
-before clicking on ``Validate``. You added a new blog entry without
-saying to what blog it belongs. There is a box on the left entitled
-``actions``, click on the menu item ``modify``. You are back to the form
-to edit the blog entry you just created, except that the form now has
-another section with a combobox titled ``add relation``. Chose
-``entry_of`` in this menu and a second combobox appears where you pick
-``MyLife``.
-
-You could also have, at the time you started to fill the form for a
-new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
-combobox titled ``add relation`` would have showed up.
-
-
-.. image:: ../../images/cbw-add-relation-entryof_en.png
- :alt: editing a blog entry to add a relation to a blog
-
-Validate the changes by clicking ``Validate``. The entity BlogEntry
-that is displayed now includes a link to the entity Blog named
-``MyLife``.
-
-.. image:: ../../images/cbw-detail-one-blogentry_en.png
- :alt: displaying the detailed view of a blogentry
-
-Note that all of this was handled by the framework and that the only input
-that was provided so far is the schema. To get a graphical view of the schema,
-point your browser to the URL http://localhost:8080/schema
-
-.. image:: ../../images/cbw-schema_en.png
- :alt: graphical view of the schema (aka data-model)
-
-
-.. _DefineViews:
-
-Define your entity views
-------------------------
-
-Each entity defined in a model is associated with default views
-allowing different renderings of the data. You can redefine each of
-them according to your needs and preferences. So let's see how the
-views are defined.
-
-
-The view selection principle
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A view is defined by a Python class which includes:
-
- - an identifier (all objects in *CubicWeb* are recorded in a
- registry and this identifier will be used as a key)
-
- - a filter to select the result sets it can be applied to
-
-A view has a set of methods complying with the `View` class interface
-(`cubicweb.common.view`).
-
-*CubicWeb* provides a lot of standard views for the type `EntityView`;
-for a complete list, read the code in directory ``cubicweb/web/views/``.
-
-A view is applied on a `result set` which contains a set of entities
-we are trying to display. *CubicWeb* uses a selector mechanism which
-computes for each available view a score: the view with the highest
-score is then used to display the given `result set`. The standard
-library of selectors is in ``cubicweb.selector``.
-
-It is possible to define multiple views for the same identifier
-and to associate selectors and filters to allow the application
-to find the most appropriate way to render the data.
-
-For example, the view named ``primary`` is the one used to display a
-single entity. We will now show you how to create a primary view for
-BlogEntry.
-
-
-Primary view customization
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you wish to modify the way a `BlogEntry` is rendered, you will have
-to subclass the `primary` view, for instance in the module ``views``
-of the cube ``cubes/blog/views.py``.
-
-The standard primary view is the most sophisticated view of all. It
-has more than a call() method. It is a template. Actually the entry
-point calls the following sequence of (redefinable) methods:
-
- * render_entity_title
-
- * render_entity_metadata
-
- * render_entity_attributes
-
- * render_entity_relations
-
- * render_side_boxes
-
-Excepted side boxes, we can see all of them already in action in the
-blog entry view. This is all described in more details in
-:ref:`primary_view`.
-
-We can for example add in front of the publication date a prefix
-specifying that the date we see is the publication date.
-
-To do so, please apply the following changes:
-
-.. sourcecode:: python
-
- from cubicweb.selectors import is_instance
- from cubicweb.web.views import primary
-
- class BlogEntryPrimaryView(primary.PrimaryView):
- __select__ = is_instance('BlogEntry')
-
- def render_entity_attributes(self, entity):
- self.w(u'<p>published on %s</p>' %
- entity.publish_date.strftime('%Y-%m-%d'))
- super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
-
-.. note::
- When a view is modified, it is not required to restart the instance
- server. Save the Python file and reload the page in your web browser
- to view the changes.
-
-You can now see that the publication date has a prefix.
-
-.. image:: ../../images/cbw-update-primary-view_en.png
- :alt: modified primary view
-
-
-The above source code defines a new primary view for ``BlogEntry``.
-
-Since views are applied to result sets and result sets can be tables of
-data, we have to recover the entity from its (row,col)-coordinates.
-The view has a ``self.w()`` method that is used to output data, in our
-example HTML output.
-
-.. note::
- You can find more details about views and selectors in :ref:`Views`.
-
-
-.. _DefineEntities:
-
-Write entities to add logic in your data
-----------------------------------------
-
-By default, CubicWeb provides a default entity for each data type defined in the schema.
-A default entity mainly contains the attributes defined in the data model.
-
-You can redefine each entity to provide additional functions to help you write your views.
-
-.. sourcecode:: python
-
- from cubicweb.entities import AnyEntity
-
- class BlogEntry(AnyEntity):
- """customized class for BlogEntry entities"""
- __regid__ = 'BlogEntry'
-
- def display_cw_logo(self):
- if 'CW' in self.title:
- return True
- else:
- return False
-
-Customizing an entity requires that your entity:
- - inherits from ``cubicweb.entities`` or any subclass
- - defines a ``__regid__`` linked to the corresponding data type of your schema
- - implements the base class by explicitly using ``__implements__``.
-
-We implemented here a function ``display_cw_logo`` which tests if the blog entry title contains 'CW'.
-This function can then be used when you customize your views. For instance, you can modify your previous ``views.py`` as follows:
-
-.. sourcecode:: python
-
- class BlogEntryPrimaryView(primary.PrimaryView):
- __select__ = is_instance('BlogEntry')
-
- ...
-
- def render_entity_title(self, entity):
- if entity.display_cw_logo():
- self.w(u'<image src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
- super(BlogEntryPrimaryView, self).render_entity_title(entity)
-
-Then each blog entry whose title contains 'CW' is shown with the CubicWeb logo in front of it.
-
-.. _UpdatingSchemaAndSynchronisingInstance:
-
-Updating the schema and synchronising the instance
---------------------------------------------------
-
-While developping your cube, you may want to update your data model. Let's say you
-want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
-
-The required steps are:
-
-1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
-
-.. sourcecode:: python
-
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
- category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
-
-2. stop your ``blogdemo`` instance:
-
-.. sourcecode:: bash
-
- cubicweb-ctl stop blogdemo
-
-3. start the cubicweb shell for your instance by running the following command:
-
-.. sourcecode:: bash
-
- cubicweb-ctl shell blogdemo
-
-4. at the cubicweb shell prompt, execute:
-
-.. sourcecode:: python
-
- add_attribute('Blog', 'category')
-
-5. restart your instance:
-
-.. sourcecode:: bash
-
- cubicweb-ctl start blogdemo
-
-6. modify a blog entity and check that the new attribute
-``category`` has been added.
-
-Of course, you may also want to add relations, entity types, etc. See :ref:`migration`
-for a list of all available migration commands.
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/customizing-the-application.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,539 @@
+.. -*- coding: utf-8 -*-
+
+.. _TutosBaseCustomizingTheApplication:
+
+Customizing your application
+----------------------------
+
+So far so good. The point is that usually, you won't get enough by assembling
+cubes out-of-the-box. You will want to customize them, have a personal look and
+feel, add your own data model and so on. Or maybe start from scratch?
+
+So let's get a bit deeper and start coding our own cube. In our case, we want
+to customize the blog we created to add more features to it.
+
+
+Create your own cube
+~~~~~~~~~~~~~~~~~~~~
+
+First, notice that if you've installed |cubicweb| using Debian packages, you will
+need the additional ``cubicweb-dev`` package to get the commands necessary to
+|cubicweb| development. All `cubicweb-ctl` commands are described in details in
+:ref:`cubicweb-ctl`.
+
+Once your |cubicweb| development environment is set up, you can create a new
+cube::
+
+ cubicweb-ctl newcube myblog
+
+This will create in the cubes directory (:file:`/path/to/forest/cubes` for source
+installation, :file:`/usr/share/cubicweb/cubes` for Debian packages installation)
+a directory named :file:`blog` reflecting the structure described in
+:ref:`cubelayout`.
+
+For packages installation, you can still create new cubes in your home directory
+using the following configuration. Let's say you want to develop your new cubes
+in `~src/cubes`, then set the following environment variables: ::
+
+ CW_CUBES_PATH=~/src/cubes
+
+and then create your new cube using: ::
+
+ cubicweb-ctl newcube --directory=~/src/cubes myblog
+
+.. Note::
+
+ We previously used `myblog` as the name of our *instance*. We're now creating
+ a *cube* with the same name. Both are different things. We'll now try to
+ specify when we talk about one or another, but keep in mind this difference.
+
+
+Cube metadata
+~~~~~~~~~~~~~
+
+A simple set of metadata about your cube are stored in the :file:`__pkginfo__.py`
+file. In our case, we want to extend the blog cube, so we have to tell that our
+cube depends on this cube, by modifying the ``__depends__`` dictionary in that
+file:
+
+.. sourcecode:: python
+
+ __depends__ = {'cubicweb': '>= 3.10.7',
+ 'cubicweb-blog': None}
+
+where the ``None`` means we do not depends on a particular version of the cube.
+
+.. _TutosBaseCustomizingTheApplicationDataModel:
+
+Extending the data model
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The data model or schema is the core of your |cubicweb| application. It defines
+the type of content your application will handle. It is defined in the file
+:file:`schema.py` of the cube.
+
+
+Defining our model
+******************
+
+For the sake of example, let's say we want a new entity type named `Community`
+with a name, a description. A `Community` will hold several blogs.
+
+.. sourcecode:: python
+
+ from yams.buildobjs import EntityType, RelationDefinition, String, RichString
+
+ class Community(EntityType):
+ name = String(maxsize=50, required=True)
+ description = RichString()
+
+ class community_blog(RelationDefinition):
+ subject = 'Community'
+ object = 'Blog'
+ cardinality = '*?'
+ composite = 'subject'
+
+The first step is the import from the :mod:`yams` package necessary classes to build
+the schema.
+
+This file defines the following:
+
+* a `Community` has a title and a description as attributes
+
+ - the name is a string that is required and can't be longer than 50 characters
+
+ - the description is a string that is not constrained and may contains rich
+ content such as HTML or Restructured text.
+
+* a `Community` may be linked to a `Blog` using the `community_blog` relation
+
+ - ``*`` means a community may be linked to 0 to N blog, ``?`` means a blog may
+ be linked to 0 to 1 community. For completeness, remember that you can also
+ use ``+`` for 1 to N, and ``1`` for single, mandatory relation (e.g. one to one);
+
+ - this is a composite relation where `Community` (e.g. the subject of the
+ relation) is the composite. That means that if you delete a community, its
+ blog will be deleted as well.
+
+Of course, there are a lot of other data types and things such as constraints,
+permissions, etc, that may be defined in the schema, but those won't be covered
+in this tutorial.
+
+Notice that our schema refers to the `Blog` entity type which is not defined
+here. But we know this type is available since we depend on the `blog` cube
+which is defining it.
+
+
+Applying changes to the model into our instance
+***********************************************
+
+Now the problem is that we created an instance using the `blog` cube, not our
+`myblog` cube, so if we don't do anything there is no way that we'll see anything
+changing in the instance.
+
+One easy way, as we've no really valuable data in the instance would be to trash and recreated it::
+
+ cubicweb-ctl stop myblog # or Ctrl-C in the terminal running the server in debug mode
+ cubicweb-ctl delete myblog
+ cubicweb-ctl create myblog
+ cubicweb-ctl start -D myblog
+
+Another way is to add our cube to the instance using the cubicweb-ctl shell
+facility. It's a python shell connected to the instance with some special
+commands available to manipulate it (the same as you'll have in migration
+scripts, which are not covered in this tutorial). In that case, we're interested
+in the `add_cube` command: ::
+
+ $ cubicweb-ctl stop myblog # or Ctrl-C in the terminal running the server in debug mode
+ $ cubicweb-ctl shell myblog
+ entering the migration python shell
+ just type migration commands or arbitrary python code and type ENTER to execute it
+ type "exit" or Ctrl-D to quit the shell and resume operation
+ >>> add_cube('myblog')
+ >>>
+ $ cubicweb-ctl start -D myblog
+
+The `add_cube` command is enough since it automatically updates our
+application to the cube's schema. There are plenty of other migration
+commands of a more finer grain. They are described in :ref:`migration`
+
+As explained, leave the shell by typing Ctrl-D. If you restart the instance and
+take another look at the schema, you'll see that changes to the data model have
+actually been applied (meaning database schema updates and all necessary stuff
+has been done).
+
+.. image:: ../../images/tutos-base_myblog-schema_en.png
+ :alt: the instance schema after adding our cube
+
+If you follow the 'info' link in the user pop-up menu, you'll also see that the
+instance is using blog and myblog cubes.
+
+.. image:: ../../images/tutos-base_myblog-siteinfo_en.png
+ :alt: the instance schema after adding our cube
+
+You can now add some communities, link them to blog, etc... You'll see that the
+framework provides default views for this entity type (we have not yet defined any
+view for it!), and also that the blog primary view will show the community it's
+linked to if any. All this thanks to the model driven interface provided by the
+framework.
+
+You'll then be able to redefine each of them according to your needs
+and preferences. We'll now see how to do such thing.
+
+.. _TutosBaseCustomizingTheApplicationCustomViews:
+
+Defining your views
+~~~~~~~~~~~~~~~~~~~
+
+|cubicweb| provides a lot of standard views in directory
+:file:`cubicweb/web/views/`. We already talked about 'primary' and 'list' views,
+which are views which apply to one ore more entities.
+
+A view is defined by a python class which includes:
+
+ - an identifier: all objects used to build the user interface in |cubicweb| are
+ recorded in a registry and this identifier will be used as a key in that
+ registry. There may be multiple views for the same identifier.
+
+ - a *selector*, which is a kind of filter telling how well a view suit to a
+ particular context. When looking for a particular view (e.g. given an
+ identifier), |cubicweb| computes for each available view with that identifier
+ a score which is returned by the selector. Then the view with the highest
+ score is used. The standard library of selectors is in
+ :mod:`cubicweb.selector`.
+
+A view has a set of methods inherited from the :class:`cubicweb.view.View` class,
+though you usually don't derive directly from this class but from one of its more
+specific child class.
+
+Last but not least, |cubicweb| provides a set of default views accepting any kind
+of entities.
+
+Want a proof? Create a community as you've already done for other entity types
+through the index page, you'll then see something like that:
+
+.. image:: ../../images/tutos-base_myblog-community-default-primary_en.png
+ :alt: the default primary view for our community entity type
+
+
+If you notice the weird messages that appear in the page: those are messages
+generated for the new data model, which have no translation yet. To fix that,
+we'll have to use dedicated `cubicweb-ctl` commands:
+
+.. sourcecode: bash
+
+ cubicweb-ctl i18ncube myblog # build/update cube's message catalogs
+ # then add translation into .po file into the cube's i18n directory
+ cubicweb-ctl i18ninstance myblog # recompile instance's message catalogs
+ cubicweb-ctl restart -D myblog # instance has to be restarted to consider new catalogs
+
+You'll then be able to redefine each of them according to your needs and
+preferences. So let's see how to do such thing.
+
+Changing the layout of the application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The layout is the general organization of the pages in the site. Views that generate
+the layout are sometimes referred to as 'templates'. They are implemented in the
+framework in the module :mod:`cubicweb.web.views.basetemplates`. By overriding
+classes in this module, you can customize whatever part you wish of the default
+layout.
+
+But notice that |cubicweb| provides many other ways to customize the
+interface, thanks to actions and components (which you can individually
+(de)activate, control their location, customize their look...) as well as
+"simple" CSS customization. You should first try to achieve your goal using such
+fine grained parametrization rather then overriding a whole template, which usually
+embeds customisation access points that you may loose in the process.
+
+But for the sake of example, let's say we want to change the generic page
+footer... We can simply add to the module ``views`` of our cube,
+e.g. :file:`cubes/myblog/views.py`, the code below:
+
+.. sourcecode:: python
+
+ from cubicweb.web.views import basetemplates
+
+ class MyHTMLPageFooter(basetemplates.HTMLPageFooter):
+
+ def footer_content(self):
+ self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
+
+ def registration_callback(vreg):
+ vreg.register_all(globals().values(), __name__, (MyHTMLPageFooter,))
+ vreg.register_and_replace(MyHTMLPageFooter, basetemplates.HTMLPageFooter)
+
+
+* Our class inherits from the default page footer to ease getting things right,
+ but this is not mandatory.
+
+* When we want to write something to the output stream, we simply call `self.w`,
+ with *must be passed an unicode string*.
+
+* The latest function is the most exotic stuff. The point is that without it, you
+ would get an error at display time because the framework wouldn't be able to
+ choose which footer to use between :class:`HTMLPageFooter` and
+ :class:`MyHTMLPageFooter`, since both have the same selector, hence the same
+ score... In this case, we want our footer to replace the default one, so we have
+ to define a :func:`registration_callback` function to control object
+ registration: the first instruction tells to register everything in the module
+ but the :class:`MyHTMLPageFooter` class, then the second to register it instead
+ of :class:`HTMLPageFooter`. Without this function, everything in the module is
+ registered blindly.
+
+.. Note::
+
+ When a view is modified while running in debug mode, it is not required to
+ restart the instance server. Save the Python file and reload the page in your
+ web browser to view the changes.
+
+We will now have this simple footer on every page of the site.
+
+
+Primary view customization
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The 'primary' view (i.e. any view with the identifier set to 'primary') is the one used to
+display all the information about a single entity. The standard primary view is one
+of the most sophisticated views of all. It has several customisation points, but
+its power comes with `uicfg`, allowing you to control it without having to
+subclass it.
+
+However this is a bit off-topic for this first tutorial. Let's say we simply want a
+custom primary view for my `Community` entity type, using directly the view
+interface without trying to benefit from the default implementation (you should
+do that though if you're rewriting reusable cubes; everything is described in more
+details in :ref:`primary_view`).
+
+
+So... Some code! That we'll put again in the module ``views`` of our cube.
+
+.. sourcecode:: python
+
+ from cubicweb.selectors import is_instance
+ from cubicweb.web.views import primary
+
+ class CommunityPrimaryView(primary.PrimaryView):
+ __select__ = is_instance('Community')
+
+ def cell_call(self, row, col):
+ entity = self.cw_rset.get_entity(row, col)
+ self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
+ if entity.description:
+ self.w(u'<p>%s</p>' % entity.printable_value('description'))
+
+What's going on here?
+
+* Our class inherits from the default primary view, here mainly to get the correct
+ view identifier, since we don't use any of its features.
+
+* We set on it a selector telling that it only applies when trying to display
+ some entity of the `Community` type. This is enough to get an higher score than
+ the default view for entities of this type.
+
+* View applying to entities usually have to define `cell_call` as entry point,
+ and are given `row` and `col` arguments tell to which entity in the result set
+ the view is applied. We can then get this entity from the result set
+ (`self.cw_rset`) by using the `get_entity` method.
+
+* To ease thing, we access our entity's attribute for display using its
+ printable_value method, which will handle formatting and escaping when
+ necessary. As you can see, you can also access attributes by their name on the
+ entity to get the raw value.
+
+
+You can now reload the page of the community we just created and see the changes.
+
+.. image:: ../../images/tutos-base_myblog-community-custom-primary_en.png
+ :alt: the custom primary view for our community entity type
+
+We've seen here a lot of thing you'll have to deal with to write views in
+|cubicweb|. The good news is that this is almost everything that is used to
+build higher level layers.
+
+.. Note::
+
+ As things get complicated and the volume of code in your cube increases, you can
+ of course still split your views module into a python package with subpackages.
+
+You can find more details about views and selectors in :ref:`Views`.
+
+
+Write entities to add logic in your data
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+|cubicweb| provides an ORM to easily programmaticaly manipulate
+entities (just like the one we have fetched earlier by calling
+`get_entity` on a result set). By default, entity
+types are instances of the :class:`AnyEntity` class, which holds a set of
+predefined methods as well as property automatically generated for
+attributes/relations of the type it represents.
+
+You can redefine each entity to provide additional methods or whatever you want
+to help you write your application. Customizing an entity requires that your
+entity:
+
+- inherits from :class:`cubicweb.entities.AnyEntity` or any subclass
+
+- defines a :attr:`__regid__` linked to the corresponding data type of your schema
+
+You may then want to add your own methods, override default implementation of some
+method, etc...
+
+.. sourcecode:: python
+
+ from cubicweb.entities import AnyEntity, fetch_config
+
+
+ class Community(AnyEntity):
+ """customized class for Community entities"""
+ __regid__ = 'Community'
+
+ fetch_attrs, fetch_order = fetch_config(['name'])
+
+ def dc_title(self):
+ return self.name
+
+ def display_cw_logo(self):
+ return 'CubicWeb' in self.description
+
+In this example:
+
+* we used convenience :func:`fetch_config` function to tell which attributes
+ should be prefetched by the ORM when looking for some related entities of this
+ type, and how they should be ordered
+
+* we overrode the standard `dc_title` method, used in various place in the interface
+ to display the entity (though in this case the default implementation would
+ have had the same result)
+
+* we implemented here a method :meth:`display_cw_logo` which tests if the blog
+ entry title contains 'CW'. It can then be used when you're writing code
+ involving 'Community' entities in your views, hooks, etc. For instance, you can
+ modify your previous views as follows:
+
+.. sourcecode:: python
+
+
+ class CommunityPrimaryView(primary.PrimaryView):
+ __select__ = is_instance('Community')
+
+ def cell_call(self, row, col):
+ entity = self.cw_rset.get_entity(row, col)
+ self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
+ if entity.display_cw_logo():
+ self.w(u'<img src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
+ if entity.description:
+ self.w(u'<p>%s</p>' % entity.printable_value('description'))
+
+Then each community whose description contains 'CW' is shown with the |cubicweb|
+logo in front of it.
+
+.. Note::
+
+ As for view, you don't have to restart your instance when modifying some entity
+ classes while your server is running in debug mode, the code will be
+ automatically reloaded.
+
+
+Extending the application by using more cubes!
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One of the goal of the |cubicweb| framework was to have truly reusable
+components. To do so, they must both behave nicely when plugged into the
+application and be easily customisable, from the data model to the user
+interface. And I think the result is pretty successful, thanks to system such as
+the selection mechanism and the choice to write views as python code which allows
+to build our page using true object oriented programming techniques, that no
+template language provides.
+
+
+A library of standard cubes is available from `CubicWeb Forge`_, to address a
+lot of common concerns such has manipulating people, files, things to do, etc. In
+our community blog case, we could be interested for instance in functionalities
+provided by the `comment` and `tag` cubes. The former provides threaded
+discussion functionalities, the latter a simple tag mechanism to classify content.
+Let's say we want to try those. We will first modify our cube's :file:`__pkginfo__.py`
+file:
+
+.. sourcecode:: python
+
+ __depends__ = {'cubicweb': '>= 3.10.7',
+ 'cubicweb-blog': None,
+ 'cubicweb-comment': None,
+ 'cubicweb-tag': None}
+
+Now, we'll simply tell on which entity types we want to activate the 'comment'
+and 'tag' facilities by adding respectively the 'comments' and 'tags' relations on
+them in our schema (:file:`schema.py`).
+
+.. sourcecode:: python
+
+ class comments(RelationDefinition):
+ subject = 'Comment'
+ object = 'BlogEntry'
+ cardinality = '1*'
+ composite = 'object'
+
+ class tags(RelationDefinition):
+ subject = 'Tag'
+ object = ('Community', 'BlogEntry')
+
+
+So in the case above we activated comments on `BlogEntry` entities and tags on
+both `Community` and `BlogEntry`. Various views from both `comment` and `tag`
+cubes will then be automatically displayed when one of those relations is
+supported.
+
+Let's synchronize the data model as we've done earlier: ::
+
+
+ $ cubicweb-ctl stop myblog
+ $ cubicweb-ctl shell myblog
+ entering the migration python shell
+ just type migration commands or arbitrary python code and type ENTER to execute it
+ type "exit" or Ctrl-D to quit the shell and resume operation
+ >>> add_cubes('comment', 'tag')
+ >>>
+
+Then restart the instance. Let's look at a blog entry:
+
+.. image:: ../../images/tutos-base_myblog-blogentry-taggable-commentable-primary_en.png
+ :alt: the primary view for a blog entry with comments and tags activated
+
+As you can see, we now have a box displaying tags and a section proposing to add
+a comment and displaying existing one below the post. All this without changing
+anything in our views, thanks to the design of generic views provided by the
+framework. Though if we take a look at a community, we won't see the tags box!
+That's because by default this box try to locate itself in the left column within
+the white frame, and this column is handled by the primary view we
+hijacked. Let's change our view to make it more extensible, by keeping both our
+custom rendering but also extension points provided by the default
+implementation.
+
+
+.. sourcecode:: python
+
+ class CommunityPrimaryView(primary.PrimaryView):
+ __select__ = is_instance('Community')
+
+ def render_entity_title(self, entity):
+ self.w(u'<h1>Welcome to the "%s" community</h1>' % entity.printable_value('name'))
+
+ def render_entity_attributes(self, entity):
+ if entity.display_cw_logo():
+ self.w(u'<img src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
+ if entity.description:
+ self.w(u'<p>%s</p>' % entity.printable_value('description'))
+
+It appears now properly:
+
+.. image:: ../../images/tutos-base_myblog-community-taggable-primary_en.png
+ :alt: the custom primary view for a community entry with tags activated
+
+You can control part of the interface independently from each others, piece by
+piece. Really.
+
+
+
+.. _`CubicWeb Forge`: http://www.cubicweb.org/project
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/tutorials/base/discovering-the-ui.rst Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,161 @@
+
+.. _TutosBaseDiscoveringTheUI:
+
+Discovering the web interface
+-----------------------------
+
+You can now access your web instance to create blogs and post messages
+by visiting the URL http://localhost:8080/.
+
+By default, anonymous access is disabled, so a login form will appear. If you
+asked to allow anonymous access when initializing the instance, click on the
+'login' link in the top right hand corner. To login, you need then use the admin
+account you specified at the time you initialized the database with
+``cubicweb-ctl create``.
+
+.. image:: ../../images/tutos-base_login-form_en.png
+ :alt: the login form
+
+
+Once authenticated, you can start playing with your instance. The default index
+page looks like the following:
+
+.. image:: ../../images/tutos-base_index_en.png
+ :alt: the index page
+
+
+Minimal configuration
+~~~~~~~~~~~~~~~~~~~~~
+
+Before creating entities, let's change that 'unset title' thing that appears
+here and there. This comes from a |cubicweb| system properties. To set it,
+click on the 'site configuration link' in the pop-up menu behind your login name
+in the upper left-hand corner
+
+.. image:: ../../images/tutos-base_user-menu_en.png
+ :alt: the user pop-up menu
+
+The site title is in the 'Ui' section. Simply set it to the desired value and
+click the 'validate' button.
+
+.. image:: ../../images/tutos-base_siteconfig_en.png
+ :alt: the site configuration form
+
+You should see a 'changes applied' message. You can now go back to the
+index page by clicking on the |cubicweb| logo in the upper left-hand corner.
+
+You will much likely still see 'unset title' at this point. This is because by
+default the index page is cached. Force a refresh of the page (by typing Ctrl-R
+in Firefox for instance) and you should now see the title you entered.
+
+
+Adding entities
+~~~~~~~~~~~~~~~
+
+The ``blog`` cube defines several entity types, among them ``Blog`` which is a
+container for ``BlogEntry`` (i.e. posts) on a particular topic. We can get a
+graphical view of the schema by clicking on the 'site schema' link in the user
+pop-up menu we've already seen:
+
+.. image:: ../../images/tutos-base_schema_en.png
+ :alt: graphical view of the schema (aka data-model)
+
+Nice isn't it? Notice that this, as most other stuff we'll see in this tutorial,
+is generated by the framework according to the model of the application. In our
+case, the model defined by the ``blog`` cube.
+
+Now let us create a few of these entities.
+
+
+Add a blog
+**********
+
+Clicking on the `[+]` at the left of the 'Blog' link on the index page will lead
+you to an HTML form to create a blog.
+
+.. image:: ../../images/tutos-base_blog-form_en.png
+ :alt: the blog creation form
+
+For instance, call this new blog 'Tech-blog' and type in 'everything about
+technology' as the description , then validate the form by clicking on
+'Validate'. You will be redirected to the `primary` view of the newly created blog.
+
+.. image:: ../../images/tutos-base_blog-primary_en.png
+ :alt: the blog primary view
+
+
+Add a blog post
+***************
+
+There are several ways to add a blog entry. The simplest is to click on the 'add
+blog entry' link in the actions box on viewing the blog you have just created.
+You will then see a form to create a post, with a 'blog entry of' field preset
+to the blog we're coming from. Enter a title, some content, click the 'validate'
+button and you're done. You will be redirected to the blog primary view, though you
+now see that it contains the blog post you've just created.
+
+.. image:: ../../images/tutos-base_blog-primary-after-post-creation_en.png
+ :alt: the blog primary view after creation of a post
+
+Notice there are some new boxes that appears in the left column.
+
+You can achieve the same thing by following the same path as we did for the blog
+creation, e.g. by clicking on the `[+]` at the left of the 'Blog entry' link on
+the index page. The diffidence being that since there is no context information,
+the 'blog entry of' selector won't be preset to the blog.
+
+
+If you click on the 'modify' link of the action box, you are back to
+the form to edit the entity you just created, except that the form now
+has another section with a combo-box entitled 'add relation'. It
+provisos a generic way to edit relations which don't appears in the
+above form. Choose the relation you want to add and a second combo box
+appears where you can pick existing entities. If there are too many
+of them, you will be offered to navigate to the target entity, that is
+go away from the form and go back to it later, once you've selected
+the entity you want to link with.
+
+.. image:: ../../images/tutos-base_form-generic-relations_en.png
+ :alt: the generic relations combo box
+
+This combo box can't appear until the entity is actually created. That's why you
+haven't seen it at creation time. You could also have hit 'Apply' instead of
+'validate' and it would have showed up.
+
+
+About ui auto-adaptation
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+One of the things that make |cubicweb| different of other frameworks is
+its automatic user interface that adapts itself according to the data being
+displayed. Let's see an example.
+
+If you go back to the home page an click on the 'Blog' link, you will be redirected
+to the primary view of the blog, the same we've seen earlier. Now, add another
+blog, go back to the index page, and click again on this link. You will see
+a very different view (namely the 'list' view).
+
+.. image:: ../../images/tutos-base_blogs-list_en.png
+ :alt: the list view when there are more than one blog to display
+
+This is because in the first case, the framework chose to use the 'primary'
+view since there was only one entity in the data to be displayed. Now that there
+are two entities, the 'list' view is more appropriate and hence is being used.
+
+There are various other places where |cubicweb| adapts to display data in the best
+way, the main being provided by the view *selection* mechanism that will be detailed
+later.
+
+
+Digging deeper
+~~~~~~~~~~~~~~
+
+By following principles explained below, you should now be able to
+create new users for your application, to configure with a finer
+grain, etc... You will notice that the index page lists a lot of types
+you don't know about. Most are built-in types provided by the framework
+to make the whole system work. You may ignore them in a first time and
+discover them as time goes.
+
+One thing that is worth playing with is the search box. It may be used in various
+way, from simple full text search to advanced queries using the :ref:`RQL` .
--- a/doc/book/en/tutorials/base/index.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/tutorials/base/index.rst Fri Mar 11 09:46:45 2011 +0100
@@ -1,30 +1,48 @@
.. -*- coding: utf-8 -*-
-.. _Tutorial:
-
-.. _tuto_blog:
+.. _TutosBase:
-Building a simple blog
-======================
+Building a simple blog with |cubicweb|
+======================================
-*CubicWeb* is a semantic web application framework that favors reuse and
+|cubicweb| is a semantic web application framework that favors reuse and
object-oriented design.
-A `cube` is a component that includes a model defining the data types and a set of
-views to display the data. A cube can be built by assembling other cubes.
+
+This tutorial is designed to help in your very first steps to start with
+|cubicweb|. We will tour through basic concepts such as:
-An `instance` is a specific installation of a cube and includes configuration
-files.
+* getting an application running by using existing components
+* discovering the default user interface
+* basic extending and customizing the look and feel of that application
+
+More advanced concepts are covered in :ref:`TutosPhotoWebSite`.
-This tutorial will show how to create a `cube` and how to use it as an
-application to run an `instance`.
+.. _TutosBaseVocab:
+
+Some vocabulary
+---------------
+
+|cubicweb| comes with a few words of vocabulary that you should know to
+understand what we're talking about. To follow this tutorial, you should at least
+know that:
+
+* a `cube` is a component that usually includes a model defining some data types
+ and a set of views to display them. A cube can be built by assembling other
+ cubes;
+
+* an `instance` is a specific installation of one or more cubes and includes
+ configuration files, a web server and a database.
+
+Reading :ref:`Concepts` for more vocabulary will be required at some point.
+
+Now, let's start the hot stuff!
.. toctree::
:maxdepth: 2
blog-in-five-minutes
- create-cube
- components
- maintemplate
+ discovering-the-ui
+ customizing-the-application
conclusion
--- a/doc/book/en/tutorials/base/maintemplate.rst Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Templates
----------
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below:
-
-.. image:: ../../images/lax-book_06-main-template-layout_en.png
-
-In this section we will demonstrate a change in one of the main
-interesting template from the three you will look for,
-that is to say, the HTMLPageHeader, the HTMLPageFooter
-and the TheMainTemplate.
-
-
-Customize a template
-~~~~~~~~~~~~~~~~~~~~
-
-Based on the diagram below, each template can be overriden
-by your customized template. To do so, we recommand you create
-a Python module ``blog.views.templates`` to keep it organized.
-In this module you will have to import the parent class you are
-interested as follows: ::
-
- from cubicweb.web.views.basetemplates import HTMLPageHeader, \
- HTMLPageFooter, TheMainTemplate
-
-and then create your sub-class::
-
- class MyBlogHTMLPageHeader(HTMLPageHeader):
- ...
-
-Customize header
-`````````````````
-
-Let's now move the search box in the header and remove the login form from the
-header. We'll show how to move it to the left column of the user interface.
-
-Let's say we do not want anymore the login menu to be in the header
-
-First, to remove the login menu, we just need to comment out the display of the
-login graphic component such as follows:
-
-.. sourcecode:: python
-
- class MyBlogHTMLPageHeader(HTMLPageHeader):
-
- def main_header(self, view):
- """build the top menu with authentification info and the rql box"""
- self.w(u'<table id="header"><tr>\n')
- self.w(u'<td id="firstcolumn">')
- self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
- self.w(u'</td>\n')
- # appliname and breadcrumbs
- self.w(u'<td id="headtext">')
- comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
- if comp and comp.propval('visible'):
- comp.dispatch(w=self.w)
- comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
- if comp and comp.propval('visible'):
- comp.dispatch(w=self.w, view=view)
- self.w(u'</td>')
- # logged user and help
- #self.w(u'<td>\n')
- #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
- #comp.dispatch(w=self.w)
- #self.w(u'</td><td>')
-
- self.w(u'<td>')
- helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
- if helpcomp: # may not be available if Card is not defined in the schema
- helpcomp.dispatch(w=self.w)
- self.w(u'</td>')
- # lastcolumn
- self.w(u'<td id="lastcolumn">')
- self.w(u'</td>\n')
- self.w(u'</tr></table>\n')
- self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
- title=False, message=False)
-
-
-
-.. image:: ../../images/lax-book_06-header-no-login_en.png
-
-Customize footer
-````````````````
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in:
-
-.. sourcecode:: python
-
- from cubicweb.web.views.basetemplates import HTMLPageFooter
-
- class MyHTMLPageFooter(HTMLPageFooter):
-
- def call(self, **kwargs):
- self.w(u'<div class="footer">')
- self.w(u'This website has been created with <a href="http://cubicweb.org">CubicWeb</a>.')
- self.w(u'</div>')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
-```````````````
-
-.. _TheMainTemplate:
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
-
-TheMainTemplate is responsible for the general layout of the entire application.
-It defines the template of ``__regid__ = main`` that is used by the application. Is
-also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
-be used based on TheMainTemplate called SimpleMainTemplate which does not have
-a top section.
-
-.. image:: ../../images/lax-book_06-simple-main-template_en.png
-
-.. XXX
-.. [WRITE ME]
-
-* customize MainTemplate and show that everything in the user
- interface can be changed
-
--- a/doc/book/en/tutorials/tools/windmill.rst Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/book/en/tutorials/tools/windmill.rst Fri Mar 11 09:46:45 2011 +0100
@@ -150,7 +150,7 @@
If you want to change cubicweb test server parameters, you can check class
variables from :class:`CubicWebServerConfig` or inherit it with overriding the
-:var:`configcls` attribute in :class:`CubicWebServerTC` ::
+:attr:`configcls` attribute in :class:`CubicWebServerTC` ::
.. sourcecode:: python
Binary file doc/book/fr/.static/cubicweb.png has changed
Binary file doc/book/fr/.static/logilab.png has changed
--- a/doc/book/fr/.static/sphinx-default.css Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,860 +0,0 @@
-/**
- * Sphinx Doc Design
- */
-
-html, body {
- background: white;
-}
-
-body {
- font-family: Verdana, sans-serif;
- font-size: 100%;
- background-color: white;
- color: black;
- margin: 0;
- padding: 0;
-}
-
-/* :::: LAYOUT :::: */
-
-div.logilablogo {
- padding: 10px 10px 10px 10px;
- height:75;
-}
-
-
-div.document {
- background-color: white;
-}
-
-div.documentwrapper {
- float: left;
- width: 100%;
-}
-
-div.bodywrapper {
- margin: 0 0 0 230px;
-}
-
-div.body {
- background-color: white;
- padding: 0 20px 30px 20px;
- border-left:solid;
- border-left-color:#e2e2e2;
- border-left-width:thin;
-}
-
-div.sphinxsidebarwrapper {
- padding: 10px 5px 0 10px;
-}
-
-div.sphinxsidebar {
- float: left;
- width: 230px;
- margin-left: -100%;
- font-size: 90%;
-}
-
-div.clearer {
- clear: both;
-}
-
-div.footer {
- color: #ff4500;
- width: 100%;
- padding: 9px 0 9px 0;
- text-align: center;
- font-size: 75%;
-}
-
-div.footer a {
- color: #ff4500;
- text-decoration: underline;
-}
-
-div.related {
- background-color: #ff7700;
- color: white;
- width: 100%;
- height: 30px;
- line-height: 30px;
- font-size: 90%;
-}
-
-div.related h3 {
- display: none;
-}
-
-div.related ul {
- margin: 0;
- padding: 0 0 0 10px;
- list-style: none;
-}
-
-div.related li {
- display: inline;
-}
-
-div.related li.right {
- float: right;
- margin-right: 5px;
-}
-
-div.related a {
- color: white;
- font-weight:bold;
-}
-
-/* ::: TOC :::: */
-
-div.sphinxsidebar {
- border-style:solid;
- border-color: white;
-/* background-color:#e2e2e2;*/
- padding-bottom:5px;
-}
-
-div.sphinxsidebar h3 {
- font-family: 'Verdanda', sans-serif;
- color: black;
- font-size: 1.2em;
- font-weight: normal;
- margin: 0;
- padding: 0;
- font-weight:bold;
- font-style:italic;
-}
-
-div.sphinxsidebar h4 {
- font-family: 'Verdana', sans-serif;
- color: black;
- font-size: 1.1em;
- font-weight: normal;
- margin: 5px 0 0 0;
- padding: 0;
- font-weight:bold;
- font-style:italic;
-}
-
-div.sphinxsidebar p {
- color: black;
-}
-
-div.sphinxsidebar p.topless {
- margin: 5px 10px 10px 10px;
-}
-
-div.sphinxsidebar ul {
- margin: 10px;
- padding: 0;
- list-style: none;
- color: black;
-}
-
-div.sphinxsidebar ul ul,
-div.sphinxsidebar ul.want-points {
- margin-left: 20px;
- list-style: square;
-}
-
-div.sphinxsidebar ul ul {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-div.sphinxsidebar a {
- color: black;
-}
-
-div.sphinxsidebar form {
- margin-top: 10px;
-}
-
-div.sphinxsidebar input {
- border: 1px solid #e2e2e2;
- font-family: sans-serif;
- font-size: 1em;
- padding-bottom: 5px;
-}
-
-/* :::: MODULE CLOUD :::: */
-div.modulecloud {
- margin: -5px 10px 5px 10px;
- padding: 10px;
- line-height: 160%;
- border: 1px solid #cbe7e5;
- background-color: #f2fbfd;
-}
-
-div.modulecloud a {
- padding: 0 5px 0 5px;
-}
-
-/* :::: SEARCH :::: */
-ul.search {
- margin: 10px 0 0 20px;
- padding: 0;
-}
-
-ul.search li {
- padding: 5px 0 5px 20px;
- background-image: url(file.png);
- background-repeat: no-repeat;
- background-position: 0 7px;
-}
-
-ul.search li a {
- font-weight: bold;
-}
-
-ul.search li div.context {
- color: #888;
- margin: 2px 0 0 30px;
- text-align: left;
-}
-
-ul.keywordmatches li.goodmatch a {
- font-weight: bold;
-}
-
-/* :::: COMMON FORM STYLES :::: */
-
-div.actions {
- padding: 5px 10px 5px 10px;
- border-top: 1px solid #cbe7e5;
- border-bottom: 1px solid #cbe7e5;
- background-color: #e0f6f4;
-}
-
-form dl {
- color: #333;
-}
-
-form dt {
- clear: both;
- float: left;
- min-width: 110px;
- margin-right: 10px;
- padding-top: 2px;
-}
-
-input#homepage {
- display: none;
-}
-
-div.error {
- margin: 5px 20px 0 0;
- padding: 5px;
- border: 1px solid #d00;
- font-weight: bold;
-}
-
-/* :::: INLINE COMMENTS :::: */
-
-div.inlinecomments {
- position: absolute;
- right: 20px;
-}
-
-div.inlinecomments a.bubble {
- display: block;
- float: right;
- background-image: url(style/comment.png);
- background-repeat: no-repeat;
- width: 25px;
- height: 25px;
- text-align: center;
- padding-top: 3px;
- font-size: 0.9em;
- line-height: 14px;
- font-weight: bold;
- color: black;
-}
-
-div.inlinecomments a.bubble span {
- display: none;
-}
-
-div.inlinecomments a.emptybubble {
- background-image: url(style/nocomment.png);
-}
-
-div.inlinecomments a.bubble:hover {
- background-image: url(style/hovercomment.png);
- text-decoration: none;
- color: #3ca0a4;
-}
-
-div.inlinecomments div.comments {
- float: right;
- margin: 25px 5px 0 0;
- max-width: 50em;
- min-width: 30em;
- border: 1px solid #2eabb0;
- background-color: #f2fbfd;
- z-index: 150;
-}
-
-div#comments {
- border: 1px solid #2eabb0;
- margin-top: 20px;
-}
-
-div#comments div.nocomments {
- padding: 10px;
- font-weight: bold;
-}
-
-div.inlinecomments div.comments h3,
-div#comments h3 {
- margin: 0;
- padding: 0;
- background-color: #2eabb0;
- color: white;
- border: none;
- padding: 3px;
-}
-
-div.inlinecomments div.comments div.actions {
- padding: 4px;
- margin: 0;
- border-top: none;
-}
-
-div#comments div.comment {
- margin: 10px;
- border: 1px solid #2eabb0;
-}
-
-div.inlinecomments div.comment h4,
-div.commentwindow div.comment h4,
-div#comments div.comment h4 {
- margin: 10px 0 0 0;
- background-color: #2eabb0;
- color: white;
- border: none;
- padding: 1px 4px 1px 4px;
-}
-
-div#comments div.comment h4 {
- margin: 0;
-}
-
-div#comments div.comment h4 a {
- color: #d5f4f4;
-}
-
-div.inlinecomments div.comment div.text,
-div.commentwindow div.comment div.text,
-div#comments div.comment div.text {
- margin: -5px 0 -5px 0;
- padding: 0 10px 0 10px;
-}
-
-div.inlinecomments div.comment div.meta,
-div.commentwindow div.comment div.meta,
-div#comments div.comment div.meta {
- text-align: right;
- padding: 2px 10px 2px 0;
- font-size: 95%;
- color: #538893;
- border-top: 1px solid #cbe7e5;
- background-color: #e0f6f4;
-}
-
-div.commentwindow {
- position: absolute;
- width: 500px;
- border: 1px solid #cbe7e5;
- background-color: #f2fbfd;
- display: none;
- z-index: 130;
-}
-
-div.commentwindow h3 {
- margin: 0;
- background-color: #2eabb0;
- color: white;
- border: none;
- padding: 5px;
- font-size: 1.5em;
- cursor: pointer;
-}
-
-div.commentwindow div.actions {
- margin: 10px -10px 0 -10px;
- padding: 4px 10px 4px 10px;
- color: #538893;
-}
-
-div.commentwindow div.actions input {
- border: 1px solid #2eabb0;
- background-color: white;
- color: #135355;
- cursor: pointer;
-}
-
-div.commentwindow div.form {
- padding: 0 10px 0 10px;
-}
-
-div.commentwindow div.form input,
-div.commentwindow div.form textarea {
- border: 1px solid #3c9ea2;
- background-color: white;
- color: black;
-}
-
-div.commentwindow div.error {
- margin: 10px 5px 10px 5px;
- background-color: #fbe5dc;
- display: none;
-}
-
-div.commentwindow div.form textarea {
- width: 99%;
-}
-
-div.commentwindow div.preview {
- margin: 10px 0 10px 0;
- background-color: #70d0d4;
- padding: 0 1px 1px 25px;
-}
-
-div.commentwindow div.preview h4 {
- margin: 0 0 -5px -20px;
- padding: 4px 0 0 4px;
- color: white;
- font-size: 1.3em;
-}
-
-div.commentwindow div.preview div.comment {
- background-color: #f2fbfd;
-}
-
-div.commentwindow div.preview div.comment h4 {
- margin: 10px 0 0 0!important;
- padding: 1px 4px 1px 4px!important;
- font-size: 1.2em;
-}
-
-/* :::: SUGGEST CHANGES :::: */
-div#suggest-changes-box input, div#suggest-changes-box textarea {
- border: 1px solid #ccc;
- background-color: white;
- color: black;
-}
-
-div#suggest-changes-box textarea {
- width: 99%;
- height: 400px;
-}
-
-
-/* :::: PREVIEW :::: */
-div.preview {
- background-image: url(style/preview.png);
- padding: 0 20px 20px 20px;
- margin-bottom: 30px;
-}
-
-
-/* :::: INDEX PAGE :::: */
-
-table.contentstable {
- width: 90%;
-}
-
-table.contentstable p.biglink {
- line-height: 150%;
-}
-
-a.biglink {
- font-size: 1.3em;
-}
-
-span.linkdescr {
- font-style: italic;
- padding-top: 5px;
- font-size: 90%;
-}
-
-/* :::: INDEX STYLES :::: */
-
-table.indextable td {
- text-align: left;
- vertical-align: top;
-}
-
-table.indextable dl, table.indextable dd {
- margin-top: 0;
- margin-bottom: 0;
-}
-
-table.indextable tr.pcap {
- height: 10px;
-}
-
-table.indextable tr.cap {
- margin-top: 10px;
- background-color: #f2f2f2;
-}
-
-img.toggler {
- margin-right: 3px;
- margin-top: 3px;
- cursor: pointer;
-}
-
-form.pfform {
- margin: 10px 0 20px 0;
-}
-
-/* :::: GLOBAL STYLES :::: */
-
-.docwarning {
- background-color: #ffe4e4;
- padding: 10px;
- margin: 0 -20px 0 -20px;
- border-bottom: 1px solid #f66;
-}
-
-p.subhead {
- font-weight: bold;
- margin-top: 20px;
-}
-
-a {
- color: black;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
-}
-
-div.body h1,
-div.body h2,
-div.body h3,
-div.body h4,
-div.body h5,
-div.body h6 {
- font-family: 'Verdana', sans-serif;
- background-color: white;
- font-weight: bold;
- color: black;
- border-bottom: 1px solid #ccc;
- margin: 20px -20px 10px -20px;
- padding: 3px 0 3px 10px;
-}
-
-div.body h1 { margin-top: 0; font-size: 150%; }
-div.body h2 { font-size: 120%; }
-div.body h3 { font-size: 100%; }
-div.body h4 { font-size: 80%; }
-div.body h5 { font-size: 600%; }
-div.body h6 { font-size: 40%; }
-
-a.headerlink {
- color: #c60f0f;
- font-size: 0.8em;
- padding: 0 4px 0 4px;
- text-decoration: none;
- visibility: hidden;
-}
-
-h1:hover > a.headerlink,
-h2:hover > a.headerlink,
-h3:hover > a.headerlink,
-h4:hover > a.headerlink,
-h5:hover > a.headerlink,
-h6:hover > a.headerlink,
-dt:hover > a.headerlink {
- visibility: visible;
-}
-
-a.headerlink:hover {
- background-color: #c60f0f;
- color: white;
-}
-
-div.body p, div.body dd, div.body li {
- text-align: justify;
- line-height: 130%;
-}
-
-div.body p.caption {
- text-align: inherit;
-}
-
-div.body td {
- text-align: left;
-}
-
-ul.fakelist {
- list-style: none;
- margin: 10px 0 10px 20px;
- padding: 0;
-}
-
-.field-list ul {
- padding-left: 1em;
-}
-
-.first {
- margin-top: 0 !important;
-}
-
-/* "Footnotes" heading */
-p.rubric {
- margin-top: 30px;
- font-weight: bold;
-}
-
-/* "Topics" */
-
-div.topic {
- background-color: #eee;
- border: 1px solid #ccc;
- padding: 0 7px 0 7px;
- margin: 10px 0 10px 0;
-}
-
-p.topic-title {
- font-size: 1.1em;
- font-weight: bold;
- margin-top: 10px;
-}
-
-/* Admonitions */
-
-div.admonition {
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 7px;
-}
-
-div.admonition dt {
- font-weight: bold;
-}
-
-div.admonition dl {
- margin-bottom: 0;
-}
-
-div.admonition p {
- display: inline;
-}
-
-div.seealso {
- background-color: #ffc;
- border: 1px solid #ff6;
-}
-
-div.warning {
- background-color: #ffe4e4;
- border: 1px solid #f66;
-}
-
-div.note {
- background-color: #eee;
- border: 1px solid #ccc;
-}
-
-p.admonition-title {
- margin: 0px 10px 5px 0px;
- font-weight: bold;
- display: inline;
-}
-
-p.admonition-title:after {
- content: ":";
-}
-
-div.body p.centered {
- text-align: center;
- margin-top: 25px;
-}
-
-table.docutils {
- border: 0;
-}
-
-table.docutils td, table.docutils th {
- padding: 1px 8px 1px 0;
- border-top: 0;
- border-left: 0;
- border-right: 0;
- border-bottom: 1px solid #aaa;
-}
-
-table.field-list td, table.field-list th {
- border: 0 !important;
-}
-
-table.footnote td, table.footnote th {
- border: 0 !important;
-}
-
-.field-list ul {
- margin: 0;
- padding-left: 1em;
-}
-
-.field-list p {
- margin: 0;
-}
-
-dl {
- margin-bottom: 15px;
- clear: both;
-}
-
-dd p {
- margin-top: 0px;
-}
-
-dd ul, dd table {
- margin-bottom: 10px;
-}
-
-dd {
- margin-top: 3px;
- margin-bottom: 10px;
- margin-left: 30px;
-}
-
-.refcount {
- color: #060;
-}
-
-dt:target,
-.highlight {
- background-color: #fbe54e;
-}
-
-dl.glossary dt {
- font-weight: bold;
- font-size: 1.1em;
-}
-
-th {
- text-align: left;
- padding-right: 5px;
-}
-
-pre {
- padding: 5px;
- background-color: #efc;
- color: #333;
- border: 1px solid #ac9;
- border-left: none;
- border-right: none;
- overflow: auto;
-}
-
-td.linenos pre {
- padding: 5px 0px;
- border: 0;
- background-color: transparent;
- color: #aaa;
-}
-
-table.highlighttable {
- margin-left: 0.5em;
-}
-
-table.highlighttable td {
- padding: 0 0.5em 0 0.5em;
-}
-
-tt {
- background-color: #ecf0f3;
- padding: 0 1px 0 1px;
- font-size: 0.95em;
-}
-
-tt.descname {
- background-color: transparent;
- font-weight: bold;
- font-size: 1.2em;
-}
-
-tt.descclassname {
- background-color: transparent;
-}
-
-tt.xref, a tt {
- background-color: transparent;
- font-weight: bold;
-}
-
-.footnote:target { background-color: #ffa }
-
-h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
- background-color: transparent;
-}
-
-.optional {
- font-size: 1.3em;
-}
-
-.versionmodified {
- font-style: italic;
-}
-
-form.comment {
- margin: 0;
- padding: 10px 30px 10px 30px;
- background-color: #eee;
-}
-
-form.comment h3 {
- background-color: #326591;
- color: white;
- margin: -10px -30px 10px -30px;
- padding: 5px;
- font-size: 1.4em;
-}
-
-form.comment input,
-form.comment textarea {
- border: 1px solid #ccc;
- padding: 2px;
- font-family: sans-serif;
- font-size: 100%;
-}
-
-form.comment input[type="text"] {
- width: 240px;
-}
-
-form.comment textarea {
- width: 100%;
- height: 200px;
- margin-bottom: 10px;
-}
-
-.system-message {
- background-color: #fda;
- padding: 5px;
- border: 3px solid red;
-}
-
-/* :::: PRINT :::: */
-@media print {
- div.document,
- div.documentwrapper,
- div.bodywrapper {
- margin: 0;
- width : 100%;
- }
-
- div.sphinxsidebar,
- div.related,
- div.footer,
- div#comments div.new-comment-box,
- #top-link {
- display: none;
- }
-}
--- a/doc/book/fr/.templates/layout.html Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-{%- block doctype -%}
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-{%- endblock %}
-{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
-{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
-{%- macro relbar %}
- <div class="related">
- <h3>Navigation</h3>
- <ul>
- {%- for rellink in rellinks %}
- <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
- <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags }}"
- accesskey="{{ rellink[2] }}">{{ rellink[3] }}</a>
- {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
- {%- endfor %}
- {%- block rootrellink %}
- <li><a href="{{ pathto('index') }}">{{ shorttitle }}</a>{{ reldelim1 }}</li>
- {%- endblock %}
- {%- for parent in parents %}
- <li><a href="{{ parent.link|e }}" accesskey="U">{{ parent.title }}</a>{{ reldelim1 }}</li>
- {%- endfor %}
- {%- block relbaritems %}{% endblock %}
- </ul>
- </div>
-{%- endmacro %}
-{%- macro sidebar %}
- {%- if builder != 'htmlhelp' %}
- <div class="sphinxsidebar">
- <div class="sphinxsidebarwrapper">
- {%- block sidebarlogo %}
- {%- if logo %}
- <p class="logo"><img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/></p>
- {%- endif %}
- {%- endblock %}
- {%- block sidebartoc %}
- {%- if display_toc %}
- <h3>Table Of Contents</h3>
- {{ toc }}
- {%- endif %}
- {%- endblock %}
- {%- block sidebarrel %}
- {%- if prev %}
- <h4>Previous topic</h4>
- <p class="topless"><a href="{{ prev.link|e }}" title="previous chapter">{{ prev.title }}</a></p>
- {%- endif %}
- {%- if next %}
- <h4>Next topic</h4>
- <p class="topless"><a href="{{ next.link|e }}" title="next chapter">{{ next.title }}</a></p>
- {%- endif %}
- {%- endblock %}
- {%- if sourcename %}
- <h3>This Page</h3>
- <ul class="this-page-menu">
- {%- if builder == 'web' %}
- <li><a href="#comments">Comments ({{ comments|length }} so far)</a></li>
- <li><a href="{{ pathto('@edit/' + sourcename)|e }}">Suggest Change</a></li>
- <li><a href="{{ pathto('@source/' + sourcename)|e }}">Show Source</a></li>
- {%- elif builder == 'html' %}
- <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}">Show Source</a></li>
- {%- endif %}
- </ul>
- {%- endif %}
- {%- if customsidebar %}
- {{ rendertemplate(customsidebar) }}
- {%- endif %}
- {%- block sidebarsearch %}
- {%- if pagename != "search" %}
- <h3>{{ builder == 'web' and 'Keyword' or 'Quick' }} search</h3>
- <form class="search" action="{{ pathto('search') }}" method="get">
- <input type="text" name="q" size="18" /> <input type="submit" value="Go" />
- <input type="hidden" name="check_keywords" value="yes" />
- <input type="hidden" name="area" value="default" />
- </form>
- {%- if builder == 'web' %}
- <p style="font-size: 90%">Enter a module, class or function name.</p>
- {%- endif %}
- {%- endif %}
- {%- endblock %}
- </div>
- </div>
- {%- endif %}
-{%- endmacro -%}
-
-<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- {%- if builder != 'htmlhelp' %}
- {%- set titlesuffix = " — " + docstitle %}
- {%- endif %}
- <title>{{ title|striptags }}{{ titlesuffix }}</title>
- {%- if builder == 'web' %}
- <link rel="stylesheet" href="{{ pathto('index') }}?do=stylesheet{%
- if in_admin_panel %}&admin=yes{% endif %}" type="text/css" />
- {%- for link, type, title in page_links %}
- <link rel="alternate" type="{{ type|e(true) }}" title="{{ title|e(true) }}" href="{{ link|e(true) }}" />
- {%- endfor %}
- {%- else %}
- <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
- <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
- {%- endif %}
- {%- if builder != 'htmlhelp' %}
- <script type="text/javascript">
- var DOCUMENTATION_OPTIONS = {
- URL_ROOT: '{{ pathto("", 1) }}',
- VERSION: '{{ release }}',
- COLLAPSE_MODINDEX: false,
- FILE_SUFFIX: '{{ file_suffix }}'
- };
- </script>
- <script type="text/javascript" src="{{ pathto('_static/jquery.js', 1) }}"></script>
- <script type="text/javascript" src="{{ pathto('_static/interface.js', 1) }}"></script>
- <script type="text/javascript" src="{{ pathto('_static/doctools.js', 1) }}"></script>
- {%- if use_opensearch %}
- <link rel="search" type="application/opensearchdescription+xml"
- title="Search within {{ docstitle }}"
- href="{{ pathto('_static/opensearch.xml', 1) }}"/>
- {%- endif %}
- {%- if favicon %}
- <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
- {%- endif %}
- {%- endif %}
-{%- block rellinks %}
- {%- if hasdoc('about') %}
- <link rel="author" title="About these documents" href="{{ pathto('about') }}" />
- {%- endif %}
- <link rel="contents" title="Global table of contents" href="{{ pathto('contents') }}" />
- <link rel="index" title="Global index" href="{{ pathto('genindex') }}" />
- <link rel="search" title="Search" href="{{ pathto('search') }}" />
- {%- if hasdoc('copyright') %}
- <link rel="copyright" title="Copyright" href="{{ pathto('copyright') }}" />
- {%- endif %}
- <link rel="top" title="{{ docstitle }}" href="{{ pathto('index') }}" />
- {%- if parents %}
- <link rel="up" title="{{ parents[-1].title|striptags }}" href="{{ parents[-1].link|e }}" />
- {%- endif %}
- {%- if next %}
- <link rel="next" title="{{ next.title|striptags }}" href="{{ next.link|e }}" />
- {%- endif %}
- {%- if prev %}
- <link rel="prev" title="{{ prev.title|striptags }}" href="{{ prev.link|e }}" />
- {%- endif %}
-{%- endblock %}
-{%- block extrahead %}{% endblock %}
- </head>
- <body>
-
-{% block logilablogo %}
-<div class="logilablogo">
- <a class="logogo" href="http://www.cubicweb.org"><img border="0" src="{{ pathto('_static/cubicweb.png', 1) }}"/></a>
- </div>
-{% endblock %}
-
-{%- block relbar1 %}{{ relbar() }}{% endblock %}
-
-{%- block sidebar1 %}{# possible location for sidebar #}{% endblock %}
-
-{%- block document %}
- <div class="document">
- <div class="documentwrapper">
- {%- if builder != 'htmlhelp' %}
- <div class="bodywrapper">
- {%- endif %}
- <div class="body">
- {% block body %}{% endblock %}
- </div>
- {%- if builder != 'htmlhelp' %}
- </div>
- {%- endif %}
- </div>
-{%- endblock %}
-
-{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
- <div class="clearer"></div>
- </div>
-
-{%- block relbar2 %}{{ relbar() }}{% endblock %}
-
-{%- block footer %}
- <div class="footer">
- {%- if hasdoc('copyright') %}
- © <a href="{{ pathto('copyright') }}">Copyright</a> {{ copyright }}.
- {%- else %}
- © Copyright {{ copyright }}.
- {%- endif %}
- {%- if last_updated %}
- Last updated on {{ last_updated }}.
- {%- endif %}
- {%- if show_sphinx %}
- Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
- {%- endif %}
- </div>
-{%- endblock %}
- </body>
-</html>
--- a/doc/book/fr/01-introduction.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,343 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Overview:
-
-Aperçu rapide de `CubicWeb`
-===========================
-
-`CubicWeb` nous permet de développer des instances d'applications web
-basées sur un ou plusieurs `cube`.
-
-Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant
-des types de données et des vues. Un `cube` est un composant re-utilisable
-regroupé avec d'autres cubes sur le système de fichiers.
-
-Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes
-où sont regroupés tous les fichiers de configuration de l'application web finale.
-
-Dans ce document, nous allons vous montrer comment créer un cube et l'utiliser
-dans une instance pour votre application web.
-
-Créez votre cube
-----------------
-
-Après avoir installé votre environement de développement `CubicWeb`, vous pouvez
-commencer à construire votre premier cube: ::
-
- cubicweb-ctl newcube blog
-
-Cela va créer dans ``/path/to/forest/cubes`` une répertoire contenant ::
-
- blog/
- |
- |-- data/
- | |-- cubes.blog.css
- | |-- cubes.blog.js
- | |-- external_resources
- |
- |-- debian/
- | |-- changelog
- | |-- compat
- | |-- control
- | |-- copyright
- | |-- cubicweb-blog.prerm
- | |-- rules
- |
- |-- entities.py
- |
- |-- i18n/
- | |-- en.po
- | |-- fr.po
- |
- |-- __init__.py
- |
- |-- MANIFEST.in
- |
- |-- migration/
- | |-- postcreate.py
- | |-- precreate.py
- |
- |-- __pkginfo__.py
- |
- |-- schema.py
- |
- |-- setup.py
- |
- |-- site_cubicweb.py
- |
- |-- sobjects.py
- |
- |-- test/
- | |-- data/
- | |-- bootstrap_cubes
- | |-- pytestconf.py
- | |-- realdb_test_blog.py
- | |-- test_blog.py
- |
- |-- views.py
-
-Toute modification apportée à votre modele de données devra
-etre effectué dans ce répertoire.
-
-
-
-Définissez votre schéma de données
-----------------------------------
-
-Le modèle de données ou schéma est au coeur d'une application `CubicWeb`.
-C'est là où vous allez devoir définir le type de contenu que votre application
-devra gérer.
-
-Votre modele de données est défini dans le fichier ``schema.py`` de votre cube
-``blog`` comme suit.
-
-::
-
- from cubicweb.schema import format_constraint
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
-
- class BlogEntry(EntityType):
- title = String(required=True, fulltextindexed=True, maxsize=256)
- publish_date = Date(default='TODAY')
- content = String(required=True, fulltextindexed=True)
- entry_of = SubjectRelation('Blog', cardinality='?*')
-
-Un ``Blog`` a un titre et une description. Le titre est une chaîne
-de caractères requise par la classe parente EntityType et ne doit
-pas excéder 50 caractères. La description est une chaîne de
-caractères sans contraintes.
-
-Une ``BlogEntry`` a un titre, une date de publication et du texte
-étant son contenu. Le titre est une chaîne de caractères qui ne
-doit pas excéder 100 caractères. La date de publication est de type Date et a
-pour valeur par défaut TODAY, ce qui signifie que lorsqu'une
-``BlogEntry`` sera créée, sa date de publication sera la date
-courante a moins de modifier ce champ. Le texte est une chaîne de
-caractères qui sera indexée en plein texte et sans contraintes.
-
-Une ``BlogEntry`` a aussi une relation nommée ``entry_of`` qui la
-relie à un ``Blog``. La cardinalité ``?*`` signifie que BlogEntry
-peut faire partie de zero a un Blog (``?`` signifie `zero ou un`) et
-qu'un Blog peut avoir une infinité de BlogEntry (``*`` signifie
-`n'importe quel nombre incluant zero`).
-Par soucis de complétude, nous rappellerons que ``+`` signifie
-`un ou plus`.
-
-
-Créez votre instance
---------------------
-
-::
-
- cubicweb-ctl create blog blogdemo
-
-Cette commande va créer un répertoire ``~/etc/cubicweb.d/blogdemo``
-contenant tous les fichiers de configuration nécessaire au lancement
-de votre application web.
-
-L'instance ``blogdemo`` est construite sur le cube ``blog``.
-
-Bienvenue dans votre application web
-------------------------------------
-
-Lancez votre application en exécutant : ::
-
- cubicweb-ctl start -D blogdemo
-
-
-Vous pouvez à présent accéder à votre application web vous permettant de
-créer des blogs et d'y poster des messages en visitant l'URL http://localhost:8080/.
-Un premier formulaire d'authentification va vous être proposé. Par défaut,
-l'application n'autorisera pas d'utilisateur anonyme à accéder a votre
-application. Vous devrez donc utiliser l'utilisateur administrateur que
-vous aurez crée lors de l'initialisation de votre base de données via
-``cubicweb-ctl create``.
-
-.. image:: images/login-form.png
-
-
-Une fois authentifié, vous pouvez commencer à jouer avec votre
-application et créer des entités. Bravo !
-
-.. image:: images/blog-demo-first-page.png
-
-
-Rappelez-vous que pour le moment, tout a été géré par la plate-forme
-`CubicWeb` et que la seule chose qui a été fournie est le schéma de
-données.
-
-Créons des entités
-------------------
-
-Nous allons maintenant créer quelques entités dans notre application.
-
-Créez un Blog
-~~~~~~~~~~~~~
-
-Créons à présent quelques entités. Cliquez sur `[+]` sur la
-droite du lien Blog. Appelez cette nouvelle entité Blog ``Tech-Blog``
-et tapez pour la description ``everything about technology``,
-puis validez le formulaire d'édition en cliquant sur le bouton
-``Validate``.
-
-.. image:: images/cbw-create-blog.fr.png
- :alt: from to create blog
-
-En cliquant sur le logo situé dans le coin gauche de la fenêtre,
-vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez
-sur le lien Blog, vous devriez voir la liste des entités Blog, en particulier
-celui que vous venez juste de créer ``Tech-Blog``.
-
-.. image:: images/cbw-list-one-blog.fr.png
- :alt: displaying a list of a single blog
-
-Si vous cliquez sur ``Tech-Blog`` vous devriez obtenir une description
-détaillée, ce qui dans notre cas, n'est rien de plus que le titre
-et la phrase ``everything about technology``
-
-Maintenant retournons sur la page d'accueil et créons un nouveau
-Blog ``MyLife`` et retournons sur la page d'accueil, puis suivons
-le lien Blog et nous constatons qu'à présent deux blogs sont listés.
-
-.. image:: images/cbw-list-two-blog.fr.png
- :alt: displaying a list of two blogs
-
-Créons un article
-~~~~~~~~~~~~~~~~~
-
-Revenons sur la page d'accueil et cliquons sur `[+]` à droite du lien
-`articles`. Appellons cette nouvelle entité ``Hello World`` et introduisons
-un peut de texte avant de ``Valider``. Vous venez d'ajouter un article
-sans avoir précisé à quel Blog il appartenait. Dans la colonne de gauche
-se trouve une boite intitulé ``actions``, cliquez sur le menu ``modifier``.
-Vous êtes de retour sur le formulaire d'édition de l'article que vous
-venez de créer, à ceci près que ce formulaire a maintenant une nouvelle
-section intitulée ``ajouter relation``. Choisissez ``entry_of`` dans ce menu,
-cela va faire apparaitre une deuxième menu déroulant dans lequel vous
-allez pouvoir séléctionner le Blog ``MyLife``.
-
-Vous auriez pu aussi, au moment où vous avez crée votre article, sélectionner
-``appliquer`` au lieu de ``valider`` et le menu ``ajouter relation`` serait apparu.
-
-.. image:: images/cbw-add-relation-entryof.fr.png
- :alt: editing a blog entry to add a relation to a blog
-
-Validez vos modifications en cliquant sur ``Valider``. L'entité article
-qui est listée contient maintenant un lien vers le Blog auquel il
-appartient, ``MyLife``.
-
-.. image:: images/cbw-detail-one-blogentry.fr.png
- :alt: displaying the detailed view of a blogentry
-
-Rappelez-vous que pour le moment, tout a été géré par la plate-forme
-`CubicWeb` et que la seule chose qui a été fournie est le schéma de
-données. D'ailleurs pour obtenir une vue graphique du schéma, visitez
-le lien `Application schema`` a l'URL suivante :
-http://localhost:8080/view?vid=schema
-
-.. image:: images/cbw-schema.fr.png
- :alt: graphical view of the schema (aka data-model)
-
-
-Définissez les vues de vos données
-----------------------------------
-
-Le principe de sélection des vues
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Une vue est une classe Python qui inclut:
-
- - un identifiant (tous les objets dans `CubicWeb` sont listés
- dans un registre et cet identifiant est utilisé comme la clé)
-
- - un filtre de sélection de `result sets` auxquels il
- peut s'appliquer
-
-`CubicWeb` fournit un ensemble de vues standard pour le type d'objet
-`EntityView`. vous poubez les trouver listées dans ``cubicweb/web/views``.
-
-Une vue est appliquée sur un `result set` qui représente l'ensemble
-des entités que nous cherchons à appliquer. `CubicWeb` utilise un
-sélecteur qui permet de calculer un score et d'identifier la vue
-la plus adaptée au `result set` que nous voulons afficher. La librarie
-standard des sélecteurs se trouve dans ``cubicweb.common.selector``
-et une librairie des méthodes utilisées pour calculer les scores
-est dans ``cubicweb.vregistry.vreq``.
-
-
-Il est possible de définir plusieurs vues ayant le meme identifiant
-et d'y associer des sélecteurs et des filtres afin de permettre à
-l'application de s'adapter au mieux aux données que nous avons
-besoin d'afficher. Nous verrons cela plus en détails dans :ref:`DefinitionVues`.
-
-On peut citer l'exemple de la vue nommée ``primary`` qui est celle utilisée
-pour visualiser une entité seule. Nous allons vous montrer comment modifier
-cette vue.
-
-Modification des vues
-~~~~~~~~~~~~~~~~~~~~~
-Si vous souhaitez modifier la manière dont est rendue un article (`Blogentry`),
-vous devez surcharger la vue ``primary`` définie dans le module ``views`` de
-votre cube, ``cubes/blog/views.py``.
-
-Nous pourrions par exemple ajouter devant la date de publication un préfixe
-indiquant que la date visualisée est la date de publication.
-
-Pour cela appliquez les modifications suivantes:
-
-::
-
- from cubicweb.web.views import baseviews
-
-
- class BlogEntryPrimaryView(baseviews.PrimaryView):
-
- accepts = ('BlogEntry',)
-
- def render_entity_title(self, entity):
- self.w(u'<h1>%s</h1>' % html_escape(entity.dc_title()))
-
- def content_format(self, entity):
- return entity.view('reledit', rtype='content_format')
-
- def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
-
- # display entity attributes with prefixes
- self.w(u'<h1>%s</h1>' % entity.title)
- self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
- self.w(u'<p>%s</p>' % entity.content)
-
- # display relations
- siderelations = []
- if self.main_related_section:
- self.render_entity_relations(entity, siderelations)
-
-.. note::
- Lors qu'une vue est modifiée il n'est pas nécessaire de relancer
- l'application. Sauvez juste le fichier Python et rechargez la page
- dans votre navigateur afin de visualiser les modifications.
-
-
-Nous pouvons voir que la date de publication est préfixée comme souhaitée.
-
-
-.. image:: images/cbw-update-primary-view.fr.png
- :alt: modified primary view
-
-
-
-Le code que nous avons modifié définit une vue primaire pour une entité de
-type `BlogEntry`.
-
-Etant donné que les vues sont appliquées sur des `result sets` et que
-les `result sets` peuvent être des tableaux de données, il est indispensable
-de récupérer l'entité selon ses coordonnées (row,col).
-
-La méthode ``self.w()`` est utilisée pour afficher des données. En particulier
-dans notre exemple, nous l'utilisons pour afficher des tags HTML et des valeurs
-des attributs de notre entité.
-
-
--- a/doc/book/fr/02-foundation.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Fondements `CubicWeb`
-=====================
-Un peu d'histoire...
---------------------
-
-`CubicWeb` est une plate-forme logicielle de développement d'application web
-qui est développée par Logilab_ depuis 2001.
-
-
-Entièrement développée en Python, `CubicWeb` publie des données provenant
-de plusieurs sources telles que des bases de données SQL, des répertoire
-LDAP et des systèmes de gestion de versions tels que subversion.
-
-L'interface utilisateur de `CubicWeb` a été spécialement conçue pour laisser
-à l'utilisateur final toute latitude pour sélectionner puis présenter les données.
-Elle permet d'explorer aisément la base de connaissances et d'afficher les
-résultats avec la présentation la mieux adaptée à la tâche en cours.
-La flexibilité de cette interface redonne à l'utilisateur le contrôle de
-paramètres d'affichage et de présentation qui sont habituellement réservés
-aux développeurs.
-
-Parmi les applications déjà réalisées, on dénombre un annuaire en ligne pour
-le grand public (voir http://www.118000.fr/), un système de gestion d'études
-numériques et de simulations pour un bureau d'études, un service de garde
-partagée d'enfants (voir http://garde-partagee.atoukontact.fr/), la gestion
-du développement de projets logiciels (voir http://www.logilab.org), etc.
-
-En 2008, `CubicWeb` a été porté pour un nouveau type de source: le datastore
-de GoogleAppEngine_.
-
-.. _GoogleAppEngine: http://code.google.com/appengine/
-
-
-Architecture globale
---------------------
-.. image:: images/archi_globale.png
-
-**Note**: en pratique la partie cliente et la partie serveur sont
-généralement intégrées dans le même processus et communiquent donc
-directement, sans nécessiter des appels distants via Pyro. Il est
-cependant important de retenir que ces deux parties sont disjointes
-et qu'il est même possible d'en exécuter plusieurs exemplaires dans
-des processus distincts pour répartir la charge globale d'un site
-sur une ou plusieurs machines.
-
-Concepts et vocabulaire
------------------------
-
-*schéma*
- le schéma définit le modèle de données d'une application sous forme
- d'entités et de relations, grâce à la bibliothèque `yams`_. C'est
- l'élément central d'une application. Il est initialement défini sur
- le système de fichiers et est stocké dans la base de données lors de
- la création d'une instance. `CubicWeb` fournit un certain nombres de
- types d'entités inclus systématiquement car nécessaire au noyau
- `CubicWeb` et une librairie de cubes devant être inclus
- explicitement le cas échéant.
-
-*type d'entité*
- une entité est un ensemble d'attributs ; l'attribut de
- base de toute entité, qui est sa clef, est l'eid
-
-*type de relation*
- les entités sont liées entre elles par des relations. Dans `CubicWeb`
- les relations sont binaires : par convention on nomme le premier terme
- d'une relation son 'sujet' et le second son 'objet'.
-
-*type d'entité final*
- les types finaux correspondent aux types de bases comme les chaînes
- de caractères, les nombres entiers... Une propriété de ces types est
- qu'ils ne peuvent être utilisés qu'uniquement comme objet d'une
- relation. Les attributs d'une entité (non finale) sont des entités
- (finales).
-
-*type de relation finale*
- une relation est dite finale si son objet est un type final. Cela revient à
- un attribut d'une entité.
-
-*entrepôt*
- ou *repository*, c'est la partie serveur RQL de `CubicWeb`. Attention à ne pas
- confondre avec un entrepôt mercurial ou encore un entrepôt debian.
-
-*source*
- une source de données est un conteneur de données quelquonque (SGBD, annuaire
- LDAP...) intégré par l'entrepôt `CubicWeb`. Un entrepôt possède au moins une source
- dite "system" contenant le schéma de l'application, l'index plein-texte et
- d'autres informations vitales au système.
-
-*configuration*
- il est possible de créer différentes configurations pour une instance :
-
- - ``repository`` : entrepôt uniquement, accessible pour les clients via Pyro
- - ``twisted`` : interface web uniquement, communiquant avec un entrepôt via Pyro
- - ``all-in-one`` : interface web et entrepôt dans un seul processus. L'entrepôt
- peut ou non être accessible via Pyro
-
-*cube*
- un cube est un modèle regroupant un ou plusieurs types de données et/ou
- des vues afin de fournir une fonctionalité précise, ou une application `CubicWeb`
- complète utilisant éventuellement d'autres cube. Les différents
- cubes disponibles sur une machine sont installés dans
- `/path/to/forest/cubicweb/cubes`
-
-*instance*
- une instance est une installation spécifique d'un cube. Sont regroupes
- dans une instance tous les fichiers de configurations necessaires au bon
- fonctionnement de votre application web. Elle referrera au(x) cube(s) sur
- le(s)quel(s) votre application se base.
- Par exemple intranet/jpl et logilab.org sont deux instances du cube jpl que
- nous avons developpes en interne.
- Les instances sont définies dans le répertoire `~/etc/cubicweb.d`.
-
-*application*
- le mot application est utilisé parfois pour parler d'une instance et parfois
- d'un composant, en fonction du contexte... Mieux vaut donc éviter de
- l'utiliser et utiliser plutôt *cube* et *instance*.
-
-*result set*
- objet encaspulant les résultats d'une requête RQL et des informations sur
- cette requête.
-
-*Pyro*
- `Python Remote Object`_, système d'objets distribués pur Python similaire à
- Java's RMI (Remote Method Invocation), pouvant être utilisé pour la
- communication entre la partie web du framework et l'entrepôt RQL.
-
-.. _`Python Remote Object`: http://pyro.sourceforge.net/
-.. _`yams`: http://www.logilab.org/project/name/yams/
-
-
-Moteur `CubicWeb`
------------------
-
-Le moteur web de `CubicWeb` consiste en quelques classes gérant un ensemble d'objets
-chargés dynamiquement au lancement de `CubicWeb`. Ce sont ces objets dynamiques, issus
-du modèle ou de la librairie, qui construisent le site web final. Les différents
-composants dynamiques sont par exemple :
-
-* coté client et serveur
-
- - les définitions d'entités, contenant la logique permettant la manipulation des
- données de l'application
-
-* coté client
-
- - les *vues* , ou encore plus spécifiquement
-
- - les boites
- - l'en-tête et le pied de page
- - les formulaires
- - les gabarits de pages
-
- - les *actions*
- - les *controleurs*
-
-* coté serveur
-
- - les crochets de notification
- - les vues de notification
-
-Les différents composants du moteur sont :
-
-* un frontal web (seul twisted disponible pour le moment), transparent du point
- de vue des objets dynamiques
-* un objet encapsulant la configuration
-* un `vregistry` (`cubicweb.cwvreg`) contenant les objets chargés dynamiquements
-
-Détail de la procédure d'enregistrement
----------------------------------------
-Au démarage le `vregistry` ou base de registres inspecte un certain nombre de
-répertoires à la recherche de définition de classes "compatible". Après une
-procédure d'enregistrement les objets sont affectés dans différents registres
-afin d'être ensuite séléctionné dynamiquement pendant le fonctionnement de
-l'application.
-
-La classe de base de tout ces objets est la classe `AppRsetObject` (module
-`cubicweb.common.appobject`).
-
-
-API Python/RQL
---------------
-
-Inspiré de la db-api standard, avec un object Connection possédant les méthodes
-cursor, rollback et commit principalement. La méthode importante est la méthode
-`execute` du curseur :
-
-`execute(rqlstring, args=None, eid_key=None, build_descr=True)`
-
-:rqlstring: la requête rql à éxécuter (unicode)
-:args: si la requête contient des substitutions, un dictionnaire contenant les
- valeurs à utiliser
-:eid_key:
- un détail d'implémentation du cache de requêtes RQL fait que si une substitution est
- utilisée pour introduire un eid *levant des ambiguités dans la résolution de
- type de la requête*, il faut spécifier par cet argument la clé correspondante
- dans le dictionnaire
-
-C'est l'objet Connection qui possède les méthodes classiques `commit` et
-`rollback`. Vous ne *devriez jamais avoir à les utiliser* lors du développement
-d'interface web sur la base du framework `CubicWeb` étant donné que la fin de la
-transaction est déterminée par celui-ci en fonction du succès d'éxécution de la
-requête.
-
-NOTE : lors de l'éxécution de requêtes de modification (SET,INSERT,DELETE), si une
-requête génère une erreur liée à la sécurité, un rollback est systématiquement
-effectuée sur la transaction courante.
-
-
-La classe `Request` (`cubicweb.web`)
-------------------------------------
-Une instance de requête est créée lorsque une requête HTTP est transmise au
-serveur web. Elle contient des informations telles que les paramètres de
-formulaires, l'utilisateur connecté, etc.
-
-**De manière plus générale une requête représente une demande d'un utilisateur,
-que se soit par HTTP ou non (on parle également de requête rql coté serveur par
-exemple)**
-
-Une instance de la classe `Request` possède les attributs :
-
-* `user`, instance de`cubicweb.common.utils.User` correspondant à l'utilisateur
- connecté
-* `form`, dictionaire contenant les valeurs de formulaire web
-* `encoding`, l'encodage de caractère à utiliser dans la réponse
-
-Mais encore :
-
-:Gestion des données de session:
- * `session_data()`, retourne un dictionaire contenant l'intégralité des
- données de la session
- * `get_session_data(key, default=None)`, retourne la valeur associée à
- la clé ou la valeur `default` si la clé n'est pas définie
- * `set_session_data(key, value)`, associe une valeur à une clé
- * `del_session_data(key)`, supprime la valeur associé à une clé
-
-
-:Gestion de cookie:
- * `get_cookie()`, retourne un dictionnaire contenant la valeur de l'entête
- HTTP 'Cookie'
- * `set_cookie(cookie, key, maxage=300)`, ajoute un en-tête HTTP `Set-Cookie`,
- avec une durée de vie 5 minutes par défault (`maxage` = None donne un cooke
- *de session"* expirant quand l'utilisateur ferme son navigateur
- * `remove_cookie(cookie, key)`, fait expirer une valeur
-
-:Gestion d'URL:
- * `url()`, retourne l'url complète de la requête HTTP
- * `base_url()`, retourne l'url de la racine de l'application
- * `relative_path()`, retourne chemin relatif de la requête
-
-:Et encore...:
- * `set_content_type(content_type, filename=None)`, place l'en-tête HTTP
- 'Content-Type'
- * `get_header(header)`, retourne la valeur associé à un en-tête HTTP
- arbitraire de la requête
- * `set_header(header, value)`, ajoute un en-tête HTTP arbitraire dans la
- réponse
- * `cursor()` retourne un curseur RQL sur la session
- * `execute(*args, **kwargs)`, raccourci vers .cursor().execute()
- * `property_value(key)`, gestion des propriétés (`CWProperty`)
- * le dictionaire `data` pour stocker des données pour partager de
- l'information entre les composants *durant l'éxécution de la requête*.
-
-A noter que cette classe est en réalité abstraite et qu'une implémentation
-concrète sera fournie par le *frontend* web utilisé (en l'occurent *twisted*
-aujourd'hui). Enfin pour les vues ou autres qui sont éxécutés coté serveur,
-la majeure partie de l'interface de `Request` est définie sur la session
-associée au client.
-
-
-La classe `AppObject`
----------------------
-
-En général :
-
-* on n'hérite pas directement des cette classe mais plutôt d'une classe
- plus spécifique comme par exemple `AnyEntity`, `EntityView`, `AnyRsetView`,
- `Action`...
-
-* pour être enregistrable, un classe fille doit définir son registre (attribut
- `__registry__`) et son identifiant (attribut `id`). Généralement on n'a pas à
- s'occuper du registre, uniquement de l'identifiant `id` :)
-
-On trouve un certain nombre d'attributs et de méthodes définis dans cette classe
-et donc commune à tous les objets de l'application :
-
-A l'enregistrement, les attributs suivants sont ajoutés dynamiquement aux
-*classes* filles:
-
-* `vreg`, le `vregistry` de l'application
-* `schema`, le schéma de l'application
-* `config`, la configuration de l'application
-
-On trouve également sur les instances les attributs :
-
-* `req`, instance de `Request`
-* `rset`, le "result set" associé à l'objet le cas échéant
-* `cursor`, curseur rql sur la session
-
-
-:Gestion d'URL:
- * `build_url(method=None, **kwargs)`, retourne une URL absolue construites à
- partir des arguments donnés. Le *controleur* devant gérer la réponse
- peut-être spécifié via l'argument spécial `method` (le branchement est
- théoriquement bien effectué automatiquement :).
-
- * `datadir_url()`, retourne l'url du répertoire de données de l'application
- (contenant les fichiers statiques tels que les images, css, js...)
-
- * `base_url()`, raccourci sur `req.base_url()`
-
- * `url_quote(value)`, version *unicode safe* de de la fonction `urllib.quote`
-
-:Manipulation de données:
-
- * `etype_rset(etype, size=1)`, raccourci vers `vreg.etype_rset()`
-
- * `eid_rset(eid, rql=None, descr=True)`, retourne un objet result set pour
- l'eid donné
- * `entity(row, col=0)`, retourne l'entité correspondant à la position données
- du "result set" associé à l'objet
-
- * `complete_entity(row, col=0, skip_bytes=True)`, équivalent à `entity` mais
- appelle également la méthode `complete()` sur l'entité avant de la retourner
-
-:Formattage de données:
- * `format_date(date, date_format=None, time=False)`
- * `format_time(time)`,
-
-:Et encore...:
-
- * `external_resource(rid, default=_MARKER)`, accède à une valeur définie dans
- le fichier de configuration `external_resource`
-
- * `tal_render(template, variables)`,
-
-
-**NOTE IMPORTANTE**
-Lorsqu'on hérite d'`AppObject` (même indirectement), il faut **toujours**
-utiliser **super()** pour récupérer les méthodes et attributs des classes
-parentes, et pas passer par l'identifiant de classe parente directement.
-(sous peine de tomber sur des bugs bizarres lors du rechargement automatique
-des vues). Par exemple, plutôt que d'écrire::
-
- class Truc(PrimaryView):
- def f(self, arg1):
- PrimaryView.f(self, arg1)
-
-Il faut écrire::
-
- class Truc(PrimaryView):
- def f(self, arg1):
- super(Truc, self).f(arg1)
-
-
-
-
-
-
-Structure standard d'un cube
-----------------------------
-
-Un cube complexe est structuré selon le modèle suivant :
-
-::
-
- moncube/
- |
- |-- schema.py
- |
- |-- entities/
- |
- |-- sobjects/
- |
- |-- views/
- |
- |-- test/
- |
- |-- i18n/
- |
- |-- data/
- |
- |-- migration/
- | |- postcreate.py
- | \- depends.map
- |
- |-- debian/
- |
- \-- __pkginfo__.py
-
-On peut utiliser de simple module python plutôt que des répertoires (packages),
-par ex.:
-
-::
-
- moncube/
- |
- |-- entities.py
- |-- hooks.py
- \-- views.py
-
-
-où :
-
-* ``schema`` contient la définition du schéma (coté serveur uniquement)
-* ``entities`` contient les définitions d'entités (coté serveur et interface web)
-* ``sobjects`` contient les crochets et/ou vues de notification (coté serveur
- uniquement)
-* ``views`` contient les différents composants de l'interface web (coté interface
- web uniquement)
-* ``test`` contient les tests spécifiques à l'application (non installé)
-* ``i18n`` contient les catalogues de messages pour les langues supportées (coté
- serveur et interface web)
-* ``data`` contient des fichiers de données arbitraires servis statiquement
- (images, css, fichiers javascripts)... (coté interface web uniquement)
-* ``migration`` contient le fichier d'initialisation de nouvelles instances
- (``postcreate.py``) et générallement un fichier donnant les dépendances `CubicWeb` du
- composant en fonction de la version de celui-ci (``depends.map``)
-* ``debian`` contient les fichiers contrôlant le packaging debian (vous y
- trouverez les fichiers classiques ``control``, ``rules``, ``changelog``... (non
- installé)
-* le fichier ``__pkginfo__.py`` donne un certain nombre de méta-données sur le
- composant, notamment le nom de la distribution et la version courante (coté
- serveur et interface web) ou encore les sous-cubes utilisés par ce
- cube.
-
-Le strict minimum étant :
-
-* le fichier ``__pkginfo__.py``
-* la définition du schéma
--- a/doc/book/fr/03-01-bis-installation.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _installationGAE:
-
-Installation de `CubicWeb` pour GoogleAppEngine
-===============================================
-
-Qu'est-ce que `LAX` ?
-=======================
-
-`LAX` (Logilab Appengine eXtension) est un framework d'application
-web basé sur `Google AppEngine`.
-
-`LAX` est un portage de la partie web de la plate-forme applicative
-développée par Logilab depuis 2001. Cette plate-forme publie des
-données tirées de bases SQL, d'annuaires LDAP et de systèmes de
-gestion de version. En avril 2008, elle a été portée pour fonctionner
-sur le "datastore" de `Google AppEngine`.
-
-XXX: faire un parallèle entre Django/GAE et LAX/GAE
-
-
-Téléchargement des sources
-==========================
-
-- Les sources de `Google AppEngine` peuvent être récupérées à l'adresse
- suivante : http://code.google.com/appengine/downloads.html
-
-- Les sources de `LAX` se trouvent à l'adresse suivante :
- http://lax.logilab.org/
-
-
-Installation
-============
-
-Une fois décompactée, l'archive `lax-0.1.0-alpha.tar.gz`, on obtient
-l'arborescence suivante::
-
- .
- |-- app.yaml
- |-- custom.py
- |-- data
- |-- ginco/
- |-- i18n/
- |-- logilab/
- |-- main.py
- |-- mx/
- |-- rql/
- |-- schema.py
- |-- simplejson/
- |-- tools/
- | |-- generate_schema_img.py
- | `-- i18ncompile.py
- |-- views.py
- |-- yams/
- `-- yapps/
-
-
-On retrouve le squelette d'une application web de `Google AppEngine`
-(fichiers ``app.yaml``, ``main.py`` en particulier) avec les dépendances
-supplémentaires nécessaires à l'utilisation du framework `LAX`
-
-
-Lancement de l'application de base
-==================================
-
-Plusieurs répertoires doivent être accessibles via la variable
-d'environnement ``PYTHONPATH`` ::
-
- $ export PYTHONPATH=/path/to/google_appengine:/path/to/google_appengine/lib/yaml/lib:/path/to/myapp/
-
-Le répertoire yaml n'est nécessaire que pour le lancement des scripts
-qui se trouvent dans lax/tools et pour l'exécution des tests unitaires.
-
-Pour démarrer::
-
- $ python /path/to/google_appengine/dev_appserver.py /path/to/lax
-
-
--- a/doc/book/fr/03-01-installation.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Installation
-============
-
-Installation de Cubicweb et de ses dépendances
-----------------------------------------------
-
-`CubicWeb` est disponible via un entrepôt Mercurial utilisant l'extension forest.
-Vous devez donc dans un premier temps vous assurer que Mercurial est bien installé
-et que vous avez l'extension forest.
-
-Installation de Mercurial
-`````````````````````````
-Veuillez vous référer a la documentation en ligne du projet Mercurial_.
-
-.. _Mercurial: http://www.selenic.com/mercurial/wiki/
-
-Installation de l'extension forest
-``````````````````````````````````
-Dans un premier temps, récupérez une copie des sources via: http://hg.akoha.org/hgforest/.
-Ensuite, ajoutez a votre ``~/.hgrc`` les lignes suivantes ::
-
- [extensions]
- hgext.forest=
- # or, if forest.py is not in the hgext dir:
- # forest=/path/to/forest.py
-
-Récupération des sources
-````````````````````````
-Clonez la foret dans votre répertoire de travail.
-
-::
-
- hg fclone http://www.logilab.org/hg/forests/cubicweb
-
-.. note::
- Nous vous recommandons de créer un lien symbolique vers l'outil ``cubicweb-ctl``
- que vous allez etre amené a utiliser.
-
- ::
-
- $ ln -s /path/to/forest/cubicweb/bin/cubicweb-ctl ~/bin
-
-Installation de Postgres
-````````````````````````
-Veuillez vous référer a la documentation en ligne du projet Postgres_.
-
-.. _Postgres: http://www.postgresql.org/
-
-Vous allez devoir installer les trois paquets suivants: `postgres-8.3`,
-`postgres-contrib-8.3` and `postgresql-plpython-8.3`.
-
-
-On pourra ensuite installer les paquets suivants :
-
-* `pyro` si vous voulez que l'entrepôt soit accessible via Pyro ou si le client
- et le serveur ne sont pas sur la même machine (auquel cas il faut installer ce
- paquet sur les machines clientes et serveur)
-
-* `python-ldap` si vous voulez utiliser une source ldap sur le serveur
-
-
-.. _ConfigurationEnv:
-
-Configuration de l'environnement
---------------------------------
-
-[FIXME]
-Ces variables ne sont plus requises pour le bon fonctionnement de `CubicWeb`, non?
-A part rajouter la foret dans le PYTHONPATH, rien de plus ne doit etre fait?
-
-Mettez à jour votre variable d'environemment PYTHONPATH afin d'y ajouter
-le chemin d'acces a votre foret ``cubicweb``.
-
-Ajouter les lignes suivantes à son `.bashrc` ou `.bash_profile` pour configurer
-votre environnement de développement ::
-
- export PYTHONPATH=/full/path/to/cubicweb-forest
-
- export ERUDI_REGISTRY=~/etc/erudi.d/
- export ERUDI_TEMPLATES=~/hg/
- export ERUDI_RUNTIME=/tmp/
-
-Cela suppose que le composant erudi que vous développez est dans un
-sous-répertoire de *~/hg/* et que vous avez créé le répertoire *~/etc/erudi.d/*
-pour que `cubicweb-ctl` y place vos instances de test.
-
-.. _ConfigurationPostgres:
-
-Configuration Postgres
-----------------------
-* Tout d'abord vous devez initialiser votre base de données Postgres via la
- commande ``initidb``.
- ::
-
- $ initdb -D /path/to/pgsql
-
- Une fois ces paquets installés vous pouvez lancer votre server de base de
- données Postgres avec la commande suivante: ::
-
- $ postgres -D /path/to/psql
-
- Si vous ne pouvez exécuter cette commande pour des raisons de permissions
- assurez-vous que votre utilisateur a droit d'écriture sur les la base de données.
-
- ::
-
- $ chown username /path/to/pgsql
-
-* Création d'un super utilisateur pour la création d'instance (**root**) ::
-
- createuser -s username
-
- Initialisez le mot de passe de ce superutilisateur ``username`` via
- ``su - postgres`` puis ``psql``.
-
- Un mot de passe de connection pour cet utilisateur vous sera demandé. Il
- faudra utiliser ce login / mot de passe à la création d'instance via
- `cubicweb-ctl`
-
-[XXX]
-Est-ce que ces etapes sont vraiment necessaires?
-sand : lors de l'installation de ma bdd cela n'a pas ete fait
-et il semble que tout aille bien. Doit etre verifie avec les experts.
-
-* installation des extensions pour l'index plein texte ::
-
- cat /usr/share/postgresql/8.1/contrib/tsearch2.sql | psql -U pgadmin template1
-
-* installation du langage plpythonu par défaut ::
-
- createlang -U pgadmin plpythonu template1
-
-
-Configuration Pyro
-------------------
-Si vous utilisez Pyro, il est nécessaire d'avoir un serveur de noms Pyro
-tournant sur votre réseau (par défaut celui-ci est repéré par une requête
-broadcast). Pour cela il faut soit :
-
-* le lancer à la main avant le démarrage de erudi avec la commande `pyro-ns`
-
-* le lancer à la main avant le démarrage de erudi sous forme d'un serveur avec
- la commande `pyro-nsd start`
-
-* éditer le fichier */etc/default/pyro-nsd* pour que le serveur de nom pyro soit
- lancé automatiquement au démarrage de la machine
-
-
--- a/doc/book/fr/03-02-create-instance.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-===================================
-Creation de votre premiere instance
-===================================
-
-
-Qu'est-ce qu'une instance?
-==========================
-
-Une instance CubicWeb consiste en un dossier situe dans ``~/etc/cubicweb.d``
-qui permettra de lancer une application web. Une instance est cree a partir
-d'un ou plusieurs cubes.
-
-Nous recommandons de ne pas definir de schema, entites ou vues dans l'instance
-meme si cela est possible dans un but de re-utilisabilite des entities et de leurs
-vues. Nous conseillons plutot de developper des cubes qui pourront par la suite
-etre utilises dans d'autres instances (approche modulaire).
-
-L'instance n'est qu'un conteneur referrant a des cubes et a des parametres
-des configuration de l'application web.
-
-Qu'est-ce qu'un cube?
-=====================
-
-Un cube definit des entities, leur vues, leur schemas et leur workflow
-dans un repertoire independant situe dans ``/path/to/forest/cubicweb/cubes/``.
-
-Lors de la creation d'une instance, vous avez la possibilite de lister
-le ou les cubes que votre instance va utiliser. Utiliser un cube signifie
-avoir a disposition dans votre instance les entites definies dans le schema
-de votre cube ainsi que les vues et les workflows.
-
-
-.. note::
- Les commandes utilisees ci-apres sont detaillees dans la section
- dediee a :ref:`cubicweb-ctl`.
-
-
-Création d'un cube
-==================
-
-Commençons par créer un squelette qui nous servira de base au développement de
-notre cube ou application ::
-
- cd ~/hg
-
- cubicweb-ctl newtemplate moncube
-
- # répondre aux questions
- cd moncube
- hg init
- hg add .
- hg ci
-
-A partir de là si tout va bien, votre cube devrait être affiché par
-`cubicweb-ctl list` dans la section *Available components*, si ce n'est pas le cas
-revoir la section :ref:`ConfigurationEnv`.
-
-
-Pour utiliser un cube, il faut le mentionner dans la variable
-__use__ du fichier __pkginfo__ de l'application. Cette variable
-contrôle à la fois le packaging de l'application (dépendances gérées
-par les utilitaires système comme les outils APT) et les composants
-effectivement utilisables lors de la création de la base
-(import_erschema('Moncomposant') ne fonctionne pas sinon).
-
-FIXME - need example code ::
-
- __use__ = ('blog', 'file')
-
-Création d'une instance de développement
-========================================
-
-Maintenant que nous avons notre squelette de modèle, on peut en créer une
-instance afin de voir ce que tout ça donne dans un simple navigateur web.
-Nous allons utiliser une configuration `all-in-one` afin de simplifier les
-choses ::
-
- cubicweb-ctl create -c all-in-one moncube moninstance
-
-Une série de questions vont être posées, la réponse par défaut est généralement
-suffisante. Vous pourrez de toute façon modifier la configuration par la suite
-en éditant les fichiers générés. Lorsqu'un login/mot de passe d'accès au sgbd
-vous est demandé, il est recommandé d'utiliser l'utilisateur créé lors de la
-:ref:`ConfigurationPostgres`.
-
-Il est important de distinguer ici l'utilisateur utilisé pour accéder au sgbd,
-et l'utilisateur utilisé pour s'authentifier dans l'application cubicweb. Lorsque
-l'application cubicweb démarre, elle utilise le login/mot de passe sgdb pour
-récupérer le schéma et gérer les transactions bas-niveau. En revanche, lorsque
-`cubicweb-ctl create` vous demande un login/mot de passe `manager` pour cubicweb, il
-s'agit d'un utilisateur qui sera créé dans l'application `cubicweb` pour pouvoir
-s'y connecter dans un premier temps et l'administrer. Il sera par la suite possible
-de créer des utilisateurs différents pour l'application.
-
-A l'issue de cette commande, la définition de votre instance se trouve dans
-*~/etc/cubicweb.d/moninstance/*. Pour la lancer, il suffit de taper ::
-
- cubicweb-ctl start -D moninstance
-
-L'option `-D` indique le *debug mode* : l'instance ne passe pas en mode serveur
-et ne se déconnecte pas du terminal, ce qui simplifie le dépannage en cas de non
-démarrage de l'instance. Vous pouvez ensuite allez voir ce que ça donne en
-pointant votre navigateur sur l'url `http://localhost:8080` (le n° de port
-dépend de votre configuration). Pour vous authentifier vous pouvez utiliser le
-login/mot de passe administrateur que vous avez spécifié lors de la création de
-l'instance.
-
-Pour arrêter l'instance, un Ctrl-C dans la fenêtre où vous l'avez lancé
-suffit. Si l'option `-D` a été omise, il faut taper ::
-
- cubicweb-ctl stop moninstance
-
-Voilà, tout est en place pour démarrer le développement du modèle...
-
-
-Utilisation de cubicweb-liveserver
-----------------------------------
-
-Afin de tester rapidement un nouveau cube, on peut également
-utiliser le script `cubicweb-liveserver` qui permet de créer une
-application en mémoire (utilisant une base de données SQLite par
-défaut) et la rendre accessible via un serveur web::
-
- cubicweb-ctl live-server moncomposant
-
-ou bien, pour utiliser une base de données existante (SQLite ou postgres)::
-
- cubicweb-ctl live-server -s monfichier_sources moncomposant
-
--- a/doc/book/fr/03-03-cubicweb-ctl.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubicweb-ctl:
-
-L'outil `cubicweb-ctl`
-======================
-`cubicweb-ctl` est le couteau suisse pour la gestion d'instances CubicWeb.
-La syntaxe générale est ::
-
- cubicweb-ctl <commande> [options commande] <arguments commandes>
-
-Pour voir les commandes disponibles ::
-
- cubicweb-ctl
- cubicweb-ctl --help
-
-A noter que les commandes disponibles varient en fonction des parties d'CubicWeb
-qui sont installées.
-
-Pour voir l'aide pour une commande spécifiques ::
-
- cubicweb-ctl <commande> --help
-
-Commandes pour la création d'un cube
-------------------------------------
-* ``newcube``, crée un nouveau cube sur le système de fichiers
- à partir du nom passé en paramètre. Cette commande crée le cube à partir
- d'une squelette d'application, incluant également les fichiers pour le
- packaging debian)
-
-Commandes pour la création d'une instance
------------------------------------------
-* ``create``, crée les fichiers de configuration d'une instance
-* ``db-create``, crée la base de données système d'une instance (tables et
- extensions uniquement)
-* ``db-init``, initialise la base de données système d'une instance (schéma,
- groupes, utilisateurs, workflows...)
-
-Par défaut ces trois commandes sont enchainées.
-
-Commande pour la création d'une instance pour Google App Engine
----------------------------------------------------------------
-* ``newgapp``, crée les fichiers de configuration d'une instance
-
-Cette commande doit être suivie de l'exécution de commandes
-permettant l'initialisation de la base de données spécifique à
-Google App Engine, appellée ``datastore``.
-
-Pour plus de détails veuillez vous référer à `LAX <>`_
-
-
-Commandes pour le lancement des instances
------------------------------------------
-* ``start``, démarre une, plusieurs, ou toutes les instances
-* ``stop``, arrêt une, plusieurs, ou toutes les instances
-* ``restart``, redémarre une, plusieurs, ou toutes les instances
-* ``status``, donne l'état des instances
-
-Commandes pour la maintenance des instances
--------------------------------------------
-* ``upgrade``, lance la migration d'instance(s) existante(s) lorsqu'une nouvelle
- version d'CubicWeb ou du composant est installée
-* ``shell``, ouvre un shell de migration pour la maintenance manuelle d'une instance
-* ``db-dump``, crée un dump de la base de données système
-* ``db-restore``, restore un dump de la base de données système
-* ``db-check``, vérifie l'intégrité des données d'une instance. Si la correction
- automatique est activée, il est conseillé de faire un dump avant cette
- opération
-* ``schema-sync``, , synchronise le schéma persistent d'une instance avec le schéma
- de l'application. Il est conseillé de faire un dump avant cette opération
-
-Commandes pour la maintenance des catalogues i18n
--------------------------------------------------
-* ``i18ncubicweb``, regénère les catalogues de messages de la librairie CubicWeb
-* ``i18ncube``, regénère les catalogues de messages d'un composant
-* ``i18ninstance``, recompile les catalogues de messages d'une instance. Cela est
- effectué automatiquement lors d'une upgrade
-
-Cf :ref:`Internationalisation`.
-
-Autres commandes
-----------------
-* ``list``, donne la liste des configurations, des composants et des instances
- disponibles
-* ``delete``, supprime une instance (fichiers de configuration et base de données)
-
-
-
-Exemples
---------
-
-Creation d'une instance a partir de cube existant
-`````````````````````````````````````````````````
-
-Afin de creer une instance a partir d'un cube existant, executez la commande
-suivant ::
-
- cubicweb-ctl create <nom_cube> <nom_instance>
-
-Cette commande va creer les fichiers de configuration d'une instance dans
-``~/etc/cubicweb.d/<nom_instance>``.
-L'outil ``cubicweb-ctl`` va vous autoriser a executer au sein de ``create``
-les commandes ``db-create`` et ``db-init`` afin de completer la creation de
-votre instance en une seule commande.
-
-Si vous decidez de ne pas le faire lorsque ``cubicweb-ctl create`` vous le
-propose, alors n'oubliez pas de lancer ces commandes (``cubicweb-ctl db-create``,
-``cubicweb-ctl db-init`` ) par la suite, sinon
-votre installation ne sera pas complete.
-
-
-Creation d'une instance a partir d'une nouveau cube
-```````````````````````````````````````````````````
-
-Creez avant tout votre nouveau cube ::
-
- cubicweb-ctl newcube <nom_cube>
-
-Cette commande va creer un nouveau cube dans ``/path/to/forest/cubicweb/cubes/<nom_cube>``.
-
-
--- a/doc/book/fr/03-04-mercurial.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Présentation de Mercurial
--------------------------
-
-Introduction
-````````````
-Mercurial_ gère un ensemble distribué d'entrepôts contenant des arbres de
-révisions (chaque révision indique les changements à effectuer pour obtenir la
-version suivante, et ainsi de suite). Localement, on dispose d'un entrepôt
-contenant un arbre de révisions, et d'un répertoire de travail. Il est possible
-de mettre dans son répertoire de travail, une des versions issue de son entrepôt
-local, de la modifier puis de la verser dans son entrepôt. Il est également
-possible de récuprer dans son entrepôt local des révisions venant d'un autre
-entrepôt, ou d'exporter ses propres révisions depuis son entrepôt local vers un
-autre entrepôt.
-
-A noter que contrairement à CVS/Subversion, on crée généralement un entrepôt par
-projet à gérer.
-
-Lors d'un développement collaboratif, on crée généralement un entrepôt central
-accessible à tout les développeurs du projet. Ces entrepôts centraux servent de
-référence. Selon ses besoins, chacun peut ensuite disposer d'un entrepôt local,
-qu'il faudra penser à synchroniser avec l'entrepôt central de temps à autre.
-
-
-Principales commandes
-`````````````````````
-* Créer un entrepôt local ::
-
- hg clone ssh://orion//home/src/prive/rep
-
-* Voir le contenu de l'entrepôt local (outil graphique en Tk) ::
-
- hg view
-
-* Ajouter un sous-répertoire ou un fichier dans le répertoire courant ::
-
- hg add rep
-
-* Placer dans son répertoire de travail une révision spécifique (ou la dernière
- revision) issue de l'entrepôt local ::
-
- hg update [identifiant-revision]
- hg up [identifiant-revision]
-
-* Récupérer dans son entrepôt local, l'arbre de révisions contenu dans un
- entrepôt distant (cette opération ne modifie pas le répertoire local) ::
-
- hg pull ssh://orion//home/src/prive/rep
- hg pull -u ssh://orion//home/src/prive/rep # équivalent à pull + update
-
-* Voir quelles sont les têtes de branches de l'entrepôt local si un `pull` a
- tiré une nouvelle branche ::
-
- hg heads
-
-* Verser le répertoire de travail dans l'entrepôt local (et créer une nouvelle
- révision) ::
-
- hg commit
- hg ci
-
-* Fusionner, avec la révision mère du répertoire local, une autre révision issue
- de l'entrepôt local (la nouvelle révision qui en résultera aura alors deux
- révisions mères) ::
-
- hg merge identifiant-revision
-
-* Exporter dans un entrepôt distant, l'arbre de révisions contenu dans son
- entrepôt local (cette opération ne modifie pas le répertoire local) ::
-
- hg push ssh://orion//home/src/prive/rep
-
-* Voir quelle sont les révisions locales non présentes dans un autre entrepôt ::
-
- hg outgoing ssh://orion//home/src/prive/rep
-
-* Voir quelle sont les révisions d'un autre entrepôt non présentes localement ::
-
- hg incoming ssh://orion//home/src/prive/rep
-
-* Voir quelle est la révision issue de l'entrepôt local qui a été sortie dans le
- répertoire de travail et modifiée ::
-
- hg parent
-
-* Voir les différences entre le répertoire de travail et la révision mère de
- l'entrepôt local, éventuellement permettant de les verser dans l'entrepôt
- local ::
-
- hg diff
- hg commit-tool
- hg ct
-
-
-Bonnes pratiques
-````````````````
-* penser à faire un `hg pull -u` régulièrement et particulièrement avant de
- faire un `hg commit`
-
-* penser à faire un `hg push` lorsque votre entrepôt contient une version
- relativement stable de vos modifications
-
-* si un `hg pull -u` a créé une nouvelle tête de branche :
-
- 1. identifier l'identifiant de celle-ci avec `hg head`
- 2. fusionner avec `hg merge`
- 3. `hg ci`
- 4. `hg push`
-
-.. _Mercurial: http://www.selenic.com/mercurial/
--- a/doc/book/fr/03-XX-external_resources.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Les ressources externes
-=======================
-
-Les ressources externes à une application regroupent l'ensemble des fichiers qui seront chargés dans l'entête des pages XHTML générées.
-Elles sont donc constituées principalement des feuilles de styles, des scripts javascripts et de certaines ressources graphiques comme l'icône favicon par exemple.
-
-
-Liste des feuilles de styles utilisées par défaut
--------------------------------------------------
-
-Les fichiers par défaut se trouve dans les sources du framework. En voici le tableau récapitulatif:
-
-+--------------------------------------+----------------------------------------------------+-----------------------------------+
-| Fichiers | Utilisation | Vues ou widget concernés |
-+======================================+====================================================+===================================+
-| web/data/cubicweb.acl.css | formulaires pour le contrôle aux accès | editgroups, security |
-| web/data/cubicweb.calendar.css | calendriers | onemonthcal, oneweekcal |
-| web/data/cubicweb.calendar_popup.css | popup calendriers | DateWidget |
-| web/data/cubicweb.css | gabarit principal de l'application | |
-| web/data/cubicweb.facets.css | surcharge du `MIT Simile Exhibit Web Widgets`_ | filter_box |
-| web/data/cubicweb.form.css | formulaires | creation, inline-creation, copya, |
-| | | inline-edition, edition, muledit |
-| web/data/cubicweb.html_tree.css | style pour les widgets d'arborescence | |
-| web/data/cubicweb.ie.css | dédié aux comportements de Internet Explorer | |
-| web/data/cubicweb.iprogress.css | style pour les widgets d'avancement | |
-| web/data/cubicweb.login.css | page et popup d'authentification | logform |
-| web/data/cubicweb.mailform.css | style utilisé dans les formulaires d'envoi de mail | |
-| web/data/cubicweb.preferences.css | style pour la page des préférences utilisateurs | systemepropertiesform |
-| web/data/cubicweb.print.css | style dédié à l'impression | |
-| web/data/cubicweb.schema.css | style dédié au schéma de l'application | |
-| web/data/cubicweb.suggest.css | surcharge utilisée pour les suggestions | |
-| web/data/cubicweb.tablesorter.css | surcharge pour le tri dans les tableau | |
-| web/data/cubicweb.tableview.css | surcharge pour le tri sélectif | |
-| web/data/cubicweb.timetable.css | style pour le widget Timetable | timetable |
-| web/data/jquery.autocomplete.css | surcharge pour le widget `jQuery autocompleter`_ | |
-| web/data/jquery.treeview.css | surcharge pour le widget `jQuery treeview`_ | |
-| web/data/pygments.css | style pour la coloration des blocs de code | |
-| web/data/timeline-bundle.css | surcharge du `MIT Simile Timeline Web Widgets`_ | TimelineWidget |
-| web/data/ui.tabs.css | surcharge pour le widget Tabs de `jQuery UI`_ | |
-+--------------------------------------+----------------------------------------------------+-----------------------------------+
-
-.. _MIT Simile Exhibit Web Widgets: http://code.google.com/p/simile-widgets/wiki/Exhibit
-.. _MIT Simile Timeline Web Widgets: http://code.google.com/p/simile-widgets/wiki/Timeline
-.. _jQuery autocompleter: http://www.dyve.net/jquery/?autocomplete
-.. _jQuery treeview: http://plugins.jquery.com/project/treeview
-.. _jQuery UI: http://docs.jquery.com/UI
-
-D'une manière générale, si vous réutiliser un nom de fichier existant, vous écrasez le contenu du fichier d'origine.
-
-
-Changer les feuilles de styles
-------------------------------
-
-Configuration statique
-~~~~~~~~~~~~~~~~~~~~~~
-Dans les sources de votre nouveau cube, vous devez éditer le fichier *data/external_resources* et définir la variable de configuration:
-
- # CSS stylesheets to include in HTML headers
- # uncomment the line below to use template specific stylesheet
- STYLESHEETS = DATADIR/cubicweb.css
-
-Les styles sont définis dans le fichier external_resources par 3 variables:
-
-- la variable STYLESHEETS est défine pour tous les types de médias
-- la variable STYLESHEETS_PRINT sont les styles applicables pour l'impression
-- la variable IE_STYLESHEETS s'appliquent uniquement aux versions d'Internet Explorer
-
-En copiant le fichier d'origine **cubicweb.css**, il est alors possible de modifier le gabarit de base du framework CubicWeb.
-Il est également possible de réutiliser le fichier d'origine.
-
-En créant un nouveau fichier **cubes.(le_nom_du_cube).css** dans le répertoire **data/** et en ajoutant une directive css @import, il est possible de réutiliser les styles définis par défaut:
-
- @import url("cubicweb.css");
-
-
-Chargement dynamique de feuilles de style dans vos vues
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Il est possible de charger des css spécifiques pour une vue par l'utilisation de la méthode add_css():
-
- self.req.add_css('mon_cube.css')
-
-
-Les ressources graphiques de base
----------------------------------
-
-Vous pouvez changer certaines ressources graphiques comme:
-
-- le logo du site:
-
- # path to the logo (relative to the application main script, seen as a
- # directory, hence .. when you are not using an absolute path)
- LOGO = DATADIR/logo.png
-
-- la 'favicon' du site:
-
- FAVICON = DATADIR/favicon.ico
-
-- le logo des flux RSS:
-
- RSS_LOGO = DATADIR/rss.png
-
-- l'icône permettant l'appel au widget 'calendrier':
-
- CALENDAR_ICON = DATADIR/calendar.png
-
-- l'icône utilisée pour la validation d'une recherche:
-
- SEARCH_GO = DATADIR/go.png
-
-- l'icône d'aide en ligne:
-
- HELP = DATADIR/help.png
-
-
-Ajouter vos scripts javascripts
--------------------------------
-
-Configuration statique
-~~~~~~~~~~~~~~~~~~~~~~
-Vous devez surcharger la variable JAVASCRIPTS dans le fichier *data/external_resources* de votre cube.
-
-Chargement dynamique de script javascript dans vos vues
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Il est possible de charger vos scripts par la méthode add_js():
-
- self.req.add_js('mon_script.js')
-
-
-Problèmes connus
-----------------
-
-Il est important de noter que la méthode de chargement dynamique ne marche pas avec les widgets Ajax. Vos fichiers devront déjà être au préalable avoir été chargés.
--- a/doc/book/fr/03-setup.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _MiseEnPlaceEnv:
-
-Installation et mise en place d'un environnement `CubicWeb`
-===========================================================
-
-.. toctree::
- :maxdepth: 1
-
- 03-01-installation.fr.txt
- 03-02-create-instance.fr.txt
- 03-03-cubicweb-ctl.fr.txt
- 03-04-mercurial.fr.txt
-
--- a/doc/book/fr/04-01-schema-stdlib.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Schémas prédéfinies dans la librairie
--------------------------------------
-
-La librairie définit un certain nombre de schémas d'entités nécessaires
-au système ou bien couramment utilisées dans les application `cubicweb`.
-Vous pouvez bien entendu étendre ces schémas au besoin.
-
-
-Schémas "systèmes"
-``````````````````
-
-* `CWUser`, utilisateurs du système
-* `CWGroup`, groupes d'utilisateurs
-* `CWEType`, types d'entité
-* `CWRType`, types de relation
-
-* `State`, état d'un workflow
-* `Transition`, transition d'un workflow
-* `TrInfo`, enregistrement d'un passage de transition pour une entité
-
-* `EmailAddress`, adresse électronique, utilisé par le système de notification
- pour les utilisateurs et par d'autres schéma optionnels
-
-* `CWProperty`, utilisé pour configurer l'application
-* `CWPermission`, utilisé pour configurer la sécurité de l'application
-
-* `Card`, fiche documentaire générique
-* `Bookmark`, un type d'entité utilisé pour permetter à un utilisateur de
- personnaliser ses liens de navigation dans l'application.
-
-
-Composants de la librairie
-``````````````````````````
-Une application est construite sur la base de plusieurs composants de base.
-Parmi les composants de base disponible, on trouve par exemple :
-
-* `ecomment`, fournit le type d'entité `Comment` permettant de commenter les
- entités du site
-
-* `emailinglist`, fournit le type d'entité `Mailinglist` regroupant des
- informations sur une liste de discussion
-
-* `efile`, fournit les types d'entités `File` et `Image` utilisés pour
- représenter des fichiers (texte ou binaire) avec quelques données
- supplémentaires comme le type MIME ou l'encodage le cas échéant ().
-
-* `elink`, fournit le type d'entité lien internet (`Link`)
-
-* `eblog`, fournit le type d'entité weblog (`Blog`)
-
-* `eperson`, fournit le type d'entité personne physique (`Person`)
-
-* `eaddressbook`, fournit les types d'entités utilisés pour représenter des n°
- de téléphone (`PhoneNumber`) et des adresses postales (`PostalAddress`)
-
-* `eclasstags`, système de classfication à base d'étiquettes (`Tag`)
-
-* `eclassfolders`, système de classification à base de dossiers hiérarchiques
- destinés à créer des rubriques de navigation (`Folder`)
-
-* `eemail`, gestion d'archives de courriers électroniques (`Email`, `Emailpart`,
- `Emailthread`)
-
-* `ebasket`, gestion de paniers (`Basket`) permettant de regrouper des entités
-
-Pour déclarer l'utilisation d'un composant, une fois celui-ci installé, ajoutez
-le nom du composant à la variable `__use__` du fichier `__pkginfo__.py` de
-votre propre composant.
--- a/doc/book/fr/04-02-schema-definition.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,361 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Définition d'un type d'entité
------------------------------
-
-Un type d'entité est définit par une classe python héritant de `EntityType`. Le
-nom de la classe correspond au nom du type. Ensuite le corps de la classe
-contient la description des attributs et des relations pour ce type d'entité,
-par exemple ::
-
- class Personne(EntityType):
- """une personne avec les propriétés et relations nécessaires à mon
- application"""
-
- nom = String(required=True, fulltextindexed=True)
- prenom = String(required=True, fulltextindexed=True)
- civilite = String(vocabulary=('M', 'Mme', 'Mlle'))
- date_naiss = Date()
- travaille_pour = SubjectRelation('Company', cardinality='?*')
-
-* le nom de l'attribut python correspond au nom de l'attribut ou de la relation
- dans cubicweb.
-
-* tout les types de bases sont disponibles nativement : `String`, `Int`, `Float`,
- `Boolean`, `Date`, `Datetime`, `Time`, `Byte`.
-
-* Chaque type d'entité a au moins les méta-relations suivantes :
-
- - `eid` (`Int`)
-
- - `creation_date` (`Datetime`)
-
- - `modification_date` (`Datetime`)
-
- - `created_by` (`CWUser`) (quel utilisateur a créé l'entité)
-
- - `owned_by` (`CWUser`) (à qui appartient l'entité, par défaut le
- créateur mais pas forcément et il peut exister plusieurs propriétaires)
-
- - `is` (`CWEType`)
-
-
-* il est également possible de définir des relations dont le type d'entité est
- l'objet en utilisant `ObjectRelation` plutôt que `SubjectRelation`
-
-* le premier argument de `SubjectRelation` et `ObjectRelation` donne
- respectivement le type d'entité objet /sujet de la relation. Cela
- peut être :
-
- * une chaine de caractères correspondant à un type d'entité
-
- * un tuple de chaines de caractères correspondant à plusieurs types d'entité
-
- * les chaînes de caractères spéciales suivantes :
-
- - "**" : tout les types d'entité
- - "*" : tout les types d'entité non méta
- - "@" : tout les types d'entité méta mais non "système" (i.e. servant à la
- description du schema en base)
-
-* il est possible d'utiliser l'attribut possible `meta` pour marquer un type
- d'entité comme étant "méta" (i.e. servant à décrire / classifier d'autre
- entités)
-
-* propriétés optionnelles des attributs et relations :
-
- - `description` : chaine de caractères décrivant un attribut ou une
- relation. Par défaut cette chaine sera utilisée dans le formulaire de saisie
- de l'entité, elle est donc destinée à aider l'utilisateur final et doit être
- marquée par la fonction `_` pour être correctement internationalisée.
-
- - `constraints` : liste de contraintes devant être respecté par la relation
- (c.f. `Contraintes`_)
-
- - `cardinality` : chaine de 2 caractères spécifiant la cardinalité de la
- relation. Le premier caractère donne la cardinalité de la relation sur le
- sujet, le 2eme sur l'objet. Quand une relation possède plusieurs sujets ou
- objets possibles, la cardinalité s'applique sur l'ensemble et non un à un (et
- doit donc à priori être cohérente...). Les valeurs possibles sont inspirées
- des expressions régulières :
-
- * `1`: 1..1
- * `?`: 0..1
- * `+`: 1..n
- * `*`: 0..n
-
- - `meta` : booléen indiquant que la relation est une méta relation (faux par
- défaut)
-
-* propriétés optionnelles des attributs :
-
- - `required` : booléen indiquant si l'attribut est obligatoire (faux par
- défaut)
-
- - `unique` : booléen indiquant si la valeur de l'attribut doit être unique
- parmi toutes les entités de ce type (faux par défaut)
-
- - `indexed` : booléen indiquant si un index doit être créé dans la base de
- données sur cette attribut (faux par défaut). C'est utile uniquement si vous
- savez que vous allez faire de nombreuses recherche sur la valeur de cet
- attribut.
-
- - `default` : valeur par défaut de l'attribut. A noter que dans le cas des
- types date, les chaines de caractères correspondant aux mots-clés RQL
- `TODAY` et `NOW` sont utilisables.
-
- - `vocabulary` : spécifie statiquement les valeurs possibles d'un attribut
-
-* propriétés optionnelles des attributs de type `String` :
-
- - `fulltextindexed` : booléen indiquant si l'attribut participe à l'index plein
- texte (faux par défaut) (*valable également sur le type `Byte`*)
-
- - `internationalizable` : booléen indiquant si la valeur de cet attribut est
- internationalisable (faux par défaut)
-
- - `maxsize` : entier donnant la taille maximum de la chaine (pas de limite par
- défaut)
-
-* propriétés optionnelles des relations :
-
- - `composite` : chaîne indiquant que le sujet (composite == 'subject') est
- composé de ou des objets de la relation. Pour le cas opposé (l'objet est
- composé de ou des sujets de la relation, il suffit de mettre 'object' comme
- valeur. La composition implique que quand la relation est supprimé (et donc
- aussi quand le composite est supprimé), le ou les composés le sont
- également.
-
-Contraintes
-```````````
-Par défaut les types de contraintes suivant sont disponibles :
-
-* `SizeConstraint` : permet de spécifier une taille minimale et/ou maximale sur
- les chaines de caractères (cas générique de `maxsize`)
-
-* `BoundConstraint` : permet de spécifier une valeur minimale et/ou maximale sur
- les types numériques
-
-* `UniqueConstraint` : identique à "unique=True"
-
-* `StaticVocabularyConstraint` : identique à "vocabulary=(...)"
-
-* `RQLConstraint` : permet de spécifier une requête RQL devant être satisfaite
- par le sujet et/ou l'objet de la relation. Dans cette requête les variables `S`
- et `O` sont préféfinies respectivement comme l'entité sujet et objet de la
- relation
-
-* `RQLVocabularyConstraint` : similaire à la précédente, mais exprimant une
- contrainte "faible", i.e. servant uniquement à limiter les valeurs apparaissant
- dans la liste déroulantes du formulaire d'édition, mais n'empêchant pas une
- autre entité d'être séléctionnée
-
-
-Définition d'un type de relation
---------------------------------
-
-Un type de relation est définit par une classe python héritant de `RelationType`. Le
-nom de la classe correspond au nom du type. Ensuite le corps de la classe
-contient la description des propriétés de ce type de relation, ainsi
-qu'éventuellement une chaine pour le sujet et une autre pour l'objet permettant
-de créer des définitions de relations associées (auquel cas il est possibles de
-donner sur la classe les propriétés de définition de relation explicitées
-ci-dessus), par exemple ::
-
- class verrouille_par(RelationType):
- """relation sur toutes les entités applicatives indiquant que celles-ci sont vérouillées
- inlined = True
- cardinality = '?*'
- subject = '*'
- object = 'CWUser'
-
-En plus des permissions, les propriétés propres aux types de relation (et donc
-partagés par toutes les définitions de relation de ce type) sont :
-
-* `inlined` : booléen contrôlant l'optimisation physique consistant à stocker la
- relation dans la table de l'entité sujet au lieu de créer une table spécifique
- à la relation. Cela se limite donc aux relations dont la cardinalité
- sujet->relation->objet vaut 0..1 ('?') ou 1..1 ('1')
-
-* `symmetric` : booléen indiquant que la relation est symétrique. i.e.
- `X relation Y` implique `Y relation X`
-
-Dans le cas de définitions de relations simultanée, `sujet` et `object` peuvent
-tout deux valoir la même chose que décrite pour le 1er argument de
-`SubjectRelation` et `ObjectRelation`.
-
-A partir du moment où une relation n'est ni mise en ligne, ni symétrique, et
-ne nécessite pas de permissions particulières, sa définition (en utilisant
-`SubjectRelation` ou `ObjectRelation`) est suffisante.
-
-
-Définition des permissions
---------------------------
-
-La définition des permissions se fait à l'aide de l'attribut `permissions` des
-types d'entité ou de relation. Celui-ci est un dictionnaire dont les clés sont
-les types d'accès (action), et les valeurs les groupes ou expressions autorisées.
-
-Pour un type d'entité, les actions possibles sont `read`, `add`, `update` et
-`delete`.
-
-Pour un type de relation, les actions possibles sont `read`, `add`, et `delete`.
-
-Pour chaque type d'accès, un tuple indique le nom des groupes autorisés et/ou
-une ou plusieurs expressions RQL devant être vérifiées pour obtenir
-l'accès. L'accès est donné à partir du moment où l'utilisateur fait parti d'un
-des groupes requis ou dès qu'une expression RQL est vérifiée.
-
-Les groupes standards sont :
-
-* `guests`
-
-* `users`
-
-* `managers`
-
-* `owners` : groupe virtuel correspondant au propriétaire d'une entité. Celui-ci
- ne peut être utilisé que pour les actions `update` et `delete` d'un type
- d'entité.
-
-Il est également possible d'utiliser des groupes spécifiques devant être pour
-cela créés dans le precreate de l'application (`migration/precreate.py`).
-
-Utilisation d'expression RQL sur les droits en écriture
-```````````````````````````````````````````````````````
-Il est possible de définir des expressions RQL donnant des droits de
-modification (`add`, `delete`, `update`) sur les types d'entité et de relation.
-
-Expression RQL pour les permissions sur un type d'entité :
-
-* il faut utiliser la classe `ERQLExpression`
-
-* l'expression utilisée correspond à la clause WHERE d'une requête RQL
-
-* dans cette expression, les variables X et U sont des références prédéfinies
- respectivement sur l'entité courante (sur laquelle l'action est vérifiée) et
- sur l'utilisateur ayant effectué la requête
-
-* il est possible d'utiliser dans cette expression les relations spéciales
- "has_<ACTION>_permission" dont le sujet est l'utilisateur et l'objet une
- variable quelquonque, signifiant ainsi que l'utilisateur doit avoir la
- permission d'effectuer l'action <ACTION> sur la ou les entités liées cette
- variable
-
-Pour les expressions RQL sur un type de relation, les principes sont les mêmes
-avec les différences suivantes :
-
-* il faut utiliser la classe `RRQLExpression` dans le cas d'une relation non
- finale
-
-* dans cette expression, les variables S, O et U sont des références
- prédéfinies respectivement sur le sujet et l'objet de la relation
- courante (sur laquelle l'action est vérifiée) et sur l'utilisateur
- ayant effectué la requête
-
-* On peut aussi définir des droits sur les attributs d'une entité (relation non
- finale), sachant les points suivants :
-
- - pour définir des expressions rql, il faut utiliser la classe `ERQLExpression`
- dans laquelle X représentera l'entité auquel appartient l'attribut
-
- - les permissions 'add' et 'delete' sont équivalentes. En pratique seul
- 'add'/'read' son pris en considération
-
-
-En plus de cela, le type d'entité `CWPermission` de la librairie standard permet
-de construire des modèles de sécurités très complexes et dynamiques. Le schéma
-de ce type d'entité est le suivant : ::
-
-
- class CWPermission(MetaEntityType):
- """entity type that may be used to construct some advanced security configuration
- """
- name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
- require_group = SubjectRelation('CWGroup', cardinality='+*',
- description=_('groups to which the permission is granted'))
- require_state = SubjectRelation('State',
- description=_("entity'state in which the permission is applyable"))
- # can be used on any entity
- require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
- description=_("link a permission to the entity. This "
- "permission should be used in the security "
- "definition of the entity's type to be useful."))
-
-
-Exemple de configuration extrait de *jpl* ::
-
- ...
-
- class Version(EntityType):
- """a version is defining the content of a particular project's release"""
-
- permissions = {'read': ('managers', 'users', 'guests',),
- 'update': ('managers', 'logilab', 'owners',),
- 'delete': ('managers', ),
- 'add': ('managers', 'logilab',
- ERQLExpression('X version_of PROJ, U in_group G,'
- 'PROJ require_permission P, P name "add_version",'
- 'P require_group G'),)}
-
- ...
-
- class version_of(RelationType):
- """link a version to its project. A version is necessarily linked to one and only one project.
- """
- permissions = {'read': ('managers', 'users', 'guests',),
- 'delete': ('managers', ),
- 'add': ('managers', 'logilab',
- RRQLExpression('O require_permission P, P name "add_version",'
- 'U in_group G, P require_group G'),)
- }
- inlined = True
-
-Cette configuration suppose indique qu'une entité `CWPermission` de nom
-"add_version" peut-être associée à un projet et donner le droit de créer des
-versions sur ce projet à des groupes spécifiques. Il est important de noter les
-points suivants :
-
-* dans ce cas il faut protéger à la fois le type d'entité "Version" et la
- relation liant une version à un projet ("version_of")
-
-* du fait de la généricité du type d'entité `CWPermission`, il faut effectuer
- l'unification avec les groupes et / ou les états le cas échéant dans
- l'expression ("U in_group G, P require_group G" dans l'exemple ci-dessus)
-
-
-Utilisation d'expression RQL sur les droits en lecture
-``````````````````````````````````````````````````````
-Les principes sont les mêmes mais avec les restrictions suivantes :
-
-* on ne peut de `RRQLExpression` sur les types de relation en lecture
-
-* les relations spéciales "has_<ACTION>_permission" ne sont pas utilisables
-
-
-Note sur l'utilisation d'expression RQL sur la permission 'add'
-```````````````````````````````````````````````````````````````
-L'utilisation d'expression RQL sur l'ajout d'entité ou de relation pose
-potentiellement un problème pour l'interface utilisateur car si l'expression
-utilise l'entité ou la relation à créer, on est pas capable de vérifier les
-droits avant d'avoir effectué l'ajout (noter que cela n'est pas un problème coté
-serveur rql car la vérification des droits est effectuée après l'ajout
-effectif). Dans ce cas les méthodes de vérification des droits (check_perm,
-has_perm) peuvent inidquer qu'un utilisateur n'a pas le droit d'ajout alors
-qu'il pourrait effectivement l'obtenir. Pour palier à ce soucis il est en général
-nécessaire dans tel cas d'utiliser une action reflétant les droits du schéma
-mais permettant de faire la vérification correctement afin qu'elle apparaisse
-bien le cas échéant.
-
-Mise à jour du schema
-`````````````````````
-
-Il faut ensuite lancer son cubicweb en mode shell ::
-
- cubicweb-ctl shell moninstance
-
-Et taper ::
-
- add_entity_type('Personne')
-
-Et on relance l'application!
--- a/doc/book/fr/04-define-schema.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Définition du modèle de données (*schéma*)
-==========================================
-
-Le schéma est l'élément central d'une application d'CubicWeb, définissant le modèle
-de données manipulé. Il est généralement défini à partir de type d'entités
-existants dans la librairie et d'autres spécifiques, généralement décrites dans
-un ou plusieurs fichiers python dans le sous-répertoire `schema` du modèle.
-
-A ce niveau il est important de noter la différence entre type de relation et
-définition de relation : un type de relation est uniquement un nom de relation
-avec éventuellement quelques propriétés supplémentaires (voir plus bas), alors
-qu'une définition de relation est un triplet complet "<type d'entité sujet>
-<type de relation> <type d'entité objet>". Eventuellement un type de relation
-sera créé implicitement si aucun n'est associé à une définition de relation du
-schema.
-
-.. include:: 04-01-schema-stdlib.fr.txt
-.. include:: 04-02-schema-definition.fr.txt
-
--- a/doc/book/fr/05-01-views-stdlib.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Vues prédéfinies dans la librairie
-----------------------------------
-Un certain nombre de vues sont utilisées pour construire l'interface web, qui
-s'appliquent à une ou plusieurs entités. On les distingue par leur identifiant,
-et les principales sont :
-
-:primary:
- vue principale pour une entité, elle est appelée par défaut lorsqu'il n'y a
- qu'un seul élément correspondant à la recherche. Cette vue est censée
- afficher le maximum d'informations à propos de l'objet.
-:secondary:
- vue secondaire d'une entité. Par défaut, Elle affiche les deux premiers
- attributs de l'entité sous la forme d'un lien cliquable amenant sur la vue
- primaire.
-:oneline:
- similaire à la vue `secondary`, mais appelée dans des cas où l'on désire que
- la vue tient sur une ligne, ou de manière générale juste avoir une vue plus
- abbrégée. Par défaut, cette vue utilise le paramètre de configuration
- `MAX_LINE_CHAR` pour contrôler la taille du résultat.
-:text:
- similaire à la vue `oneline`, mais ne devant pas contenir de html.
-:incontext, outofcontext:
- similaire à la vue `secondary`, mais appelé si l'entité est considérée comme
- en dehors ou dans son contexte. Par défault renvoie respectivement le
- résultat de `textincontext` et `textoutofcontext` entouré par un lien
- permettant d'accéder à la vue primaire de l'entité
-:textincontext, textoutofcontext:
- similaire à la vue `text`, mais appelé si l'entité est considérée comme
- en dehors ou dans son contexte. Par défault renvoie respectivement le
- résultat des méthodes `.dc_title` et `.dc_long_title` de l'entité
-:list:
- crée une liste html (<ul>) et appelle la vue `listitem` pour chaque entité
-:listitem:
- redirige par défaut vers la vue `outofcontext`
-:rss:
- crée unvue RSS/XML et appelle la vue `rssitem` pour chaque entité
-:rssitem:
- crée unvue RSS/XML pour une entité à partir des résultats renvoyés par les
- méthodes dublin core de l'objet (`dc_*`)
-
-Vues de départ :
-
-:index:
- page d'acceuil
-:schema:
- affiche le schéma de l'application
-
-Vues particulières :
-
-:noresult:
- appelé si le result set est vide
-:finall:
- affiche la valeur de la cellule sans transformation (dans le cas d'une
- entité non finale, on voit son eid). Appelable sur n'importe quel result
- set.
-:table:
- crée une table html (<table>) et appelle la vue `cell` pour chaque cellule
- du résultat. Appelable sur n'importe quel result set.
-:cell:
- par défaut redirige sur la vue `final` si c'est une entité finale
- ou sur la vue `outofcontext` sinon
-:null:
- vue toujours appelable et ne retournant rien
--- a/doc/book/fr/05-define-views.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _DefinitionVues:
-
-Définition de vues
-==================
-
-Les classes de base des vues
-----------------------------
-
-La class `View` (`cubicweb.common.view`)
-````````````````````````````````````````
-Un vue écrit dans son flux de sortie via son attribut `w` (`UStreamIO`).
-
-L'interface de base des vues est la suivante :
-
-* `dispatch(**context)`, appelle ("rend") la vue en appellent `call` ou
- `cell_call` en fonction des arguments passé
-* `call(**kwargs)`, appelle la vue pour un result set complet ou nul
-* `cell_call(row, col, **kwargs)`, appelle la vue pour une cellule donnée d'un
- result set
-* `url()`, retourne l'url permettant d'obtenir cette vue avec le result set en
- cours
-* `view(__vid, rset, __fallback_vid=None, **kwargs)`, appelle la vue
- d'identificant `__vid` sur le result set donné. Il est possible de données un
- identificant de vue de "fallback" qui sera utilisé si la vue demandée n'est
- pas applicable au result set
-
-* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, pareil que `view` mais
- passe automatiquement le flux en argument
-
-* `html_headers()`, retourne une liste d'en-tête HTML à placer par le template
- principal
-
-* `page_title()`, retourne le titre à utiliser dans l'en tête HTML `title`
-
-* `creator(eid)`, retourne l'eid et le login du créateur de l'entité ayant
- l'eid passé en argument
-
-Autres classes de base :
-
-* `EntityView`, vue s'appliquant à aux lignes ou cellule contenant une entité
- (eg un eid)
-* `StartupView`, vue de départ n'ayant pas besoin de result set
-* `AnyRsetView`, vue s'appliquant à n'importe quelle result set
-
-Le mecanisme de selection de vues
----------------------------------
-
-Pour un identifiant de vue donne, plusieurs vues peuvent etre definies.
-`CubicWeb` utilise un selecteur qui permet de calculer un score et d'identifier
-la vue la plus appropriee a appliquer dans le contexte. La librairie du selecteur
-se trouve dans ``cubicweb.common.selector`` et une librairie des methodes utilisees
-pour calculer les scores est dans ``cubicweb.vregistry.vreq``.
-
-[FROM-LAX-BOOK]
-
-Tip: when modifying views, you do not need to restart the local
-server. Just save the file in your editor and reload the page in your
-browser to see the changes.
-
-With `LAX`, views are defined by Python classes. A view includes :
-
-- an identifier (all objects in `LAX` are entered in a registry
- and this identifier will be used as a key)
-
-- a filter to select the resulsets it can be applied to
-
-`LAX` provides a lot of standard views, for a complete list, you
-will have to read the code in directory ``ginco/web/views/`` (XXX
-improve doc).
-
-For example, the view named ``primary`` is the one used to display
-a single entity.
-
-If you want to change the way a ``BlogEntry`` is displayed, just
-override the view ``primary`` in ``BlogDemo/views.py`` ::
-
- 01. from ginco.web.views import baseviews
- 02.
- 03. class BlogEntryPrimaryView(baseviews.PrimaryView):
- 04.
- 05. accepts = ('BlogEntry',)
- 06.
- 07. def cell_call(self, row, col):
- 08. entity = self.rset.get_entity(row, col)
- 09. self.w(u'<h1>%s</h1>' % entity.title)
- 10. self.w(u'<p>published on %s in category %s</p>' % \
- 11. (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
- 12. self.w(u'<p>%s</p>' % entity.text)
-
-The above source code defines a new primary view (`line 03`) for
-``BlogEntry`` (`line 05`).
-
-Since views are applied to resultsets and resulsets can be tables of
-data, it is needed to recover the entity from its (row,col)
-coordinates (`line 08`). We will get to this in more detail later.
-
-The view has a ``self.w()`` method that is used to output data. Here `lines
-09-12` output HTML tags and values of the entity's attributes.
-
-When displaying same blog entry as before, you will notice that the
-page is now looking much nicer.
-
-.. image:: images/lax-book.09-new-view-blogentry.fr.png
- :alt: blog entries now look much nicer
-
-Let us now improve the primary view of a blog ::
-
- 01. class BlogPrimaryView(baseviews.PrimaryView):
- 02.
- 03. accepts = ('Blog',)
- 04.
- 05. def cell_call(self, row, col):
- 06. entity = self.rset.get_entity(row, col)
- 07. self.w(u'<h1>%s</h1>' % entity.title)
- 08. self.w(u'<p>%s</p>' % entity.description)
- 09. rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
- 10. self.wview('primary', rset)
-
-In the above source code, `lines 01-08` are similar to the previous
-view we defined.
-
-At `line 09`, a simple request in made to build a resultset with all
-the entities linked to the current ``Blog`` entity by the relationship
-``entry_of``. The part of the framework handling the request knows
-about the schema and infer that such entities have to be of the
-``BlogEntry`` kind and retrieves them.
-
-The request returns a selection of data called a resultset. At
-`line 10` the view 'primary' is applied to this resultset to output
-HTML.
-
-**This is to be compared to interfaces and protocols in object-oriented
-languages. Applying a given view to all the entities of a resultset only
-requires the availability, for each entity of this resultset, of a
-view with that name that can accepts the entity.**
-
-Assuming we added entries to the blog titled `MyLife`, displaying it
-now allows to read its description and all its entries.
-
-.. image:: images/lax-book.10-blog-with-two-entries.fr.png
- :alt: a blog and all its entries
-
-**Before we move forward, remember that the selection/view principle is
-at the core of `LAX`. Everywhere in the engine, data is requested
-using the RQL language, then HTML/XML/text/PNG is output by applying a
-view to the resultset returned by the query. That is where most of the
-flexibility comes from.**
-
-[WRITE ME]
-
-* implementing interfaces, calendar for blog entries
-* show that a calendar view can export data to ical
-
-We will implement the ginco.interfaces.ICalendarable interfaces on
-entities.BloEntry and apply the OneMonthCalendar and iCalendar views
-to resultsets like "Any E WHERE E is BlogEntry"
-
-* create view "blogentry table" with title, publish_date, category
-
-We will show that by default the view that displays
-"Any E,D,C WHERE E publish_date D, E category C" is the table view.
-Of course, the same can be obtained by calling
-self.wview('table',rset)
-
-* in view blog, select blogentries and apply view "blogentry table"
-* demo ajax by filtering blogentry table on category
-
-we did the same with 'primary', but with tables we can turn on filters
-and show that ajax comes for free.
-[FILLME]
-
-Les templates ou patron
------------------------
-
-Les patrons (ou *template*) sont des cas particulier de vue ne dépendant a
-priori pas d'un result set. La classe de base `Template` (`cubicweb.common.view`)
-est une classe dérivée de la classe `View`.
-
-Pour construire une page HTML, un *template principal* est utilisé. Généralement
-celui possédant l'identifiant 'main' est utilisé (ce n'est pas le cas lors
-d'erreur dans celui-ci ou pour le formulaire de login par exemple). Ce patron
-utilise d'autres patrons en plus des vues dépendants du contenu pour générer la
-page à renvoyer.
-
-C'est ce template qui est chargé :
-
-1. d'éxécuter la requête RQL des données à afficher le cas échéant
-2. éventuellement de déterminer la vue à utiliser pour l'afficher si non
- spécifiée
-3. de composer la page à retourner
-
-
-Le patron principal par défaut (`cubicweb.web.views.basetemplates.TheMainTemplate`)
------------------------------------------------------------------------------------
-
-Le template principal par défaut construit la page selon la décomposition
-suivante :
-
-.. image:: images/main_template_layout.png
-
-Le rectancle contenant le `view.dispatch()` représente l'emplacement où est
-inséré la vue de contenu à afficher. Les autres représentent des sous-templates
-appelé pour construire la page. Les implémentations par défaut de tout ces
-templates sont dans le module `cubicweb.web.views.basetemplates`. Vous pouvez
-évidemment surcharger l'un des sous-templates pour modifier l'aspect visuel
-d'une partie désirée de la page.
-
-On peut également contrôler certains comportements du template principal à
-l'aide des paramètres de formulaire suivante :
-
-* `__notemplate`, si présente (quelque soit la valeur associée), seule la vue de
- contenu est renvoyée
-* `__force_display`, si présente et contient une valeur non nulle, pas de
- navigation quelque soit le nombre d'entités à afficher
-* `__method`, si le result set à afficher ne contient qu'une entité et que ce
- paramètre est spécifié, celui-ci désigne une méthode à appeler sur l'entité
- en lui donnant en argument le dictionnaire des paramètres de formulaire, avant
- de reprendre le comportement classique (s'insère entre les étapes 1. et
- 2. décrites ci-dessus)
-
-
-.. include:: 05-01-views-stdlib.fr.txt
-
-
-Vues xml, binaires...
----------------------
-Pour les vues générants autre que du html (une image générée dynamiquement par
-exemple), et qui ne peuvent donc généralement pas être incluse dans la page
-HTML générée par le template principal (voir ci-dessus), il faut :
-
-* placer l'attribut `templatable` de la classe à `False`
-* indiquer via l'attribut `content_type` de la classe le type MIME généré par la
- vue 'application/octet-stream'
-
-Pour les vues générants un contenu binaire (une image générée dynamiquement par
-exemple), il faut également placer l'attribut `binary` de la classe à `True` (ce
-qui implique `templatable == False` afin que l'attribut `w` de la vue soit
-remplacé par un flux binaire plutôt que unicode.
-
-
-Quelques trucs (X)HTML à respecter
-----------------------------------
-Certains navigateurs (dont firefox) n'aime pas les `<div>` vides (par vide
-j'entend sans contenu dans la balise, il peut y avoir des attributs), faut
-toujours mettre `<div></div>` même s'il n'y a rien dedans, et non `<div/>`.
--- a/doc/book/fr/06-define-workflows.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Définition de workflow
-======================
-
-Avant-propos
-------------
-
-Un worflow décrit comment les entités vont être utilisés à travers différents états. Nous avons donc pour un workflow donné un ensemble d'états, un "graphe de transition" c'est-à-dire la liste des transitions possibles d'un état à un autre.
-
-Nous allons définir ici un simple workflow pour l'exemple du blog avec seulement deux états: `en attente` et `publié`. Il est nécessaire d'avoir préalablement créé une application simple *CubicWeb* en dix minutes (voir :ref:`BlogFiveMinutes`).
-
-Mise en place du workflow
--------------------------
-
-Nous allons créer un workflow pour contrôler la qualité des BlogEntry soumis à l'instance. Lorsque un BlogEntry est créé par un utilisateur, son état doit être `en attente`. Pour être visible par tous, il doit être ensuite mis à l'état `publié`. Pour le changement d'état d'`en attente` à `publié`, nous avons besoin d'une transition que nous appellerons `approuve_blogentry`.
-
-Un état BlogEntry ne doit pas pouvoir être modifiable par les utilisateurs. Nous allons donc créé un groupe de modération `moderateurs` et ce groupe aura les permissions idoines pour publier un BlogEntry.
-
-Il existe deux manières de créer un workflow: depuis l'interface utilisateur ou en le définissant dans le fichier ``migration/postcreate.py``.
-Ce script est exécuté à chaque lancement de la commande ``cubicweb-ctl db-init``.
-Nous encourageons vivement la création dans ``migration/postcreate.py`` que nous allons vous montrer ici. Lire `Sous le capot`_ pour en comprendre les raisons.
-
-L'état d'une entité est sauvegardé par l'attribut `in_state` qui peut être ajouté à votre schéma d'entité par deux façons:
-
-* héritage direct en utilisant la classe `cubicweb.schema.WorkflowableEntityType`
-* par délégation en utilisant `cubicweb.schema.make_worflowable` (utilisable comme un décorateur également)
-
-Pour notre exemple de BlogEntry, nous devons avoir:
-
-.. sourcecode:: python
-
- from cubicweb.schema import WorkflowableEntityType
-
- class BlogEntry(EntityType, WorkflowableEntityType):
- ...
-
-
-Création des états, transitions et les permissions de groupe
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Le script ``postcreate.py`` est exécuté dans un environnement spécial où plusieurs primitives *CubicWeb* peuvent être utilsées.
-Elles sont toutes définies dans ``class ServerMigrationHelper``.
-Nous allons maintenant voir dans le prochain example celles utilisées pour créer un workflow.
-
-Pour définir notre workflow pour BlogDemo, veuillez ajouter les lignes suivantes au script ``migration/postcreate.py``:
-
-.. sourcecode:: python
-
- _ = unicode
-
- moderators = add_entity('CWGroup', name=u"modérateurs")
-
-Cela va ajouter le groupe utilisateur `moderators`.
-
-.. sourcecode:: python
-
- wf = add_workflow(u'une description succincte de votre workflow', 'BlogEntry')
-
-Ceci va premièrement instancier un nouvel objet workflow avec une description sommaire mais pertinente et le type d'entité concerné (un tuple pour être utilisé pour des valeurs multiples).
-
-.. sourcecode:: python
-
- submitted = wf.add_state(_('submitted'), initial=True)
- published = wf.add_state(_('published'))
-
-``add_state`` attend comme premier argument le nom de l'état que vous voulez créer et un argument optionnel pour signifier si c'est l'état initial supposé pour ce type d'entité.
-
-.. sourcecode:: python
-
- wf.add_transition(_('approuve_blogentry'), (submitted,), published, ('moderators', 'managers'),)
-
-``add_transition`` attend:
-
- * comme premier argument le nom de la transition
- * ensuite la liste des états pour lesquels les transitions peuvent être tirées,
- * l'état attendu en fin de transition,
- * et les permissions
- (c'est-à-dire la liste des goupes utilisateurs qui peuvnet appliquer la transition; l'utilisateur devant appartenir à l'un des groupes listés pour être autoriser à exécuter l'action).
-
-.. sourcecode:: python
-
-
- checkpoint()
-
-.. note::
- Dans le script de création d'un workflow, penser à mettre `_()` autour des noms d'états et de transitions pour que ceux si soient pris en compte par les scripts de gestion des catalogues i18n.
-
-En complément de condition sur des groupes utilisateur dont l'utilisateur doit appartenir à l'in d'entre eux, vous pouvez utiliser une RQL condition.
-Dans ce cas, l'utilisateur peut seulement exécuter une action si les deux conditions sont satisfaites.
-
-Pour la condition RQL sur une transition, on peut y mettre les substitutions suivantes :
-
-* `%(eid)s`, eid de l'objet
-* `%(ueid)s`, eid de l'utilisateur qui fait la requête
-* `%(seid)s`, eid de l'état courant de l'objet
-
-.. image:: ../../images/03-transitions-view.en.png
-
-Vous pouvez remarqué que dans la boîte d'action d'un BlogEntry, l'état est maintenant listé ainsi que les possibles transitions définis pour l'état en cours dans le workflow. Les transitions ne sont seulement affichées pour les utilisateurs ayant les bonnes permissions.
-Dans notre exemple, la transition `approuve_blogentry` sera seulement affichée pour les utilisateurs appartenant au groupe `moderators` or `managers`.
-
-
-Sous le capot
-~~~~~~~~~~~~~
-
-Un workflow est une collection d'entités de type `State`` et ``Transition`` qui sont des types d'entités standards de *CubicWeb*.
-
-Par exemple, les lignes précédentes:
-
-.. sourcecode:: python
-
- submitted = wf.add_state(_('en attente'), initial=True)
- published = wf.add_state(_('publié'))
-
-vont créé deux entités de type ``State``, l'une avec le nom 'submitted' et l'autre avec le nom 'published'. Tandis que:
-
-.. sourcecode:: python
-
- wf.add_transition(_('approuve_blogentry'), (submitted,), published, ('moderators', 'managers'),)
-
-va créé une entité de type ``Transition`` avec le nom `approuve_blogentry` qui sera relié aux entités ``State`` créées précédemment.
-
-Dès lors, nous pouvons utiliser l'interface d'administration pour ces opérations. Mais ce n'est pas recommandé à cause de la complexité superflue et du fait que ces changements ne seront locaux qu'à cette instance.
-
-En effet, si vous créez les états et les transitions à travers l'interface utilisateur, la prochaine initialisation de la base de données vous oblige à recréer toutes les entités.
-L'interface utilisateur devrait être seulement connu par vous pour la visualisation des états et transitions, mais ce n'est pas celle appropriée pour définir vos workflows applicatifs.
-
-
--- a/doc/book/fr/07-01-define-entities.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Paramétrages et extensions spécifiques
---------------------------------------
-
-Valeurs par défaut dynamiques
-`````````````````````````````
-Il est possible de définir dans le schéma des valeurs par défaut *statiques*.
-Il est également possible de définir des valeurs par défaut *dynamiques* en
-définissant sur la classe d'entité une méthode `default_<nom attribut>` pour
-un attribut donnée.
-
-
-Contrôle des attributs chargés et du tri par défaut
-```````````````````````````````````````````````````
-* l'attribut de classe `fetch_attrs` permet de définir sur une classe d'entité
- la liste des noms des attributs ou relations devant être chargés
- automatiquement lors de la récupération d'entité(s) de ce type. Dans le cas
- des relations, on est limité aux relations *sujets de cardinalité `?` ou `1`*.
-
-* la méthode de classe `fetch_order(attr, var)` prend en argument un nom
- d'attribut (ou de relation) et un nom de variable et doit retourner une chaine
- à utiliser dans la close "ORDERBY" d'une requête RQL pour trier
- automatiquement les listes d'entités de ce type selon cet attribut, ou `None`
- si l'on ne veut pas de tri sur l'attribut passé en argument. Par défaut les
- entités sont triées selon leur date de création
-
-* la méthode de classe `fetch_unrelated_order(attr, var)` est similaire à la
- méthode `fetch_order` mais est utilisée essentiellement pour contrôler le tri
- des listes déroulantes permettant de créer des relations dans la vue d'édition
- d'une entité
-
-La fonction `fetch_config(fetchattrs, mainattr=None)` permet de simplifier la
-définition des attributs à précharger et du tri en retournant une liste des
-attributs à précharger (en considérant ceux de la classe `AnyEntity`
-automatiquement) et une fonction de tri sur l'attribut "principal" (le 2eme
-argument si spécifié ou sinon le premier attribut de la liste `fetchattrs`).
-Cette fonction est définie dans le package `ginco.entities`.
-
-Par exemple : ::
-
- class Transition(AnyEntity):
- """..."""
- id = 'Transition'
- fetch_attrs, fetch_order = fetch_config(['name'])
-
-Indique que pour le type d'entité "Transition" il faut précharger l'attribut
-"name" et trier par défaut selon cet attribut.
-
-
-Contrôle des formulaires d'édition
-``````````````````````````````````
-Il est possible de contrôler les attributs/relations dans la vue d'édition
-simple ou multiple à l'aide des *rtags* suivants :
-
-* `primary`, indique qu'un attribut ou une relation doit être incorporé dans
- les formulaires d'édition simple et multiple. Dans le cas d'une relation,
- le formulaire d'édition de l'entité liée sera inclus dans le formulaire
-
-* `secondary`, indique qu'un attribut ou une relation doit être incorporé dans
- le formulaire d'édition simple uniquement. Dans le cas d'une relation,
- le formulaire d'édition de l'entité liée sera inclus dans le formulaire
-
-* `generic`, indique qu'une relation doit être incorporé dans le formulaire
- d'édition simple dans la boite générique d'ajout de relation
-
-* `generated`, indique qu'un attribut est caculé dynamiquement ou autre, et
- qu'il ne doit donc pas être présent dans les formulaires d'édition
-
-Au besoin il est possible de surcharger la méthode
-`relation_category(rtype, x='subject')` pour calculer dynamiquement la catégorie
-d'édition d'une relation.
-
-
-Contrôle de la boîte "add_related"
-``````````````````````````````````
-La boite `add related` est une boite automatique proposant de créer une entité
-qui sera automatiquement liée à l'entité de départ (le contexte dans lequel
-s'affiche la boite). Par défaut, les liens présents dans cette boite sont
-calculés en fonction des propriétés du schéma de l'entité visualisée, mais il
-est possible de les spécifier explicitement à l'aide des *rtags* suivants :
-
-* `link`, indique qu'une relation est généralement créée vers une entité
- existante et qu'il ne faut donc pas faire apparaitre de lien pour cette
- relation
-
-* `create`, indique qu'une relation est généralement créée vers de nouvelles
- entités et qu'il faut donc faire apparaitre un lien pour créer une nouvelle
- entité et la lier automatiquement
-
-Au besoin il est possible de surcharger la méthode
-`relation_mode(rtype, targettype, x='subject')` pour caculer dynamiquement la
-catégorie de création d'une relation.
-
-A noter également que si au moins une action dans la catégorie "addrelated" est
-trouvée pour le contexte courant, le fonctionnement automatique est désactivé
-en faveur du fonctionnement explicite (i.e. affichage des actions de la
-catégorie "addrelated" uniquement).
-
-Contrôle des formulaires de filtrage de table
-`````````````````````````````````````````````
-La vue "table" par défaut gère dynamiquement un formulaire de filtrage du
-contenu de celle-ci. L'algorithme est le suivant :
-
-1. on considère que la première colonne contient les entités à restreindre
-2. on recupère la première entité de la table (ligne 0) pour "représenter"
- toutes les autres
-3. pour toutes les autres variables définies dans la requête originale :
-
- 1. si la variable est liée à la variable principale par au moins une
- n'importe quelle relation
- 2. on appelle la méthode `filterform_vocabulary(rtype, x)` sur l'entité
- et si rien est retourné (ou plus exactement un tuple de valeur `None`,
- voir ci-dessous) on passe à la variable suivante, sinon un élément de
- formulaire de filtrage sera créé avec les valeurs de vocabulaire
- retournées
-
-4. il n'y a pas d'autres limitations sur le rql, il peut comporter des clauses
- de tris, de groupes... Des fonctions javascripts sont utilisées pour
- regénérer une requête à partir de la requête de départ et des valeurs
- séléctionnées dans les filtres de formulaire.
-
-
-La méthode `filterform_vocabulary(rtype, x, var, rqlst, args, cachekey)` prend
-en argument le nom d'une relation et la "cible", qui indique si l'entité sur
-laquelle la méthode est appellée est sujet ou objet de la relation. Elle doit
-retourner :
-
-* un 2-uple de None si elle ne sait pas gérer cette relation
-
-* un type et une liste contenant le vocabulaire
-
- * la liste doit contenir des couples (valeur, label)
- * le type indique si la valeur désigne un nombre entier (`type == 'int'`), une
- chaîne de caractères (`type == 'string'`) ou une entité non finale (`type
- == 'eid'`)
-
-Par exemple dans notre application de gestion de tickets, on veut pouvoir
-filtrés ceux-ci par :
-
-* type
-* priorité
-* état (in_state)
-* étiquette (tags)
-* version (done_in)
-
-On définit donc la méthode suivante : ::
-
-
- class Ticket(AnyEntity):
-
- ...
-
- def filterform_vocabulary(self, rtype, x, var, rqlst, args, cachekey):
- _ = self.req._
- if rtype == 'type':
- return 'string', [(x, _(x)) for x in ('bug', 'story')]
- if rtype == 'priority':
- return 'string', [(x, _(x)) for x in ('minor', 'normal', 'important')]
- if rtype == 'done_in':
- rql = insert_attr_select_relation(rqlst, var, rtype, 'num')
- return 'eid', self.req.execute(rql, args, cachekey)
- return super(Ticket, self).filterform_vocabulary(rtype, x, var, rqlst,
- args, cachekey)
-
-
-NOTE: Le support du filtrage sur les étiquettes et l'état est installé
-automatiquement, pas besoin de le gérer ici.
--- a/doc/book/fr/07-data-as-objects.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Manipulation des données stockées
-=================================
-
-Les classes `Entity` et `AnyEntity`
------------------------------------
-Pour fournir un comportement spécifique à un type d'entité, il suffit de définir
-une classe héritant de la class `ginco.entities.AnyEntity`. En général il faut
-définir ces classes dans un module du package `entities` d'une application pour
-qu'elle soit disponible à la fois coté serveur et coté client.
-
-La classe `AnyEntity` est une classe chargée dynamiquement héritant de la classe
-de base `Entity` (`ginco.common.entity`). On définit une sous-classe pour
-ajouter des méthodes ou spécialiser les comportements d'un type d'entité donné.
-
-Des descripteurs sont ajoutés à l'enregistrement pour initialiser la classe en
-fonction du schéma :
-
-* on peut accéder aux attributs définis dans le schéma via les attributs de même
- nom sur les instances (valeur typée)
-
-* on peut accéder aux relations définies dans le schéma via les attributs de même
- nom sur les instances (liste d'instances d'entité)
-
-Les méthodes définies sur la classe `AnyEntity` ou `Entity` sont les suivantes :
-
-* `has_eid()`, retourne vrai si l'entité à un eid affecté (i.e. pas en cours de
- création)
-
-* `check_perm(action)`, vérifie que l'utilisateur à le droit d'effectuer
- l'action demandée sur l'entité
-
-:Formattage et génération de la sortie:
-
- * `view(vid, **kwargs)`, applique la vue donnée à l'entité
-
- * `absolute_url(**kwargs)`, retourne une URL absolue permettant d'accéder à la
- vue primaire d'une entité
-
- * `rest_path()`, renvoie une l'URL REST relative permettant d'obtenir l'entité
-
- * `format(attr)`, retourne le format (type MIME) du champ passé en argument
-
- * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`,
- retourne une chaine permettant l'affichage dans un format donné de la valeur
- d'un attribut (la valeur est automatiquement récupérée au besoin)
-
- * `display_name(form='')`, retourne une chaîne pour afficher le type de
- l'entité, en spécifiant éventuellement la forme désirée ('plural' pour la
- forme plurielle)
-
-:Gestion de données:
-
- * `as_rset()`, transforme l'entité en un resultset équivalent simulant
- le résultat de la requête `Any X WHERE X eid _eid_`
-
- * `complete(skip_bytes=True)`, effectue une requête permettant de récupérer d'un
- coup toutes les valeurs d'attributs manquant sur l'entité
-
- * `get_value(name)`, récupere la valeur associée à l'attribut passé en argument
-
- * `related(rtype, x='subject', limit=None, entities=False)`, retourne une liste
- des entités liées à l'entité courant par la relation donnée en argument
-
- * `unrelated(rtype, targettype, x='subject', limit=None)`, retourne un result set
- des entités not liées à l'entité courante par la relation donnée en argument
- et satisfaisants les contraintes de celle-ci
-
- * `set_attributes(**kwargs)`, met à jour la liste des attributs avec
- les valeurs correspondantes passées sous forme d'arguments nommés
-
- * `copy_relations(ceid)`, copie les relations de l'entité ayant l'eid passé en
- argument sur l'entité courante
-
- * `last_modified(view)`, retourne la date à laquelle on doit considérer
- l'objet comme modifié (utiliser par la gestion de cache HTTP)
-
- * `delete()` permet de supprimer l'entité représentée
-
-:Meta-données standard (Dublin Core):
-
- * `dc_title()`, retourne une chaine unicode correspondant à la méta-donnée
- 'Title' (utilise par défaut le premier attribut non 'meta' du schéma de
- l'entité)
-
- * `dc_long_title()`, comme dc_title mais peut retourner un titre plus détaillé
-
- * `dc_description(format='text/plain')`, retourne une chaine unicode
- correspondant à la méta-donnée 'Description' (cherche un attribut
- 'description' par défaut)
-
- * `dc_authors()`, retourne une chaine unicode correspondant à la méta-donnée
- 'Authors' (propriétaires par défaut)
-
- * `dc_date(date_format=None)`, retourne une chaine unicode
- correspondant à la méta-donnée 'Date' (date de modification par défaut)
-
-:Contrôle du vocabulaire pour les relations:
-
- * `vocabulary(rtype, x='subject', limit=None)`, appelée notamment
- par les vues d'édition d'erudi, elle renvoie une liste de couple
- (label, eid) des entités qui pourraient être liées à l'entité
- via la relation `rtype`
- * `subject_relation_vocabulary(rtype, limit=None)`, appelée
- en interne par `vocabulary` dans le cas d'une relation sujet
- * `object_relation_vocabulary(rtype, limit=None)`, appelée
- en interne par `vocabulary` dans le cas d'une relation objet
- * `relation_vocabulary(rtype, targettype, x, limit=None)`, appelé
- en interne par `subject_relation_vocabulary` et `object_relation_vocabulary`
-
-
-Les *rtags*
------------
-Les *rtags* permettent de spécifier certains comportements propres aux relations
-d'un type d'entité donné (voir plus loin). Ils sont définis sur la classe
-d'entité via l'attribut `rtags` qui est un dictionnaire dont les clés sont un
-triplet ::
-
- <type de relation>, <type d'entité cible>, <position du contexte ("subject" ou "object"
-
-et les valeurs un `set` ou un tuple de marqueurs définissant des propriétés
-s'appliquant à cette relation.
-
-Il est possible de simplifier ce dictionnaire :
-
-* si l'on veut spécifier un seul marqueur, il n'est pas nécessaire d'utiliser
- un tuple comme valeur, le marqueur seul (chaine de caractères) suffit
-* si l'on s'intéresse uniquement à un type de relation et non à la cible et à la
- position du contexte (ou que celui-ci n'est pas ambigüe), on peut simplement
- utiliser le nom du type de relation comme clé
-* si l'on veut qu'un marqueur s'applique quelque soit le type d'entité cible, il
- faut utiliser la chaine `*` comme type d'entité cible
-
-A noter également que ce dictionnaire est *traité à la création de la classe*.
-Il est automatiquement fusionné avec celui de la ou des classe(s) parentes (pas
-besoin de copier celui de la classe parent pour le modifier). De même modifier
-celui-ci après création de la classe n'aura aucun effet...
-
-
-.. include:: 07-01-define-entities.fr.txt
--- a/doc/book/fr/08-site-config.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Interface de configuration du site
-==================================
-
-.. image:: images/lax-book.03-site-config-panel.fr.png
-
-This panel allows you to configure the appearance of your application site.
-Six menus are available and we will go through each of them to explain how
-to use them.
-
-Navigation
-~~~~~~~~~~
-This menu provides you a way to adjust some navigation options depending on
-your needs, such as the number of entities to display by page of results.
-Follows the detailled list of available options :
-
-* navigation.combobox-limit : maximum number of entities to display in related
- combo box (sample format: 23)
-* navigation.page-size : maximum number of objects displayed by page of results
- (sample format: 23)
-* navigation.related-limit : maximum number of related entities to display in
- the primary view (sample format: 23)
-* navigation.short-line-size : maximum number of characters in short description
- (sample format: 23)
-
-UI
-~~
-This menu provides you a way to customize the user interface settings such as
-date format or encoding in the produced html.
-Follows the detailled list of available options :
-
-* ui.date-format : how to format date in the ui ("man strftime" for format description)
-* ui.datetime-format : how to format date and time in the ui ("man strftime" for format
- description)
-* ui.default-text-format : default text format for rich text fields.
-* ui.encoding : user interface encoding
-* ui.fckeditor :should html fields being edited using fckeditor (a HTML WYSIWYG editor).
- You should also select text/html as default text format to actually get fckeditor.
-* ui.float-format : how to format float numbers in the ui
-* ui.language : language of the user interface
-* ui.main-template : id of main template used to render pages
-* ui.site-title : site title, which is displayed right next to the logo in the header
-* ui.time-format : how to format time in the ui ("man strftime" for format description)
-
-
-Actions
-~~~~~~~
-This menu provides a way to configure the context in which you expect the actions
-to be displayed to the user and if you want the action to be visible or not.
-You must have notice that when you view a list of entities, an action box is
-available on the left column which display some actions as well as a drop-down
-menu for more actions.
-
-The context available are :
-
-* mainactions : actions listed in the left box
-* moreactions : actions listed in the `more` menu of the left box
-* addrelated : add actions listed in the left box
-* useractions : actions listed in the first section of drop-down menu
- accessible from the right corner user login link
-* siteactions : actions listed in the second section of drop-down menu
- accessible from the right corner user login link
-* hidden : select this to hide the specific action
-
-Boxes
-~~~~~
-The application has already a pre-defined set of boxes you can use right away.
-This configuration section allows you to place those boxes where you want in the
-application interface to customize it.
-
-The available boxes are :
-
-* actions box : box listing the applicable actions on the displayed data
-
-* boxes_blog_archives_box : box listing the blog archives
-
-* possible views box : box listing the possible views for the displayed data
-
-* rss box : RSS icon to get displayed data as a RSS thread
-
-* search box : search box
-
-* startup views box : box listing the configuration options available for
- the application site, such as `Preferences` and `Site Configuration`
-
-Components
-~~~~~~~~~~
-[WRITE ME]
-
-Contextual components
-~~~~~~~~~~~~~~~~~~~~~
-[WRITE ME]
-
--- a/doc/book/fr/09-instance-config.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Configuration d'une instance
-============================
-
-À la création d'une instance, un fichier de configuration est généré dans ::
-
- $(CW_REGISTRY)/<instance>/<nom configuration>.conf
-
-par exemple ::
-
- /etc/cubicweb.d/jpl/all-in-one.conf
-
-C'est un simple fichier texte au format INI. Dans la description suivante,
-chaque nom d'option est préfixé de sa section et suivi de sa valeur par défaut
-le cas échéant, e.g. "`<section>.<option>` [valeur]".
-
-
-Configuration du serveur web
-----------------------------
-:`web.auth-mode` [cookie]:
- mode d'authentification, cookie ou http
-:`web.realm`:
- realm de l'application en mode d'authentification http
-:`web.http-session-time` [0]:
- délai d'inactivité d'une session HTTP avant sa fermeture automatique. Durée
- en secondes, 0 signifiant pas d'expiration (ou plus exactement lors de la
- fermeture du navigateur du client)
-
-:`main.anonymous-user`, `main.anonymous-password`:
- login et mot de passe à utiliser pour se connecter au serveur RQL lors des
- connexions HTTP anonymes. Il faut que le compte CWUser associé existe.
-
-:`main.base-url`:
- url de base du site, à utiliser pour générer les urls des pages web
-
-Configuration https
-```````````````````
-Il est possible de rendre un site accessible en http pour les connections
-anonymes et en https pour les utilisateurs authentifié. Il faut pour cela
-utiliser apache (par ex.) pour la redirection et la variable `main.https-url` du
-fichier de configuration.
-
-:Exemple:
-
- pour une redirection apache d'un site accessible via `http://localhost/demo`
- et `https://localhost/demo` et qui tourne en réalité sur le port 8080, il
- faut avoir pour la version http : ::
-
- RewriteCond %{REQUEST_URI} ^/demo
- RewriteRule ^/demo$ /demo/
- RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
-
- et pour la version https : ::
-
- RewriteCond %{REQUEST_URI} ^/demo
- RewriteRule ^/demo$ /demo/
- RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]
-
-
- et on aura dans le fichier all-in-one.conf de l'instance : ::
-
- base-url = http://localhost/demo
- https-url = `https://localhost/demo`
-
-Configuration de l'interface web
---------------------------------
-:`web.embed-allowed`:
- expression régulière correspondant aux sites pouvant être "incorporé" dans
- le site (controleur 'embed')
-:`web.submit-url`:
- url à laquelle les bugs rencontrés dans l'application peuvent être posté
-
-
-Configuration du serveur RQL
-----------------------------
-:`main.host`:
- nom de l'hôte s'il ne peut être détecter correctement
-:`main.pid-file`:
- fichier où sera écrit le pid du serveur
-:`main.uid`:
- compte utilisateur à utiliser pour le lancement du serveur quand il est
- lancé en root par init
-:`main.session-time [30*60]`:
- temps d'expiration d'une session RQL
-:`main.query-log-file`:
- fichier dans lequel écrire toutes les requêtes RQL éxécutées par le serveur
-
-
-Configuration Pyro pour l'instance
------------------------------------
-Coté serveur web :
-
-:`pyro-client.pyro-application-id`:
- identifiant pyro du serveur RQL (e.g. le nom de l'instance)
-
-Coté serveur RQL :
-
-:`pyro-server.pyro-port`:
- numéro de port pyro. Si aucune valeur n'est spécifiée, un port est attribué
- automatiquement.
-
-Coté serveur RQL et serveur web :
-
-:`pyro-name-server.pyro-ns-host`:
- nom de l'hôte hébergeant le serveur de nom pyro. Si aucune valeur n'est
- spécifié, il est localisé par une requête de broadcast
-:`pyro-name-server.pyro-ns-group` [cubicweb]:
- groupe pyro sous lequel enregistrer l'application
-
-
-Configuration courriel
-----------------------
-Coté serveur RQL et serveur web :
-
-:`email.mangle-emails [no]`:
- indique si les adresses email doivent être affichées telle quelle ou
- transformée
-
-Coté serveur RQL :
-
-:`email.smtp-host [mail]`:
- nom de l'hôte hébergeant le serveur SMTP à utiliser pour le courriel sortant
-:`email.smtp-port [25]`:
- port du serveur SMTP à utiliser pour le courriel sortant
-:`email.sender-name`:
- nom à utiliser pour les courriels sortant de l'application
-:`email.sender-addr`:
- adresse à utiliser pour les courriels sortant de l'application
-:`email.default-dest-addrs`:
- adresses de destination par défaut, si utilisé par la configuration de la
- diffusion du modèle (séparées par des virgules)
-:`email.supervising-addrs`:
- addresses de destination des courriels de supervision (séparées par des
- virgules)
-
-
-Configuration journalisation
-----------------------------
-:`main.log-threshold`:
- niveau de filtrage des messages (DEBUG, INFO, WARNING, ERROR)
-:`main.log-file`:
- fichier dans lequel écrire les messages
-
-
-Configuration Eproperties
--------------------------
-D'autres paramètres de configuration sont sous la forme d'entités `CWProperty`
-dans la base de données. Il faut donc les éditer via l'interface web ou par des
-requêtes rql.
-
-:`ui.encoding`:
- encodage de caractères à utiliser pour l'interface web
-:`navigation.short-line-size`: # XXX should be in ui
- nombre de caractères maximum pour les affichages "courts"
-:`navigation.page-size`:
- nombre d'entités maximum à afficher par page de résultat
-:`navigation.related-limit`:
- nombre d'entités liées maximum à afficher sur la vue primaire d'une entité
-:`navigation.combobox-limit`:
- nombre d'entités non liées maximum à afficher sur les listes déroulantes de
- la vue d'édition d'une entité
--- a/doc/book/fr/10-form-management.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Gestion de formulaires
-======================
-
-Contrôle de la génération automatique de formulaire pour les entités manipulée
-------------------------------------------------------------------------------
-XXX FILLME
-
-* les formulaires 'edition' et 'creation'
-
-Le formulaire généré par défaut ne vous convient pas ? Vous êtes peut-être pas
-obligé de le refaire à la main ! :)
-
-* rtags primary, secondary, generated, generic,
- `Entity.relation_category(rtype, x='subject')`
-* inline_view (now a rtag?)
-* spécification widget
-
-
-Fonctionnement du contrôleur d'édition par défaut (id: 'edit')
---------------------------------------------------------------
-
-Contrôle de l'édition
-`````````````````````
-Prérequis: les paramètres liés aux entités à éditer sont spécifiés de la forme ::
-
- <nom de champ>:<eid de l'entité>
-
-où l'eid de l'entité pourra être une lettre dans le cas d'une entité à créer. On
-dénommera ces paramètres comme *qualifié*.
-
-1. récupération des entités à éditer en cherchant les paramètres de formulaire
- commençant par 'eid:' ayant également un paramètre '__type' associé
- (également *qualifié* par l'eid évidemment)
-
-2. pour tous les attributs et relations de chaque entité à éditer
-
- 1. recherche d'un paramètre 'edits-<nom relation>' ou 'edito-<nom relation>'
- qualifié dans le cas d'une relation dont l'entité est objet
- 2. si trouvé, la valeur récupérée est considérée comme la valeur originale
- pour cette relation, et on cherche la (ou les) nouvelle(s) valeur(s) dans
- le paramètre <nom relation> (qualifié)
- 3. si la valeur est différente de l'originale, une requête de modification en
- base est effectuée
-
-3. pour chaque entité à éditer
-
- 1. si un paramètre `__linkto` qualifié est spécifié, sa valeur doit être une
- chaine (ou une liste de chaine) de la forme : ::
-
- <relation type>:<eids>:<target>
-
- où <target> vaut 'subject' ou 'object' et chaque eid peut-être séparé d'un
- autre par un '_'. Target spécifie *l'entité éditée* est sujet ou objet de la
- relation et chaque relation ainsi spécifiée sera insérée.
-
- 2. si un paramètre `__cloned_eid` qualifié est spécifié pour une entité, les
- relations de l'entité spécifiée en valeur de cette argument sont copiées sur
- l'entité éditée
-
-
- 3. si un paramètre `__delete` qualifié est spécifié, sa valeur doit être une
- chaine (ou une liste de chaine) de la forme : ::
-
- <subject eids>:<relation type>:<object eids>
-
- où chaque eid sujet ou objet peut-être séparé d'un autre par un '_'. Chaque
- relation ainsi spécifiée sera supprimée.
-
- 4. si un paramètre `__insert` qualifié est spécifié, sa valeur doit être de
- même format que pour `__delete`, mais chaque relation ainsi spécifiée sera
- insérée.
-
-4. si les paramètres `__insert` et/ou `__delete` sont trouvés non qualifiés,
- ils sont interprétés comme décrit ci-dessus (quelque soit le nombre d'entité
- édité)
-
-5. si aucune entité n'est éditée mais que le formulaire contient les paramètres
- `__linkto` et `eid`, celui-ci est interprété en prenant la valeur spécifié
- par le paramètre `eid` pour désigner l'entité sur laquelle ajouter les
- relations
-
-
-A noter que :
-
-* si le paramètre `__action_delete` est trouvé, toutes les entités comme
- spécifiées à éditer seront supprimées
-
-* si le paramètre `__action_cancel` est trouvé, aucune action n'est effectuée
-
-* si le paramètre `__action_apply` est trouvé, l'édition est effectuée
- normalement mais la redirection sera effectuée sur le formulaire (cf `Contrôle
- de la redirection`_)
-
-* le paramètre `__method` est également supporté comme sur le template principal
- (XXX not very consistent, maybe __method should be dealed in the view controller)
-
-* si aucune entité à éditer n'est trouvée et qu'il n'y a pas de paramètre
- `__action_delete`, `__action_cancel`, `__linkto`, `__delete` ou `__insert`,
- une erreur est levée
-
-* placer dans le formulaire le paramètre `__message` permettra d'utiliser la
- valeur de ce paramètre comme message d'information à l'utilisateur une fois
- l'édition effectuée.
-
-
-Contrôle de la redirection
-``````````````````````````
-Une fois que l'édition s'est bien passé, reste un problème : c'est bien beau
-tout ça, mais où qu'on va maintenant ?? Si rien n'est spécifié, le controlleur
-se débrouille, mais comme il fait pas toujours ce qu'on voudrait, on peut
-controller ça en utilisant les paramètres suivant :
-
-* `__redirectpath`: chemin de l'url (relatif à la racine du site, sans paramètre
- de formulaire
-
-* `__redirectparams`: paramètres de formulaires à ajouter au chemin
-
-* `__redirectrql`: requête RQL de redirection
-
-* `__redirectvid`: identifiant de vue de redirection
-
-* `__errorurl`: url du formulaire original, utilisé pour la redirection en cas
- d'erreur de validation pendant l'édition. Si celui-ci n'est pas spécifié, une
- page d'erreur sera présentée plutot qu'un retour sur le formulaire (qui est le
- cas échéant responsable d'afficher les erreurs)
-
-* `__form_id`: identifiant de vue du formulaire original, utilisée si
- `__action_apply` est trouvé
-
-En général on utilise soit `__redirectpath et `__redirectparams` soit
-`__redirectrql` et `__redirectvid`.
--- a/doc/book/fr/11-ajax-json.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-AJAX
-====
-JSON bla bla
-XXX FILLME
-
-
-Le contrôleur 'json'
---------------------
-XXX FILLME
-
-
-API Javascript
---------------
-XXX FILLME
--- a/doc/book/fr/12-internationalization.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _internationalization:
-
-
-Internationalization
-====================
-
-The internationalization
-
-Le système d'internationalisation de l'interface web d'erudi est basé sur le
-système `GNU gettext`_.
-
-.. _`GNU gettext`: http://www.gnu.org/software/gettext/
-
-Messages à internationaliser
-----------------------------
-
-Marquage des messages à internaliser
-````````````````````````````````````
-Les chaines de caractères à internationaliser sont marqués par l'appel à la
-fonction `_` *OU* par la méthode équivalent de la requête dans le code python ou
-dans les expressions python de template TAL.
-
-Dans les templates erudi-tal, il est également possible d'insérer une chaine à
-traduire via les balises `i18n:content` et `i18n:replace`.
-
-De plus des messages correspondant aux entités/relations utilisés par le schéma
-de l'application seront automatiquement ajoutés.
-
-Renvoi d'un message internationalisé lors de la construction d'une page
-```````````````````````````````````````````````````````````````````````
-La fonction *built-in* `_` ne doit servir qu'**à marquer les messages à
-traduire**, non pas à récupérer une traduction. Il faut pour cela utiliser la
-méthode `_` de l'objet requête, sans quoi vous récupérerez l'identifiant de
-message au lieu de sa traduction dans la langue propre à la requête.1
-
-
-Gestion des catalogues de traduction
-------------------------------------
-Une fois l'application rendu internationalisable coté code, reste à gérer les
-catalogues de traductions. erudi-ctl intègre pour cela les commandes suivantes :
-
-* `i18ncubicweb`, met à jour les catalogues de messages *de la librairie
- erudi*. Sauf si vous développez sur le framework (et non votre propre
- application), vous ne devriez pas avoir à utiliser cette commande
-
-* `i18ncube`, met à jour les catalogues de messages *du composant* (ou de tous
- les composants). A la suite de cette commande, vous devez mettre à jour les
- fichiers de traduction *.po* dans le sous-répertoire "i18n" de votre
- template. Évidemment les traductions précédentes toujours utilisées ont été
- conservées.
-
-* `i18ninstance`, recompile les catalogues de messages *d'une instance* (ou de
- toutes les instances) après mise à jour des catalogues de son composant. Cela
- est effectué automatiquement lors d'une création ou d'une mise à jour. Les
- catalogues de messages compilés se trouvent dans le répertoire
- "i18n/<lang>/LC_MESSAGES/erudi.mo" de l'application où `lang` est
- l'identifiant de la langue ('en' ou 'fr' par exemple).
-
-
-Le cas classique
-````````````````
-Vous avez ajouté et/ou modifié des messages d'un composant utilisé par votre
-application (en ajoutant une nouvelle vue ou en ayant modifié le schéma par
-exemple) :
-
-1. `cubicweb-ctl i18ncube <composant>`
-2. éditer les fichiers <composant>/xxx.po dans pour y rajouter les traductions
- manquantes (`msgstr` vide)
-3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18ninstance <monapplication>`
-
--- a/doc/book/fr/12-ui-components.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-Autres composants de l'interface web
-====================================
-
-Actions
--------
-XXXFILLME
-
-Component, VComponent
----------------------
-XXXFILLME
-
-CWProperty
----------
-XXXFILLME
--- a/doc/book/fr/13-security.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Utilisateurs de l'application : Le contrôle d'accès
-===================================================
-
-
-Vocabulaire
------------
-* Personne, Societe définissent deux *types* d'entité
-* "Personne travaille_pour Societé" déclare qu'une relation
- travaille_pour peut exister entre une entité de type Personne et une
- entité de type Societe. L'ensemble des règles de ce type appliqué
- à la relation "travaille_pour" définit le schéma de la relation
- "travaille_pour"
-
-
-Description du modèle de sécurité
----------------------------------
-
-Le modèle de sécurité de CubicWeb est un modèle fondé sur des `Access
-Control List`. Les notions sont les suivantes :
-
-* utilisateurs et groupes d'utilisateurs
-* un utilisateur appartient à au moins un groupe
-* droits (lire, modifier, créer, supprimer)
-* les droits sont attribués aux groupes (et non aux utilisateurs)
-
-Pour CubicWeb plus spécifiquement :
-
-* on associe les droits au niveau des schemas d'entites / relations
-* pour chaque type d'entité, on distingue les droits de lecture,
- ajout, modification et suppression
-* pour chaque type de relation, on distingue les droits de lecture,
- ajout et suppression (on ne peut pas modifer une relation)
-* les groupes de base sont : Administrateurs, Utilisateurs, Invités
-* les utilisateurs font par défaut parti du groupe Utilisateurs
-* on a un groupe virtuel "Utilisateurs Propriétaires", auquel on peut
- associer uniquement les droits de suppression et de modification
-* on ne peut pas mettre d'utilisateurs dans ce groupe, ils y sont
- ajoutés implicitement dans le contexte des objets dont ils sont
- propriétaires
-* les droits de ce groupe ne sont vérifiés que sur
- modification / suppression si tous les autres groupes auxquels
- l'utilisateur appartient se sont vu interdir l'accès
-
--- a/doc/book/fr/14-hooks.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Les crochets (*hooks*)
-======================
-
-XXX FILLME
-
-Les crochets sont appelés avant ou après la mise à jour d'une entité ou d'une
-relations dans le dépot
-
-Leur prototypes sont les suivants
-
-
- * after_add_entity (session, entity)
- * after_update_entity (session, entity)
- * after_delete_entity (session, eid)
- * before_add_entity (session, entity)
- * before_update_entity (session, entity)
- * before_delete_entity (session, eid)
-
- * after_add_relation (session, fromeid, rtype, toeid)
- * after_delete_relation (session, fromeid, rtype, toeid)
- * before_add_relation (session, fromeid, rtype, toeid)
- * before_delete_relation (session, fromeid, rtype, toeid)
-
- * server_startup
- * server_shutdown
-
- * session_open
- * session_close
-
--- a/doc/book/fr/15-notifications.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Gestion de notifications
-========================
-
-XXX FILLME
--- a/doc/book/fr/16-rql.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Le langage RQL (Relation Query Language)
-========================================
-
-Voir la `documentation de RQL <file:///home/sandrine/src/fcubicweb/rql/doc/build/html/index.html>`_ .
-
-
-[TODO]
-Specific link to RQL complete documentation to remove duplicated content.
-
--- a/doc/book/fr/17-migration.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Migration
-=========
-
-Une des idées de base d'Erudi est la création incrémentale d'application, et
-pour cela de nombreuses actions sont fournies afin de facilement faire évoluer
-une application et tout particulièrement le modèle de données manipulé sans
-perdre les données des instances existantes.
-
-La version courante d'un modèle d'application est données dans le fichier
-`__pkginfo__.py` sous forme d'un tuple de 3 entiers.
-
-
-Gestion des scripts de migrations
----------------------------------
-Les scripts des migrations doivent être placés dans le répertoire `migration` de
-l'application, et nommé de la manière suivante :
-
-::
-
- <n° de version X.Y.Z>[_<description>]_<mode>.py
-
-dans lequel :
-
-* X.Y.Z correspond au n° de version du modèle vers lequel le script permet de
- migrer,
-
-* le *mode* (entre le dernier "_" et l'extension ".py") indique à quelle partie
- de l'application (serveur RQL, serveur web) le script s'applique en cas
- d'installation distribuée. Il peut valoir :
-
- * `common`, s'applique aussi bien sur le serveur RQL que sur le serveur web,
- et met à jour des fichiers sur le disque (migration de fichier de
- configuration par exemple).
-
- * `web`, s'applique uniquement sur le serveur web, et met à jour des fichiers
- sur le disque
-
- * `repository`, s'applique uniquement sur le serveur RQL, et met à jour des
- fichiers sur le disque
-
- * `Any`, s'applique uniquement sur le serveur RQL, et met à jour des
- données en base (migrations de schéma et de données par ex.)
-
-
-Toujours dans le répertoire `migration`, le fichier spécial `depends.map` permet
-d'indiquer que pour migrer vers une version spécifique du modèle, il faut tout
-d'abord avoir migrer vers une version données de erudi. Ce fichier peut contenir
-des commentaires (lignes commençant par un "#"), et une dépendance est notée sur
-une ligne de la manière suivante : ::
-
- <n° de version du modèle X.Y.Z> : <n° de version erudi X.Y.Z>
-
-Par exemple ::
-
- 0.12.0: 2.26.0
- 0.13.0: 2.27.0
- # 0.14 works with 2.27 <= erudi <= 2.28 at least
- 0.15.0: 2.28.0
-
-
-Contexte de base
-----------------
-Les identifiants suivants sont préféfinis dans les scripts de migration :
-
-* `config`, configuration de l'instance
-
-* `interactive_mode`, booléen indiquant si le script est éxécuté en mode
- interactif ou non
-
-* `appltemplversion`, version du modèle d'application de l'instance
-
-* `applerudiversion`, version erudi de l'instance
-
-* `templversion`, version du modéle d'application installée
-
-* `erudiversion`, version erudi installée
-
-* `confirm(question)`, fonction posant une question et retournant vrai si
- l'utilisateur a répondu oui, faux sinon (retourne toujours vrai en mode non
- interactif)
-
-* `_`, fonction équivalente à `unicode` permettant de marquer des chaines à
- internationaliser dans les scripts de migration
-
-Dans les scripts "repository", les identifiants suivant sont également définis :
-
-* `checkpoint`, demande confirmant et effectue un "commit" au point d'appel
-
-* `repo_schema`, schéma persistent de l'instance (i.e. schéma de l'instance en
- cours de migration)
-
-* `newschema`, schéma installé sur le système de fichier (i.e. schéma de la
- version à jour du modèle et de erudi)
-
-* `sqlcursor`, un curseur SQL pour les très rares cas où il est réellement
- nécessaire ou avantageux de passer par du sql
-
-* `repo`, l'objet repository
-
-
-Migration de schéma
--------------------
-Les fonctions de migration de schéma suivantes sont disponibles dans les scripts
-"repository" :
-
-* `add_attribute(etype, attrname, attrtype=None, commit=True)`, ajoute un
- nouvel attribut à un type d'entité existante. Si le type de celui-ci n'est pas
- spécifié il est extrait du schéma à jour.
-
-* `drop_attribute(etype, attrname, commit=True)`, supprime un
- attribut à un type d'entité existante.
-
-* `rename_attribute(etype, oldname, newname, commit=True)`, renomme un attribut
-
-* `add_entity_type(etype, auto=True, commit=True)`, ajoute un nouveau type
- d'entité. Si `auto` est vrai, toutes les relations utilisant ce type d'entité
- et ayant un type d'entité connu à l'autre extrémité vont également être
- ajoutées.
-
-* `drop_entity_type(etype, commit=True)`, supprime un type d'entité et toutes
- les relations l'utilisant.
-
-* `rename_entity_type(oldname, newname, commit=True)`, renomme un type d'entité
-
-* `add_relation_type(rtype, addrdef=True, commit=True)`, ajoute un nouveau type
- de relation. Si `addrdef` est vrai, toutes les définitions de relation de ce
- type seront également ajoutées.
-
-* `drop_relation_type(rtype, commit=True)`, supprime un type de relation et
- toutes les définitions de ce type.
-
-* `rename_relation(oldname, newname, commit=True)`, renomme une relation.
-
-* `add_relation_definition(subjtype, rtype, objtype, commit=True)`, ajoute une
- définition de relation.
-
-* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, supprime
- une définition de relation.
-
-* `synchronize_permissions(ertype, commit=True)`, synchronise les permissions
- d'un type d'entité ou de relation
-
-* `synchronize_rschema(rtype, commit=True)`, synchronise les propriétés et
- permissions d'un type de relation.
-
-* `synchronize_eschema(etype, commit=True)`, synchronise les propriétés et
- permissions d'un type d'entité.
-
-* `synchronize_schema(commit=True)`, synchronise le schéma persistent avec le
- schéma à jour (mais sans ajouter ni supprimer de nouveaux types d'entités ou
- de relations ni de définitions de relation).
-
-* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, change
- les propriétés d'une definition de relation en utilisant les arguments nommés
- pour les propriétés à changer.
-
-* `set_widget(etype, rtype, widget, commit=True)`, change le widget à utiliser
- pour la relation <rtype> du type d'entité <etype>
-
-* `set_size_constraint(etype, rtype, size, commit=True)`, change la contrainte
- de taille pour la relation <rtype> du type d'entité <etype>
-
-
-Migration de données
---------------------
-Les fonctions de migration de données suivantes sont disponibles dans les scripts
-"repository" :
-
-* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, éxécute une
- requête rql arbitraire, d'interrogation ou de modification. Un objet result
- set est retourné.
-
-* `add_entity(etype, *args, **kwargs)`, ajoute une nouvelle entité du type
- données. La valeur des attributs et relations est spécifiée en utilisant les
- arguments nommés et positionnels.
-
-
-Création de workflow
---------------------
-Les fonctions de création de workflow suivantes sont disponibles dans les scripts
-"repository" :
-
-* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, ajoute un
- nouvel état de workflow
-
-* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`,
- ajoute une nouvelle transtion de workflow
-
-Migration de configuration
---------------------------
-Les fonctions de migration de configuration suivantes sont disponibles dans tout
-les scripts :
-
-* `option_renamed(oldname, newname)`, indique qu'une option a été renommée
-
-* `option_group_change(option, oldgroup, newgroup)`, indique qu'une option a
- changé de groupe
-
-* `option_added(oldname, newname)`, indique qu'une option a été ajoutée
-
-* `option_removed(oldname, newname)`, indique qu'une option a été supprimée
-
-
-Autres fonctions de migration
------------------------------
-Ces fonctions ne sont utilisés que pour des opérations de bas niveau
-irréalisables autrement ou pour réparer des bases cassées lors de session
-interactive. Elles sont disponibles dans les scripts "repository".
-
-* `sqlexec(sql, args=None, ask_confirm=True)`, éxécute une requête sql
- arbitraire, à n'utiliser
-
-* `add_entity_type_table(etype, commit=True)`
-* `add_relation_type_table(rtype, commit=True)`
-* `uninline_relation(rtype, commit=True)`
--- a/doc/book/fr/18-tests.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Tests
-=====
-
-Écriture de tests unitaires
----------------------------
-Le framework de test fournit principalement deux classes de tests dans le module
-`ginco.devtools.apptest`:
-
-* `EnvBasedTC`, pour simuler un environnement complet (web + repository)
-* `RepositoryBasedTC`, pour simuler un environnement de repository uniquement
-
-Ces deux classes ont quasiment la même interface et proposent un certain nombre de méthodes
-rendant l'écriture de test puissante et rapide.
-
-XXXFILLME describe API
-
-Dans la plupart des cas, vous allez vouloir hériter de `EnvBasedTC` pour écrire des tests
-unitaires ou fonctionnels pour vos entités, vues, crochets...
-
-
-Test des courriels de notifications
-```````````````````````````````````
-Lors de l'éxécution de tests les courriels potentiellement générés ne sont pas réellement
-envoyé mais se retrouve dans la liste `MAILBOX` du module `ginco.devtools.apptest`. Cette
-liste est remise à zéro au *setUp* de chaque test (par le setUp des classes `EnvBasedTC`
-et `RepositoryBasedTC`).
-
-Vous pouvez donc tester vos notifications en analysant le contenu de cette liste, qui
-contient des objets ayant deux attributs :
-* `recipients`, la liste des destinataires
-* `msg`, l'objet email.Message
-
-
-Tests automatiques
-------------------
-XXXFILLME
--- a/doc/book/fr/19-i18n.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Internationalisation:
-
-
-Internationalisation
-====================
-
-Le système d'internationalisation de l'interface web de CubicWeb est basé sur le
-système `GNU gettext`_.
-
-.. _`GNU gettext`: http://www.gnu.org/software/gettext/
-
-Messages à internationaliser
-----------------------------
-
-Marquage des messages à internaliser
-````````````````````````````````````
-Les chaines de caractères à internationaliser sont marqués par l'appel à la
-fonction `_` *OU* par la méthode équivalent de la requête dans le code python ou
-dans les expressions python de template TAL.
-
-Dans les templates cubicweb-tal, il est également possible d'insérer une chaine à
-traduire via les balises `i18n:content` et `i18n:replace`.
-
-De plus des messages correspondant aux entités/relations utilisés par le schéma
-de l'application seront automatiquement ajoutés.
-
-Renvoi d'un message internationalisé lors de la construction d'une page
-```````````````````````````````````````````````````````````````````````
-La fonction *built-in* `_` ne doit servir qu'**à marquer les messages à
-traduire**, non pas à récupérer une traduction. Il faut pour cela utiliser la
-méthode `_` de l'objet requête, sans quoi vous récupérerez l'identifiant de
-message au lieu de sa traduction dans la langue propre à la requête.1
-
-
-Gestion des catalogues de traduction
-------------------------------------
-Une fois l'application rendu internationalisable coté code, reste à gérer les
-catalogues de traductions. cubicweb-ctl intègre pour cela les commandes suivantes :
-
-* `i18ncubicweb`, met à jour les catalogues de messages *de la librairie
- cubicweb*. Sauf si vous développez sur le framework (et non votre propre
- application), vous ne devriez pas avoir à utiliser cette commande
-
-* `i18ncube`, met à jour les catalogues de messages *du composant* (ou de tous
- les composants). A la suite de cette commande, vous devez mettre à jour les
- fichiers de traduction *.po* dans le sous-répertoire "i18n" de votre
- template. Évidemment les traductions précédentes toujours utilisées ont été
- conservées.
-
-* `i18ninstance`, recompile les catalogues de messages *d'une instance* (ou de
- toutes les instances) après mise à jour des catalogues de son composant. Cela
- est effectué automatiquement lors d'une création ou d'une mise à jour. Les
- catalogues de messages compilés se trouvent dans le répertoire
- "i18n/<lang>/LC_MESSAGES/cubicweb.mo" de l'application où `lang` est
- l'identifiant de la langue sur 2 lettres ('en' ou 'fr' par exemple)
-
-
-Le cas classique
-````````````````
-Vous avez ajouté et/ou modifié des messages d'un composant utilisé par votre
-application (en ajoutant une nouvelle vue ou en ayant modifié le schéma par
-exemple) :
-
-1. `cubicweb-ctl i18ncube <composant>`
-2. éditer les fichiers <composant>/xxx.po dans pour y rajouter les traductions
- manquantes (`msgstr` vide)
-3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18ninstance <monapplication>`
-
--- a/doc/book/fr/20-01-intro.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Introduction à `LAX`
-====================
-
-
-Concepts et vocabulaire
------------------------
-
-*schéma*
- le schéma définit le modèle de données d'une application sous forme
- d'entités et de relations. C'est l'élément central d'une
- application.
-
-*result set*
- objet encaspulant les résultats d'une requête à l'entrepôt de données
- et des informations sur cette requête.
-
-*vue*
- une vue est une manière de représenter les données d'un `result set`
- sous forme HTML, CSV, JSON, etc.
-
-
-
-Définition d'une application de Blog
-====================================
-
-La première chose à faire est de copier le répertoire ``lax``
-vers un nouveau répertoire qui sera votre application ``Google AppEngine``::
-
- $ cp -r lax myapp
-
-Définition du schéma
---------------------
-
-Ouvrir le fichier ``myapp/schema.py`` afin de définir le schéma des
-données manipulées. La syntaxe de la définition est la même que celle
-proposée par `Google AppEngine` mais il faut remplacer la ligne
-d'import::
-
- from google.appengine.ext import db
-
-par celle-ci::
-
- from ginco.goa import db
-
-
-Un exemple de schéma de données pour un ``Blog`` pourrait être::
-
- from ginco.goa import db
-
- class BlogEntry(db.Model):
- # un titre à donner à l'entrée
- title = db.StringProperty(required=True)
- # la date à laquelle le blog est créé
- diem = db.DateProperty(required=True, auto_now_add=True)
- # le contenu de l'entrée
- content = db.TextProperty()
- # une entrée peut en citer une autre
- cites = db.SelfReferenceProperty()
-
-
-Personnalisation des vues
--------------------------
-
-`LAX` permet d'obtenir directement, à partir de la définition
-du schéma, de générer des vues de consultation, d'ajout et
-de modification pour tous les types de donées manipulés.
-Il est toutefois généralement souhaitable de personnaliser
-les vues de consultations.
-
-Dans `LAX`, les vues sont représentées par des classes Python.
-Une vue se caractèrise par :
-
-- un identifiant (tous les objets dans `LAX` sont enregistrés
- dans un registre et cet identifiant sert de clé pour y retrouver
- la vue)
-
-- une description des types de données auxquels elle s'applique
-
-Il existe dans `LAX` des vues prédéfinies et utilisées par le moteur
-d'affichage. Pour avoir une liste exhaustive de ces vues prédéfinies,
-vous pouvez consulter cette page. (XXX mettre le lien vers la liste).
-Par exemple, la vue ``primary`` est la vue utilisée pour générer la
-page principale de consultation d'un objet.
-
-Par exemple, si on souhaite modifier la page principale d'une entrée de
-blog, il faut surcharger la vue ``primary`` des objets ``BlogEntry`` dans
-le fichier ``myapp/views.py``::
-
- from ginco.web.views import baseviews
-
- class BlogEntryPrimaryView(baseviews.PrimaryView):
- accepts = ('BlogEntry',)
-
- def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
- self.w(u'<h1>%s</h1>' % entity.title)
- self.w(u'<div>%s</div>' entity.content)
-
-
-Génération du graphique de schéma
----------------------------------
-
-Il existe une vue ``schema`` qui permet d'afficher un graphique
-représantant les différents types d'entités définis dans le schéma
-ainsi que les relations entre ces types. Ce graphique doit être généré
-statiquement. Le script à utiliser pour générer ce schéma est
-dans ``myapp/tools``. Ce script nécessite d'avoir accès aux
-bibliothèques fournies par le SDK de ``Google AppEngine``. Il faut
-donc modifier son PYTHONPATH::
-
- $ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
- $ python tools/generate_schema_img.py
-
-
-Génération des fichiers de traduction
--------------------------------------
-
-Des catalogues de traduction se trouvent dans `myapp/i18n`. Il faut
-pour l'instant les mettre à jour à la main (et/ou avec les outils
-``GNU`` comme ``xgettext``) et ensuite les compiler grâce au script
-``myapp/tools/i18ncompile.py``::
-
- $ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
- $ python tools/i18ncompile.py
-
--- a/doc/book/fr/20-02-install.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _installationGAE:
-
-Installation de `CubicWeb` pour GoogleAppEngine
-===============================================
-
-Qu'est-ce que `LAX` ?
-=======================
-
-`LAX` (Logilab Appengine eXtension) est un framework d'application
-web basé sur `Google AppEngine`.
-
-`LAX` est un portage de la partie web de la plate-forme applicative
-développée par Logilab depuis 2001. Cette plate-forme publie des
-données tirées de bases SQL, d'annuaires LDAP et de systèmes de
-gestion de version. En avril 2008, elle a été portée pour fonctionner
-sur le "datastore" de `Google AppEngine`.
-
-XXX: faire un parallèle entre Django/GAE et LAX/GAE
-
-
-Téléchargement des sources
-==========================
-
-- Les sources de `Google AppEngine` peuvent être récupérées à l'adresse
- suivante : http://code.google.com/appengine/downloads.html
-
-- Les sources de `LAX` se trouvent à l'adresse suivante :
- http://lax.logilab.org/
-
-
-Installation
-============
-
-Une fois décompactée, l'archive `lax-0.1.0-alpha.tar.gz`, on obtient
-l'arborescence suivante::
-
- .
- |-- app.yaml
- |-- custom.py
- |-- data
- |-- ginco/
- |-- i18n/
- |-- logilab/
- |-- main.py
- |-- mx/
- |-- rql/
- |-- schema.py
- |-- simplejson/
- |-- tools/
- | |-- generate_schema_img.py
- | `-- i18ncompile.py
- |-- views.py
- |-- yams/
- `-- yapps/
-
-
-On retrouve le squelette d'une application web de `Google AppEngine`
-(fichiers ``app.yaml``, ``main.py`` en particulier) avec les dépendances
-supplémentaires nécessaires à l'utilisation du framework `LAX`
-
-
-Lancement de l'application de base
-==================================
-
-Plusieurs répertoires doivent être accessibles via la variable
-d'environnement ``PYTHONPATH`` ::
-
- $ export PYTHONPATH=/path/to/google_appengine:/path/to/google_appengine/lib/yaml/lib:/path/to/myapp/
-
-Le répertoire yaml n'est nécessaire que pour le lancement des scripts
-qui se trouvent dans lax/tools et pour l'exécution des tests unitaires.
-
-Pour démarrer::
-
- $ python /path/to/google_appengine/dev_appserver.py /path/to/lax
-
-
--- a/doc/book/fr/20-03-create-app.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Créer une application simple
-============================
-
-[TRADUISEZ-MOI]
--- a/doc/book/fr/20-04-develop-views.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Définir l'interface utilisateur avec des vues
-=============================================
-
-`LAX` provides out-of-the-box a web interface that is generated from
-the schema definition: entities can be created, displayed, updated and
-deleted. As display views are not very fancy, it is usually necessary
-to develop your own.
-
-
-With `LAX`, views are defined by Python classes. A view includes :
-
-- an identifier (all objects in `LAX` are entered in a registry
- and this identifier will be used as a key)
-
-- a filter to select the resulsets it can be applied to
-
-`LAX` provides a lot of standard views, for a complete list, you
-will have to read the code in directory views (XXX improve doc).
-For example, the view named ``primary`` is the one used to display
-a single entity.
-
-If you want to change the way a ``BlogEntry`` is displayed, just
-override the view ``primary`` in ``BlogDemo/views.py`` ::
-
- from ginco.web.views import baseviews
-
- class BlogEntryPrimaryView(baseviews.PrimaryView):
-
- accepts = ('BlogEntry',)
-
- def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
- self.w(u'<h1>%s</h1>' % entity.title)
- self.w(u'<div>%s</div>' % entity.publish_date)
- self.w(u'<div>%s</div>' % entity.category)
- self.w(u'<div>%s</div>' entity.content)
-
-[WRITE ME]
-
-* Defining views with selection/views
-
-* implementing interfaces, calendar for blog entries
-
-* show that a calendar view can export data to ical
-
-* create view "blogentry table" with title, publish_date, category
-
-* in view blog, select blogentries and apply view "blogentry table"
-
-* demo ajax by filtering blogentry table on category
-
-Components
-===========
-
-[WRITE ME]
-
-* explain the component architecture
-
-* add comments to the blog by importing the comments component
-
-MainTemplate
-============
-
-[WRITE ME]
-
-* customize MainTemplate and show that everything in the user
- interface can be changed
-
-
-RSS Channel
-===========
-
-[WRITE ME]
-
-* show that the RSS view can be used to display an ordered selection
- of blog entries, thus providing a RSS channel
-
-* show that a different selection (by category) means a different channel
-
-RQL
-====
-
-[WRITE ME]
-
-* talk about the Relation Query Language
-
-URL Rewriting
-=============
-
-[WRITE ME]
-
-* show how urls are mapped to selections and views and explain URLRewriting
-
-Security
-=========
-
-[WRITE ME]
-
-* talk about security access rights and show that security is defined
- using RQL
-
--- a/doc/book/fr/20-05-components.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Composants
-===========
-
-[TRADUISEZ-MOI]
--- a/doc/book/fr/20-06-maintemplate.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-MainTemplate
-============
-
-[TRADUISEZ-MOI]
--- a/doc/book/fr/20-07-rss-xml.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Canaux RSS et exports XML
-=========================
-
-[TRADUISEZ-MOI]
--- a/doc/book/fr/20-08-rql.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-RQL
-===
-
-[TRADUISEZ-MOI]
--- a/doc/book/fr/20-09-urlrewrite.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Ré-écriture d'URLs
-==================
-
-[TRANSLATE ME]
-
-
--- a/doc/book/fr/20-10-security.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Securité
-=========
-
-[TRADUISEZ-MOI]
-
--- a/doc/book/fr/20-google-appengine.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _contents:
-
-==========================
-Google AppEngine Datastore
-==========================
-
-
-.. include:: 20-01-intro.fr.txt
-.. include:: 20-02-install.fr.txt
-.. include:: 20-03-create-app.fr.txt
-.. include:: 20-04-develop-views.fr.txt
-.. include:: 20-05-components.fr.txt
-.. include:: 20-06-maintemplate.fr.txt
-.. include:: 20-07-rss-xml.fr.txt
-.. include:: 20-08-rql.fr.txt
-.. include:: 20-09-urlrewrite.fr.txt
-.. include:: 20-10-security.fr.txt
--- a/doc/book/fr/21-01-architecture.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Architecture du serveur
------------------------
-
-.. image:: images/server-class-diagram.png
-
-`Diagramme ArgoUML`_
-
-[FIXME]
-Make a downloadable source of zargo file.
-
-.. _`Diagramme ArgoUML`: cubicweb.zargo
--- a/doc/book/fr/21-02-querier.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Déroulement de l'éxecution d'une requête en multi-source avec insertion de sécurité
-```````````````````````````````````````````````````````````````````````````````````
-
-* 3 sources (system, ldap (Euser) et rql (Card)
-* permission en lecture Card is elle appartient à l'utilisateur
-
-Soit la requête de départ: ::
-
- Any X,T WHERE X owned_by U, U login "syt", X title T
-
-1. récupération arbre de syntaxe et solution (+cache) ::
-
- -> {X: Card, U: Euser}, {X: Blog, U: Euser}, {X: Bookmark, U: Euser}
-
-2. insertion sécurité ::
-
- -> Any X,T WHERE X owned_by U, U login "syt", X title T, EXISTS(X owned_by UEID) / {X: Card, U: Euser}
- Any X,T WHERE X owned_by U, U login "syt", X title T / {X: Blog, U: Euser}, {X: Bookmark, U: Euser}
-
-3. construction plan
- 0. preprocessing (annotation des arbres de syntaxe)
-
- 1. Any U WHERE U login "syt" / {U: Euser}
- [system+ldap] => table1/varmap1{U:U2}
-
- 2. Any X,T WHERE X owned_by U2, X title T / {X: Blog, U: Euser}, {X: Bookmark, U: Euser}
- [varmap1|system] => TABLE2
-
- 3 Deux alernatives:
-
- 1. Any X,T WHERE X is Card, X title T {X: Card} ::
-
- [system+rql] => table3/varmap3{X:X3, T:T3}
-
- Any X3,T3 WHERE X3 owned_by U2, X3 title T3, EXISTS(X owned_by UEID) / {X3: Card, U2: Euser} ::
-
- [(varmap1, varmap3)|system] => TABLE2
-
- 2 Any X WHERE X is Card X owned_by U2, EXISTS(X owned_by UEID) / {X: Card, U2: Euser} ::
-
- [varmap1|system] => EIDS
-
- Any X,T WHERE X title T, X eid IN(EIDS) {X: Card} ::
-
- [system+rql] => TABLE2
-
- 4. renvoie contenu TABLE2.
- Note : si aggrégat / tri / distinct TABLE2 est nécessairement une table temporaire et besoin d'une
- étape AggrStep supplémentaire
-
-4. éxécution du plan
-
-5. [construction description]
-
-6. renvoie ResultSet
-
-Notes sur UNION
-```````````````
-
-* en multi-sources, les résultats des unions peuvent être mélangés
--- a/doc/book/fr/21-03-modules.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-:mod:`cubes.addressbook`
-========================
-
-.. automodule:: cubes.addressbook
- :members:
-
-:mod:`cubes.basket`
-========================
-
-.. automodule:: cubes.basket
- :members:
-
-:mod:`cubes.blog`
-========================
-
-.. automodule:: cubes.blog
- :members:
-
-:mod:`cubes.book`
-========================
-
-.. automodule:: cubes.book
- :members:
-
-:mod:`cubes.comment`
-========================
-
-.. automodule:: cubes.comment
- :members:
-
-:mod:`cubes.company`
-========================
-
-.. automodule:: cubes.company
- :members:
-
-
-:mod:`cubes.conference`
-========================
-
-.. automodule:: cubes.conference
- :members:
-
-:mod:`cubes.email`
-========================
-
-.. automodule:: cubes.email
- :members:
-
-:mod:`cubes.event`
-========================
-
-.. automodule:: cubes.event
- :members:
-
-:mod:`cubes.expense`
-========================
-
-.. automodule:: cubes.expense
- :members:
-
-
-:mod:`cubes.file`
-========================
-
-.. automodule:: cubes.file
- :members:
-
-:mod:`cubes.folder`
-========================
-
-.. automodule:: cubes.folder
- :members:
-
-:mod:`cubes.i18ncontent`
-========================
-
-.. automodule:: cubes.i18ncontent
- :members:
-
-:mod:`cubes.invoice`
-========================
-
-.. automodule:: cubes.invoice
- :members:
-
-:mod:`cubes.keyword`
-========================
-
-.. automodule:: cubes.keyword
- :members:
-
-:mod:`cubes.link`
-========================
-
-.. automodule:: cubes.link
- :members:
-
-:mod:`cubes.mailinglist`
-========================
-
-.. automodule:: cubes.mailinglist
- :members:
-
-:mod:`cubes.person`
-========================
-
-.. automodule:: cubes.person
- :members:
-
-:mod:`cubes.shopcart`
-========================
-
-.. automodule:: cubes.shopcart
- :members:
-
-:mod:`cubes.skillmat`
-========================
-
-.. automodule:: cubes.skillmat
- :members:
-
-:mod:`cubes.tag`
-========================
-
-.. automodule:: cubes.tag
- :members:
-
-:mod:`cubes.task`
-========================
-
-.. automodule:: cubes.task
- :members:
-
-:mod:`cubes.workcase`
-========================
-
-.. automodule:: cubes.workcase
- :members:
-
-:mod:`cubes.workorder`
-========================
-
-.. automodule:: cubes.workorder
- :members:
-
-:mod:`cubes.zone`
-========================
-
-.. automodule:: cubes.zone
- :members:
-
-:mod:`cubicweb`
-===============
-
-.. automodule:: cubicweb
- :members:
-
-:mod:`cubicweb.common`
-======================
-
-.. automodule:: cubicweb.common
- :members:
-
-:mod:`cubicweb.devtools`
-========================
-
-.. automodule:: cubicweb.devtools
- :members:
-
-:mod:`cubicweb.entities`
-========================
-
-.. automodule:: cubicweb.entities
- :members:
-
-:mod:`cubicweb.etwist`
-======================
-
-.. automodule:: cubicweb.etwist
- :members:
-
-:mod:`cubicweb.goa`
-===================
-
-.. automodule:: cubicweb.goa
- :members:
-
-:mod:`cubicweb.schemas`
-=======================
-
-.. automodule:: cubicweb.schemas
- :members:
-
-:mod:`cubicweb.server`
-======================
-
-.. automodule:: cubicweb.server
- :members:
-
-:mod:`cubicweb.sobjects`
-========================
-
-.. automodule:: cubicweb.sobjects
- :members:
-
-:mod:`cubicweb.web`
-===================
-
-.. automodule:: cubicweb.web
- :members:
-
-:mod:`cubicweb.web.views`
-=========================
-
-.. automodule:: cubicweb.web.views
- :members:
-
-
-:mod:`cubicweb.wsgi`
-====================
-
-.. automodule:: cubicweb.wsgi
- :members:
-
-:mod:`indexer`
-==============
-
-.. automodule:: indexer
- :members:
-
-:mod:`logilab`
-==============
-
-.. automodule:: logilab
- :members:
-
-
-
-:mod:`rql`
-==========
-
-.. automodule:: rql
- :members:
-
-:mod:`yams`
-===========
-
-.. automodule:: yams
- :members:
--- a/doc/book/fr/21-references.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _contents:
-
-Références
-==========
-
-.. toctree::
- :maxdepth: 1
-
- 21-01-architecture.fr.txt
- 21-02-querier.fr.txt
- 21-03-modules.fr.txt
--- a/doc/book/fr/22-faq.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Foire Aux Questions
-===================
-
-[FILL ME]
-
-* A quoi servent les crochets?
-
- Les crochets sont appeles lorsqu'une requete RQL est executee. Cela
- permet d'executer des actions specifiques lors d'un acces a la base
- de donnees, ce qui donne un controle de la base de donnees afin de
- prevenir l'insertion de `mauvaises` entites dans la base.
-
-* Quand utiliser un template HTML plutot qu'un composant graphique?
-
- Un template HTML ne peut contenir de logique, il ne permettra donc
- que de definir une vue statique. Un composant permet lui de gerer
- plus de logique et d'operations sur le contexte dans lequel il
- s'applique. Il faut donc bien reflechir avant de decider de l'un ou
- de l'autre, mais vous avez la possibilite de choisir.
-
-* Comment mettre à jour une base de données après avoir modifié le schéma?
-
- Cela dépend de ce qui a été modifié dans le schéma.
-
- * Modification d'une relation non finale
-
- * Modification d'une relation finale
-
-[TO COMPLETE]
-
-* Comment créer un utilisateur anonyme?
-
- Cela vous permet d'acceder a votre site sans avoir besoin de vous authentifier.
- Dans le fichier ``all-in-one.conf`` de votre instance, définir l'utilisateur
- anonyme en initilisant les valeurs des variables suivantes ::
-
- # login of the Erudi user account to use for anonymous user (if you want to
- # allow anonymous)
- anonymous-user=anon
-
- # password of the Erudi user account matching login
- anonymous-password=anon
-
- Vous devez aussi vous assurer que cet utilisateur `anon` existe dans la base
- de données, le plus simple étant de s'identifier sur votre application en
- administrateur et de rajouter l'utilisateur `anon` via l'interface d'administration.
-
-* Quelle est la différence entre `AppRsetObject` et `AppObject` ?
-
- La différence entre la classe `AppRsetObject` et la classe `AppObject` est que
- les instances de la premières sont séléctionnées pour une requête et un "result
- set" et alors que les secondes ne sont séléctionnées qu'en fonction de leur
- identifiant.
--- a/doc/book/fr/MERGE_ME-tut-create-app.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,394 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-Tutoriel : créer votre première application web pour Postgres
-=============================================================
-
-
-[TRANSLATE ME TO FRENCH]
-
-Ce tutoriel va vous guider pas à pas a construire une apllication web
-de gestion de Blog afin de vous faire découvrir les fonctionnalités de
-`CubicWeb`.
-
-Nous supposons que vous avec déjà suivi le guide :ref:`MiseEnPlaceEnv`
-
-
-This tutorial will guide you step by step to build a blog application
-and discover the unique features of `LAX`. It assumes that you followed
-the :ref:`installation` guidelines and that both the `AppEngine SDK` and the
-`LAX` framework are setup on your computer.
-
-Creating a new application
---------------------------
-
-We choosed in this tutorial to develop a blog as an example of web application
-and will go through each required steps/actions to have it running with `LAX`.
-When you installed `LAX`, you saw a directory named ``skel``. Make a copy of
-this directory and call it ``BlogDemo``.
-
-The location of this directory does not matter. But once decided, make sure your ``PYTHONPATH`` is properly set (:ref:`installation`).
-
-
-Defining a schema
------------------
-
-With `LAX`, the schema/datamodel is the core of the application. This is where
-you will define the type of content you have to hanlde in your application.
-
-Let us start with something simple and improve on it iteratively.
-
-In schema.py, we define two entities : ``Blog`` and ``BlogEntry``.
-
-::
-
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
-
- class BlogEntry(EntityType):
- title = String(maxsize=100, required=True)
- publish_date = Date(default='TODAY')
- text = String(fulltextindexed=True)
- category = String(vocabulary=('important','business'))
- entry_of = SubjectRelation('Blog', cardinality='?*')
-
-A Blog has a title and a description. The title is a string that is
-required by the class EntityType and must be less than 50 characters.
-The description is a string that is not constrained.
-
-A BlogEntry has a title, a publish_date and a text. The title is a
-string that is required and must be less than 100 characters. The
-publish_date is a Date with a default value of TODAY, meaning that
-when a BlogEntry is created, its publish_date will be the current day
-unless it is modified. The text is a string that will be indexed in
-the full-text index and has no constraint.
-
-A BlogEntry also has a relationship ``entry_of`` that link it to a
-Blog. The cardinality ``?*`` means that a BlogEntry can be part of
-zero or one Blog (``?`` means `zero or one`) and that a Blog can
-have any number of BlogEntry (``*`` means `any number including
-zero`). For completeness, remember that ``+`` means `one or more`.
-
-Running the application
------------------------
-
-Defining this simple schema is enough to get us started. Make sure you
-followed the setup steps described in detail in the installation
-chapter (especially visiting http://localhost:8080/_load as an
-administrator), then launch the application with the command::
-
- python dev_appserver.py BlogDemo
-
-and point your browser at http://localhost:8080/ (if it is easier for
-you, use the on-line demo at http://lax.appspot.com/).
-
-.. image:: images/lax-book.00-login.fr.png
- :alt: login screen
-
-After you log in, you will see the home page of your application. It
-lists the entity types: Blog and BlogEntry. If these links read
-``blog_plural`` and ``blogentry_plural`` it is because
-internationalization (i18n) is not working for you yet. Please ignore
-this for now.
-
-.. image:: images/lax-book.01-start.fr.png
- :alt: home page
-
-Creating system entities
-------------------------
-You can only create new users if you decided not to use google authentication.
-
-
-[WRITE ME : create users manages permissions etc]
-
-
-
-Creating application entites
-----------------------------
-
-Create a Blog
-~~~~~~~~~~~~~
-
-Let us create a few of these entities. Click on the [+] at the right
-of the link Blog. Call this new Blog ``Tech-blog`` and type in
-``everything about technology`` as the description, then validate the
-form by clicking on ``Validate``.
-
-.. image:: images/lax-book.02-create-blog.fr.png
- :alt: from to create blog
-
-Click on the logo at top left to get back to the home page, then
-follow the Blog link that will list for you all the existing Blog.
-You should be seeing a list with a single item ``Tech-blog`` you
-just created.
-
-.. image:: images/lax-book.03-list-one-blog.fr.png
- :alt: displaying a list of a single blog
-
-Clicking on this item will get you to its detailed description except
-that in this case, there is not much to display besides the name and
-the phrase ``everything about technology``.
-
-.. image:: images/lax-book.04-detail-one-blog.fr.png
- :alt: displaying the detailed view of a blog
-
-Now get back to the home page by clicking on the top-left logo, then
-create a new Blog called ``MyLife`` and get back to the home page
-again to follow the Blog link for the second time. The list now
-has two items.
-
-.. image:: images/lax-book.05-list-two-blog.fr.png
- :alt: displaying a list of two blogs
-
-
-Create a BlogEntry
-~~~~~~~~~~~~~~~~~~
-
-Get back to the home page and click on [+] at the right of the link
-BlogEntry. Call this new entry ``Hello World`` and type in some text
-before clicking on ``Validate``. You added a new blog entry without
-saying to what blog it belongs. There is a box on the left entitled
-``actions``, click on the menu item ``modify``. You are back to the form
-to edit the blog entry you just created, except that the form now has
-another section with a combobox titled ``add relation``. Chose
-``entry_of`` in this menu and a second combobox appears where you pick
-``MyLife``.
-
-You could also have, at the time you started to fill the form for a
-new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
-combobox titled ``add relation`` would have showed up.
-
-.. image:: images/lax-book.06-add-relation-entryof.fr.png
- :alt: editing a blog entry to add a relation to a blog
-
-Validate the changes by clicking ``Validate``. The entity BlogEntry
-that is displayed now includes a link to the entity Blog named
-``MyLife``.
-
-.. image:: images/lax-book.07-detail-one-blogentry.fr.png
- :alt: displaying the detailed view of a blogentry
-
-Remember that all of this was handled by the framework and that the
-only input that was provided so far is the schema. To get a graphical
-view of the schema, run the ``laxctl genschema BlogDemo`` command as
-explained in the installation section and point your browser to the
-URL http://localhost:8080/schema
-
-.. image:: images/lax-book.08-schema.fr.png
- :alt: graphical view of the schema (aka data-model)
-
-Site configuration
-------------------
-
-.. image:: images/lax-book.03-site-config-panel.fr.png
-
-This panel allows you to configure the appearance of your application site.
-Six menus are available and we will go through each of them to explain how
-to use them.
-
-Navigation
-~~~~~~~~~~
-This menu provides you a way to adjust some navigation options depending on
-your needs, such as the number of entities to display by page of results.
-Follows the detailled list of available options :
-
-* navigation.combobox-limit : maximum number of entities to display in related
- combo box (sample format: 23)
-* navigation.page-size : maximum number of objects displayed by page of results
- (sample format: 23)
-* navigation.related-limit : maximum number of related entities to display in
- the primary view (sample format: 23)
-* navigation.short-line-size : maximum number of characters in short description
- (sample format: 23)
-
-UI
-~~
-This menu provides you a way to customize the user interface settings such as
-date format or encoding in the produced html.
-Follows the detailled list of available options :
-
-* ui.date-format : how to format date in the ui ("man strftime" for format description)
-* ui.datetime-format : how to format date and time in the ui ("man strftime" for format
- description)
-* ui.default-text-format : default text format for rich text fields.
-* ui.encoding : user interface encoding
-* ui.fckeditor :should html fields being edited using fckeditor (a HTML WYSIWYG editor).
- You should also select text/html as default text format to actually get fckeditor.
-* ui.float-format : how to format float numbers in the ui
-* ui.language : language of the user interface
-* ui.main-template : id of main template used to render pages
-* ui.site-title : site title, which is displayed right next to the logo in the header
-* ui.time-format : how to format time in the ui ("man strftime" for format description)
-
-
-Actions
-~~~~~~~
-This menu provides a way to configure the context in which you expect the actions
-to be displayed to the user and if you want the action to be visible or not.
-You must have notice that when you view a list of entities, an action box is
-available on the left column which display some actions as well as a drop-down
-menu for more actions.
-
-The context available are :
-
-* mainactions : actions listed in the left box
-* moreactions : actions listed in the `more` menu of the left box
-* addrelated : add actions listed in the left box
-* useractions : actions listed in the first section of drop-down menu
- accessible from the right corner user login link
-* siteactions : actions listed in the second section of drop-down menu
- accessible from the right corner user login link
-* hidden : select this to hide the specific action
-
-Boxes
-~~~~~
-The application has already a pre-defined set of boxes you can use right away.
-This configuration section allows you to place those boxes where you want in the
-application interface to customize it.
-
-The available boxes are :
-
-* actions box : box listing the applicable actions on the displayed data
-
-* boxes_blog_archives_box : box listing the blog archives
-
-* possible views box : box listing the possible views for the displayed data
-
-* rss box : RSS icon to get displayed data as a RSS thread
-
-* search box : search box
-
-* startup views box : box listing the configuration options available for
- the application site, such as `Preferences` and `Site Configuration`
-
-Components
-~~~~~~~~~~
-[WRITE ME]
-
-Contextual components
-~~~~~~~~~~~~~~~~~~~~~
-[WRITE ME]
-
-Set-up a workflow
------------------
-
-Before starting, make sure you refresh your mind by reading [link to
-definition_workflow chapter].
-
-We want to create a workflow to control the quality of the BlogEntry
-submitted on your application. When a BlogEntry is created by a user
-its state should be `submitted`. To be visible to all, it needs to
-be in the state `published`. To move from `submitted` to `published`
-we need a transition that we can name `approve_blogentry`.
-
-We do not want every user to be allowed to change the state of a
-BlogEntry. We need to define a group of user, `moderators`, and
-this group will have appropriate permissions to approve BlogEntry
-to be published and visible to all.
-
-There are two ways to create a workflow, form the user interface,
-and also by defining it in ``migration/postcreate.py``. This script
-is executed each time a new ``./bin/laxctl db-init`` is done.
-If you create the states and transitions through the user interface
-this means that next time you will need to initialize the database
-you will have to re-create all the entities.
-We strongly recommand you create the workflow in ``migration\postcreate.py``
-and we will now show you how.
-The user interface would only be a reference for you to view the states
-and transitions but is not the appropriate interface to define your
-application workflow.
-
-Update the schema
-~~~~~~~~~~~~~~~~~
-To enable a BlogEntry to have a State, we have to define a relation
-``in_state`` in the schema of BlogEntry. Please do as follows, add
-the line ``in_state (...)``::
-
- class BlogEntry(EntityType):
- title = String(maxsize=100, required=True)
- publish_date = Date(default='TODAY')
- text_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- text = String(fulltextindexed=True)
- category = String(vocabulary=('important','business'))
- entry_of = SubjectRelation('Blog', cardinality='?*')
- in_state = SubjectRelation('State', cardinality='1*')
-
-As you updated the schema, you will have re-execute ``./bin/laxctl db-init``
-to initialize the database and migrate your existing entities.
-[WRITE ABOUT MIGRATION]
-
-Create states, transitions and group permissions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At the time the ``postcreate.py`` script is executed, several methods
-can be used. They are all defined in the ``class ServerMigrationHelper``.
-We will only discuss the method we use to create a wrokflow here.
-
-To define our workflow for BlogDemo, please add the following lines
-to ``migration/postcreate.py``::
-
- _ = unicode
-
- moderators = add_entity('CWGroup', name=u"moderators")
-
- submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
- published = add_state(_('published'), 'BlogEntry')
-
- add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
-
- checkpoint()
-
-``add_entity`` is used here to define the new group of users that we
-need to define the transitions, `moderators`.
-If this group required by the transition is not defined before the
-transition is created, it will not create the relation `transition
-require the group moderator`.
-
-``add_state`` expects as the first argument the name of the state you are
-willing to create, then the entity type on which the state can be applied,
-and an optionnal argument to set if the state is the initial state
-of the entity type or not.
-
-``add_transition`` expects as the first argument the name of the
-transition, then the entity type on which we can apply the transition,
-then the list of possible initial states from which the transition
-can be applied, the target state of the transition, and the permissions
-(e.g. list of the groups of users who can apply the transition).
-
-.. image:: images/lax-book.03-transitions-view.fr.png
-
-You can now notice that in the actions box of a BlogEntry, the state
-is now listed as well as the possible transitions from this state
-defined by the workflow. This transition, as defined in the workflow,
-will only being displayed for the users belonging to the group
-moderators of managers.
-
-Change view permission
-~~~~~~~~~~~~~~~~~~~~~~
-
-
-
-Conclusion
-----------
-
-Exercise
-~~~~~~~~
-
-Create new blog entries in ``Tech-blog``.
-
-What we learned
-~~~~~~~~~~~~~~~
-
-Creating a simple schema was enough to set up a new application that
-can store blogs and blog entries.
-
-What is next ?
-~~~~~~~~~~~~~~
-
-Although the application is fully functionnal, its look is very
-basic. In the following section we will learn to create views to
-customize how data is displayed.
-
-
--- a/doc/book/fr/MERGE_ME-tut-create-gae-app.fr.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _tutorielGAE:
-
-Tutoriel : créer votre première application web pour Google AppEngine
-=====================================================================
-
-Ce tutoriel va vous guider pas à pas a construire une apllication web
-de gestion de Blog afin de vous faire découvrir les fonctionnalités de
-`CubicWeb`.
-
-Nous supposons que vous avec déjà suivi le guide :ref:`installationGAE`.
-
-
-Créez une nouvelle application
-------------------------------
-
-Nous choisissons dans ce tutoriel de développer un blog comme un exemple
-d'application web et nous allons expliciter toutes les étapes nécessaires
-à sa réalisation.
-
-::
-
- cubicweb-ctl newgapp blogdemo
-
-`newgapp` est la commande permettant de créer une instance `CubicWeb` pour
-le datastore.
-
-Assurez-vous que votre variable d'environnement ``PYTHONPATH`` est correctement
-initialisée (:ref:`installationGAE`)
-
-Définissez un schéma
---------------------
-
-Le modèle de données ou schéma est au coeur d'une application `CubicWeb`.
-C'est là où vous allez devoir définir le type de contenu que votre application
-devra gérer.
-
-Commençons par un schéma simple que nous améliorerons progressivemment.
-
-Une fois votre instance ``blogdemo`` crée, vous trouverez un fichier ``schema.py``
-contenant la définition des entités suivantes : ``Blog`` and ``BlogEntry``.
-
-::
-
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
-
- class BlogEntry(EntityType):
- title = String(maxsize=100, required=True)
- publish_date = Date(default='TODAY')
- text = String(fulltextindexed=True)
- category = String(vocabulary=('important','business'))
- entry_of = SubjectRelation('Blog', cardinality='?*')
-
-
-Un ``Blog`` a un titre et une description. Le titre est une chaîne
-de caractères requise par la classe parente EntityType and ne doit
-pas excéder 50 caractères. La description est une chaîne de
-caractères sans contraintes.
-
-Une ``BlogEntry`` a un titre, une date de publication et du texte
-étant son contenu. Le titre est une chaîne de caractères qui ne
-doit pas excéder 100 caractères. La date de publication est de type Date et a
-pour valeur par défaut TODAY, ce qui signifie que lorsqu'une
-``BlogEntry`` sera créée, sa date de publication sera la date
-courante a moins de modifier ce champ. Le texte est une chaîne de
-caractères qui sera indexée en plein texte et sans contraintes.
-
-Une ``BlogEntry`` a aussi une relation nommée ``entry_of`` qui la
-relie à un ``Blog``. La cardinalité ``?*`` signifie que BlogEntry
-peut faire partie de zero a un Blog (``?`` signifie `zero ou un`) et
-qu'un Blog peut avoir une infinité de BlogEntry (``*`` signifie
-`n'importe quel nombre incluant zero`).
-Par soucis de complétude, nous rappellerons que ``+`` signifie
-`un ou plus`.
-
-Lancez l'application
---------------------
-
-Définir ce simple schéma est suffisant pour commencer. Assurez-vous
-que vous avez suivi les étapes décrites dans la section installation
-(en particulier visitez http://localhost:8080/_load en tant qu'administrateur
-afin d'initialiser le datastore), puis lancez votre application avec la commande ::
-
- python dev_appserver.py BlogDemo
-
-puis dirigez vous vers http://localhost:8080/ (ou si c'est plus facile
-vous pouvez utiliser la démo en ligne http://lax.appspot.com/).
-[FIXME] -- changer la demo en ligne en quelque chose qui marche (!)
-
-.. image:: images/lax-book.00-login.fr.png
- :alt: login screen
-
-Après vous être authentifié, vous arrivez sur la page d'accueil de votre
-application. Cette page liste les types d'entités accessibles dans votre
-application, en l'occurrence : Blog et Articles. Si vous lisez ``blog_plural``
-et ``blogentry_plural`` cela signifie que l'internationalisation (i18n)
-n'a pas encore fonctionné. Ignorez cela pour le moment.
-
-.. image:: images/lax-book.01-start.fr.png
- :alt: home page
-
-Créez des entités système
--------------------------
-
-Vous ne pourrez créer de nouveaux utilisateurs que dans le cas où vous
-avez choisi de ne pas utiliser l'authentification Google.
-
-
-[WRITE ME : create users manages permissions etc]
-
-
-
-Créez des entités applicatives
-------------------------------
-
-Créez un Blog
-~~~~~~~~~~~~~
-
-Créons à présent quelques entités. Cliquez sur `[+]` sur la
-droite du lien Blog. Appelez cette nouvelle entité Blog ``Tech-Blog``
-et tapez pour la description ``everything about technology``,
-puis validez le formulaire d'édition en cliquant sur le bouton
-``Validate``.
-
-
-.. image:: images/lax-book.02-create-blog.fr.png
- :alt: from to create blog
-
-En cliquant sur le logo situé dans le coin gauche de la fenêtre,
-vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez
-sur le lien Blog, vous devriez voir la liste des entités Blog, en particulier
-celui que vous venez juste de créer ``Tech-Blog``.
-
-.. image:: images/lax-book.03-list-one-blog.fr.png
- :alt: displaying a list of a single blog
-
-Si vous cliquez sur ``Tech-Blog`` vous devriez obtenir une description
-détaillée, ce qui dans notre cas, n'est rien de plus que le titre
-et la phrase ``everything about technology``
-
-
-.. image:: images/lax-book.04-detail-one-blog.fr.png
- :alt: displaying the detailed view of a blog
-
-Maintenant retournons sur la page d'accueil et créons un nouveau
-Blog ``MyLife`` et retournons sur la page d'accueil, puis suivons
-le lien Blog et nous constatons qu'à présent deux blogs sont listés.
-
-.. image:: images/lax-book.05-list-two-blog.fr.png
- :alt: displaying a list of two blogs
-
-Créons un article
-~~~~~~~~~~~~~~~~~
-
-Revenons sur la page d'accueil et cliquons sur `[+]` à droite du lien
-`articles`. Appellons cette nouvelle entité ``Hello World`` et introduisons
-un peut de texte avant de ``Valider``. Vous venez d'ajouter un article
-sans avoir précisé à quel Blog il appartenait. Dans la colonne de gauche
-se trouve une boite intitulé ``actions``, cliquez sur le menu ``modifier``.
-Vous êtes de retour sur le formulaire d'édition de l'article que vous
-venez de créer, à ceci près que ce formulaire a maintenant une nouvelle
-section intitulée ``ajouter relation``. Choisissez ``entry_of`` dans ce menu,
-cela va faire apparaitre une deuxième menu déroulant dans lequel vous
-allez pouvoir séléctionner le Blog ``MyLife``.
-
-Vous auriez pu aussi, au moment où vous avez crée votre article, sélectionner
-``appliquer`` au lieu de ``valider`` et le menu ``ajouter relation`` serait apparu.
-
-.. image:: images/lax-book.06-add-relation-entryof.fr.png
- :alt: editing a blog entry to add a relation to a blog
-
-Validez vos modifications en cliquant sur ``Valider``. L'entité article
-qui est listée contient maintenant un lien vers le Blog auquel il
-appartient, ``MyLife``.
-
-.. image:: images/lax-book.07-detail-one-blogentry.fr.png
- :alt: displaying the detailed view of a blogentry
-
-Rappelez-vous que pour le moment, tout a été géré par la plate-forme
-`CubicWeb` et que la seule chose qui a été fournie est le schéma de
-données. D'ailleurs pour obtenir une vue graphique du schéma, exécutez
-la commande ``laxctl genschema blogdemo`` et vous pourrez visualiser
-votre schéma a l'URL suivante : http://localhost:8080/schema
-
-.. image:: images/lax-book.08-schema.fr.png
- :alt: graphical view of the schema (aka data-model)
-
-
-Change view permission
-~~~~~~~~~~~~~~~~~~~~~~
-
-
-
-Conclusion
-----------
-
-Exercise
-~~~~~~~~
-
-Create new blog entries in ``Tech-blog``.
-
-What we learned
-~~~~~~~~~~~~~~~
-
-Creating a simple schema was enough to set up a new application that
-can store blogs and blog entries.
-
-What is next ?
-~~~~~~~~~~~~~~
-
-Although the application is fully functionnal, its look is very
-basic. In the following section we will learn to create views to
-customize how data is displayed.
-
-
--- a/doc/book/fr/conf.py Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-# -*- coding: utf-8 -*-
-# 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 <http://www.gnu.org/licenses/>.
-"""
-
-"""
-#
-# Cubicweb documentation build configuration file, created by
-# sphinx-quickstart on Fri Oct 31 09:10:36 2008.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# The contents of this file are pickled, so don't put values in the namespace
-# that aren't pickleable (module imports are okay, they're removed automatically).
-#
-# All configuration values have a default value; values that are commented out
-# serve to show the default value.
-
-import sys, os
-
-# If your extensions are in another directory, add it here. If the directory
-# is relative to the documentation root, use os.path.abspath to make it
-# absolute, like shown here.
-#sys.path.append(os.path.abspath('some/directory'))
-
-# General configuration
-# ---------------------
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
-autoclass_content = 'both'
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['.templates']
-
-# The suffix of source filenames.
-source_suffix = '.txt'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General substitutions.
-project = 'Cubicweb'
-copyright = '2008-2010, Logilab'
-
-# The default replacements for |version| and |release|, also used in various
-# other places throughout the built documents.
-#
-# The short X.Y version.
-version = '0.54'
-# The full version, including alpha/beta/rc tags.
-release = '3.0'
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-today_fmt = '%B %d, %Y'
-
-# List of documents that shouldn't be included in the build.
-#unused_docs = []
-
-# List of directories, relative to source directories, that shouldn't be searched
-# for source files.
-#exclude_dirs = []
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-
-# Options for HTML output
-# -----------------------
-
-# The style sheet to use for HTML and HTML Help pages. A file of that name
-# must exist either in Sphinx' static/ path, or in one of the custom paths
-# given in html_static_path.
-html_style = 'sphinx-default.css'
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-html_title = '%s %s' % (project, release)
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (within the static path) to place at the top of
-# the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['.static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-html_use_modindex = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, the reST sources are included in the HTML build as _sources/<name>.
-#html_copy_source = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
-html_file_suffix = '.html'
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Cubicwebdoc'
-
-
-# Options for LaTeX output
-# ------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, document class [howto/manual]).
-latex_documents = [
- ('index', 'Cubicweb.tex', 'Cubicweb Documentation',
- 'Logilab', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_use_modindex = True
Binary file doc/book/fr/images/archi_globale.png has changed
Binary file doc/book/fr/images/blog-demo-first-page.png has changed
Binary file doc/book/fr/images/cbw-add-relation-entryof.fr.png has changed
Binary file doc/book/fr/images/cbw-create-blog.fr.png has changed
Binary file doc/book/fr/images/cbw-detail-one-blogentry.fr.png has changed
Binary file doc/book/fr/images/cbw-list-one-blog.fr.png has changed
Binary file doc/book/fr/images/cbw-list-two-blog.fr.png has changed
Binary file doc/book/fr/images/cbw-schema.fr.png has changed
Binary file doc/book/fr/images/cbw-update-primary-view.fr.png has changed
Binary file doc/book/fr/images/lax-book.00-login.fr.png has changed
Binary file doc/book/fr/images/lax-book.01-start.fr.png has changed
Binary file doc/book/fr/images/lax-book.02-cookie-values.fr.png has changed
Binary file doc/book/fr/images/lax-book.02-create-blog.fr.png has changed
Binary file doc/book/fr/images/lax-book.03-list-one-blog.fr.png has changed
Binary file doc/book/fr/images/lax-book.03-site-config-panel.fr.png has changed
Binary file doc/book/fr/images/lax-book.03-state-submitted.fr.png has changed
Binary file doc/book/fr/images/lax-book.03-transitions-view.fr.png has changed
Binary file doc/book/fr/images/lax-book.04-detail-one-blog.fr.png has changed
Binary file doc/book/fr/images/lax-book.05-list-two-blog.fr.png has changed
Binary file doc/book/fr/images/lax-book.06-add-relation-entryof.fr.png has changed
Binary file doc/book/fr/images/lax-book.06-header-no-login.fr.png has changed
Binary file doc/book/fr/images/lax-book.06-main-template-layout.fr.png has changed
Binary file doc/book/fr/images/lax-book.06-main-template-logo.fr.png has changed
Binary file doc/book/fr/images/lax-book.06-simple-main-template.fr.png has changed
Binary file doc/book/fr/images/lax-book.07-detail-one-blogentry.fr.png has changed
Binary file doc/book/fr/images/lax-book.08-schema.fr.png has changed
Binary file doc/book/fr/images/lax-book.09-new-view-blogentry.fr.png has changed
Binary file doc/book/fr/images/lax-book.10-blog-with-two-entries.fr.png has changed
Binary file doc/book/fr/images/login-form.png has changed
Binary file doc/book/fr/images/main_template_layout.png has changed
Binary file doc/book/fr/images/server-class-diagram.png has changed
--- a/doc/book/fr/index.txt Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _contents:
-
-==========================================================
-`CubicWeb`, le web sémantique est un jeu de construction !
-==========================================================
-
-`CubicWeb` permet de déployer rapidement des applications Web de gestion de
-connaissances, à partir du schéma des données manipulées.
-
-Avec un moteur piloté par un modèle de données, un
-véritable langage de requête, un mécanisme de sélection des vues pour la
-génération automatique de HTML/XML/text, des composants ré-utilisables,
-`CubicWeb` constitue une solution clefs en main pour promouvoir un
-développement rapide et efficace.
-
-Si vous aimez Python et sa librairie standard, vous avez des chances d'aimer
-`CubicWeb` qui inclut sa propre librairie standard de cubes.
-
-Pour les curieux, un :ref:`Overview` et de sa simplicité.
-
-Pour les impatients, :ref:`MiseEnPlaceEnv`.
-
-.. _Logilab: http://www.logilab.fr/
-.. _GoogleAppEngine: http://code.google.com/appengine/
-
-
-
-Table des matieres
-==================
-
-
-.. toctree::
- :maxdepth: 1
-
- 01-introduction.fr.txt
- 02-foundation.fr.txt
- 03-setup.fr.txt
- 04-define-schema.fr.txt
- 05-define-views.fr.txt
- 06-define-workflows.fr.txt
- 07-data-as-objects.fr.txt
- 08-site-config.fr.txt
- 09-instance-config.fr.txt
- 10-form-management.fr.txt
- 11-ajax-json.fr.txt
- 12-ui-components.fr.txt
- 13-security.fr.txt
- 14-hooks.fr.txt
- 15-notifications.fr.txt
- 16-rql.fr.txt
- 17-migration.fr.txt
- 18-tests.fr.txt
- 19-i18n.fr.txt
- 20-google-appengine.fr.txt
- 21-references.fr.txt
- 22-faq.fr.txt
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
--- a/doc/book/fr/makefile Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-MKHTML=mkdoc
-MKHTMLOPTS=--doctype article --target html --stylesheet standard
-SRC=.
-
-TXTFILES:= $(wildcard *.txt)
-TARGET := $(TXTFILES:.txt=.html)
-
-# You can set these sphinx variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-
-# Internal variables for sphinx
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-
-.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " all to make standalone HTML files, developer manual and API doc"
- @echo " apidoc to make API doc"
- @echo " html to make standalone HTML files"
- @echo "--- "
- @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " changes to make an overview over all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
-
-clean:
- rm -rf apidoc/
- rm -f *.html
- -rm -rf build/*
-
-all: ${TARGET} apidoc html
-
-%.html: %.txt
- ${MKHTML} ${MKHTMLOPTS} $<
-
-#apydoc:
-# epydoc --html -o epydoc/ -n ../server/*.py ../core/*.py ../common/*.py ../server/*/*.py ../modpython/*/*.py ../common/*/*.py
-apidoc:
- epydoc --html -o apidoc -n "cubicweb" --exclude=setup --exclude=__pkginfo__ ../../../
-
-# run sphinx ###
-html:
- mkdir -p build/html build/doctrees
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html
- @echo
- @echo "Build finished. The HTML pages are in build/html."
-
-pickle:
- mkdir -p build/pickle build/doctrees
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
- @echo
- @echo "Build finished; now you can process the pickle files or run"
- @echo " sphinx-web build/pickle"
- @echo "to start the sphinx-web server."
-
-web: pickle
-
-htmlhelp:
- mkdir -p build/htmlhelp build/doctrees
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in build/htmlhelp."
-
-latex:
- mkdir -p build/latex build/doctrees
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
- @echo
- @echo "Build finished; the LaTeX files are in build/latex."
- @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
- "run these through (pdf)latex."
-
-changes:
- mkdir -p build/changes build/doctrees
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
- @echo
- @echo "The overview file is in build/changes."
-
-linkcheck:
- mkdir -p build/linkcheck build/doctrees
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in build/linkcheck/output.txt."
--- a/doc/tools/pyjsrest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/doc/tools/pyjsrest.py Fri Mar 11 09:46:45 2011 +0100
@@ -4,6 +4,7 @@
"""
from __future__ import with_statement
+import os.path as osp
import sys, os, getopt, re
def clean_comment(match):
@@ -71,34 +72,48 @@
if rst_dir is None and len(args) != 1:
rst_dir = 'apidocs'
js_dir = opts.get('--jspath') or opts.get('-p')
- if not os.path.exists(os.path.join(rst_dir)):
- os.makedirs(os.path.join(rst_dir))
+ if not osp.exists(osp.join(rst_dir)):
+ os.makedirs(osp.join(rst_dir))
- f_index = open(os.path.join(rst_dir, 'index.rst'), 'wb')
- f_index.write('''
+ index = set()
+ for js_path, js_dirs, js_files in os.walk(js_dir):
+ rst_path = re.sub('%s%s*' % (js_dir, osp.sep), '', js_path)
+ for js_file in js_files:
+ if not js_file.endswith('.js'):
+ continue
+ if js_file in FILES_TO_IGNORE:
+ continue
+ if not osp.exists(osp.join(rst_dir, rst_path)):
+ os.makedirs(osp.join(rst_dir, rst_path))
+ rst_content = extract_rest(js_path, js_file)
+ filename = osp.join(rst_path, js_file[:-3])
+ # add to index
+ index.add(filename)
+ # save rst file
+ with open(osp.join(rst_dir, filename) + '.rst', 'wb') as f_rst:
+ f_rst.write(rst_content)
+ stream = open(osp.join(rst_dir, 'index.rst'), 'w')
+ stream.write('''
.. toctree::
:maxdepth: 1
-'''
-)
- for js_path, js_dirs, js_files in os.walk(js_dir):
- rst_path = re.sub('%s%s*' % (js_dir, os.path.sep), '', js_path)
- for js_file in js_files:
- if not js_file.endswith('.js'):
- continue
- if not os.path.exists(os.path.join(rst_dir, rst_path)):
- os.makedirs(os.path.join(rst_dir, rst_path))
- rst_content = extract_rest(js_path, js_file)
- filename = os.path.join(rst_path, js_file[:-3])
- # add to index
- f_index.write(' %s\n' % filename)
- # save rst file
- with open(os.path.join(rst_dir, filename) + '.rst', 'wb') as f_rst:
- f_rst.write(rst_content)
- f_index.close()
+''')
+ # first write expected files in order
+ for fileid in INDEX_IN_ORDER:
+ try:
+ index.remove(fileid)
+ except:
+ raise Exception(
+ 'Bad file id %s referenced in INDEX_IN_ORDER in %s, '
+ 'fix this please' % (fileid, __file__))
+ stream.write(' %s\n' % fileid)
+ # append remaining, by alphabetical order
+ for fileid in sorted(index):
+ stream.write(' %s\n' % fileid)
+ stream.close()
def extract_rest(js_dir, js_file):
- js_filepath = os.path.join(js_dir, js_file)
+ js_filepath = osp.join(js_dir, js_file)
filecontent = open(js_filepath, 'U').read()
comments = get_doc_comments(filecontent)
rst = rest_title(js_file, 0)
@@ -106,5 +121,50 @@
rst += '\n\n'.join(comments)
return rst
+INDEX_IN_ORDER = [
+ 'cubicweb',
+ 'cubicweb.python',
+ 'cubicweb.htmlhelpers',
+ 'cubicweb.ajax',
+
+ 'cubicweb.lazy',
+ 'cubicweb.tabs',
+ 'cubicweb.ajax.box',
+ 'cubicweb.facets',
+ 'cubicweb.widgets',
+ 'cubicweb.image',
+ 'cubicweb.flot',
+ 'cubicweb.calendar',
+ 'cubicweb.preferences',
+ 'cubicweb.edition',
+ 'cubicweb.reledit',
+ 'cubicweb.iprogress',
+ 'cubicweb.rhythm',
+ 'cubicweb.gmap',
+ 'cubicweb.timeline-ext',
+]
+
+FILES_TO_IGNORE = set([
+ 'jquery.js',
+ 'jquery.treeview.js',
+ 'jquery.json.js',
+ 'jquery.tablesorter.js',
+ 'jquery.timePicker.js',
+ 'jquery.flot.js',
+ 'jquery.corner.js',
+ 'jquery.ui.js',
+ 'ui.core.js',
+ 'ui.tabs.js',
+ 'ui.slider.js',
+ 'excanvas.js',
+ 'gmap.utility.labeledmarker.js',
+
+ 'cubicweb.fckcwconfig.js',
+ 'cubicweb.fckcwconfig-full.js',
+ 'cubicweb.goa.js',
+ 'cubicweb.compat.js',
+ 'cubicweb.timeline-bundle.js',
+ ])
+
if __name__ == '__main__':
parse_js_files()
--- a/entities/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entities/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -35,6 +35,11 @@
__regid__ = 'Any'
__implements__ = ()
+ @classmethod
+ def cw_create_url(cls, req, **kwargs):
+ """ return the url of the entity creation form for this entity type"""
+ return req.build_url('add/%s' % cls.__regid__, **kwargs)
+
# meta data api ###########################################################
def dc_title(self):
--- a/entities/adapters.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entities/adapters.py Fri Mar 11 09:46:45 2011 +0100
@@ -27,8 +27,10 @@
from logilab.mtconverter import TransformError
from logilab.common.decorators import cached
+from cubicweb import ValidationError
from cubicweb.view import EntityAdapter, implements_adapter_compat
-from cubicweb.selectors import implements, is_instance, relation_possible
+from cubicweb.selectors import (implements, is_instance, relation_possible,
+ match_exception)
from cubicweb.interfaces import IDownloadable, ITree, IProgress, IMileStone
@@ -66,6 +68,7 @@
class INotifiableAdapter(EntityAdapter):
+ __needs_bw_compat__ = True
__regid__ = 'INotifiable'
__select__ = is_instance('Any')
@@ -155,6 +158,7 @@
class IDownloadableAdapter(EntityAdapter):
"""interface for downloadable entities"""
+ __needs_bw_compat__ = True
__regid__ = 'IDownloadable'
__select__ = implements(IDownloadable, warn=False) # XXX for bw compat, else should be abstract
@@ -206,6 +210,7 @@
.. automethod: children_rql
.. automethod: path
"""
+ __needs_bw_compat__ = True
__regid__ = 'ITree'
__select__ = implements(ITree, warn=False) # XXX for bw compat, else should be abstract
@@ -333,8 +338,8 @@
for entity in child.cw_adapt_to('ITree').prefixiter(_done):
yield entity
+ @implements_adapter_compat('ITree')
@cached
- @implements_adapter_compat('ITree')
def path(self):
"""Returns the list of eids from the root object to this object."""
path = []
@@ -364,6 +369,7 @@
You should at least override progress_info an in_progress methods on concret
implementations.
"""
+ __needs_bw_compat__ = True
__regid__ = 'IProgress'
__select__ = implements(IProgress, warn=False) # XXX for bw compat, should be abstract
@@ -432,6 +438,7 @@
class IMileStoneAdapter(IProgressAdapter):
+ __needs_bw_compat__ = True
__regid__ = 'IMileStone'
__select__ = implements(IMileStone, warn=False) # XXX for bw compat, should be abstract
@@ -463,3 +470,24 @@
def contractors(self):
"""returns the list of persons supposed to work on this task"""
raise NotImplementedError
+
+
+# error handling adapters ######################################################
+
+from cubicweb import UniqueTogetherError
+
+class IUserFriendlyError(EntityAdapter):
+ __regid__ = 'IUserFriendlyError'
+ __abstract__ = True
+ def __init__(self, *args, **kwargs):
+ self.exc = kwargs.pop('exc')
+ super(IUserFriendlyError, self).__init__(*args, **kwargs)
+
+
+class IUserFriendlyUniqueTogether(IUserFriendlyError):
+ __select__ = match_exception(UniqueTogetherError)
+ def raise_user_exception(self):
+ etype, rtypes = self.exc.args
+ msg = self._cw._('violates unique_together constraints (%s)') % (
+ ', '.join([self._cw._(rtype) for rtype in rtypes]))
+ raise ValidationError(self.entity.eid, dict((col, msg) for col in rtypes))
--- a/entities/schemaobjs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entities/schemaobjs.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,12 +15,16 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""schema definition related entities
+"""schema definition related entities"""
-"""
__docformat__ = "restructuredtext en"
+import re
+from socket import gethostname
+
from logilab.common.decorators import cached
+from logilab.common.textutils import text_to_dict
+from logilab.common.configuration import OptionError
from yams.schema import role_name
@@ -30,6 +34,58 @@
from cubicweb.entities import AnyEntity, fetch_config
+class _CWSourceCfgMixIn(object):
+ @property
+ def dictconfig(self):
+ return self.config and text_to_dict(self.config) or {}
+
+ def update_config(self, skip_unknown=False, **config):
+ from cubicweb.server import SOURCE_TYPES
+ from cubicweb.server.serverconfig import (SourceConfiguration,
+ generate_source_config)
+ cfg = self.dictconfig
+ cfg.update(config)
+ options = SOURCE_TYPES[self.type].options
+ sconfig = SourceConfiguration(self._cw.vreg.config, options=options)
+ for opt, val in cfg.iteritems():
+ try:
+ sconfig.set_option(opt, val)
+ except OptionError:
+ if skip_unknown:
+ continue
+ raise
+ cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
+ self.set_attributes(config=cfgstr)
+
+
+class CWSource(_CWSourceCfgMixIn, AnyEntity):
+ __regid__ = 'CWSource'
+ fetch_attrs, fetch_order = fetch_config(['name', 'type'])
+
+ @property
+ def host_config(self):
+ dictconfig = self.dictconfig
+ host = gethostname()
+ for hostcfg in self.host_configs:
+ if hostcfg.match(host):
+ self.info('matching host config %s for source %s',
+ hostcfg.match_host, self.name)
+ dictconfig.update(hostcfg.dictconfig)
+ return dictconfig
+
+ @property
+ def host_configs(self):
+ return self.reverse_cw_host_config_of
+
+
+class CWSourceHostConfig(_CWSourceCfgMixIn, AnyEntity):
+ __regid__ = 'CWSourceHostConfig'
+ fetch_attrs, fetch_order = fetch_config(['match_host', 'config'])
+
+ def match(self, hostname):
+ return re.match(self.match_host, hostname)
+
+
class CWEType(AnyEntity):
__regid__ = 'CWEType'
fetch_attrs, fetch_order = fetch_config(['name'])
@@ -83,7 +139,7 @@
rtype = self.name
stype = rdef.stype
otype = rdef.otype
- msg = self._cw._("can't set inlined=%(inlined)s, "
+ msg = self._cw._("can't set inlined=True, "
"%(stype)s %(rtype)s %(otype)s "
"has cardinality=%(card)s")
raise ValidationError(self.eid, {qname: msg % locals()})
--- a/entities/test/unittest_wfobjs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entities/test/unittest_wfobjs.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,21 +15,19 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+
from __future__ import with_statement
+
+from cubicweb import ValidationError
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb import ValidationError
from cubicweb.server.session import security_enabled
+
def add_wf(self, etype, name=None, default=False):
if name is None:
name = etype
- wf = self.execute('INSERT Workflow X: X name %(n)s', {'n': unicode(name)}).get_entity(0, 0)
- self.execute('SET WF workflow_of ET WHERE WF eid %(wf)s, ET name %(et)s',
- {'wf': wf.eid, 'et': etype})
- if default:
- self.execute('SET ET default_workflow WF WHERE WF eid %(wf)s, ET name %(et)s',
- {'wf': wf.eid, 'et': etype})
- return wf
+ return self.shell().add_workflow(name, etype, default=default,
+ ensure_workflowable=False)
def parse_hist(wfhist):
return [(ti.previous_state.name, ti.new_state.name,
@@ -55,8 +53,9 @@
wf.add_state(u'foo', initial=True)
self.commit()
wf.add_state(u'foo')
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'name-subject': 'workflow already have a state of that name'})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a state of that name'})
# no pb if not in the same workflow
wf2 = add_wf(self, 'Company')
foo = wf2.add_state(u'foo', initial=True)
@@ -65,8 +64,9 @@
bar = wf.add_state(u'bar')
self.commit()
bar.set_attributes(name=u'foo')
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'name-subject': 'workflow already have a state of that name'})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a state of that name'})
def test_duplicated_transition(self):
wf = add_wf(self, 'Company')
@@ -74,8 +74,9 @@
bar = wf.add_state(u'bar')
wf.add_transition(u'baz', (foo,), bar, ('managers',))
wf.add_transition(u'baz', (bar,), foo)
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
# no pb if not in the same workflow
wf2 = add_wf(self, 'Company')
foo = wf.add_state(u'foo', initial=True)
@@ -86,8 +87,9 @@
biz = wf.add_transition(u'biz', (bar,), foo)
self.commit()
biz.set_attributes(name=u'baz')
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'name-subject': 'workflow already have a transition of that name'})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
class WorkflowTC(CubicWebTC):
@@ -125,6 +127,7 @@
# fetch the entity using the new session
trs = list(cnx.user().cw_adapt_to('IWorkflowable').possible_transitions())
self.assertEqual(len(trs), 0)
+ cnx.close()
def _test_manager_deactivate(self, user):
iworkflowable = user.cw_adapt_to('IWorkflowable')
@@ -150,10 +153,10 @@
s = wf.add_state(u'foo', initial=True)
self.commit()
with security_enabled(self.session, write=False):
- ex = self.assertRaises(ValidationError, self.session.execute,
- 'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
- {'x': self.user().eid, 's': s.eid})
- self.assertEqual(ex.errors, {'in_state-subject': "state doesn't belong to entity's workflow. "
+ with self.assertRaises(ValidationError) as cm:
+ self.session.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
+ {'x': self.user().eid, 's': s.eid})
+ self.assertEqual(cm.exception.errors, {'in_state-subject': "state doesn't belong to entity's workflow. "
"You may want to set a custom workflow for this entity first."})
def test_fire_transition(self):
@@ -197,18 +200,19 @@
cnx = self.login('tutu')
req = self.request()
iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
- ex = self.assertRaises(ValidationError,
- iworkflowable.fire_transition, 'deactivate')
- self.assertEqual(ex.errors, {'by_transition-subject': "transition may not be fired"})
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('deactivate')
+ self.assertEqual(cm.exception.errors, {'by_transition-subject': "transition may not be fired"})
cnx.close()
cnx = self.login('member')
req = self.request()
iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('deactivate')
cnx.commit()
- ex = self.assertRaises(ValidationError,
- iworkflowable.fire_transition, 'activate')
- self.assertEqual(ex.errors, {'by_transition-subject': "transition may not be fired"})
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('activate')
+ self.assertEqual(cm.exception.errors, {'by_transition-subject': "transition may not be fired"})
+ cnx.close()
def test_fire_transition_owned_by(self):
self.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
@@ -280,9 +284,9 @@
self.assertEqual(iworkflowable.subworkflow_input_transition(), None)
# force back to swfstate1 is impossible since we can't any more find
# subworkflow input transition
- ex = self.assertRaises(ValidationError,
- iworkflowable.change_state, swfstate1, u'gadget')
- self.assertEqual(ex.errors, {'to_state-subject': "state doesn't belong to entity's workflow"})
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.change_state(swfstate1, u'gadget')
+ self.assertEqual(cm.exception.errors, {'to_state-subject': "state doesn't belong to entity's workflow"})
self.rollback()
# force back to state1
iworkflowable.change_state('state1', u'gadget')
@@ -317,8 +321,9 @@
state3 = mwf.add_state(u'state3')
mwf.add_wftransition(u'swftr1', swf, state1,
[(swfstate2, state2), (swfstate2, state3)])
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'subworkflow_exit-subject': u"can't have multiple exits on the same state"})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'subworkflow_exit-subject': u"can't have multiple exits on the same state"})
def test_swf_fire_in_a_row(self):
# sub-workflow
@@ -435,8 +440,9 @@
wf.add_state('asleep')
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': self.member.eid})
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'custom_workflow-subject': u'workflow has no initial state'})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'custom_workflow-subject': u'workflow has no initial state'})
def test_custom_wf_bad_etype(self):
"""try to set a custom workflow which doesn't apply to entity type"""
@@ -444,8 +450,9 @@
wf.add_state('asleep', initial=True)
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': self.member.eid})
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors, {'custom_workflow-subject': u"workflow isn't a workflow for this type"})
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors, {'custom_workflow-subject': u"workflow isn't a workflow for this type"})
def test_del_custom_wf(self):
"""member in some state shared by the new workflow, nothing has to be
@@ -590,9 +597,9 @@
cnx = self.login('stduser')
user = cnx.user(self.session)
iworkflowable = user.cw_adapt_to('IWorkflowable')
- ex = self.assertRaises(ValidationError,
- iworkflowable.fire_transition, 'activate')
- self.assertEqual(self._cleanup_msg(ex.errors['by_transition-subject']),
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('activate')
+ self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
u"transition isn't allowed from")
cnx.close()
@@ -600,9 +607,9 @@
cnx = self.login('stduser')
user = cnx.user(self.session)
iworkflowable = user.cw_adapt_to('IWorkflowable')
- ex = self.assertRaises(ValidationError,
- iworkflowable.fire_transition, 'dummy')
- self.assertEqual(self._cleanup_msg(ex.errors['by_transition-subject']),
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('dummy')
+ self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
u"transition isn't allowed from")
cnx.close()
@@ -614,9 +621,9 @@
iworkflowable.fire_transition('deactivate')
cnx.commit()
session.set_pool()
- ex = self.assertRaises(ValidationError,
- iworkflowable.fire_transition, 'deactivate')
- self.assertEqual(self._cleanup_msg(ex.errors['by_transition-subject']),
+ with self.assertRaises(ValidationError) as cm:
+ iworkflowable.fire_transition('deactivate')
+ self.assertEqual(self._cleanup_msg(cm.exception.errors['by_transition-subject']),
u"transition isn't allowed from")
cnx.rollback()
session.set_pool()
--- a/entities/wfobjs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entities/wfobjs.py Fri Mar 11 09:46:45 2011 +0100
@@ -489,7 +489,7 @@
try:
return self.current_state.name
except AttributeError:
- self.warning('entity %s has no state', self)
+ self.warning('entity %s has no state', self.entity)
return None
@property
--- a/entity.py Fri Dec 10 12:17:18 2010 +0100
+++ b/entity.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -19,7 +19,6 @@
__docformat__ = "restructuredtext en"
-from copy import copy
from warnings import warn
from logilab.common import interface
@@ -313,6 +312,9 @@
return '<Entity %s %s %s at %s>' % (
self.e_schema, self.eid, self.cw_attr_cache.keys(), id(self))
+ def __cmp__(self, other):
+ raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+
def __json_encode__(self):
"""custom json dumps hook to dump the entity's eid
which is not part of dict structure itself
@@ -321,107 +323,6 @@
dumpable['eid'] = self.eid
return dumpable
- def __nonzero__(self):
- return True
-
- def __hash__(self):
- return id(self)
-
- def __cmp__(self, other):
- raise NotImplementedError('comparison not implemented for %s' % self.__class__)
-
- def __contains__(self, key):
- return key in self.cw_attr_cache
-
- def __iter__(self):
- return iter(self.cw_attr_cache)
-
- def __getitem__(self, key):
- if key == 'eid':
- warn('[3.7] entity["eid"] is deprecated, use entity.eid instead',
- DeprecationWarning, stacklevel=2)
- return self.eid
- return self.cw_attr_cache[key]
-
- def __setitem__(self, attr, value):
- """override __setitem__ to update self.edited_attributes.
-
- Typically, a before_[update|add]_hook could do::
-
- entity['generated_attr'] = generated_value
-
- and this way, edited_attributes will be updated accordingly. Also, add
- the attribute to skip_security since we don't want to check security
- for such attributes set by hooks.
- """
- if attr == 'eid':
- warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead',
- DeprecationWarning, stacklevel=2)
- self.eid = value
- else:
- self.cw_attr_cache[attr] = value
- # don't add attribute into skip_security if already in edited
- # attributes, else we may accidentaly skip a desired security check
- if hasattr(self, 'edited_attributes') and \
- attr not in self.edited_attributes:
- self.edited_attributes.add(attr)
- self._cw_skip_security_attributes.add(attr)
-
- def __delitem__(self, attr):
- """override __delitem__ to update self.edited_attributes on cleanup of
- undesired changes introduced in the entity's dict. For example, see the
- code snippet below from the `forge` cube:
-
- .. sourcecode:: python
-
- edited = self.entity.edited_attributes
- has_load_left = 'load_left' in edited
- if 'load' in edited and self.entity.load_left is None:
- self.entity.load_left = self.entity['load']
- elif not has_load_left and edited:
- # cleanup, this may cause undesired changes
- del self.entity['load_left']
-
- """
- del self.cw_attr_cache[attr]
- if hasattr(self, 'edited_attributes'):
- self.edited_attributes.remove(attr)
-
- def clear(self):
- self.cw_attr_cache.clear()
-
- def get(self, key, default=None):
- return self.cw_attr_cache.get(key, default)
-
- def setdefault(self, attr, default):
- """override setdefault to update self.edited_attributes"""
- value = self.cw_attr_cache.setdefault(attr, default)
- # don't add attribute into skip_security if already in edited
- # attributes, else we may accidentaly skip a desired security check
- if hasattr(self, 'edited_attributes') and \
- attr not in self.edited_attributes:
- self.edited_attributes.add(attr)
- self._cw_skip_security_attributes.add(attr)
- return value
-
- def pop(self, attr, default=_marker):
- """override pop to update self.edited_attributes on cleanup of
- undesired changes introduced in the entity's dict. See `__delitem__`
- """
- if default is _marker:
- value = self.cw_attr_cache.pop(attr)
- else:
- value = self.cw_attr_cache.pop(attr, default)
- if hasattr(self, 'edited_attributes') and attr in self.edited_attributes:
- self.edited_attributes.remove(attr)
- return value
-
- def update(self, values):
- """override update to update self.edited_attributes. See `__setitem__`
- """
- for attr, value in values.items():
- self[attr] = value # use self.__setitem__ implementation
-
def cw_adapt_to(self, interface):
"""return an adapter the entity to the given interface name.
@@ -592,12 +493,6 @@
# entity cloning ##########################################################
- def cw_copy(self):
- thecopy = copy(self)
- thecopy.cw_attr_cache = copy(self.cw_attr_cache)
- thecopy._cw_related_cache = {}
- return thecopy
-
def copy_relations(self, ceid): # XXX cw_copy_relations
"""copy relations of the object with the given eid on this
object (this method is called on the newly created copy, and
@@ -682,7 +577,7 @@
rdef = rschema.rdef(self.e_schema, attrschema)
if not self._cw.user.matching_groups(rdef.get_groups('read')) \
or (attrschema.type == 'Password' and skip_pwd):
- self[attr] = None
+ self.cw_attr_cache[attr] = None
continue
yield attr
@@ -738,10 +633,14 @@
# if some outer join are included to fetch inlined relations
rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected),
','.join(rql))
- rset = self._cw.execute(rql, {'x': self.eid}, build_descr=False)[0]
+ try:
+ rset = self._cw.execute(rql, {'x': self.eid}, build_descr=False)[0]
+ except IndexError:
+ raise Exception('unable to fetch attributes for entity with eid %s'
+ % self.eid)
# handle attributes
for i in xrange(1, lastattr):
- self[str(selected[i-1][0])] = rset[i]
+ self.cw_attr_cache[str(selected[i-1][0])] = rset[i]
# handle relations
for i in xrange(lastattr, len(rset)):
rtype, role = selected[i-1][0]
@@ -761,7 +660,7 @@
:param name: name of the attribute to get
"""
try:
- value = self.cw_attr_cache[name]
+ return self.cw_attr_cache[name]
except KeyError:
if not self.cw_is_saved():
return None
@@ -769,21 +668,20 @@
try:
rset = self._cw.execute(rql, {'x': self.eid})
except Unauthorized:
- self[name] = value = None
+ self.cw_attr_cache[name] = value = None
else:
assert rset.rowcount <= 1, (self, rql, rset.rowcount)
try:
- self[name] = value = rset.rows[0][0]
+ self.cw_attr_cache[name] = value = rset.rows[0][0]
except IndexError:
# probably a multisource error
self.critical("can't get value for attribute %s of entity with eid %s",
name, self.eid)
if self.e_schema.destination(name) == 'String':
- # XXX (syt) imo emtpy string is better
- self[name] = value = self._cw._('unaccessible')
+ self.cw_attr_cache[name] = value = self._cw._('unaccessible')
else:
- self[name] = value = None
- return value
+ self.cw_attr_cache[name] = value = None
+ return value
def related(self, rtype, role='subject', limit=None, entities=False): # XXX .cw_related
"""returns a resultset of related entities
@@ -799,7 +697,7 @@
if not self.has_eid():
if entities:
return []
- return self.empty_rset()
+ return self._cw.empty_rset()
rql = self.cw_related_rql(rtype, role)
rset = self._cw.execute(rql, {'x': self.eid})
self.cw_set_relation_cache(rtype, role, rset)
@@ -987,7 +885,6 @@
you should override this method to clear them as well.
"""
# clear attributes cache
- haseid = 'eid' in self
self._cw_completed = False
self.cw_attr_cache.clear()
# clear relations cache
@@ -1014,9 +911,9 @@
kwargs)
kwargs.pop('x')
# update current local object _after_ the rql query to avoid
- # interferences between the query execution itself and the
- # edited_attributes / skip_security_attributes machinery
- self.update(kwargs)
+ # interferences between the query execution itself and the cw_edited /
+ # skip_security machinery
+ self.cw_attr_cache.update(kwargs)
def set_relations(self, **kwargs): # XXX cw_set_relations
"""add relations to the given object. To set a relation where this entity
@@ -1047,58 +944,13 @@
self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
{'x': self.eid}, **kwargs)
- # server side utilities ###################################################
-
- def _cw_rql_set_value(self, attr, value):
- """call by rql execution plan when some attribute is modified
-
- don't use dict api in such case since we don't want attribute to be
- added to skip_security_attributes.
-
- This method is for internal use, you should not use it.
- """
- self.cw_attr_cache[attr] = value
+ # server side utilities ####################################################
def _cw_clear_local_perm_cache(self, action):
for rqlexpr in self.e_schema.get_rqlexprs(action):
self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
- @property
- def _cw_skip_security_attributes(self):
- try:
- return self.__cw_skip_security_attributes
- except:
- self.__cw_skip_security_attributes = set()
- return self.__cw_skip_security_attributes
-
- def _cw_set_defaults(self):
- """set default values according to the schema"""
- for attr, value in self.e_schema.defaults():
- if not self.cw_attr_cache.has_key(attr):
- self[str(attr)] = value
-
- def _cw_check(self, creation=False):
- """check this entity against its schema. Only final relation
- are checked here, constraint on actual relations are checked in hooks
- """
- # necessary since eid is handled specifically and yams require it to be
- # in the dictionary
- if self._cw is None:
- _ = unicode
- else:
- _ = self._cw._
- if creation:
- # on creations, we want to check all relations, especially
- # required attributes
- relations = [rschema for rschema in self.e_schema.subject_relations()
- if rschema.final and rschema.type != 'eid']
- elif hasattr(self, 'edited_attributes'):
- relations = [self._cw.vreg.schema.rschema(rtype)
- for rtype in self.edited_attributes]
- else:
- relations = None
- self.e_schema.check(self, creation=creation, _=_,
- relations=relations)
+ # deprecated stuff #########################################################
@deprecated('[3.9] use entity.cw_attr_value(attr)')
def get_value(self, name):
@@ -1120,7 +972,7 @@
def set_related_cache(self, rtype, role, rset):
self.cw_set_relation_cache(rtype, role, rset)
- @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role, rset)')
+ @deprecated('[3.9] use entity.cw_clear_relation_cache(rtype, role)')
def clear_related_cache(self, rtype=None, role=None):
self.cw_clear_relation_cache(rtype, role)
@@ -1128,6 +980,109 @@
def related_rql(self, rtype, role='subject', targettypes=None):
return self.cw_related_rql(rtype, role, targettypes)
+ @property
+ @deprecated('[3.10] use entity.cw_edited')
+ def edited_attributes(self):
+ return self.cw_edited
+
+ @property
+ @deprecated('[3.10] use entity.cw_edited.skip_security')
+ def skip_security_attributes(self):
+ return self.cw_edited.skip_security
+
+ @property
+ @deprecated('[3.10] use entity.cw_edited.skip_security')
+ def _cw_skip_security_attributes(self):
+ return self.cw_edited.skip_security
+
+ @property
+ @deprecated('[3.10] use entity.cw_edited.querier_pending_relations')
+ def querier_pending_relations(self):
+ return self.cw_edited.querier_pending_relations
+
+ @deprecated('[3.10] use key in entity.cw_attr_cache')
+ def __contains__(self, key):
+ return key in self.cw_attr_cache
+
+ @deprecated('[3.10] iter on entity.cw_attr_cache')
+ def __iter__(self):
+ return iter(self.cw_attr_cache)
+
+ @deprecated('[3.10] use entity.cw_attr_cache[attr]')
+ def __getitem__(self, key):
+ if key == 'eid':
+ warn('[3.7] entity["eid"] is deprecated, use entity.eid instead',
+ DeprecationWarning, stacklevel=2)
+ return self.eid
+ return self.cw_attr_cache[key]
+
+ @deprecated('[3.10] use entity.cw_attr_cache.get(attr[, default])')
+ def get(self, key, default=None):
+ return self.cw_attr_cache.get(key, default)
+
+ @deprecated('[3.10] use entity.cw_attr_cache.clear()')
+ def clear(self):
+ self.cw_attr_cache.clear()
+ # XXX clear cw_edited ?
+
+ @deprecated('[3.10] use entity.cw_edited[attr] = value or entity.cw_attr_cache[attr] = value')
+ def __setitem__(self, attr, value):
+ """override __setitem__ to update self.cw_edited.
+
+ Typically, a before_[update|add]_hook could do::
+
+ entity['generated_attr'] = generated_value
+
+ and this way, cw_edited will be updated accordingly. Also, add
+ the attribute to skip_security since we don't want to check security
+ for such attributes set by hooks.
+ """
+ if attr == 'eid':
+ warn('[3.7] entity["eid"] = value is deprecated, use entity.eid = value instead',
+ DeprecationWarning, stacklevel=2)
+ self.eid = value
+ else:
+ try:
+ self.cw_edited[attr] = value
+ except AttributeError:
+ self.cw_attr_cache[attr] = value
+
+ @deprecated('[3.10] use del entity.cw_edited[attr]')
+ def __delitem__(self, attr):
+ """override __delitem__ to update self.cw_edited on cleanup of
+ undesired changes introduced in the entity's dict. For example, see the
+ code snippet below from the `forge` cube:
+
+ .. sourcecode:: python
+
+ edited = self.entity.cw_edited
+ has_load_left = 'load_left' in edited
+ if 'load' in edited and self.entity.load_left is None:
+ self.entity.load_left = self.entity['load']
+ elif not has_load_left and edited:
+ # cleanup, this may cause undesired changes
+ del self.entity['load_left']
+ """
+ del self.cw_edited[attr]
+
+ @deprecated('[3.10] use entity.cw_edited.setdefault(attr, default)')
+ def setdefault(self, attr, default):
+ """override setdefault to update self.cw_edited"""
+ return self.cw_edited.setdefault(attr, default)
+
+ @deprecated('[3.10] use entity.cw_edited.pop(attr[, default])')
+ def pop(self, attr, *args):
+ """override pop to update self.cw_edited on cleanup of
+ undesired changes introduced in the entity's dict. See `__delitem__`
+ """
+ return self.cw_edited.pop(attr, *args)
+
+ @deprecated('[3.10] use entity.cw_edited.update(values)')
+ def update(self, values):
+ """override update to update self.cw_edited. See `__setitem__`
+ """
+ self.cw_edited.update(values)
+
# attribute and relation descriptors ##########################################
@@ -1143,8 +1098,12 @@
return self
return eobj.cw_attr_value(self._attrname)
+ @deprecated('[3.10] assign to entity.cw_attr_cache[attr] or entity.cw_edited[attr]')
def __set__(self, eobj, value):
- eobj[self._attrname] = value
+ if hasattr(eobj, 'cw_edited') and not eobj.cw_edited.saved:
+ eobj.cw_edited[self._attrname] = value
+ else:
+ eobj.cw_attr_cache[self._attrname] = value
class Relation(object):
--- a/etwist/server.py Fri Dec 10 12:17:18 2010 +0100
+++ b/etwist/server.py Fri Mar 11 09:46:45 2011 +0100
@@ -408,15 +408,17 @@
website = server.Site(root_resource)
# serve it via standard HTTP on port set in the configuration
port = config['port'] or 8080
- reactor.listenTCP(port, website)
+ interface = config['interface']
+ reactor.listenTCP(port, website, interface=interface)
if not config.debugmode:
if sys.platform == 'win32':
raise ConfigurationError("Under windows, you must use the service management "
"commands (e.g : 'net start my_instance)'")
from logilab.common.daemon import daemonize
LOGGER.info('instance started in the background on %s', root_resource.base_url)
- if daemonize(config['pid-file']):
- return # child process
+ whichproc = daemonize(config['pid-file'], umask=config['umask'])
+ if whichproc: # 1 = orig process, 2 = first fork, None = second fork (eg daemon process)
+ return whichproc # parent process
root_resource.init_publisher() # before changing uid
if config['uid'] is not None:
try:
--- a/etwist/test/unittest_server.py Fri Dec 10 12:17:18 2010 +0100
+++ b/etwist/test/unittest_server.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,6 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.etwist.server import host_prefixed_baseurl
@@ -25,9 +22,9 @@
class HostPrefixedBaseURLTC(CubicWebTC):
def _check(self, baseurl, host, waited):
- self.assertEquals(host_prefixed_baseurl(baseurl, host), waited,
- 'baseurl %s called through host %s should be considered as %s'
- % (baseurl, host, waited))
+ self.assertEqual(host_prefixed_baseurl(baseurl, host), waited,
+ 'baseurl %s called through host %s should be considered as %s'
+ % (baseurl, host, waited))
def test1(self):
self._check('http://www.cubicweb.org/hg/', 'code.cubicweb.org',
--- a/etwist/twconfig.py Fri Dec 10 12:17:18 2010 +0100
+++ b/etwist/twconfig.py Fri Mar 11 09:46:45 2011 +0100
@@ -23,7 +23,6 @@
* the "all-in-one" configuration to get a web instance running in a twisted
web server integrating a repository server in the same process (only available
if the repository part of the software is installed
-
"""
__docformat__ = "restructuredtext en"
@@ -31,8 +30,10 @@
from logilab.common.configuration import Method
+from cubicweb.cwconfig import CONFIGURATIONS
from cubicweb.web.webconfig import WebConfiguration, merge_options
+
class TwistedConfiguration(WebConfiguration):
"""web instance (in a twisted web server) client of a RQL server"""
name = 'twisted'
@@ -45,6 +46,12 @@
'help': 'http server port number (default to 8080)',
'group': 'web', 'level': 0,
}),
+ ('interface',
+ {'type' : 'string',
+ 'default': "",
+ 'help': 'http server address on which to listen (default to everywhere)',
+ 'group': 'web', 'level': 1,
+ }),
('max-post-length',
{'type' : 'bytes',
'default': '100MB',
@@ -76,12 +83,6 @@
the repository rather than the user running the command',
'group': 'main', 'level': WebConfiguration.mode == 'system'
}),
- ('session-time',
- {'type' : 'time',
- 'default': '30min',
- 'help': 'session expiration time, default to 30 minutes',
- 'group': 'main', 'level': 1,
- }),
('pyro-server',
{'type' : 'yn',
# pyro is only a recommends by default, so don't activate it here
@@ -98,6 +99,9 @@
from socket import gethostname
return 'http://%s:%s/' % (self['host'] or gethostname(), self['port'] or 8080)
+
+CONFIGURATIONS.append(TwistedConfiguration)
+
try:
from cubicweb.server.serverconfig import ServerConfiguration
@@ -114,5 +118,8 @@
"""tell if pyro is activated for the in memory repository"""
return self['pyro-server']
+
+ CONFIGURATIONS.append(AllInOneConfiguration)
+
except ImportError:
pass
--- a/etwist/twctl.py Fri Dec 10 12:17:18 2010 +0100
+++ b/etwist/twctl.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,7 +21,7 @@
from cubicweb.web.webctl import WebCreateHandler
# trigger configuration registration
-import cubicweb.etwist.twconfig # pylint: disable-msg=W0611
+import cubicweb.etwist.twconfig # pylint: disable=W0611
class TWCreateHandler(WebCreateHandler):
cfgname = 'twisted'
@@ -32,7 +32,7 @@
def start_server(self, config):
from cubicweb.etwist import server
- server.run(config)
+ return server.run(config)
class TWStopHandler(CommandHandler):
cmdname = 'stop'
--- a/ext/rest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/ext/rest.py Fri Mar 11 09:46:45 2011 +0100
@@ -47,6 +47,8 @@
from cubicweb import UnknownEid
from cubicweb.ext.html4zope import Writer
+from cubicweb.web.views import vid_from_rset # XXX better not to import c.w.views here...
+
# We provide our own parser as an attempt to get rid of
# state machine reinstanciation
@@ -93,6 +95,23 @@
return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
**options)], []
+def rql_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
+ """:rql:`Any X,Y WHERE X is CWUser, X login Y:table`"""
+ _cw = inliner.document.settings.context._cw
+ text = text.strip()
+ if ':' in text:
+ rql, vid = text.rsplit(u':', 1)
+ rql = rql.strip()
+ else:
+ rql, vid = text, None
+ _cw.ensure_ro_rql(rql)
+ rset = _cw.execute(rql, {'userid': _cw.user.eid})
+ if vid is None:
+ vid = vid_from_rset(_cw, rset, _cw.vreg.schema)
+ view = _cw.vreg['views'].select(vid, _cw, rset=rset)
+ content = view.render()
+ set_classes(options)
+ return [nodes.raw('', content, format='html')], []
def winclude_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
@@ -288,6 +307,7 @@
return
_INITIALIZED = True
register_canonical_role('eid', eid_reference_role)
+ register_canonical_role('rql', rql_role)
directives.register_directive('winclude', winclude_directive)
if pygments_directive is not None:
directives.register_directive('sourcecode', pygments_directive)
--- a/ext/test/unittest_rest.py Fri Dec 10 12:17:18 2010 +0100
+++ b/ext/test/unittest_rest.py Fri Mar 11 09:46:45 2011 +0100
@@ -56,5 +56,17 @@
''')
+
+ def test_rql_role_with_vid(self):
+ context = self.context()
+ out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:table`')
+ self.assert_(out.endswith('<a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a>'
+ '</td></tr></tbody></table></div>\n</div>\n</p>\n'))
+
+ def test_rql_role_without_vid(self):
+ context = self.context()
+ out = rest_publish(context, ':rql:`Any X WHERE X is CWUser`')
+ self.assertEqual(out, u'<p><h1>cwuser_plural</h1><div class="section"><a href="http://testing.fr/cubicweb/cwuser/admin" title="">admin</a></div><div class="section"><a href="http://testing.fr/cubicweb/cwuser/anon" title="">anon</a></div></p>\n')
+
if __name__ == '__main__':
unittest_main()
--- a/hooks/integrity.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/integrity.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,8 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""Core hooks: check for data integrity according to the instance'schema
validity
+"""
-"""
__docformat__ = "restructuredtext en"
from threading import Lock
@@ -31,7 +31,6 @@
from cubicweb.selectors import is_instance
from cubicweb.uilib import soup2xhtml
from cubicweb.server import hook
-from cubicweb.server.hook import set_operation
# special relations that don't have to be checked for integrity, usually
# because they are handled internally by hooks (so we trust ourselves)
@@ -62,27 +61,25 @@
_UNIQUE_CONSTRAINTS_LOCK.release()
class _ReleaseUniqueConstraintsOperation(hook.Operation):
- def commit_event(self):
- pass
def postcommit_event(self):
_release_unique_cstr_lock(self.session)
def rollback_event(self):
_release_unique_cstr_lock(self.session)
-class _CheckRequiredRelationOperation(hook.LateOperation):
- """checking relation cardinality has to be done after commit in
- case the relation is being replaced
+class _CheckRequiredRelationOperation(hook.DataOperationMixIn,
+ hook.LateOperation):
+ """checking relation cardinality has to be done after commit in case the
+ relation is being replaced
"""
+ containercls = list
role = key = base_rql = None
def precommit_event(self):
- session =self.session
+ session = self.session
pendingeids = session.transaction_data.get('pendingeids', ())
pendingrtypes = session.transaction_data.get('pendingrtypes', ())
- # poping key is not optional: if further operation trigger new deletion
- # of relation, we'll need a new operation
- for eid, rtype in session.transaction_data.pop(self.key):
+ for eid, rtype in self.get_data():
# recheck pending eids / relation types
if eid in pendingeids:
continue
@@ -100,13 +97,11 @@
class _CheckSRelationOp(_CheckRequiredRelationOperation):
"""check required subject relation"""
role = 'subject'
- key = '_cwisrel'
base_rql = 'Any O WHERE S eid %%(x)s, S %s O'
class _CheckORelationOp(_CheckRequiredRelationOperation):
"""check required object relation"""
role = 'object'
- key = '_cwiorel'
base_rql = 'Any S WHERE O eid %%(x)s, S %s O'
@@ -115,15 +110,32 @@
category = 'integrity'
-class CheckCardinalityHook(IntegrityHook):
+class CheckCardinalityHookBeforeDeleteRelation(IntegrityHook):
"""check cardinalities are satisfied"""
- __regid__ = 'checkcard'
- events = ('after_add_entity', 'before_delete_relation')
+ __regid__ = 'checkcard_before_delete_relation'
+ events = ('before_delete_relation',)
def __call__(self):
- getattr(self, self.event)()
+ rtype = self.rtype
+ if rtype in DONT_CHECK_RTYPES_ON_DEL:
+ return
+ session = self._cw
+ eidfrom, eidto = self.eidfrom, self.eidto
+ pendingrdefs = session.transaction_data.get('pendingrdefs', ())
+ if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
+ return
+ card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
+ if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
+ _CheckSRelationOp.get_instance(self._cw).add_data((eidfrom, rtype))
+ if card[1] in '1+' and not session.deleted_in_transaction(eidto):
+ _CheckORelationOp.get_instance(self._cw).add_data((eidto, rtype))
- def after_add_entity(self):
+class CheckCardinalityHookAfterAddEntity(IntegrityHook):
+ """check cardinalities are satisfied"""
+ __regid__ = 'checkcard_after_add_entity'
+ events = ('after_add_entity',)
+
+ def __call__(self):
eid = self.entity.eid
eschema = self.entity.e_schema
for rschema, targetschemas, role in eschema.relation_definitions():
@@ -133,11 +145,10 @@
rdef = rschema.role_rdef(eschema, targetschemas[0], role)
if rdef.role_cardinality(role) in '1+':
if role == 'subject':
- set_operation(self._cw, '_cwisrel', (eid, rschema.type),
- _CheckSRelationOp, list)
+ op = _CheckSRelationOp.get_instance(self._cw)
else:
- set_operation(self._cw, '_cwiorel', (eid, rschema.type),
- _CheckORelationOp, list)
+ op = _CheckORelationOp.get_instance(self._cw)
+ op.add_data((eid, rschema.type))
def before_delete_relation(self):
rtype = self.rtype
@@ -150,26 +161,24 @@
return
card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
- set_operation(self._cw, '_cwisrel', (eidfrom, rtype),
- _CheckSRelationOp, list)
+ _CheckSRelationOp.get_instance(self._cw).add_data((eidfrom, rtype))
if card[1] in '1+' and not session.deleted_in_transaction(eidto):
- set_operation(self._cw, '_cwiorel', (eidto, rtype),
- _CheckORelationOp, list)
+ _CheckORelationOp.get_instance(self._cw).add_data((eidto, rtype))
-class _CheckConstraintsOp(hook.LateOperation):
+class _CheckConstraintsOp(hook.DataOperationMixIn, hook.LateOperation):
""" check a new relation satisfy its constraints """
-
+ containercls = list
def precommit_event(self):
session = self.session
- for values in session.transaction_data.pop('check_constraints_op'):
+ for values in self.get_data():
eidfrom, rtype, eidto, constraints = values
# first check related entities have not been deleted in the same
# transaction
if session.deleted_in_transaction(eidfrom):
- return
+ continue
if session.deleted_in_transaction(eidto):
- return
+ continue
for constraint in constraints:
# XXX
# * lock RQLConstraint as well?
@@ -183,9 +192,6 @@
self.critical('can\'t check constraint %s, not supported',
constraint)
- def commit_event(self):
- pass
-
class CheckConstraintHook(IntegrityHook):
"""check the relation satisfy its constraints
@@ -201,9 +207,8 @@
constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
'constraints')
if constraints:
- hook.set_operation(self._cw, 'check_constraints_op',
- (self.eidfrom, self.rtype, self.eidto, tuple(constraints)),
- _CheckConstraintsOp, list)
+ _CheckConstraintsOp.get_instance(self._cw).add_data(
+ (self.eidfrom, self.rtype, self.eidto, constraints))
class CheckAttributeConstraintHook(IntegrityHook):
@@ -217,14 +222,13 @@
def __call__(self):
eschema = self.entity.e_schema
- for attr in self.entity.edited_attributes:
+ for attr in self.entity.cw_edited:
if eschema.subjrels[attr].final:
constraints = [c for c in eschema.rdef(attr).constraints
if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
if constraints:
- hook.set_operation(self._cw, 'check_constraints_op',
- (self.entity.eid, attr, None, tuple(constraints)),
- _CheckConstraintsOp, list)
+ _CheckConstraintsOp.get_instance(self._cw).add_data(
+ (self.entity.eid, attr, None, constraints))
class CheckUniqueHook(IntegrityHook):
@@ -234,9 +238,8 @@
def __call__(self):
entity = self.entity
eschema = entity.e_schema
- for attr in entity.edited_attributes:
+ for attr, val in entity.cw_edited.iteritems():
if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
- val = entity[attr]
if val is None:
continue
rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
@@ -255,18 +258,17 @@
events = ('before_delete_entity', 'before_update_entity')
def __call__(self):
- if self.event == 'before_delete_entity' and self.entity.name == 'owners':
+ entity = self.entity
+ if self.event == 'before_delete_entity' and entity.name == 'owners':
msg = self._cw._('can\'t be deleted')
- raise ValidationError(self.entity.eid, {None: msg})
- elif self.event == 'before_update_entity' and \
- 'name' in self.entity.edited_attributes:
- newname = self.entity.pop('name')
- oldname = self.entity.name
+ raise ValidationError(entity.eid, {None: msg})
+ elif self.event == 'before_update_entity' \
+ and 'name' in entity.cw_edited:
+ oldname, newname = entity.cw_edited.oldnewvalue('name')
if oldname == 'owners' and newname != oldname:
qname = role_name('name', 'subject')
msg = self._cw._('can\'t be changed')
- raise ValidationError(self.entity.eid, {qname: msg})
- self.entity['name'] = newname
+ raise ValidationError(entity.eid, {qname: msg})
class TidyHtmlFields(IntegrityHook):
@@ -277,15 +279,16 @@
def __call__(self):
entity = self.entity
metaattrs = entity.e_schema.meta_attributes()
+ edited = entity.cw_edited
for metaattr, (metadata, attr) in metaattrs.iteritems():
- if metadata == 'format' and attr in entity.edited_attributes:
+ if metadata == 'format' and attr in edited:
try:
- value = entity[attr]
+ value = edited[attr]
except KeyError:
continue # no text to tidy
if isinstance(value, unicode): # filter out None and Binary
if getattr(entity, str(metaattr)) == 'text/html':
- entity[attr] = soup2xhtml(value, self._cw.encoding)
+ edited[attr] = soup2xhtml(value, self._cw.encoding)
class StripCWUserLoginHook(IntegrityHook):
@@ -295,41 +298,51 @@
events = ('before_add_entity', 'before_update_entity',)
def __call__(self):
- user = self.entity
- if 'login' in user.edited_attributes and user.login:
- user.login = user.login.strip()
+ login = self.entity.cw_edited.get('login')
+ if login:
+ self.entity.cw_edited['login'] = login.strip()
# 'active' integrity hooks: you usually don't want to deactivate them, they are
# not really integrity check, they maintain consistency on changes
-class _DelayedDeleteOp(hook.Operation):
+class _DelayedDeleteOp(hook.DataOperationMixIn, hook.Operation):
"""delete the object of composite relation except if the relation has
actually been redirected to another composite
"""
- key = base_rql = None
+ base_rql = None
def precommit_event(self):
session = self.session
pendingeids = session.transaction_data.get('pendingeids', ())
neweids = session.transaction_data.get('neweids', ())
- # poping key is not optional: if further operation trigger new deletion
- # of composite relation, we'll need a new operation
- for eid, rtype in session.transaction_data.pop(self.key):
+ eids_by_etype_rtype = {}
+ for eid, rtype in self.get_data():
# don't do anything if the entity is being created or deleted
if not (eid in pendingeids or eid in neweids):
etype = session.describe(eid)[0]
- session.execute(self.base_rql % (etype, rtype), {'x': eid})
+ key = (etype, rtype)
+ if key not in eids_by_etype_rtype:
+ eids_by_etype_rtype[key] = [str(eid)]
+ else:
+ eids_by_etype_rtype[key].append(str(eid))
+ for (etype, rtype), eids in eids_by_etype_rtype.iteritems():
+ # quite unexpectedly, not deleting too many entities at a time in
+ # this operation benefits to the exec speed (possibly on the RQL
+ # parsing side)
+ start = 0
+ incr = 500
+ while start < len(eids):
+ session.execute(self.base_rql % (etype, ','.join(eids[start:start+incr]), rtype))
+ start += incr
class _DelayedDeleteSEntityOp(_DelayedDeleteOp):
"""delete orphan subject entity of a composite relation"""
- key = '_cwiscomp'
- base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT X %s Y'
+ base_rql = 'DELETE %s X WHERE X eid IN (%s), NOT X %s Y'
class _DelayedDeleteOEntityOp(_DelayedDeleteOp):
"""check required object relation"""
- key = '_cwiocomp'
- base_rql = 'DELETE %s X WHERE X eid %%(x)s, NOT Y %s X'
+ base_rql = 'DELETE %s X WHERE X eid IN (%s), NOT Y %s X'
class DeleteCompositeOrphanHook(hook.Hook):
@@ -349,8 +362,8 @@
composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
'composite')
if composite == 'subject':
- set_operation(self._cw, '_cwiocomp', (self.eidto, self.rtype),
- _DelayedDeleteOEntityOp)
+ _DelayedDeleteOEntityOp.get_instance(self._cw).add_data(
+ (self.eidto, self.rtype))
elif composite == 'object':
- set_operation(self._cw, '_cwiscomp', (self.eidfrom, self.rtype),
- _DelayedDeleteSEntityOp)
+ _DelayedDeleteSEntityOp.get_instance(self._cw).add_data(
+ (self.eidfrom, self.rtype))
--- a/hooks/metadata.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/metadata.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -23,7 +23,6 @@
from cubicweb.selectors import is_instance
from cubicweb.server import hook
-from cubicweb.server.utils import eschema_eid
class MetaDataHook(hook.Hook):
@@ -41,11 +40,12 @@
def __call__(self):
timestamp = datetime.now()
- self.entity.setdefault('creation_date', timestamp)
- self.entity.setdefault('modification_date', timestamp)
+ edited = self.entity.cw_edited
+ edited.setdefault('creation_date', timestamp)
+ edited.setdefault('modification_date', timestamp)
if not self._cw.get_shared_data('do-not-insert-cwuri'):
- cwuri = u'%seid/%s' % (self._cw.base_url(), self.entity.eid)
- self.entity.setdefault('cwuri', cwuri)
+ cwuri = u'%s%s' % (self._cw.base_url(), self.entity.eid)
+ edited.setdefault('cwuri', cwuri)
class UpdateMetaAttrsHook(MetaDataHook):
@@ -60,14 +60,14 @@
# XXX to be really clean, we should turn off modification_date update
# explicitly on each command where we do not want that behaviour.
if not self._cw.vreg.config.repairing:
- self.entity.setdefault('modification_date', datetime.now())
+ self.entity.cw_edited.setdefault('modification_date', datetime.now())
-class _SetCreatorOp(hook.Operation):
+class SetCreatorOp(hook.DataOperationMixIn, hook.Operation):
def precommit_event(self):
session = self.session
- for eid in session.transaction_data.pop('set_creator_op'):
+ for eid in self.get_data():
if session.deleted_in_transaction(eid):
# entity have been created and deleted in the same transaction
continue
@@ -76,30 +76,6 @@
session.add_relation(eid, 'created_by', session.user.eid)
-class SetIsHook(MetaDataHook):
- """create a new entity -> set is and is_instance_of relations
-
- those relations are inserted using sql so they are not hookable.
- """
- __regid__ = 'setis'
- events = ('after_add_entity',)
-
- def __call__(self):
- if hasattr(self.entity, '_cw_recreating'):
- return
- session = self._cw
- entity = self.entity
- try:
- session.system_sql('INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, entity.e_schema)))
- except IndexError:
- # during schema serialization, skip
- return
- for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
- session.system_sql('INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_eid(session, eschema)))
-
-
class SetOwnershipHook(MetaDataHook):
"""create a new entity -> set owner and creator metadata"""
__regid__ = 'setowner'
@@ -108,11 +84,12 @@
def __call__(self):
if not self._cw.is_internal_session:
self._cw.add_relation(self.entity.eid, 'owned_by', self._cw.user.eid)
- hook.set_operation(self._cw, 'set_creator_op', self.entity.eid, _SetCreatorOp)
+ SetCreatorOp.get_instance(self._cw).add_data(self.entity.eid)
+
-class _SyncOwnersOp(hook.Operation):
+class SyncOwnersOp(hook.DataOperationMixIn, hook.Operation):
def precommit_event(self):
- for compositeeid, composedeid in self.session.transaction_data.pop('sync_owners_op'):
+ for compositeeid, composedeid in self.get_data():
self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
'NOT EXISTS(X owned_by U, X eid %(x)s)',
{'c': compositeeid, 'x': composedeid})
@@ -132,9 +109,9 @@
eidfrom, eidto = self.eidfrom, self.eidto
composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite')
if composite == 'subject':
- hook.set_operation(self._cw, 'sync_owners_op', (eidfrom, eidto), _SyncOwnersOp)
+ SyncOwnersOp.get_instance(self._cw).add_data( (eidfrom, eidto) )
elif composite == 'object':
- hook.set_operation(self._cw, 'sync_owners_op', (eidto, eidfrom), _SyncOwnersOp)
+ SyncOwnersOp.get_instance(self._cw).add_data( (eidto, eidfrom) )
class FixUserOwnershipHook(MetaDataHook):
--- a/hooks/notification.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/notification.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,6 +22,7 @@
from logilab.common.textutils import normalize_text
+from cubicweb import RegistryNotFound
from cubicweb.selectors import is_instance
from cubicweb.server import hook
from cubicweb.sobjects.supervising import SupervisionMailOp
@@ -42,8 +43,14 @@
category = 'notification'
def select_view(self, vid, rset, row=0, col=0):
- return self._cw.vreg['views'].select_or_none(vid, self._cw, rset=rset,
- row=row, col=col)
+ try:
+ return self._cw.vreg['views'].select_or_none(vid, self._cw, rset=rset,
+ row=row, col=col)
+ except RegistryNotFound: # can happen in some config
+ # (e.g. repo only config with no
+ # notification views registered by
+ # the instance's cubes)
+ return None
class StatusChangeHook(NotificationHook):
@@ -69,7 +76,6 @@
'comment': comment, 'previous_state': entity.previous_state.name,
'current_state': entity.new_state.name})
-
class RelationChangeHook(NotificationHook):
__regid__ = 'notifyrelationchange'
events = ('before_add_relation', 'after_add_relation',
@@ -125,7 +131,7 @@
if session.added_in_transaction(self.entity.eid):
return # entity is being created
# then compute changes
- attrs = [k for k in self.entity.edited_attributes
+ attrs = [k for k in self.entity.cw_edited
if not k in self.skip_attrs]
if not attrs:
return
@@ -140,7 +146,7 @@
rset = session.execute(rql, {'x': self.entity.eid})
for i, attr in enumerate(attrs):
oldvalue = rset[0][i]
- newvalue = self.entity[attr]
+ newvalue = self.entity.cw_edited[attr]
if oldvalue != newvalue:
thisentitychanges.add((attr, oldvalue, newvalue))
if thisentitychanges:
@@ -168,8 +174,9 @@
if self._cw.added_in_transaction(self.entity.eid):
return False
if self.entity.e_schema == 'CWUser':
- if not (self.entity.edited_attributes - frozenset(('eid', 'modification_date',
- 'last_login_time'))):
+ if not (frozenset(self.entity.cw_edited)
+ - frozenset(('eid', 'modification_date',
+ 'last_login_time'))):
# don't record last_login_time update which are done
# automatically at login time
return False
--- a/hooks/security.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/security.py Fri Mar 11 09:46:45 2011 +0100
@@ -31,12 +31,9 @@
eschema = entity.e_schema
# ._cw_skip_security_attributes is there to bypass security for attributes
# set by hooks by modifying the entity's dictionnary
- dontcheck = entity._cw_skip_security_attributes
if editedattrs is None:
- try:
- editedattrs = entity.edited_attributes
- except AttributeError:
- editedattrs = entity # XXX unexpected
+ editedattrs = entity.cw_edited
+ dontcheck = editedattrs.skip_security
for attr in editedattrs:
if attr in dontcheck:
continue
@@ -46,39 +43,26 @@
if creation and not rdef.permissions.get('update'):
continue
rdef.check_perm(session, 'update', eid=eid)
- # don't update dontcheck until everything went fine: see usage in
- # after_update_entity, where if we got an Unauthorized at hook time, we will
- # retry and commit time
- dontcheck |= frozenset(editedattrs)
-class _CheckEntityPermissionOp(hook.LateOperation):
+class CheckEntityPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
def precommit_event(self):
- #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
session = self.session
- for values in session.transaction_data.pop('check_entity_perm_op'):
- entity = session.entity_from_eid(values[0])
- action = values[1]
+ for eid, action, edited in self.get_data():
+ entity = session.entity_from_eid(eid)
entity.cw_check_perm(action)
- check_entity_attributes(session, entity, values[2:],
- creation=self.creation)
-
- def commit_event(self):
- pass
+ check_entity_attributes(session, entity, edited,
+ creation=(action == 'add'))
-class _CheckRelationPermissionOp(hook.LateOperation):
+class CheckRelationPermissionOp(hook.DataOperationMixIn, hook.LateOperation):
def precommit_event(self):
session = self.session
- for args in session.transaction_data.pop('check_relation_perm_op'):
- action, rschema, eidfrom, eidto = args
+ for action, rschema, eidfrom, eidto in self.get_data():
rdef = rschema.rdef(session.describe(eidfrom)[0],
session.describe(eidto)[0])
rdef.check_perm(session, action, fromeid=eidfrom, toeid=eidto)
- def commit_event(self):
- pass
-
@objectify_selector
@lltrace
@@ -98,9 +82,8 @@
events = ('after_add_entity',)
def __call__(self):
- hook.set_operation(self._cw, 'check_entity_perm_op',
- (self.entity.eid, 'add') + tuple(self.entity.edited_attributes),
- _CheckEntityPermissionOp, creation=True)
+ CheckEntityPermissionOp.get_instance(self._cw).add_data(
+ (self.entity.eid, 'add', self.entity.cw_edited) )
class AfterUpdateEntitySecurityHook(SecurityHook):
@@ -115,11 +98,10 @@
except Unauthorized:
self.entity._cw_clear_local_perm_cache('update')
# save back editedattrs in case the entity is reedited later in the
- # same transaction, which will lead to edited_attributes being
+ # same transaction, which will lead to cw_edited being
# overwritten
- hook.set_operation(self._cw, 'check_entity_perm_op',
- (self.entity.eid, 'update') + tuple(self.entity.edited_attributes),
- _CheckEntityPermissionOp, creation=False)
+ CheckEntityPermissionOp.get_instance(self._cw).add_data(
+ (self.entity.eid, 'update', self.entity.cw_edited) )
class BeforeDelEntitySecurityHook(SecurityHook):
@@ -156,9 +138,8 @@
return
rschema = self._cw.repo.schema[self.rtype]
if self.rtype in ON_COMMIT_ADD_RELATIONS:
- hook.set_operation(self._cw, 'check_relation_perm_op',
- ('add', rschema, self.eidfrom, self.eidto),
- _CheckRelationPermissionOp)
+ CheckRelationPermissionOp.get_instance(self._cw).add_data(
+ ('add', rschema, self.eidfrom, self.eidto) )
else:
rdef = rschema.rdef(self._cw.describe(self.eidfrom)[0],
self._cw.describe(self.eidto)[0])
--- a/hooks/syncschema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/syncschema.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -30,7 +30,6 @@
from yams import buildobjs as ybo, schema2sql as y2sql
from logilab.common.decorators import clear_cache
-from logilab.common.testlib import mock_object
from cubicweb import ValidationError
from cubicweb.selectors import is_instance
@@ -121,18 +120,21 @@
def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
errors = {}
# don't use getattr(entity, attr), we would get the modified value if any
- for attr in entity.edited_attributes:
+ for attr in entity.cw_edited:
if attr in ro_attrs:
- newval = entity.pop(attr)
- origval = getattr(entity, attr)
+ origval, newval = entity.cw_edited.oldnewvalue(attr)
if newval != origval:
errors[attr] = session._("can't change the %s attribute") % \
display_name(session, attr)
- entity[attr] = newval
if errors:
raise ValidationError(entity.eid, errors)
+class _MockEntity(object): # XXX use a named tuple with python 2.6
+ def __init__(self, eid):
+ self.eid = eid
+
+
class SyncSchemaHook(hook.Hook):
"""abstract class for schema synchronization hooks (in the `syncschema`
category)
@@ -259,12 +261,17 @@
gmap = group_mapping(session)
cmap = ss.cstrtype_mapping(session)
for rtype in (META_RTYPES - VIRTUAL_RTYPES):
- rschema = schema[rtype]
+ try:
+ rschema = schema[rtype]
+ except:
+ if rtype == 'cw_source':
+ continue # XXX 3.10 migration
+ raise
sampletype = rschema.subjects()[0]
desttype = rschema.objects()[0]
rdef = copy(rschema.rdef(sampletype, desttype))
- rdef.subject = mock_object(eid=entity.eid)
- mock = mock_object(eid=None)
+ rdef.subject = _MockEntity(eid=entity.eid)
+ mock = _MockEntity(eid=None)
ss.execschemarql(session.execute, mock, ss.rdef2rql(rdef, cmap, gmap))
def revertprecommit_event(self):
@@ -311,11 +318,10 @@
return # watched changes to final relation type are unexpected
session = self.session
if 'fulltext_container' in self.values:
+ op = UpdateFTIndexOp.get_instance(session)
for subjtype, objtype in rschema.rdefs:
- hook.set_operation(session, 'fti_update_etypes', subjtype,
- UpdateFTIndexOp)
- hook.set_operation(session, 'fti_update_etypes', objtype,
- UpdateFTIndexOp)
+ op.add_data(subjtype)
+ op.add_data(objtype)
# update the in-memory schema first
self.oldvalues = dict( (attr, getattr(rschema, attr)) for attr in self.values)
self.rschema.__dict__.update(self.values)
@@ -607,8 +613,7 @@
syssource.update_rdef_null_allowed(self.session, rdef)
self.null_allowed_changed = True
if 'fulltextindexed' in self.values:
- hook.set_operation(session, 'fti_update_etypes', rdef.subject,
- UpdateFTIndexOp)
+ UpdateFTIndexOp.get_instance(session).add_data(rdef.subject)
def revertprecommit_event(self):
if self.rdef is None:
@@ -700,14 +705,14 @@
syssource.update_rdef_unique(session, rdef)
self.unique_changed = True
+
class CWUniqueTogetherConstraintAddOp(MemSchemaOperation):
entity = None # make pylint happy
def precommit_event(self):
session = self.session
prefix = SQL_PREFIX
table = '%s%s' % (prefix, self.entity.constraint_of[0].name)
- cols = ['%s%s' % (prefix, r.rtype.name)
- for r in self.entity.relations]
+ cols = ['%s%s' % (prefix, r.name) for r in self.entity.relations]
dbhelper= session.pool.source('system').dbhelper
sqls = dbhelper.sqls_create_multicol_unique_index(table, cols)
for sql in sqls:
@@ -717,9 +722,10 @@
def postcommit_event(self):
eschema = self.session.vreg.schema.schema_by_eid(self.entity.constraint_of[0].eid)
- attrs = [r.rtype.name for r in self.entity.relations]
+ attrs = [r.name for r in self.entity.relations]
eschema._unique_together.append(attrs)
+
class CWUniqueTogetherConstraintDelOp(MemSchemaOperation):
entity = oldcstr = None # for pylint
cols = [] # for pylint
@@ -742,6 +748,7 @@
if set(ut) != cols]
eschema._unique_together = unique_together
+
# operations for in-memory schema synchronization #############################
class MemSchemaCWETypeDel(MemSchemaOperation):
@@ -904,7 +911,7 @@
def __call__(self):
entity = self.entity
- if entity.get('final'):
+ if entity.cw_edited.get('final'):
return
CWETypeAddOp(self._cw, entity=entity)
@@ -918,8 +925,8 @@
entity = self.entity
check_valid_changes(self._cw, entity, ro_attrs=('final',))
# don't use getattr(entity, attr), we would get the modified value if any
- if 'name' in entity.edited_attributes:
- oldname, newname = hook.entity_oldnewvalue(entity, 'name')
+ if 'name' in entity.cw_edited:
+ oldname, newname = entity.cw_edited.oldnewvalue('name')
if newname.lower() != oldname.lower():
CWETypeRenameOp(self._cw, oldname=oldname, newname=newname)
@@ -962,8 +969,8 @@
entity = self.entity
rtypedef = ybo.RelationType(name=entity.name,
description=entity.description,
- inlined=entity.get('inlined', False),
- symmetric=entity.get('symmetric', False),
+ inlined=entity.cw_edited.get('inlined', False),
+ symmetric=entity.cw_edited.get('symmetric', False),
eid=entity.eid)
MemSchemaCWRTypeAdd(self._cw, rtypedef=rtypedef)
@@ -978,10 +985,10 @@
check_valid_changes(self._cw, entity)
newvalues = {}
for prop in ('symmetric', 'inlined', 'fulltext_container'):
- if prop in entity.edited_attributes:
- old, new = hook.entity_oldnewvalue(entity, prop)
+ if prop in entity.cw_edited:
+ old, new = entity.cw_edited.oldnewvalue(prop)
if old != new:
- newvalues[prop] = entity[prop]
+ newvalues[prop] = new
if newvalues:
rschema = self._cw.vreg.schema.rschema(entity.name)
CWRTypeUpdateOp(self._cw, rschema=rschema, entity=entity,
@@ -1066,8 +1073,8 @@
attr = 'ordernum'
else:
attr = prop
- if attr in entity.edited_attributes:
- old, new = hook.entity_oldnewvalue(entity, attr)
+ if attr in entity.cw_edited:
+ old, new = entity.cw_edited.oldnewvalue(attr)
if old != new:
newvalues[prop] = new
if newvalues:
@@ -1120,7 +1127,7 @@
class AfterAddCWUniqueTogetherConstraintHook(SyncSchemaHook):
__regid__ = 'syncadd_cwuniquetogether_constraint'
__select__ = SyncSchemaHook.__select__ & is_instance('CWUniqueTogetherConstraint')
- events = ('after_add_entity', 'after_update_entity')
+ events = ('after_add_entity',)
def __call__(self):
CWUniqueTogetherConstraintAddOp(self._cw, entity=self.entity)
@@ -1137,9 +1144,9 @@
schema = self._cw.vreg.schema
cstr = self._cw.entity_from_eid(self.eidfrom)
entity = schema.schema_by_eid(self.eidto)
- cols = [r.rtype.name
- for r in cstr.relations]
- CWUniqueTogetherConstraintDelOp(self._cw, entity=entity, oldcstr=cstr, cols=cols)
+ cols = [r.name for r in cstr.relations]
+ CWUniqueTogetherConstraintDelOp(self._cw, entity=entity,
+ oldcstr=cstr, cols=cols)
# permissions synchronization hooks ############################################
@@ -1185,32 +1192,33 @@
-class UpdateFTIndexOp(hook.SingleLastOperation):
+class UpdateFTIndexOp(hook.DataOperationMixIn, hook.SingleLastOperation):
"""operation to update full text indexation of entity whose schema change
- We wait after the commit to as the schema in memory is only updated after the commit.
+ We wait after the commit to as the schema in memory is only updated after
+ the commit.
"""
def postcommit_event(self):
session = self.session
source = session.repo.system_source
- to_reindex = session.transaction_data.pop('fti_update_etypes', ())
+ schema = session.repo.vreg.schema
+ to_reindex = self.get_data()
self.info('%i etypes need full text indexed reindexation',
len(to_reindex))
- schema = self.session.repo.vreg.schema
for etype in to_reindex:
rset = session.execute('Any X WHERE X is %s' % etype)
self.info('Reindexing full text index for %i entity of type %s',
len(rset), etype)
still_fti = list(schema[etype].indexable_attributes())
for entity in rset.entities():
- source.fti_unindex_entity(session, entity.eid)
+ source.fti_unindex_entities(session, [entity])
for container in entity.cw_adapt_to('IFTIndexable').fti_containers():
if still_fti or container is not entity:
- source.fti_unindex_entity(session, container.eid)
- source.fti_index_entity(session, container)
- if len(to_reindex):
- # Transaction have already been committed
+ source.fti_unindex_entities(session, [container])
+ source.fti_index_entities(session, [container])
+ if to_reindex:
+ # Transaction has already been committed
session.pool.commit()
--- a/hooks/syncsession.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/syncsession.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Core hooks: synchronize living session on persistent data changes
+"""Core hooks: synchronize living session on persistent data changes"""
-"""
__docformat__ = "restructuredtext en"
from yams.schema import role_name
@@ -56,26 +55,25 @@
class _DeleteGroupOp(_GroupOperation):
"""synchronize user when a in_group relation has been deleted"""
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
groups = self.cnxuser.groups
try:
groups.remove(self.group)
except KeyError:
self.error('user %s not in group %s', self.cnxuser, self.group)
- return
class _AddGroupOp(_GroupOperation):
"""synchronize user when a in_group relation has been added"""
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
groups = self.cnxuser.groups
if self.group in groups:
self.warning('user %s already in group %s', self.cnxuser,
self.group)
- return
- groups.add(self.group)
+ else:
+ groups.add(self.group)
class SyncInGroupHook(SyncSessionHook):
@@ -98,7 +96,7 @@
self.cnxid = cnxid
hook.Operation.__init__(self, session)
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
try:
self.session.repo.close(self.cnxid)
@@ -123,7 +121,7 @@
class _DelCWPropertyOp(hook.Operation):
"""a user's custom properties has been deleted"""
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
try:
del self.cwpropdict[self.key]
@@ -134,7 +132,7 @@
class _ChangeCWPropertyOp(hook.Operation):
"""a user's custom properties has been added/changed"""
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
self.cwpropdict[self.key] = self.value
@@ -142,7 +140,7 @@
class _AddCWPropertyOp(hook.Operation):
"""a user's custom properties has been added/changed"""
- def commit_event(self):
+ def postcommit_event(self):
"""the observed connections pool has been commited"""
cwprop = self.cwprop
if not cwprop.for_user:
@@ -157,13 +155,15 @@
def __call__(self):
key, value = self.entity.pkey, self.entity.value
+ if key.startswith('sources.'):
+ return
session = self._cw
try:
value = session.vreg.typed_value(key, value)
except UnknownProperty:
qname = role_name('pkey', 'subject')
- raise ValidationError(self.entity.eid,
- {qname: session._('unknown property key')})
+ msg = session._('unknown property key %s') % key
+ raise ValidationError(self.entity.eid, {qname: msg})
except ValueError, ex:
qname = role_name('value', 'subject')
raise ValidationError(self.entity.eid,
@@ -180,10 +180,12 @@
def __call__(self):
entity = self.entity
- if not ('pkey' in entity.edited_attributes or
- 'value' in entity.edited_attributes):
+ if not ('pkey' in entity.cw_edited or
+ 'value' in entity.cw_edited):
return
key, value = entity.pkey, entity.value
+ if key.startswith('sources.'):
+ return
session = self._cw
try:
value = session.vreg.typed_value(key, value)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/syncsources.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,33 @@
+from cubicweb import ValidationError
+from cubicweb.selectors import is_instance
+from cubicweb.server import hook
+
+class SourceHook(hook.Hook):
+ __abstract__ = True
+ category = 'cw.sources'
+
+
+class SourceAddedOp(hook.Operation):
+ def precommit_event(self):
+ self.session.repo.add_source(self.entity)
+
+class SourceAddedHook(SourceHook):
+ __regid__ = 'cw.sources.added'
+ __select__ = SourceHook.__select__ & is_instance('CWSource')
+ events = ('after_add_entity',)
+ def __call__(self):
+ SourceAddedOp(self._cw, entity=self.entity)
+
+
+class SourceRemovedOp(hook.Operation):
+ def precommit_event(self):
+ self.session.repo.remove_source(self.uri)
+
+class SourceRemovedHook(SourceHook):
+ __regid__ = 'cw.sources.removed'
+ __select__ = SourceHook.__select__ & is_instance('CWSource')
+ events = ('before_delete_entity',)
+ def __call__(self):
+ if self.entity.name == 'system':
+ raise ValidationError(self.entity.eid, {None: 'cant remove system source'})
+ SourceRemovedOp(self._cw, uri=self.entity.name)
--- a/hooks/test/unittest_hooks.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/test/unittest_hooks.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,6 +20,7 @@
note: most schemahooks.py hooks are actually tested in unittest_migrations.py
"""
+from __future__ import with_statement
from logilab.common.testlib import TestCase, unittest_main
@@ -115,8 +116,9 @@
def test_unsatisfied_constraints(self):
releid = self.execute('SET U in_group G WHERE G name "owners", U login "admin"')[0][0]
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.errors,
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.errors,
{'in_group-object': u'RQLConstraint NOT O name "owners" failed'})
def test_html_tidy_hook(self):
@@ -143,13 +145,12 @@
entity.set_attributes(name=u'wf2')
self.assertEqual(entity.description, u'yo')
entity.set_attributes(description=u'R&D<p>yo')
- entity.pop('description')
+ entity.cw_attr_cache.pop('description')
self.assertEqual(entity.description, u'R&D<p>yo</p>')
-
def test_metadata_cwuri(self):
entity = self.request().create_entity('Workflow', name=u'wf1')
- self.assertEqual(entity.cwuri, self.repo.config['base-url'] + 'eid/%s' % entity.eid)
+ self.assertEqual(entity.cwuri, self.repo.config['base-url'] + str(entity.eid))
def test_metadata_creation_modification_date(self):
_now = datetime.now()
@@ -228,25 +229,25 @@
class CWPropertyHooksTC(CubicWebTC):
def test_unexistant_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
- self.assertEqual(ex.errors, {'pkey-subject': 'unknown property key'})
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
- self.assertEqual(ex.errors, {'pkey-subject': 'unknown property key'})
+ with self.assertRaises(ValidationError) as cm:
+ self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+ self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
+ with self.assertRaises(ValidationError) as cm:
+ self.execute('INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+ self.assertEqual(cm.exception.errors, {'pkey-subject': 'unknown property key bla.bla'})
def test_site_wide_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
- self.assertEqual(ex.errors, {'for_user-subject': "site-wide property can't be set for user"})
+ with self.assertRaises(ValidationError) as cm:
+ self.execute('INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
+ self.assertEqual(cm.exception.errors, {'for_user-subject': "site-wide property can't be set for user"})
def test_bad_type_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
- self.assertEqual(ex.errors, {'value-subject': u'unauthorized value'})
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
- self.assertEqual(ex.errors, {'value-subject': u'unauthorized value'})
+ with self.assertRaises(ValidationError) as cm:
+ self.execute('INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
+ self.assertEqual(cm.exception.errors, {'value-subject': u'unauthorized value'})
+ with self.assertRaises(ValidationError) as cm:
+ self.execute('INSERT CWProperty X: X pkey "ui.language", X value "hop"')
+ self.assertEqual(cm.exception.errors, {'value-subject': u'unauthorized value'})
class SchemaHooksTC(CubicWebTC):
--- a/hooks/test/unittest_syncschema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/test/unittest_syncschema.py Fri Mar 11 09:46:45 2011 +0100
@@ -24,7 +24,7 @@
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.devtools.repotest import schema_eids_idx, restore_schema_eids_idx
-def teardown_module(*args):
+def tearDownModule(*args):
del SchemaModificationHooksTC.schema_eids
class SchemaModificationHooksTC(CubicWebTC):
--- a/hooks/workflow.py Fri Dec 10 12:17:18 2010 +0100
+++ b/hooks/workflow.py Fri Mar 11 09:46:45 2011 +0100
@@ -135,7 +135,7 @@
qname = role_name('to_state', 'subject')
msg = session._("state doesn't belong to entity's current workflow")
raise ValidationError(self.trinfo.eid, {'to_state': msg})
- tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
+ tostate = wftr.get_exit_point(forentity, trinfo.cw_attr_cache['to_state'])
if tostate is not None:
# reached an exit point
msg = session._('exiting from subworkflow %s')
@@ -185,12 +185,14 @@
entity = self.entity
# first retreive entity to which the state change apply
try:
- foreid = entity['wf_info_for']
+ foreid = entity.cw_attr_cache['wf_info_for']
except KeyError:
qname = role_name('wf_info_for', 'subject')
msg = session._('mandatory relation')
raise ValidationError(entity.eid, {qname: msg})
forentity = session.entity_from_eid(foreid)
+ # see comment in the TrInfo entity definition
+ entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for)
iworkflowable = forentity.cw_adapt_to('IWorkflowable')
# then check it has a workflow set, unless we're in the process of changing
# entity's workflow
@@ -213,7 +215,7 @@
or not session.write_security)
# no investigate the requested state change...
try:
- treid = entity['by_transition']
+ treid = entity.cw_attr_cache['by_transition']
except KeyError:
# no transition set, check user is a manager and destination state
# is specified (and valid)
@@ -221,7 +223,7 @@
qname = role_name('by_transition', 'subject')
msg = session._('mandatory relation')
raise ValidationError(entity.eid, {qname: msg})
- deststateeid = entity.get('to_state')
+ deststateeid = entity.cw_attr_cache.get('to_state')
if not deststateeid:
qname = role_name('by_transition', 'subject')
msg = session._('mandatory relation')
@@ -247,8 +249,8 @@
if not tr.may_be_fired(foreid):
msg = session._("transition may not be fired")
raise ValidationError(entity.eid, {qname: msg})
- if entity.get('to_state'):
- deststateeid = entity['to_state']
+ deststateeid = entity.cw_attr_cache.get('to_state')
+ if deststateeid is not None:
if not cowpowers and deststateeid != tr.destination(forentity).eid:
qname = role_name('by_transition', 'subject')
msg = session._("transition isn't allowed")
@@ -262,8 +264,8 @@
else:
deststateeid = tr.destination(forentity).eid
# everything is ok, add missing information on the trinfo entity
- entity['from_state'] = fromstate.eid
- entity['to_state'] = deststateeid
+ entity.cw_edited['from_state'] = fromstate.eid
+ entity.cw_edited['to_state'] = deststateeid
nocheck = session.transaction_data.setdefault('skip-security', set())
nocheck.add((entity.eid, 'from_state', fromstate.eid))
nocheck.add((entity.eid, 'to_state', deststateeid))
@@ -278,11 +280,12 @@
def __call__(self):
trinfo = self.entity
- _change_state(self._cw, trinfo['wf_info_for'],
- trinfo['from_state'], trinfo['to_state'])
- forentity = self._cw.entity_from_eid(trinfo['wf_info_for'])
+ rcache = trinfo.cw_attr_cache
+ _change_state(self._cw, rcache['wf_info_for'], rcache['from_state'],
+ rcache['to_state'])
+ forentity = self._cw.entity_from_eid(rcache['wf_info_for'])
iworkflowable = forentity.cw_adapt_to('IWorkflowable')
- assert iworkflowable.current_state.eid == trinfo['to_state']
+ assert iworkflowable.current_state.eid == rcache['to_state']
if iworkflowable.main_workflow.eid != iworkflowable.current_workflow.eid:
_SubWorkflowExitOp(self._cw, forentity=forentity, trinfo=trinfo)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n/de.po Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,4341 @@
+# cubicweb i18n catalog
+# Copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# Logilab <contact@logilab.fr>
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.0\n"
+"POT-Creation-Date: 2006-01-12 17:35+CET\n"
+"PO-Revision-Date: 2010-09-15 14:55+0200\n"
+"Last-Translator: Dr. Leo <fhaxbox66@googlemail.com>\n"
+"Language-Team: English <devel@logilab.fr.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, python-format
+msgid ""
+"\n"
+"%(user)s changed status from <%(previous_state)s> to <%(current_state)s> for "
+"entity\n"
+"'%(title)s'\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"url: %(url)s\n"
+msgstr ""
+"\n"
+"%(user)s hat den Zustand geändert von <%(previous_state)s> in <"
+"%(current_state)s> für die Entität\n"
+"'%(title)s'\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"url: %(url)s\n"
+
+#, python-format
+msgid " from state %(fromstate)s to state %(tostate)s\n"
+msgstr " aus dem Zustand %(fromstate)s in den Zustand %(tostate)s\n"
+
+msgid " :"
+msgstr " :"
+
+#, python-format
+msgid "%(attr)s set to %(newvalue)s"
+msgstr "%(attr)s geändert in %(newvalue)s"
+
+#, python-format
+msgid "%(attr)s updated from %(oldvalue)s to %(newvalue)s"
+msgstr "%(attr)s geändert von %(oldvalue)s in %(newvalue)s"
+
+#, python-format
+msgid "%(cstr)s constraint failed for value %(value)r"
+msgstr "%(cstr)s Einschränkung verletzt für Wert %(value)r"
+
+#, python-format
+msgid "%(etype)s by %(author)s"
+msgstr ""
+
+#, python-format
+msgid "%(firstname)s %(surname)s"
+msgstr "%(firstname)s %(surname)s"
+
+#, python-format
+msgid "%(subject)s %(etype)s #%(eid)s (%(login)s)"
+msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
+
+#, python-format
+msgid "%(value)r doesn't match the %(regexp)r regular expression"
+msgstr "%(value)r entspricht nicht dem regulären Ausdruck %(regexp)r"
+
+#, python-format
+msgid "%d days"
+msgstr "%d Tage"
+
+#, python-format
+msgid "%d hours"
+msgstr "%d Stunden"
+
+#, python-format
+msgid "%d minutes"
+msgstr "%d Minuten"
+
+#, python-format
+msgid "%d months"
+msgstr "%d Monate"
+
+#, python-format
+msgid "%d seconds"
+msgstr "%d Sekunden"
+
+#, python-format
+msgid "%d weeks"
+msgstr "%d Wochen"
+
+#, python-format
+msgid "%d years"
+msgstr "%d Jahre"
+
+#, python-format
+msgid "%d days"
+msgstr "%d Tage"
+
+#, python-format
+msgid "%d hours"
+msgstr "%d Stunden"
+
+#, python-format
+msgid "%d minutes"
+msgstr "%d Minuten"
+
+#, python-format
+msgid "%d months"
+msgstr "%d Monate"
+
+#, python-format
+msgid "%d seconds"
+msgstr "%d Sekunden"
+
+#, python-format
+msgid "%d weeks"
+msgstr "%d Wochen"
+
+#, python-format
+msgid "%d years"
+msgstr "%d Jahre"
+
+#, python-format
+msgid "%s error report"
+msgstr "%s Fehlerbericht"
+
+#, python-format
+msgid "%s not estimated"
+msgstr "%s unbekannt(e)"
+
+#, python-format
+msgid "%s software version of the database"
+msgstr "Software-Version der Datenbank %s"
+
+#, python-format
+msgid "%s updated"
+msgstr "%s aktualisiert"
+
+msgid "(UNEXISTANT EID)"
+msgstr "(EID nicht gefunden)"
+
+msgid "(loading ...)"
+msgstr "(laden...)"
+
+msgid "**"
+msgstr "0..n 0..n"
+
+msgid "*+"
+msgstr "0..n 1..n"
+
+msgid "*1"
+msgstr "0..n 1"
+
+msgid "*?"
+msgstr "0..n 0..1"
+
+msgid "+*"
+msgstr "1..n 0..n"
+
+msgid "++"
+msgstr "1..n 1..n"
+
+msgid "+1"
+msgstr "1..n 1"
+
+msgid "+?"
+msgstr "1..n 0..1"
+
+msgid "1*"
+msgstr "1 0..n"
+
+msgid "1+"
+msgstr "1 1..n"
+
+msgid "11"
+msgstr "1 1"
+
+msgid "1?"
+msgstr "1 0..1"
+
+#, python-format
+msgid "<%s not specified>"
+msgstr "<%s nicht spezifiziert>"
+
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+"<div>Dieses Schema des Datenmodells enthält<>keine</em> Meta-Daten, aber Sie "
+"können ein <a href=\"%s\">vollständiges Schema</a> mit Meta-Daten anzeigen.</"
+"div>"
+
+msgid "<not specified>"
+msgstr "<nicht spezifiziert>"
+
+msgid "?*"
+msgstr "0..1 0..n"
+
+msgid "?+"
+msgstr "0..1 1..n"
+
+msgid "?1"
+msgstr "0..1 1"
+
+msgid "??"
+msgstr "0..1 0..1"
+
+msgid "AND"
+msgstr "UND"
+
+msgid "About this site"
+msgstr "Über diese Seite"
+
+msgid "Any"
+msgstr "irgendein"
+
+msgid "Attributes permissions:"
+msgstr "Rechte der Attribute"
+
+msgid "Attributes with non default permissions:"
+msgstr "Attribute mit nicht-standard-Berechtigungen"
+
+# schema pot file, generated on 2009-09-16 16:46:55
+#
+# singular and plural forms for each entity type
+msgid "BaseTransition"
+msgstr "Übergang (abstrakt)"
+
+msgid "BaseTransition_plural"
+msgstr "Übergänge (abstrakt)"
+
+msgid "Bookmark"
+msgstr "Lesezeichen"
+
+msgid "Bookmark_plural"
+msgstr "Lesezeichen"
+
+msgid "Boolean"
+msgstr "Boolean"
+
+msgid "Boolean_plural"
+msgstr "Booleans"
+
+msgid "BoundConstraint"
+msgstr "gebundene Einschränkung"
+
+msgid "BoundaryConstraint"
+msgstr "Rand-einschränkung"
+
+msgid "Browse by category"
+msgstr "nach Kategorien navigieren"
+
+msgid "Browse by entity type"
+msgstr "nach Identitätstyp navigieren"
+
+msgid "Bytes"
+msgstr "Bytes"
+
+msgid "Bytes_plural"
+msgstr "Bytes"
+
+msgid "CWAttribute"
+msgstr "Attribut"
+
+msgid "CWAttribute_plural"
+msgstr "Attribute"
+
+msgid "CWCache"
+msgstr "Cache"
+
+msgid "CWCache_plural"
+msgstr "Caches"
+
+msgid "CWConstraint"
+msgstr "Einschränkung"
+
+msgid "CWConstraintType"
+msgstr "Einschränkungstyp"
+
+msgid "CWConstraintType_plural"
+msgstr "Einschränkungstypen"
+
+msgid "CWConstraint_plural"
+msgstr "Einschränkungen"
+
+msgid "CWEType"
+msgstr "Entitätstyp"
+
+msgctxt "inlined:CWRelation.from_entity.subject"
+msgid "CWEType"
+msgstr "Entitätstyp"
+
+msgctxt "inlined:CWRelation.to_entity.subject"
+msgid "CWEType"
+msgstr "Entitätstyp"
+
+msgid "CWEType_plural"
+msgstr "Entitätstypen"
+
+msgid "CWGroup"
+msgstr "Gruppe"
+
+msgid "CWGroup_plural"
+msgstr "Gruppen"
+
+msgid "CWPermission"
+msgstr "Berechtigung"
+
+msgid "CWPermission_plural"
+msgstr "Berechtigungen"
+
+msgid "CWProperty"
+msgstr "Eigenschaft"
+
+msgid "CWProperty_plural"
+msgstr "Eigenschaften"
+
+msgid "CWRType"
+msgstr "Relationstyp"
+
+msgctxt "inlined:CWRelation.relation_type.subject"
+msgid "CWRType"
+msgstr "Relationstyp"
+
+msgid "CWRType_plural"
+msgstr "Relationstypen"
+
+msgid "CWRelation"
+msgstr "Relation"
+
+msgid "CWRelation_plural"
+msgstr "Relationen"
+
+msgid "CWSource"
+msgstr ""
+
+msgid "CWSourceHostConfig"
+msgstr ""
+
+msgid "CWSourceHostConfig_plural"
+msgstr ""
+
+msgid "CWSource_plural"
+msgstr ""
+
+msgid "CWUniqueTogetherConstraint"
+msgstr "unique-together-Einschränkung"
+
+msgid "CWUniqueTogetherConstraint_plural"
+msgstr "unique-together-Einschränkungen"
+
+msgid "CWUser"
+msgstr "Nutzer"
+
+msgid "CWUser_plural"
+msgstr "Nutzer"
+
+#, python-format
+msgid ""
+"Can't restore %(role)s relation %(rtype)s to entity %(eid)s which is already "
+"linked using this relation."
+msgstr ""
+"Kann die Relation %(role)s %(rtype)s zu einer Entität %(eid)s nicht wieder "
+"herstellen, die durch diese Relation bereits mit einer anderen Entität "
+"verbunden ist."
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s between %(subj)s and %(obj)s, that relation "
+"does not exists anymore in the schema."
+msgstr ""
+"Kann die Relation %(rtype)s zwischen %(subj)s und %(obj)s nicht wieder "
+"herstellen, diese Relation existiert nicht mehr in dem Schema."
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
+"exists anymore in the schema."
+msgstr ""
+"Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
+"diese Relation existiert nicht mehr in dem Schema."
+
+#, python-format
+msgid ""
+"Can't restore relation %(rtype)s, %(role)s entity %(eid)s doesn't exist "
+"anymore."
+msgstr ""
+"Kann die Relation %(rtype)s nicht wieder herstellen, die Entität %(role)s "
+"%(eid)s existiert nicht mehr."
+
+#, python-format
+msgid ""
+"Can't undo addition of relation %(rtype)s from %(subj)s to %(obj)s, doesn't "
+"exist anymore"
+msgstr ""
+"Kann das Hinzufügen der Relation %(rtype)s von %(subj)s zu %(obj)s nicht "
+"rückgängig machen , diese Relation existiert nicht mehr."
+
+#, python-format
+msgid ""
+"Can't undo creation of entity %(eid)s of type %(etype)s, type no more "
+"supported"
+msgstr ""
+"Kann die Erstelllung der Entität %(eid)s vom Typ %(etype)s nicht rückgängig "
+"machen, dieser Typ existiert nicht mehr."
+
+#, python-format
+msgid "Data connection graph for %s"
+msgstr "Graf der Datenverbindungen für %s"
+
+msgid "Date"
+msgstr "Datum"
+
+msgid "Date_plural"
+msgstr "Daten"
+
+msgid "Datetime"
+msgstr "Datum und Uhrzeit"
+
+msgid "Datetime_plural"
+msgstr "Daten und Uhrzeiten"
+
+msgid "Decimal"
+msgstr "Dezimalzahl"
+
+msgid "Decimal_plural"
+msgstr "Dezimalzahlen"
+
+msgid "Do you want to delete the following element(s) ?"
+msgstr "Wollen Sie das/die folgend(n) Element(e) löschen?"
+
+msgid "Download schema as OWL"
+msgstr "Herunterladen des Schemas im OWL-Format"
+
+msgid "EmailAddress"
+msgstr "Email-Adresse"
+
+msgctxt "inlined:CWUser.use_email.subject"
+msgid "EmailAddress"
+msgstr "Email-Adresse"
+
+msgid "EmailAddress_plural"
+msgstr "Email-Adressen"
+
+msgid "Entities"
+msgstr "Entitäten"
+
+msgid "Entity types"
+msgstr "Entitätstypen"
+
+msgid "ExternalUri"
+msgstr "Externer Uri"
+
+msgid "ExternalUri_plural"
+msgstr "Externe Uris"
+
+msgid "Float"
+msgstr "Gleitkommazahl"
+
+msgid "Float_plural"
+msgstr "Gleitkommazahlen"
+
+# schema pot file, generated on 2009-12-03 09:22:35
+#
+# singular and plural forms for each entity type
+msgid "FormatConstraint"
+msgstr "Format-Einschränkung"
+
+msgid "From:"
+msgstr "Von:"
+
+msgid "Garbage collection information"
+msgstr "Information zur Speicherbereinigung"
+
+msgid "Got rhythm?"
+msgstr "Hast Du Rhythmus ?"
+
+msgid "Help"
+msgstr "Hilfe"
+
+msgid "Index"
+msgstr "Index"
+
+msgid "Instance"
+msgstr "Instanz"
+
+msgid "Int"
+msgstr "Ganzzahl"
+
+msgid "Int_plural"
+msgstr "Ganzzahlen"
+
+msgid "Interval"
+msgstr "Zeitraum"
+
+msgid "IntervalBoundConstraint"
+msgstr "interval-Einschränkung"
+
+msgid "Interval_plural"
+msgstr "Intervalle"
+
+msgid "Looked up classes"
+msgstr "gesuchte Klassen"
+
+msgid "Most referenced classes"
+msgstr "meist-referenzierte Klassen"
+
+msgid "New BaseTransition"
+msgstr "neuer Übergang (abstrakt)"
+
+msgid "New Bookmark"
+msgstr "Neues Lesezeichen"
+
+msgid "New CWAttribute"
+msgstr "Neue finale Relationsdefinition"
+
+msgid "New CWCache"
+msgstr "Neuer Anwendungs-Cache"
+
+msgid "New CWConstraint"
+msgstr "Neue Einschränkung"
+
+msgid "New CWConstraintType"
+msgstr "Neuer Einschränkungstyp"
+
+msgid "New CWEType"
+msgstr "Neuer Entitätstyp"
+
+msgid "New CWGroup"
+msgstr "Neue Gruppe"
+
+msgid "New CWPermission"
+msgstr "Neue Berechtigung"
+
+msgid "New CWProperty"
+msgstr "Neue Eigenschaft"
+
+msgid "New CWRType"
+msgstr "Neuer Relationstyp"
+
+msgid "New CWRelation"
+msgstr "Neue Relation"
+
+msgid "New CWSource"
+msgstr ""
+
+msgid "New CWSourceHostConfig"
+msgstr ""
+
+msgid "New CWUniqueTogetherConstraint"
+msgstr "Neue unique-together-Einschränkung"
+
+msgid "New CWUser"
+msgstr "Neuer Nutzer"
+
+msgid "New EmailAddress"
+msgstr "Neue Email-Adresse"
+
+msgid "New ExternalUri"
+msgstr "Neuer externer URI"
+
+msgid "New RQLExpression"
+msgstr "Neuer RQL Ausdruck"
+
+msgid "New State"
+msgstr "Neuer Zustand"
+
+msgid "New SubWorkflowExitPoint"
+msgstr "Neuer subworkflow-Endpunkt"
+
+msgid "New TrInfo"
+msgstr "Neue Übergangsinformation"
+
+msgid "New Transition"
+msgstr "Neuer Übergang"
+
+msgid "New Workflow"
+msgstr "Neuer workflow"
+
+msgid "New WorkflowTransition"
+msgstr "Neuer workflow-Übergang"
+
+#, python-format
+msgid "No account? Try public access at %s"
+msgstr "Kein Konto? Zur öffentlichen Website: %s"
+
+msgid "No result matching query"
+msgstr "Ihre Suche ergab keine Treffer."
+
+msgid "Non exhaustive list of views that may apply to entities of this type"
+msgstr ""
+"nicht abschließende Liste von Ansichten, die auf Entitäten dieses Typs "
+"Anwendung finden"
+
+msgid "OR"
+msgstr "oder"
+
+msgid "Parent class:"
+msgstr "Elternklasse"
+
+msgid "Password"
+msgstr "Passwort"
+
+msgid "Password_plural"
+msgstr "Passwörter"
+
+msgid "Permissions for entity types"
+msgstr "Berechtigungen für Entitätstypen"
+
+msgid "Permissions for relations"
+msgstr "Berechtigungen für Relationen"
+
+msgid "Please note that this is only a shallow copy"
+msgstr "Achtung: dies ist nur eine flache Kopie!"
+
+msgid "Powered by CubicWeb"
+msgstr "Powered by CubicWeb"
+
+msgid "RQLConstraint"
+msgstr "RQL-Einschränkung"
+
+msgid "RQLExpression"
+msgstr "RQL-Ausdruck"
+
+msgid "RQLExpression_plural"
+msgstr "RQL-Ausdrücke"
+
+msgid "RQLUniqueConstraint"
+msgstr "RQL Einschränkung bzgl. Eindeutigkeit"
+
+msgid "RQLVocabularyConstraint"
+msgstr "RQL Wortschatz-Einschränkung"
+
+msgid "Recipients:"
+msgstr "Adressaten:"
+
+msgid "RegexpConstraint"
+msgstr "regulärer Ausdruck Einschränkung"
+
+msgid "Registry's content"
+msgstr "Inhalt der Registry"
+
+msgid "Relation types"
+msgstr "Relationstypen"
+
+msgid "Relations"
+msgstr "Relationen"
+
+msgid "Repository"
+msgstr "Ablage"
+
+#, python-format
+msgid "Schema %s"
+msgstr "Schema %s"
+
+msgid "Schema of the data model"
+msgstr "Schema des Datenmodells"
+
+msgid "Search for"
+msgstr "Suchen"
+
+msgid "SizeConstraint"
+msgstr "Größeneinschränkung"
+
+msgid ""
+"Source's configuration for a particular host. One key=value per line, "
+"authorized keys depending on the source's type, overriding values defined on "
+"the source."
+msgstr ""
+
+msgid "Startup views"
+msgstr "Startansichten"
+
+msgid "State"
+msgstr "Zustand"
+
+msgid "State_plural"
+msgstr "Zustände"
+
+msgid "StaticVocabularyConstraint"
+msgstr "Wortschatz-Einschränkung"
+
+msgid "String"
+msgstr "String"
+
+msgid "String_plural"
+msgstr "Strings"
+
+msgid "Sub-classes:"
+msgstr "Unterklassen"
+
+msgid "SubWorkflowExitPoint"
+msgstr "Subworkflow Endpunkt"
+
+msgid "SubWorkflowExitPoint_plural"
+msgstr "subworkflow Endpunkte"
+
+msgid "Subject:"
+msgstr "Subjekt :"
+
+msgid "Submit bug report"
+msgstr "Fehlerbericht senden"
+
+msgid "Submit bug report by mail"
+msgstr "Diesen Bericht als E-Mail senden"
+
+#, python-format
+msgid "The view %s can not be applied to this query"
+msgstr "Die Ansicht %s ist auf diese Anfrage nicht anwendbar."
+
+#, python-format
+msgid "The view %s could not be found"
+msgstr "Die Ansicht %s konnte nicht gefunden werden."
+
+msgid "There is no default workflow"
+msgstr "Dieser Entitätstyp hat standardmäßig keinen Workflow."
+
+msgid "This BaseTransition"
+msgstr "Diese abstracte Transition"
+
+msgid "This Bookmark"
+msgstr "Dieses Lesezeichen"
+
+msgid "This CWAttribute"
+msgstr "diese finale Relationsdefinition"
+
+msgid "This CWCache"
+msgstr "Dieser Anwendungs-Cache"
+
+msgid "This CWConstraint"
+msgstr "diese Einschränkung"
+
+msgid "This CWConstraintType"
+msgstr "Dieser Einschränkungstyp"
+
+msgid "This CWEType"
+msgstr "Dieser Entitätstyp"
+
+msgid "This CWGroup"
+msgstr "Diese Gruppe"
+
+msgid "This CWPermission"
+msgstr "Diese Berechtigung"
+
+msgid "This CWProperty"
+msgstr "Diese Eigenschaft"
+
+msgid "This CWRType"
+msgstr "Dieser Relationstyp"
+
+msgid "This CWRelation"
+msgstr "Diese Relation"
+
+msgid "This CWSource"
+msgstr ""
+
+msgid "This CWSourceHostConfig"
+msgstr ""
+
+msgid "This CWUniqueTogetherConstraint"
+msgstr "Diese unique-together-Einschränkung"
+
+msgid "This CWUser"
+msgstr "Dieser Nutzer"
+
+msgid "This EmailAddress"
+msgstr "Diese E-Mail-Adresse"
+
+msgid "This ExternalUri"
+msgstr "dieser externe URI"
+
+msgid "This RQLExpression"
+msgstr "Dieser RQL-Ausdruck"
+
+msgid "This State"
+msgstr "Dieser Zustand"
+
+msgid "This SubWorkflowExitPoint"
+msgstr "Dieser Subworkflow Endpunkt"
+
+msgid "This TrInfo"
+msgstr "Diese Übergangs-Information"
+
+msgid "This Transition"
+msgstr "Dieser Übergang"
+
+msgid "This Workflow"
+msgstr "Dieser Workflow"
+
+msgid "This WorkflowTransition"
+msgstr "Dieser Workflow-Übergang"
+
+msgid "This entity type permissions:"
+msgstr "Berechtigungen für diesen Entitätstyp"
+
+msgid "Time"
+msgstr "Zeit"
+
+msgid "Time_plural"
+msgstr "Zeiten"
+
+msgid "TrInfo"
+msgstr "Übergangs-Information"
+
+msgid "TrInfo_plural"
+msgstr "Übergangs-Informationen"
+
+msgid "Transition"
+msgstr "Übergang"
+
+msgid "Transition_plural"
+msgstr "Übergänge"
+
+msgid "UniqueConstraint"
+msgstr "eindeutige Einschränkung"
+
+msgid "Unreachable objects"
+msgstr "unzugängliche Objekte"
+
+msgid "Used by:"
+msgstr "benutzt von:"
+
+msgid "Web server"
+msgstr "Web-Server"
+
+msgid "What's new?"
+msgstr "Was ist neu?"
+
+msgid "Workflow"
+msgstr "Workflow"
+
+msgid "Workflow history"
+msgstr "Workflow-Chronik"
+
+msgid "WorkflowTransition"
+msgstr "Workflow-Übergang"
+
+msgid "WorkflowTransition_plural"
+msgstr "Workflow-Übergänge"
+
+msgid "Workflow_plural"
+msgstr "Workflows"
+
+msgid ""
+"You can either submit a new file using the browse button above, or choose to "
+"remove already uploaded file by checking the \"detach attached file\" check-"
+"box, or edit file content online with the widget below."
+msgstr ""
+"Sie können entweder mit dem bouton\n"
+"\"Durchsuchen\" oberhalb eine neue Datei hochladen, eine bereits "
+"hochgeladene Datei durch anklicken des Kästchens \"angehängte Datei abtrennen"
+"\" entfernen, oder den Datei-Inhalt mit dem Widget unterhalb editieren."
+
+msgid ""
+"You can either submit a new file using the browse button above, or edit file "
+"content online with the widget below."
+msgstr ""
+"Sie können entweder mit dem bouton\n"
+"\"Durchsuchen\" oberhalb eine neue Datei hochladen, oder den Datei-Inhalt "
+"mit dem Widget unterhalb editieren."
+
+msgid "You can use any of the following substitutions in your text"
+msgstr "Sie können die folgenden Ersetzungen in Ihrem Text verwenden:"
+
+msgid ""
+"You have no access to this view or it can not be used to display the current "
+"data."
+msgstr ""
+"Sie haben entweder keinen Zugriff auf diese Ansicht, oder die Ansicht kann "
+"nicht zur Anzeite dieser Daten verwendet werden."
+
+msgid ""
+"You're not authorized to access this page. If you think you should, please "
+"contact the site administrator."
+msgstr ""
+"Sie haben keinen Zugriff auf diese Seite.Bitte wenden Sie sich ggfs. an "
+"Ihren Administrator."
+
+#, python-format
+msgid "[%s supervision] changes summary"
+msgstr "[%s supervision] Beschreibung der Änderungen"
+
+msgid ""
+"a RQL expression which should return some results, else the transition won't "
+"be available. This query may use X and U variables that will respectivly "
+"represents the current entity and the current user"
+msgstr ""
+"ein RQL-Ausdruck, der einige Treffer liefern sollte, sonst wird der Übergang "
+"nicht verfügbar sein. Diese Abfrage kann X und U Variable benutzen, die "
+"jeweils die aktuelle Entität und den aktuellen Nutzer repräsentieren."
+
+msgid "a URI representing an object in external data store"
+msgstr "ein URI, der ein Objekt in einem externen Data-Store repräsentiert"
+
+msgid "a float is expected"
+msgstr "Eine Dezimalzahl (float) wird erwartet."
+
+msgid ""
+"a simple cache entity characterized by a name and a validity date. The "
+"target application is responsible for updating timestamp when necessary to "
+"invalidate the cache (typically in hooks). Also, checkout the AppObject."
+"get_cache() method."
+msgstr ""
+
+msgid "abstract base class for transitions"
+msgstr "abstrakte Basisklasse für Übergänge"
+
+msgid "action(s) on this selection"
+msgstr "Aktionen(en) bei dieser Auswahl"
+
+msgid "actions"
+msgstr "Aktionen"
+
+msgid "activate"
+msgstr "aktivieren"
+
+msgid "activated"
+msgstr "aktiviert"
+
+msgid "add"
+msgstr "hinzufügen"
+
+msgid "add Bookmark bookmarked_by CWUser object"
+msgstr "Lesezeichen"
+
+msgid "add CWAttribute constrained_by CWConstraint subject"
+msgstr "Einschränkung"
+
+msgid "add CWAttribute read_permission RQLExpression subject"
+msgstr "RQL-Ausdruck zum lesen"
+
+msgid "add CWAttribute relation_type CWRType object"
+msgstr "Attributdefinition"
+
+msgid "add CWAttribute update_permission RQLExpression subject"
+msgstr "RQL-Ausdruck für Berechtigung zum Aktualisieren"
+
+msgid "add CWEType add_permission RQLExpression subject"
+msgstr "RQL-Ausdruck für Berechtigung zum Hinzufügen"
+
+msgid "add CWEType delete_permission RQLExpression subject"
+msgstr "RQL-Ausdruck für Berechtigung zum Löschen"
+
+msgid "add CWEType read_permission RQLExpression subject"
+msgstr "RQL-Ausdruck für Berechtigung zum Lesen"
+
+msgid "add CWEType update_permission RQLExpression subject"
+msgstr "RQL-Ausdruck für Berechtigung zum Aktualisieren"
+
+msgid "add CWProperty for_user CWUser object"
+msgstr "Eigenschaft"
+
+msgid "add CWRelation add_permission RQLExpression subject"
+msgstr "RQL-Ausdruck hinzufügen"
+
+msgid "add CWRelation constrained_by CWConstraint subject"
+msgstr "Einschränkung"
+
+msgid "add CWRelation delete_permission RQLExpression subject"
+msgstr "RQL-Ausdruck löschen"
+
+msgid "add CWRelation read_permission RQLExpression subject"
+msgstr "RQL-Ausdruck lesen"
+
+msgid "add CWRelation relation_type CWRType object"
+msgstr "Relationsdefinition"
+
+msgid "add CWSourceHostConfig cw_host_config_of CWSource object"
+msgstr ""
+
+msgid "add CWUniqueTogetherConstraint constraint_of CWEType object"
+msgstr "unique-together-Einschränkung hinzufügen"
+
+msgid "add CWUser in_group CWGroup object"
+msgstr "Nutzer"
+
+msgid "add CWUser use_email EmailAddress subject"
+msgstr "Email-Adresse"
+
+msgid "add State allowed_transition Transition object"
+msgstr "Anfangszustand"
+
+msgid "add State allowed_transition Transition subject"
+msgstr "erlaubter Übergang"
+
+msgid "add State allowed_transition WorkflowTransition subject"
+msgstr "Workflow-Übergang"
+
+msgid "add State state_of Workflow object"
+msgstr "Status"
+
+msgid "add Transition condition RQLExpression subject"
+msgstr "Bedingung"
+
+msgid "add Transition destination_state State object"
+msgstr "ausstehender Übergang"
+
+msgid "add Transition destination_state State subject"
+msgstr "Zielstatus"
+
+msgid "add Transition transition_of Workflow object"
+msgstr "Übergang"
+
+msgid "add WorkflowTransition condition RQLExpression subject"
+msgstr "Workflow-Übergang"
+
+msgid "add WorkflowTransition subworkflow_exit SubWorkflowExitPoint subject"
+msgstr "Subworkflow Exit-Punkt"
+
+msgid "add WorkflowTransition transition_of Workflow object"
+msgstr "Workflow-Übergang"
+
+msgctxt "inlined:CWRelation.from_entity.subject"
+msgid "add a CWEType"
+msgstr "einen Entitätstyp hinzufügen"
+
+msgctxt "inlined:CWRelation.to_entity.subject"
+msgid "add a CWEType"
+msgstr "einen Entitätstyp hinzufügen"
+
+msgctxt "inlined:CWRelation.relation_type.subject"
+msgid "add a CWRType"
+msgstr "einen Relationstyp hinzufügen"
+
+msgctxt "inlined:CWUser.use_email.subject"
+msgid "add a EmailAddress"
+msgstr "Email-Adresse hinzufügen"
+
+msgid "add a new permission"
+msgstr "eine Berechtigung hinzufügen"
+
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "add_permission"
+msgstr "kann hinzugefügt werden durch"
+
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgctxt "CWEType"
+msgid "add_permission"
+msgstr "Berechtigung hinzufügen"
+
+msgctxt "CWRelation"
+msgid "add_permission"
+msgstr "Berechtigung hinzufügen"
+
+msgid "add_permission_object"
+msgstr "hat die Berechtigung zum Hinzufügen"
+
+msgctxt "CWGroup"
+msgid "add_permission_object"
+msgstr "kann hinzufügen"
+
+msgctxt "RQLExpression"
+msgid "add_permission_object"
+msgstr "benutzt, um die Hinzufüge-Berechtigung zu festzulegen für"
+
+msgid "add_relation"
+msgstr "hinzufügen"
+
+#, python-format
+msgid "added %(etype)s #%(eid)s (%(title)s)"
+msgstr "Hinzufügen der Entität %(etype)s #%(eid)s (%(title)s)"
+
+#, python-format
+msgid ""
+"added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #"
+"%(eidto)s"
+msgstr ""
+"Die Relation %(rtype)s von %(frometype)s #%(eidfrom)s zu %(toetype)s #"
+"%(eidto)s wurde hinzugefügt."
+
+msgid "addrelated"
+msgstr "hinzufügen"
+
+msgid "address"
+msgstr "Adresse"
+
+msgctxt "EmailAddress"
+msgid "address"
+msgstr "Adresse"
+
+msgid "alias"
+msgstr "Alias"
+
+msgctxt "EmailAddress"
+msgid "alias"
+msgstr "Alias"
+
+msgid "allow to set a specific workflow for an entity"
+msgstr "erlaube, einen bestimmten Workflow für eine Entität zu setzen"
+
+msgid "allowed transitions from this state"
+msgstr "erlaubte Übergänge von diesem Zustand"
+
+msgid "allowed_transition"
+msgstr "erlaubter Übergang"
+
+msgctxt "State"
+msgid "allowed_transition"
+msgstr "erlaubter Übergang"
+
+msgid "allowed_transition_object"
+msgstr "ausstehende Zustände"
+
+msgctxt "BaseTransition"
+msgid "allowed_transition_object"
+msgstr "ausstehende Zustände"
+
+msgctxt "Transition"
+msgid "allowed_transition_object"
+msgstr "ausstehende Zustände"
+
+msgctxt "WorkflowTransition"
+msgid "allowed_transition_object"
+msgstr "ausstehende Zustände"
+
+msgid "am/pm calendar (month)"
+msgstr "am/pm Kalender (Monat)"
+
+msgid "am/pm calendar (semester)"
+msgstr "am/pm Kalender (Halbjahr)"
+
+msgid "am/pm calendar (week)"
+msgstr "am/pm Kalender (Woche)"
+
+msgid "am/pm calendar (year)"
+msgstr "am/pm Kalender (Jahr)"
+
+msgid "an electronic mail address associated to a short alias"
+msgstr "Eine E-Mail-Adresse wurde mit einem Alias verknüpft."
+
+msgid "an error occurred"
+msgstr "Es ist ein Fehler aufgetreten."
+
+msgid "an error occurred while processing your request"
+msgstr "Während der Bearbeitung Ihrer Anfrage ist ein Fehler aufgetreten."
+
+msgid "an error occurred, the request cannot be fulfilled"
+msgstr ""
+"Es ist ein Fehler aufgetreten, Ihre Anfrage kann nicht bearbeitet werden."
+
+msgid "an integer is expected"
+msgstr "Ganze Zahl (integer) erwartet."
+
+msgid "and linked"
+msgstr "und verknüpft"
+
+msgid "and/or between different values"
+msgstr "und/oder zwischen verschiedenen Werten"
+
+msgid "anonymous"
+msgstr "anonym"
+
+msgid "application entities"
+msgstr "Anwendungs-Entitäten"
+
+msgid "april"
+msgstr "April"
+
+#, python-format
+msgid "archive for %(author)s"
+msgstr ""
+
+#, python-format
+msgid "archive for %(month)s/%(year)s"
+msgstr ""
+
+#, python-format
+msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
+msgstr ""
+"Die Entität %(eid)s ´vom Typ %(etype)s muss mindestens mit einer \n"
+"anderen durch die Relation %(rtype)s verknüpft sein."
+
+msgid "attribute"
+msgstr "Attribut"
+
+msgid "august"
+msgstr "August"
+
+msgid "authentication failure"
+msgstr "Nutzername oder Passwort falsch"
+
+msgid "auto"
+msgstr "automatisch"
+
+msgid "automatic"
+msgstr "automatisch"
+
+msgid "bad value"
+msgstr "Unzulässiger Wert"
+
+msgid "base url"
+msgstr "Basis-URL"
+
+msgid "bookmark has been removed"
+msgstr "Das Lesezeichen wurde gelöscht."
+
+msgid "bookmark this page"
+msgstr "diese Seite merken"
+
+msgid "bookmark this search"
+msgstr "diese Suche merken"
+
+msgid "bookmarked_by"
+msgstr "Lesezeichen angelegt durch"
+
+msgctxt "Bookmark"
+msgid "bookmarked_by"
+msgstr "Lesezeichen angelegt durch"
+
+msgid "bookmarked_by_object"
+msgstr "hat Lesezeichen"
+
+msgctxt "CWUser"
+msgid "bookmarked_by_object"
+msgstr "verwendet Lesezeichen"
+
+msgid "bookmarks"
+msgstr "Lesezeichen"
+
+msgid "bookmarks are used to have user's specific internal links"
+msgstr "Lesezeichen werden für nutzer-spezifische interne Links verwendet"
+
+msgid "boxes"
+msgstr "Boxen"
+
+msgid "bug report sent"
+msgstr "Fehlerbericht gesendet"
+
+msgid "button_apply"
+msgstr "Anwenden"
+
+msgid "button_cancel"
+msgstr "Abbrechen"
+
+msgid "button_delete"
+msgstr "Löschen"
+
+msgid "button_ok"
+msgstr "OK"
+
+msgid "by"
+msgstr "durch"
+
+msgid "by relation"
+msgstr "durch die Relation"
+
+msgid "by_transition"
+msgstr "je Übergang"
+
+msgctxt "TrInfo"
+msgid "by_transition"
+msgstr "je Übergang"
+
+msgid "by_transition_object"
+msgstr "Übergangsinformation"
+
+msgctxt "BaseTransition"
+msgid "by_transition_object"
+msgstr "Übergangsinformation"
+
+msgctxt "Transition"
+msgid "by_transition_object"
+msgstr "Übergangsinformation"
+
+msgctxt "WorkflowTransition"
+msgid "by_transition_object"
+msgstr "Übergangsinformation"
+
+msgid "calendar"
+msgstr "Kalender anzeigen"
+
+msgid "calendar (month)"
+msgstr "Kalender (monatlich)"
+
+msgid "calendar (semester)"
+msgstr "Kalender (halbjährlich)"
+
+msgid "calendar (week)"
+msgstr "Kalender (wöchentlich)"
+
+msgid "calendar (year)"
+msgstr "Kalender (jährlich)"
+
+msgid "can not resolve entity types:"
+msgstr "Die Typen konnten nicht ermittelt werden:"
+
+msgid "can't be changed"
+msgstr "kann nicht geändert werden"
+
+msgid "can't be deleted"
+msgstr "kann nicht entfernt werden"
+
+#, python-format
+msgid "can't change the %s attribute"
+msgstr "Kann das Attribut %s nicht ändern."
+
+#, python-format
+msgid "can't connect to source %s, some data may be missing"
+msgstr "Keine Verbindung zu der Quelle %s, einige Daten könnten fehlen"
+
+#, python-format
+msgid "can't display data, unexpected error: %s"
+msgstr "Kann die Daten aufgrund des folgenden Fehlers nicht anzeigen: %s"
+
+msgid "can't have multiple exits on the same state"
+msgstr "Mehrere Ausgänge aus demselben Zustand nicht möglich."
+
+#, python-format
+msgid "can't parse %(value)r (expected %(format)s)"
+msgstr ""
+"Kann den Wert %(value)r nicht analysieren (erwartetes Format: %(format)s)"
+
+#, python-format
+msgid ""
+"can't set inlined=True, %(stype)s %(rtype)s %(otype)s has cardinality="
+"%(card)s"
+msgstr ""
+
+msgid "cancel"
+msgstr ""
+
+msgid "cancel select"
+msgstr "Auswahl aufheben"
+
+msgid "cancel this insert"
+msgstr "diese Einfügung aufheben"
+
+msgid "cardinality"
+msgstr "Kardinalität"
+
+msgctxt "CWAttribute"
+msgid "cardinality"
+msgstr "Kardinalität"
+
+msgctxt "CWRelation"
+msgid "cardinality"
+msgstr "Kardinalität"
+
+msgid "category"
+msgstr "Kategorie"
+
+#, python-format
+msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
+msgstr "Änderung des Zustands von %(etype)s #%(eid)s (%(title)s)"
+
+msgid "changes applied"
+msgstr "Änderungen übernommen"
+
+msgid "click here to see created entity"
+msgstr "Hier klicken, um die angelegte Entität anzusehen"
+
+msgid "click here to see edited entity"
+msgstr ""
+
+msgid "click on the box to cancel the deletion"
+msgstr "Klicken Sie die Box an, um das Löschen rückgängig zu machen."
+
+msgid "click to add a value"
+msgstr "Klicken Sie, um einen Wert hinzuzufügen"
+
+msgid "click to delete this value"
+msgstr "Klicken Sie, um diesen Wert zu löschen"
+
+msgid "click to edit this field"
+msgstr "Klicken Sie, um dieses Feld zu editieren"
+
+msgid "comment"
+msgstr "Kommentar"
+
+msgctxt "TrInfo"
+msgid "comment"
+msgstr "Kommentar"
+
+msgid "comment_format"
+msgstr "Format"
+
+msgctxt "TrInfo"
+msgid "comment_format"
+msgstr "Format"
+
+msgid "components"
+msgstr "Komponenten"
+
+msgid "components_etypenavigation"
+msgstr "nach Typ filtern"
+
+msgid "components_etypenavigation_description"
+msgstr "Erlaubt die Sortierung von Suchergebnissen nach Entitätstyp"
+
+msgid "components_navigation"
+msgstr "Seitennavigation"
+
+msgid "components_navigation_description"
+msgstr "Paginierungs-Komponente für große Ergebnismengen"
+
+msgid "components_rqlinput"
+msgstr "rql Eingabefeld"
+
+msgid "components_rqlinput_description"
+msgstr "das rql-Eingabefeld im Seitenkopf"
+
+msgid "composite"
+msgstr ""
+
+msgctxt "CWRelation"
+msgid "composite"
+msgstr "composite"
+
+msgid "condition"
+msgstr "Bedingung"
+
+msgctxt "BaseTransition"
+msgid "condition"
+msgstr "Bedingung"
+
+msgctxt "Transition"
+msgid "condition"
+msgstr "Bedingung"
+
+msgctxt "WorkflowTransition"
+msgid "condition"
+msgstr "Bedingung"
+
+msgid "condition_object"
+msgstr "Bedingung von"
+
+msgctxt "RQLExpression"
+msgid "condition_object"
+msgstr "Bedingung von"
+
+msgid "conditions"
+msgstr "Bedingungen"
+
+msgid "config"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "config"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "config"
+msgstr ""
+
+msgid "config mode"
+msgstr "Konfigurationsmodus"
+
+msgid "config type"
+msgstr "Konfigurationstyp"
+
+msgid "confirm password"
+msgstr "Passwort bestätigen"
+
+msgid "constrained_by"
+msgstr "eingeschränkt durch"
+
+msgctxt "CWAttribute"
+msgid "constrained_by"
+msgstr "eingeschränkt durch"
+
+msgctxt "CWRelation"
+msgid "constrained_by"
+msgstr "eingeschränkt durch"
+
+msgid "constrained_by_object"
+msgstr "Einschränkungen"
+
+msgctxt "CWConstraint"
+msgid "constrained_by_object"
+msgstr "Einschränkungen"
+
+msgid "constraint factory"
+msgstr "Einschränkungs-Factory"
+
+msgid "constraint_of"
+msgstr ""
+
+msgctxt "CWUniqueTogetherConstraint"
+msgid "constraint_of"
+msgstr ""
+
+msgid "constraint_of_object"
+msgstr ""
+
+msgctxt "CWEType"
+msgid "constraint_of_object"
+msgstr ""
+
+msgid "constraints"
+msgstr "Einschränkungen"
+
+msgid "constraints applying on this relation"
+msgstr "auf diese Relation angewandte Einschränkung"
+
+msgid "content type"
+msgstr "MIME-Typ"
+
+msgid "context"
+msgstr "Kontext"
+
+msgid "context where this box should be displayed"
+msgstr "Kontext, in dem diese Box angezeigt werden soll"
+
+msgid "context where this component should be displayed"
+msgstr "Kontext, in dem diese Komponente angezeigt werden soll"
+
+msgid "context where this facet should be displayed, leave empty for both"
+msgstr ""
+"Kontext, wo diese Nachricht angezeigt werden soll; für beides: frei lassen."
+
+msgid "control subject entity's relations order"
+msgstr ""
+
+msgid "copy"
+msgstr "kopieren"
+
+msgid "core relation indicating a user's groups"
+msgstr "Kernrelation für die Gruppen eines Nutzers"
+
+msgid ""
+"core relation indicating owners of an entity. This relation implicitly put "
+"the owner into the owners group for the entity"
+msgstr ""
+
+msgid "core relation indicating the original creator of an entity"
+msgstr "Kernrelation für den Urheber einer Entität"
+
+msgid "core relation indicating the type of an entity"
+msgstr "Kernrelation für den Identitätstyp"
+
+msgid ""
+"core relation indicating the types (including specialized types) of an entity"
+msgstr ""
+
+msgid "cost"
+msgstr "Kosten"
+
+msgid "could not connect to the SMTP server"
+msgstr "Keine Verbindung mit dem SMTP-Server"
+
+msgid "create an index for quick search on this attribute"
+msgstr "Erstelle einen Index zur schnellen Suche über dieses Attribut"
+
+msgid "create an index page"
+msgstr "Eine Index-Seite anlegen"
+
+msgid "created on"
+msgstr "angelegt am"
+
+msgid "created_by"
+msgstr "erstellt von"
+
+msgid "created_by_object"
+msgstr "hat erstellt"
+
+msgid "creating Bookmark (Bookmark bookmarked_by CWUser %(linkto)s)"
+msgstr "Erstelle Lesezeichen für %(linkto)s"
+
+msgid "creating CWAttribute (CWAttribute relation_type CWRType %(linkto)s)"
+msgstr "Erstelle Attribut %(linkto)s"
+
+msgid ""
+"creating CWConstraint (CWAttribute %(linkto)s constrained_by CWConstraint)"
+msgstr "Erstelle Einschränkung für attribute %(linkto)s"
+
+msgid ""
+"creating CWConstraint (CWRelation %(linkto)s constrained_by CWConstraint)"
+msgstr "Erstelle Einschränkung für Relation %(linkto)s"
+
+msgid "creating CWProperty (CWProperty for_user CWUser %(linkto)s)"
+msgstr "Erstelle Eigenschaft für Nutzer %(linkto)s"
+
+msgid "creating CWRelation (CWRelation relation_type CWRType %(linkto)s)"
+msgstr "Erstelle Relation %(linkto)s"
+
+msgid ""
+"creating CWSourceHostConfig (CWSourceHostConfig cw_host_config_of CWSource "
+"%(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating CWUniqueTogetherConstraint (CWUniqueTogetherConstraint "
+"constraint_of CWEType %(linkto)s)"
+msgstr ""
+
+msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)"
+msgstr "Erstelle neuen Nutzer in Gruppe %(linkto)s"
+
+msgid "creating EmailAddress (CWUser %(linkto)s use_email EmailAddress)"
+msgstr "Erstelle E-Mail-Adresse für Nutzer %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr "RQL-Ausdruck für Leseberechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s update_permission "
+"RQLExpression)"
+msgstr "RQL Ausdruck für Aktualisierungs-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
+msgstr "Erstelle rql-Ausdruck für Hinzufüge-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s delete_permission RQLExpression)"
+msgstr "Erstelle rql-Ausdruck für Lösch-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s read_permission RQLExpression)"
+msgstr "Erstelle rql-Ausdruck für Lese-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWEType %(linkto)s update_permission RQLExpression)"
+msgstr "Erstelle rql-Ausdruck für Aktualisierungs-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWRelation %(linkto)s add_permission RQLExpression)"
+msgstr "RQL-Ausdruck zur Vergabe der Hinzufüge-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWRelation %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr "RQL-Ausdruck zur Vergabe der Lösch-Berechtigung für %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWRelation %(linkto)s read_permission RQLExpression)"
+msgstr "RQL-Ausdruck zur Vergabe der Lese-Berechtigung für %(linkto)s"
+
+msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
+msgstr "Erstelle RQL-Ausdruck für Übergang %(linkto)s"
+
+msgid ""
+"creating RQLExpression (WorkflowTransition %(linkto)s condition "
+"RQLExpression)"
+msgstr "Erstelle RQL-Ausdruck für Workflow-Übergang %(linkto)s"
+
+msgid "creating State (State allowed_transition Transition %(linkto)s)"
+msgstr "Erstelle einen zustand, der den Übergang %(linkto)s auslösen kann."
+
+msgid "creating State (State state_of Workflow %(linkto)s)"
+msgstr "Erstelle Zustand des Workflows %(linkto)s"
+
+msgid "creating State (Transition %(linkto)s destination_state State)"
+msgstr "Erstelle Zielzustand für Übergang %(linkto)s"
+
+msgid ""
+"creating SubWorkflowExitPoint (WorkflowTransition %(linkto)s "
+"subworkflow_exit SubWorkflowExitPoint)"
+msgstr "Erstelle Subworkflow Exit-Punkt für Workflow-Übergang %(linkto)s"
+
+msgid "creating Transition (State %(linkto)s allowed_transition Transition)"
+msgstr "Erstelle auslösbaren Übergang für Zustand %(linkto)s"
+
+msgid "creating Transition (Transition destination_state State %(linkto)s)"
+msgstr "Erstelle Übergang, der zu Zustand %(linkto)s führt."
+
+msgid "creating Transition (Transition transition_of Workflow %(linkto)s)"
+msgstr "Erstelle Übergang des Workflows %(linkto)s"
+
+msgid ""
+"creating WorkflowTransition (State %(linkto)s allowed_transition "
+"WorkflowTransition)"
+msgstr "Erstelle Workflow-Übergang, der zum Zustand %(linkto)s führt."
+
+msgid ""
+"creating WorkflowTransition (WorkflowTransition transition_of Workflow "
+"%(linkto)s)"
+msgstr "Erstelle Workflow-Übergang des Workflows %(linkto)s"
+
+msgid "creation"
+msgstr "Erstellung"
+
+msgid "creation date"
+msgstr "Erstellungsdatum"
+
+msgid "creation time of an entity"
+msgstr "Erstellungszeitpunkt einer Entität"
+
+msgid "creation_date"
+msgstr "Erstellungsdatum"
+
+msgid "cstrtype"
+msgstr "Typ der Einschränkung"
+
+msgctxt "CWConstraint"
+msgid "cstrtype"
+msgstr "Einschränkungstyp"
+
+msgid "cstrtype_object"
+msgstr "benutzt von"
+
+msgctxt "CWConstraintType"
+msgid "cstrtype_object"
+msgstr "Einschränkungstyp von"
+
+msgid "csv entities export"
+msgstr "CSV-Export von Entitäten"
+
+msgid "csv export"
+msgstr "CSV-Export"
+
+msgid "ctxcomponents"
+msgstr "Kontext-Komponenten"
+
+msgid "ctxcomponents_anonuserlink"
+msgstr ""
+
+msgid "ctxcomponents_anonuserlink_description"
+msgstr ""
+
+msgid "ctxcomponents_appliname"
+msgstr ""
+
+msgid "ctxcomponents_appliname_description"
+msgstr ""
+
+msgid "ctxcomponents_bookmarks_box"
+msgstr "Lesezeichen-Box"
+
+msgid "ctxcomponents_bookmarks_box_description"
+msgstr "Box mit einer Liste der Lesezeichen des Nutzers"
+
+msgid "ctxcomponents_breadcrumbs"
+msgstr "Brotkrumen"
+
+msgid "ctxcomponents_breadcrumbs_description"
+msgstr ""
+"Anzeigen eines Pfads zur Lokalisierung der aktuellen Seite innerhalb der Site"
+
+msgid "ctxcomponents_download_box"
+msgstr "Download-Box"
+
+msgid "ctxcomponents_download_box_description"
+msgstr ""
+
+msgid "ctxcomponents_edit_box"
+msgstr "Aktionsbox"
+
+msgid "ctxcomponents_edit_box_description"
+msgstr "Box mit verfügbaren Aktionen für die angezeigten Daten"
+
+msgid "ctxcomponents_facet.filters"
+msgstr "Filter"
+
+msgid "ctxcomponents_facet.filters_description"
+msgstr "Box mit Filter für aktuelle Suchergebnis-Funktionalität"
+
+msgid "ctxcomponents_logo"
+msgstr "Icon"
+
+msgid "ctxcomponents_logo_description"
+msgstr "Das Anwendungs-Ikon angezeigt im Bildschirmkopf"
+
+msgid "ctxcomponents_metadata"
+msgstr "Metadaten für Entität metadata"
+
+msgid "ctxcomponents_metadata_description"
+msgstr ""
+
+msgid "ctxcomponents_possible_views_box"
+msgstr "Box mit möglichen Ansichten"
+
+msgid "ctxcomponents_possible_views_box_description"
+msgstr "Box mit möglichen Ansichten für die angezeigten Daten"
+
+msgid "ctxcomponents_prevnext"
+msgstr "vorherige/nächste Entität"
+
+msgid "ctxcomponents_prevnext_description"
+msgstr ""
+"display link to go from one entity to another on entities implementing the "
+"\"previous/next\" interface."
+
+msgid "ctxcomponents_rss"
+msgstr "RSS-Box"
+
+msgid "ctxcomponents_rss_description"
+msgstr "RSS icon um die angezeigten Daten als RSS-Thread zu erhalten"
+
+msgid "ctxcomponents_search_box"
+msgstr "Suchbox"
+
+msgid "ctxcomponents_search_box_description"
+msgstr "Suchbox"
+
+msgid "ctxcomponents_startup_views_box"
+msgstr "Box für Start-Ansicht"
+
+msgid "ctxcomponents_startup_views_box_description"
+msgstr "Box mit möglichen Start-Ansichten"
+
+msgid "ctxcomponents_userstatus"
+msgstr ""
+
+msgid "ctxcomponents_userstatus_description"
+msgstr ""
+
+msgid "ctxcomponents_wfhistory"
+msgstr "Workflow-Chronik"
+
+msgid "ctxcomponents_wfhistory_description"
+msgstr "Zeite die Workflow-Chronik."
+
+msgid "ctxtoolbar"
+msgstr "Werkzeugleiste"
+
+msgid "custom_workflow"
+msgstr "angepasster Workflow"
+
+msgid "custom_workflow_object"
+msgstr "angepasster Workflow von"
+
+msgid "cw_dont_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_dont_cross"
+msgstr ""
+
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgid "cw_host_config_of"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "cw_host_config_of"
+msgstr ""
+
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgid "cw_may_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_may_cross"
+msgstr ""
+
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgid "cw_source"
+msgstr ""
+
+msgid "cw_source_object"
+msgstr ""
+
+msgid "cw_support"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_support"
+msgstr ""
+
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWEType"
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_support_object"
+msgstr ""
+
+msgid "cwetype-box"
+msgstr "Box-Ansicht"
+
+msgid "cwetype-description"
+msgstr "Beschreibung"
+
+msgid "cwetype-permissions"
+msgstr "Berechtigungen"
+
+msgid "cwetype-views"
+msgstr "Ansichten"
+
+msgid "cwetype-workflow"
+msgstr "Workflow"
+
+msgid "cwgroup-main"
+msgstr "Beschreibung"
+
+msgid "cwgroup-permissions"
+msgstr "Berechtigungen"
+
+msgid "cwrtype-description"
+msgstr "Beschreibung"
+
+msgid "cwrtype-permissions"
+msgstr "Berechtigungen"
+
+msgid "cwuri"
+msgstr "interner URI"
+
+msgid "data directory url"
+msgstr "URL des Daten-Pools"
+
+msgid "date"
+msgstr "Datum"
+
+msgid "deactivate"
+msgstr "deaktivieren"
+
+msgid "deactivated"
+msgstr "deaktiviert"
+
+msgid "december"
+msgstr "Dezember"
+
+msgid "default"
+msgstr "Standardwert"
+
+msgid "default text format for rich text fields."
+msgstr "Standardformat für Textfelder"
+
+msgid "default user workflow"
+msgstr "Standard-Workflow für Nutzer"
+
+msgid "default value"
+msgstr "Standardwert"
+
+msgid "default workflow for an entity type"
+msgstr "Standard-Workflow eines Entitätstyps"
+
+msgid "default_workflow"
+msgstr "Standard-Workflow"
+
+msgctxt "CWEType"
+msgid "default_workflow"
+msgstr "Standard-Workflow"
+
+msgid "default_workflow_object"
+msgstr "Standard-Workflow von"
+
+msgctxt "Workflow"
+msgid "default_workflow_object"
+msgstr "Standard-Workflow von"
+
+msgid "defaultval"
+msgstr "Standard-Wert"
+
+msgctxt "CWAttribute"
+msgid "defaultval"
+msgstr "Standard-Wert"
+
+msgid "define a CubicWeb user"
+msgstr "Einen CubicWeb-Nutzer definieren"
+
+msgid "define a CubicWeb users group"
+msgstr "Eine CubicWeb-Nutzergruppe definieren"
+
+msgid ""
+"define a final relation: link a final relation type from a non final entity "
+"to a final entity type. used to build the instance schema"
+msgstr ""
+
+msgid ""
+"define a non final relation: link a non final relation type from a non final "
+"entity to a non final entity type. used to build the instance schema"
+msgstr ""
+
+msgid "define a relation type, used to build the instance schema"
+msgstr ""
+"Definieren eines Relationstyps, der zur Erstellung des Instanz-Schemas "
+"benutzt wird."
+
+msgid "define a rql expression used to define permissions"
+msgstr "Definieren eines RQL-Ausdrucks zur Festlegung von Berechtigungen."
+
+msgid "define a schema constraint"
+msgstr "Eine Schema-Einschränkung definieren"
+
+msgid "define a schema constraint type"
+msgstr "den Typ einer Schema-Einschränkung definieren"
+
+msgid "define an entity type, used to build the instance schema"
+msgstr "definieren eines Entitätstyps zur Erstellung des Instanz-Schemas"
+
+msgid "define how we get out from a sub-workflow"
+msgstr "Definieren, wie man aus einem Sub-Workflow herauskommt"
+
+msgid "defines a sql-level multicolumn unique index"
+msgstr "definiert auf SQL-Ebene einen eindeutigen Index über mehrere Spalten"
+
+msgid ""
+"defines what's the property is applied for. You must select this first to be "
+"able to set value"
+msgstr ""
+"definiert, worauf die Eigenschaft angewendet wird. Sie müssen dies zunächst "
+"markieren,um den Wert zuzuweisen."
+
+msgid "delete"
+msgstr "löschen"
+
+msgid "delete this bookmark"
+msgstr "dieses Lesezeichen löschen"
+
+msgid "delete this permission"
+msgstr "dieses Recht löschen"
+
+msgid "delete this relation"
+msgstr "diese Relation löschen"
+
+msgid "delete_permission"
+msgstr "kann gelöscht werden durch"
+
+msgctxt "CWEType"
+msgid "delete_permission"
+msgstr "Lösch-Berechtigung"
+
+msgctxt "CWRelation"
+msgid "delete_permission"
+msgstr "Lösch-Berechtigung"
+
+msgid "delete_permission_object"
+msgstr "hat Lösch-Berechtigung"
+
+msgctxt "CWGroup"
+msgid "delete_permission_object"
+msgstr "hat Lösch-Berechtigung für"
+
+msgctxt "RQLExpression"
+msgid "delete_permission_object"
+msgstr "hat die Berechtigung, zu löschen"
+
+#, python-format
+msgid "deleted %(etype)s #%(eid)s (%(title)s)"
+msgstr "Löschen der Entität %(etype)s #%(eid)s (%(title)s)"
+
+#, python-format
+msgid ""
+"deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #"
+"%(eidto)s"
+msgstr ""
+"Relation %(rtype)s von %(frometype)s #%(eidfrom)s zu %(toetype)s #%(eidto)s "
+"gelöscht"
+
+msgid "depends on the constraint type"
+msgstr "hängt vom Einschränkungsgyp ab"
+
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "BaseTransition"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "CWAttribute"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "CWEType"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "CWRType"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "CWRelation"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "State"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "Transition"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "Workflow"
+msgid "description"
+msgstr "Beschreibung"
+
+msgctxt "WorkflowTransition"
+msgid "description"
+msgstr "Beschreibung"
+
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "BaseTransition"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "CWAttribute"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "CWEType"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "CWRType"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "CWRelation"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "State"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "Transition"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "Workflow"
+msgid "description_format"
+msgstr "Format"
+
+msgctxt "WorkflowTransition"
+msgid "description_format"
+msgstr "Format"
+
+msgid "destination state for this transition"
+msgstr "Zielzustand dieses Übergangs"
+
+msgid "destination state must be in the same workflow as our parent transition"
+msgstr "Zielzustand muss im selben Workflow sein wie unser Parent-Übergang"
+
+msgid "destination state of a transition"
+msgstr "Zielzustand eines Übergangs"
+
+msgid ""
+"destination state. No destination state means that transition should go back "
+"to the state from which we've entered the subworkflow."
+msgstr ""
+"Zielzustand. Kein Zielzustand bedeutet, dass der Übergang in den Zustand "
+"zurückführen soll, von dem aus der Subworkflow erreicht wurde."
+
+msgid "destination_state"
+msgstr "Zielzustand"
+
+msgctxt "SubWorkflowExitPoint"
+msgid "destination_state"
+msgstr "Zielzustand"
+
+msgctxt "Transition"
+msgid "destination_state"
+msgstr "Zielzustand"
+
+msgid "destination_state_object"
+msgstr "Ziel von"
+
+msgctxt "State"
+msgid "destination_state_object"
+msgstr "Ziel von"
+
+msgid "detach attached file"
+msgstr "angehängte Datei abtrennen"
+
+msgid "display order of the box"
+msgstr "angezeigte Reihenfolge der Boxen"
+
+msgid "display order of the component"
+msgstr "angezeigte Reihenfolge der Komponenten"
+
+msgid "display order of the facet"
+msgstr "angezeigte Reihenfolge der Facetten"
+
+msgid "display the box or not"
+msgstr "die Box anzeigen oder nicht"
+
+msgid "display the component or not"
+msgstr "die Komponente anzeigen oder nicht"
+
+msgid "display the facet or not"
+msgstr "die Facette anzeigen oder nicht"
+
+msgid ""
+"distinct label to distinguate between other permission entity of the same "
+"name"
+msgstr ""
+"Zusätzliches Label, um von anderen Berechtigungsentitäten unterscheiden zu "
+"können."
+
+msgid "download"
+msgstr "Herunterladen"
+
+#, python-format
+msgid "download %s"
+msgstr "Herunterladen von %s"
+
+msgid "download icon"
+msgstr "Ikone 'herunterladen'"
+
+msgid "download schema as owl"
+msgstr "Schema als OWL herunterladen"
+
+msgid "edit bookmarks"
+msgstr "Lesezeichen bearbeiten"
+
+msgid "edit canceled"
+msgstr "Änderungen verwerfen"
+
+msgid "edit the index page"
+msgstr "Index-Seite bearbeiten"
+
+msgid "editable-table"
+msgstr "bearbeitbare Tabelle"
+
+msgid "eid"
+msgstr ""
+
+msgid "email address to use for notification"
+msgstr "E-Mail-Adresse für Mitteilungen."
+
+msgid "emails successfully sent"
+msgstr "E-Mails erfolgreich versandt."
+
+msgid "embed"
+msgstr "einbetten"
+
+msgid "embedded html"
+msgstr "HTML-Inhalt"
+
+msgid "embedding this url is forbidden"
+msgstr "Einbettung dieses URLs ist nicht erlaubt."
+
+msgid "entities deleted"
+msgstr "Entitäten gelöscht"
+
+msgid "entity copied"
+msgstr "Entität kopiert"
+
+msgid "entity created"
+msgstr "Entität erstellt"
+
+msgid "entity creation"
+msgstr "Erstellung der Entität"
+
+msgid "entity deleted"
+msgstr "Entität gelöscht"
+
+msgid "entity deletion"
+msgstr "Löschen der Entität"
+
+msgid "entity edited"
+msgstr "Entität bearbeitet"
+
+msgid "entity has no workflow set"
+msgstr "Entität hat keinen Workflow"
+
+msgid "entity linked"
+msgstr "Entität verknüpft"
+
+msgid "entity type"
+msgstr "Entitätstyp"
+
+msgid ""
+"entity type that may be used to construct some advanced security "
+"configuration"
+msgstr ""
+"Entitätstyp zum Aufbau einer fortgeschrittenen Sicherheitskonfiguration."
+
+msgid "entity types which may use this workflow"
+msgstr "Entitätstypen, die diesen Workflow benutzen können."
+
+msgid "entity update"
+msgstr "Aktualisierung der Entität"
+
+msgid "error while embedding page"
+msgstr "Fehler beim Einbetten der Seite"
+
+msgid "error while publishing ReST text"
+msgstr "Fehler beim Übersetzen von reST"
+
+#, python-format
+msgid "error while querying source %s, some data may be missing"
+msgstr ""
+"Fehler beim Zugriff auf Quelle %s, möglicherweise sind die Daten "
+"unvollständig."
+
+msgid "eta_date"
+msgstr "Enddatum"
+
+msgid "exit state must be a subworkflow state"
+msgstr "Exit-Zustand muss ein Subworkflow-Zustand sein."
+
+msgid "exit_point"
+msgstr "Exit-Punkt "
+
+msgid "exit_point_object"
+msgstr "Exit-Punkt für"
+
+#, python-format
+msgid "exiting from subworkflow %s"
+msgstr "verlasse Subworkflow %s"
+
+msgid "expected:"
+msgstr "erwartet:"
+
+msgid "expression"
+msgstr "Ausdruck"
+
+msgctxt "RQLExpression"
+msgid "expression"
+msgstr "Ausdruck"
+
+msgid "exprtype"
+msgstr "Typ des Ausdrucks"
+
+msgctxt "RQLExpression"
+msgid "exprtype"
+msgstr "Typ des Ausdrucks"
+
+msgid "external page"
+msgstr "externe Seite"
+
+msgid "facet.filters"
+msgstr ""
+
+msgid "facetbox"
+msgstr "Facetten-Box"
+
+msgid "facets_created_by-facet"
+msgstr "\"erstellt durch\" facet"
+
+msgid "facets_created_by-facet_description"
+msgstr ""
+
+msgid "facets_cw_source-facet"
+msgstr ""
+
+msgid "facets_cw_source-facet_description"
+msgstr ""
+
+msgid "facets_cwfinal-facet"
+msgstr "\"finaler Entitäts- oder Relationstyp\" facet"
+
+msgid "facets_cwfinal-facet_description"
+msgstr ""
+
+msgid "facets_etype-facet"
+msgstr "\"Entitätstyp\" facet"
+
+msgid "facets_etype-facet_description"
+msgstr ""
+
+msgid "facets_has_text-facet"
+msgstr "\"hat Text\" facet"
+
+msgid "facets_has_text-facet_description"
+msgstr ""
+
+msgid "facets_in_group-facet"
+msgstr "\"in Gruppe\" facet"
+
+msgid "facets_in_group-facet_description"
+msgstr ""
+
+msgid "facets_in_state-facet"
+msgstr "\"in Zustand\" facet"
+
+msgid "facets_in_state-facet_description"
+msgstr ""
+
+#, python-format
+msgid "failed to uniquify path (%s, %s)"
+msgstr "Konnte keinen eindeutigen Dateinamen erzeugen (%s, %s)"
+
+msgid "february"
+msgstr "Februar"
+
+msgid "file tree view"
+msgstr "Baumansicht (Dateien)"
+
+msgid "final"
+msgstr ""
+
+msgctxt "CWEType"
+msgid "final"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "final"
+msgstr ""
+
+msgid "first name"
+msgstr "Vorname"
+
+msgid "firstname"
+msgstr "Vorname"
+
+msgctxt "CWUser"
+msgid "firstname"
+msgstr "Vorname"
+
+msgid "foaf"
+msgstr "FOAF"
+
+msgid "follow"
+msgstr "dem Link folgen"
+
+#, python-format
+msgid "follow this link for more information on this %s"
+msgstr "Folgend Sie dem Link für mehr Informationen über %s"
+
+msgid "follow this link if javascript is deactivated"
+msgstr "Folgen Sie diesem Link, falls Javascript deaktiviert ist."
+
+msgid "for_user"
+msgstr "für den Nutzer"
+
+msgctxt "CWProperty"
+msgid "for_user"
+msgstr "für Nutzer"
+
+msgid "for_user_object"
+msgstr "hat als Eigenschaft"
+
+msgctxt "CWUser"
+msgid "for_user_object"
+msgstr "verwendet die Eigenschaften"
+
+msgid "friday"
+msgstr "Freitag"
+
+msgid "from"
+msgstr "von"
+
+#, python-format
+msgid "from %(date)s"
+msgstr "vom %(date)s"
+
+msgid "from_entity"
+msgstr "der Entität"
+
+msgctxt "CWAttribute"
+msgid "from_entity"
+msgstr "Attribut der Entität"
+
+msgctxt "CWRelation"
+msgid "from_entity"
+msgstr "Relation der Entität"
+
+msgid "from_entity_object"
+msgstr "der Entität"
+
+msgctxt "CWEType"
+msgid "from_entity_object"
+msgstr "Subjektrelation"
+
+msgid "from_interval_start"
+msgstr "Von"
+
+msgid "from_state"
+msgstr "des Zustands"
+
+msgctxt "TrInfo"
+msgid "from_state"
+msgstr "Anfangszustand"
+
+msgid "from_state_object"
+msgstr "Übergänge aus diesem Zustand"
+
+msgctxt "State"
+msgid "from_state_object"
+msgstr "Anfangszustand von"
+
+msgid "full text or RQL query"
+msgstr "Volltextsuche oder RQL-Anfrage"
+
+msgid "fulltext_container"
+msgstr "Container des indizierten Textes"
+
+msgctxt "CWRType"
+msgid "fulltext_container"
+msgstr "zu indizierendes Objekt"
+
+msgid "fulltextindexed"
+msgstr "Indizierung des Textes"
+
+msgctxt "CWAttribute"
+msgid "fulltextindexed"
+msgstr "indizierter Text"
+
+msgid "generic plot"
+msgstr "generischer Plot"
+
+msgid "generic relation to link one entity to another"
+msgstr "generische Relation zur Verbindung einer Entität mit einer anderen"
+
+msgid ""
+"generic relation to specify that an external entity represent the same "
+"object as a local one: http://www.w3.org/TR/owl-ref/#sameAs-def"
+msgstr ""
+"generische Relation, die anzeigt, dass eine Entität mit einer anderen Web-"
+"Ressource identisch ist (siehe http://www.w3.org/TR/owl-ref/#sameAs-def)."
+
+msgid "go back to the index page"
+msgstr "Zurück zur Index-Seite"
+
+msgid "granted to groups"
+msgstr "an Gruppen gewährt"
+
+#, python-format
+msgid "graphical representation of %(appid)s data model"
+msgstr "graphische Darstellung des Datenmodells von %(appid)s"
+
+#, fuzzy, python-format
+msgid ""
+"graphical representation of the %(etype)s entity type from %(appid)s data "
+"model"
+msgstr ""
+"graphische Darstellung des Datenmodells des Entitätstyps (etype)s von "
+"%(appid)s"
+
+#, python-format
+msgid ""
+"graphical representation of the %(rtype)s relation type from %(appid)s data "
+"model"
+msgstr ""
+"graphische Darstellung des Datenmodells des Relationstyps %(rtype)s von "
+"%(appid)s"
+
+msgid "group in which a user should be to be allowed to pass this transition"
+msgstr ""
+"Gruppe, zu welcher der Nutzer gehören muss, um die Transaktion durchzuführen"
+
+msgid "groups"
+msgstr "Gruppen"
+
+msgid "groups grant permissions to the user"
+msgstr "die Gruppen geben dem Nutzer Rechte"
+
+msgid "groups to which the permission is granted"
+msgstr "Gruppen, denen dieses Recht verliehen ist"
+
+msgid "guests"
+msgstr "Gäste"
+
+msgid "hCalendar"
+msgstr "hCalendar"
+
+msgid "has_text"
+msgstr "enthält Text"
+
+msgid "header-left"
+msgstr ""
+
+msgid "header-right"
+msgstr ""
+
+msgid "hide filter form"
+msgstr "Filter verbergen"
+
+msgid ""
+"how to format date and time in the ui (\"man strftime\" for format "
+"description)"
+msgstr ""
+"Wie formatiert man das Datum Interface im (\"man strftime\" für die "
+"Beschreibung des neuen Formats"
+
+msgid "how to format date in the ui (\"man strftime\" for format description)"
+msgstr ""
+"Wie formatiert man das Datum im Interface (\"man strftime\" für die "
+"Beschreibung des Formats)"
+
+msgid "how to format float numbers in the ui"
+msgstr "Wie man Dezimalzahlen (float) im Interface formatiert"
+
+msgid "how to format time in the ui (\"man strftime\" for format description)"
+msgstr ""
+"Wie man die Uhrzeit im Interface (\"man strftime\" für die "
+"Formatbeschreibung)"
+
+msgid "i18n_bookmark_url_fqs"
+msgstr "Parameter"
+
+msgid "i18n_bookmark_url_path"
+msgstr "Pfad"
+
+msgid "i18n_login_popup"
+msgstr "Anmelden"
+
+msgid "i18ncard_*"
+msgstr "0..n"
+
+msgid "i18ncard_+"
+msgstr "1..n"
+
+msgid "i18ncard_1"
+msgstr "1"
+
+msgid "i18ncard_?"
+msgstr "0..1"
+
+msgid "i18nprevnext_next"
+msgstr "Weiter"
+
+msgid "i18nprevnext_previous"
+msgstr "Zurück"
+
+msgid "i18nprevnext_up"
+msgstr "eine Ebene auf"
+
+msgid "iCalendar"
+msgstr "iCalendar"
+
+msgid "id of main template used to render pages"
+msgstr "id der Hauptvorlage"
+
+msgid "identical to"
+msgstr "identisch mit"
+
+msgid "identical_to"
+msgstr "identisch mit"
+
+msgid "identity"
+msgstr "ist identisch mit"
+
+msgid "identity_object"
+msgstr "ist identisch mit"
+
+msgid ""
+"if full text content of subject/object entity should be added to other side "
+"entity (the container)."
+msgstr ""
+"falls der indizierte Text der Subjekt/Objekt-Entität der anderen Seite der "
+"Relation (dem Container) hinzugefügt werden muss"
+
+msgid "image"
+msgstr "Bild"
+
+msgid "in_group"
+msgstr "in der Gruppe"
+
+msgctxt "CWUser"
+msgid "in_group"
+msgstr "gehört zu der Gruppe"
+
+msgid "in_group_object"
+msgstr "Mitglieder"
+
+msgctxt "CWGroup"
+msgid "in_group_object"
+msgstr "enthält die Nutzer"
+
+msgid "in_state"
+msgstr "Zustand"
+
+msgid "in_state_object"
+msgstr "Zustand von"
+
+msgid "incontext"
+msgstr "im Kontext"
+
+msgid "incorrect captcha value"
+msgstr "Unzulässiger Wert für Überschrift"
+
+#, python-format
+msgid "incorrect value (%(value)s) for type \"%(type)s\""
+msgstr "Wert %(value)s ungültig für den Typ \"%(type)s\""
+
+msgid "index this attribute's value in the plain text index"
+msgstr "indizieren des Wertes dieses Attributs im Volltext-Index"
+
+msgid "indexed"
+msgstr "Index"
+
+msgctxt "CWAttribute"
+msgid "indexed"
+msgstr "indiziert"
+
+msgid "indicate the current state of an entity"
+msgstr "zeigt den aktuellen Zustand einer Entität an"
+
+msgid ""
+"indicate which state should be used by default when an entity using states "
+"is created"
+msgstr ""
+"zeigt an, welcher Zustand standardmäßig benutzt werden soll, wenn eine "
+"Entität erstellt wird"
+
+msgid "info"
+msgstr "Information"
+
+#, python-format
+msgid "initial estimation %s"
+msgstr "Erste Schätzung %s"
+
+msgid "initial state for this workflow"
+msgstr "Anfangszustand für diesen Workflow"
+
+msgid "initial_state"
+msgstr "Anfangszustand"
+
+msgctxt "Workflow"
+msgid "initial_state"
+msgstr "Anfangszustand"
+
+msgid "initial_state_object"
+msgstr "Anfangszustand von"
+
+msgctxt "State"
+msgid "initial_state_object"
+msgstr "Anfangszustand von"
+
+msgid "inlined"
+msgstr "eingereiht"
+
+msgctxt "CWRType"
+msgid "inlined"
+msgstr "eingereiht"
+
+msgid "instance home"
+msgstr "Startseite der Instanz"
+
+msgid "instance schema"
+msgstr "Schema der Instanz"
+
+msgid "internal entity uri"
+msgstr "interner URI"
+
+msgid "internationalizable"
+msgstr "internationalisierbar"
+
+msgctxt "CWAttribute"
+msgid "internationalizable"
+msgstr "internationalisierbar"
+
+#, python-format
+msgid "invalid action %r"
+msgstr "Ungültige Aktion %r"
+
+#, python-format
+msgid "invalid value %(value)s, it must be one of %(choices)s"
+msgstr "Wert %(value)s ungültig, er muss zwischen %(choices)s"
+
+msgid "is"
+msgstr "vom Typ"
+
+msgid "is object of:"
+msgstr "ist Objekt von"
+
+msgid "is subject of:"
+msgstr "ist Subjekt von"
+
+msgid ""
+"is the subject/object entity of the relation composed of the other ? This "
+"implies that when the composite is deleted, composants are also deleted."
+msgstr ""
+"Ist die Subjekt/Objekt-Entität der Relation aus der anderen Seite "
+"zusammengesetzt?Falls ja, werden beim Löschen der Entität auch deren "
+"Bausteine gelöscht."
+
+msgid "is this attribute's value translatable"
+msgstr "Ist der Wert dieses Attributs übersetzbar?"
+
+msgid "is this relation equivalent in both direction ?"
+msgstr "Ist diese Relation in beiden Richtungen äquivalent?"
+
+msgid ""
+"is this relation physically inlined? you should know what you're doing if "
+"you are changing this!"
+msgstr ""
+"Ist diese Relation in die Datenbank eingereiht? Sie sollten wissen, was Sie "
+"tun, wenn Sie dies ändern."
+
+msgid "is_instance_of"
+msgstr "ist eine Instanz von"
+
+msgid "is_instance_of_object"
+msgstr "Typ von"
+
+msgid "is_object"
+msgstr "hat als Instanz"
+
+msgid "january"
+msgstr "Januar"
+
+msgid "july"
+msgstr "Juli"
+
+msgid "june"
+msgstr "Juni"
+
+msgid "label"
+msgstr "gekennzeichnet"
+
+msgctxt "CWPermission"
+msgid "label"
+msgstr "gekennzeichnet"
+
+msgid "language of the user interface"
+msgstr "Sprache der Nutzer-Schnittstelle"
+
+msgid "last connection date"
+msgstr "Datum der letzten Verbindung"
+
+msgid "last login time"
+msgstr "Datum der letzten Verbindung"
+
+msgid "last name"
+msgstr "Name"
+
+msgid "last usage"
+msgstr "letzte Benutzung"
+
+msgid "last_login_time"
+msgstr "Datum der letzten Verbindung"
+
+msgctxt "CWUser"
+msgid "last_login_time"
+msgstr "Datum der letzten Verbindung"
+
+msgid "latest modification time of an entity"
+msgstr "Datum der letzten Änderung einer Entität"
+
+msgid "latest update on"
+msgstr "letzte Änderung am"
+
+msgid "left"
+msgstr "links"
+
+msgid ""
+"link a permission to the entity. This permission should be used in the "
+"security definition of the entity's type to be useful."
+msgstr ""
+"verknüpft eine Berechtigung mit einer Entität. Um Nützlich zu sein, sollte "
+"diese Berechtigung in der Sicherheitsdefinition des Entitätstyps benutzt "
+"werden."
+
+msgid ""
+"link a property to the user which want this property customization. Unless "
+"you're a site manager, this relation will be handled automatically."
+msgstr ""
+"verknüpft eine Eigenschaft mit einem Nutzer, der diese Personalisierung "
+"wünscht. Sofern Sie nicht Site-Manager sind, wird diese Relation automatisch "
+"behandelt."
+
+msgid "link a relation definition to its object entity type"
+msgstr "verknüpft eine Relationsdefinition mit dem Entitätstyp ihres Objekts"
+
+msgid "link a relation definition to its relation type"
+msgstr "verknüpft eine Relationsdefinition mit ihrem Relationstyp"
+
+msgid "link a relation definition to its subject entity type"
+msgstr "verknüpft eine Relationsdefinition mit dem Entitätstyp ihres Subjekts"
+
+msgid "link a state to one or more workflow"
+msgstr "verknüpft einen Zustand mit einem oder mehreren Workflows"
+
+msgid "link a transition information to its object"
+msgstr "verknüpft eine Übergangsinformation mit ihrem Objekt"
+
+msgid "link a transition to one or more workflow"
+msgstr "verknüpft einen Übergang mit einem oder mehreren Workflows"
+
+msgid "link a workflow to one or more entity type"
+msgstr "verknüpft einen Workflow mit einem oder mehreren Entitätstypen"
+
+msgid "list"
+msgstr "Liste"
+
+msgid "log in"
+msgstr "anmelden"
+
+msgid "log out first"
+msgstr "Melden Sie sich zuerst ab."
+
+msgid "login"
+msgstr "Anmeldung"
+
+msgctxt "CWUser"
+msgid "login"
+msgstr "Anmeldung"
+
+msgid "login / password"
+msgstr ""
+
+msgid "login or email"
+msgstr "Nutzername oder E-Mail-Adresse"
+
+msgid "login_action"
+msgstr "Melden Sie sich an"
+
+msgid "logout"
+msgstr "Abmelden"
+
+#, python-format
+msgid "loop in %(rel)s relation (%(eid)s)"
+msgstr ""
+"Endlosschleife gefunden in der Relation %(rel)s von der Entität #%(eid)s"
+
+msgid "main informations"
+msgstr "Allgemeine Informationen"
+
+msgid "main_tab"
+msgstr ""
+
+msgid "mainvars"
+msgstr "Hauptvariablen"
+
+msgctxt "RQLExpression"
+msgid "mainvars"
+msgstr "Hauptvariablen"
+
+msgid "manage"
+msgstr "Verwalten"
+
+msgid "manage bookmarks"
+msgstr "Lesezeichen verwalten"
+
+msgid "manage permissions"
+msgstr "Rechte verwalten"
+
+msgid "manage security"
+msgstr "Sicherheitsverwaltung"
+
+msgid "managers"
+msgstr "Administratoren"
+
+msgid "mandatory relation"
+msgstr "obligatorische Relation"
+
+msgid "march"
+msgstr "März"
+
+msgid "match_host"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "match_host"
+msgstr ""
+
+msgid "maximum number of characters in short description"
+msgstr "Maximale Anzahl von Zeichen in der Kurzbeschreibung"
+
+msgid "maximum number of entities to display in related combo box"
+msgstr "maximale Anzahl Entitäten zur Anzeige im Listenfeld"
+
+msgid "maximum number of objects displayed by page of results"
+msgstr "maximale Anzahl pro Seite angezeigter Objekte"
+
+msgid "maximum number of related entities to display in the primary view"
+msgstr "maximale anzahl verknüpfter Entitäten zur Anzeige auf der hauptseite"
+
+msgid "may"
+msgstr "Mai"
+
+msgid "memory leak debugging"
+msgstr "Fehlersuche bei Speicherlöschern"
+
+msgid "milestone"
+msgstr "Meilenstein"
+
+#, python-format
+msgid "missing parameters for entity %s"
+msgstr "Fehlende Parameter für Entität %s"
+
+msgid "modification"
+msgstr "Änderung"
+
+msgid "modification_date"
+msgstr "Datum der Änderung"
+
+msgid "modify"
+msgstr "ändern"
+
+msgid "monday"
+msgstr "Montag"
+
+msgid "more actions"
+msgstr "weitere Aktionen"
+
+msgid "more info about this workflow"
+msgstr "mehr Informationen zu diesem Workflow"
+
+msgid "multiple edit"
+msgstr "mehrfache Bearbeitung"
+
+msgid "my custom search"
+msgstr "meine personalisierte Suche"
+
+msgid "name"
+msgstr "Name"
+
+msgctxt "BaseTransition"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWCache"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWConstraintType"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWEType"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWGroup"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWPermission"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWRType"
+msgid "name"
+msgstr "Name"
+
+msgctxt "CWSource"
+msgid "name"
+msgstr ""
+
+msgctxt "State"
+msgid "name"
+msgstr "Name"
+
+msgctxt "Transition"
+msgid "name"
+msgstr "Name"
+
+msgctxt "Workflow"
+msgid "name"
+msgstr "Name"
+
+msgctxt "WorkflowTransition"
+msgid "name"
+msgstr "Name"
+
+msgid "name of the cache"
+msgstr "Name des Caches"
+
+msgid ""
+"name of the main variables which should be used in the selection if "
+"necessary (comma separated)"
+msgstr ""
+"Name der Hauptvariablen, die in der Auswahl benutzt werden sollten (mehrere "
+"Variablen durch ',' trennen!)"
+
+msgid "name of the source"
+msgstr ""
+
+msgid "name or identifier of the permission"
+msgstr "Name (oder Bezeichner) der Berechtigung"
+
+msgid "navbottom"
+msgstr "zum Seitenende"
+
+msgid "navcontentbottom"
+msgstr "zum Hauptinhalt"
+
+msgid "navcontenttop"
+msgstr "zum Seitenanfang"
+
+msgid "navigation"
+msgstr "Navigation"
+
+msgid "navigation.combobox-limit"
+msgstr "Anzahl Entitäten pro Listenfeld"
+
+msgid "navigation.page-size"
+msgstr "Anzahl Suchergebnisse"
+
+msgid "navigation.related-limit"
+msgstr "Anzahl Entitäten in der Hauptansicht"
+
+msgid "navigation.short-line-size"
+msgstr "Kurzbeschreibung"
+
+msgid "navtop"
+msgstr "zum Hauptinhalt springen"
+
+msgid "new"
+msgstr "neu"
+
+msgid "next_results"
+msgstr "weitere Ergebnisse"
+
+msgid "no"
+msgstr "Nein"
+
+msgid "no associated permissions"
+msgstr "keine entsprechende Berechtigung"
+
+#, python-format
+msgid "no edited fields specified for entity %s"
+msgstr "kein Eingabefeld spezifiziert Für Entität %s"
+
+msgid "no related entity"
+msgstr "keine verknüpfte Entität"
+
+msgid "no related project"
+msgstr "kein verknüpftes Projekt"
+
+msgid "no repository sessions found"
+msgstr "keine Datenbank-Sitzung gefunden"
+
+msgid "no selected entities"
+msgstr "keine Entitäten ausgewählt"
+
+#, python-format
+msgid "no such entity type %s"
+msgstr "Der Entitätstyp '%s' existiert nicht."
+
+msgid "no version information"
+msgstr "Keine Versionsangaben."
+
+msgid "no web sessions found"
+msgstr "Keine Sitzung gefunden."
+
+msgid "normal"
+msgstr "normal"
+
+msgid "not authorized"
+msgstr "nicht authrisiert"
+
+msgid "not selected"
+msgstr "nicht ausgewählt"
+
+msgid "november"
+msgstr "November"
+
+msgid "object"
+msgstr "Objekt"
+
+msgid "object type"
+msgstr "Objekttyp"
+
+msgid "october"
+msgstr "Oktober"
+
+msgid "one month"
+msgstr "ein Monat"
+
+msgid "one week"
+msgstr "eine Woche"
+
+msgid "oneline"
+msgstr "eine Zeile"
+
+msgid "only select queries are authorized"
+msgstr "Nur Auswahl-Anfragen sind erlaubt."
+
+msgid "open all"
+msgstr "alle öffnen"
+
+msgid "opened sessions"
+msgstr "offene Sitzungen"
+
+msgid "opened web sessions"
+msgstr "offene Web-Sitzungen"
+
+msgid "options"
+msgstr "Optionen"
+
+msgid "order"
+msgstr "Reihenfolge"
+
+msgid "ordernum"
+msgstr "Reihenfolge"
+
+msgctxt "CWAttribute"
+msgid "ordernum"
+msgstr "Ordnungszahl"
+
+msgctxt "CWRelation"
+msgid "ordernum"
+msgstr "Ordnungszahl"
+
+msgid "owl"
+msgstr "OWL"
+
+msgid "owlabox"
+msgstr "OWL ABox"
+
+msgid "owned_by"
+msgstr "gehört zu"
+
+msgid "owned_by_object"
+msgstr "besitzt"
+
+msgid "owners"
+msgstr "Besitzer"
+
+msgid "ownership"
+msgstr "Eigentum"
+
+msgid "ownerships have been changed"
+msgstr "Die Eigentumsrechte sind geändert worden."
+
+msgid "pageid-not-found"
+msgstr ""
+"Notwendige Daten scheinen nicht mehr gültig zu sein. Bitte laden Sie die "
+"Seite neu und beginnen Sie von vorn."
+
+msgid "password"
+msgstr "Passwort"
+
+msgid "password and confirmation don't match"
+msgstr "Das Passwort stimmt nicht mit der Bestätigung überein."
+
+msgid "path"
+msgstr "Pfad"
+
+msgctxt "Bookmark"
+msgid "path"
+msgstr "Pfad"
+
+msgid "permission"
+msgstr "Recht"
+
+msgid "permissions"
+msgstr "Rechte"
+
+msgid "permissions for this entity"
+msgstr "Rechte für diese Entität"
+
+msgid "pick existing bookmarks"
+msgstr "Wählen Sie aus den bestehenden lesezeichen aus"
+
+msgid "pkey"
+msgstr "Schlüssel"
+
+msgctxt "CWProperty"
+msgid "pkey"
+msgstr "code der Eigenschaft"
+
+msgid "please correct errors below"
+msgstr "Bitte die nachstehenden Fehler korrigieren"
+
+msgid "please correct the following errors:"
+msgstr "Bitte korrigieren Sie die folgenden Fehler:"
+
+msgid "possible views"
+msgstr "Mögliche Ansichten"
+
+msgid "prefered_form"
+msgstr "bevorzugte form"
+
+msgctxt "EmailAddress"
+msgid "prefered_form"
+msgstr "bevorzugte form"
+
+msgid "prefered_form_object"
+msgstr "bevorzugte form vor"
+
+msgctxt "EmailAddress"
+msgid "prefered_form_object"
+msgstr "bevorzugte form von"
+
+msgid "preferences"
+msgstr "Einstellungen"
+
+msgid "previous_results"
+msgstr "vorige Ergebnisse"
+
+msgid "primary"
+msgstr "primär"
+
+msgid "primary_email"
+msgstr "primäre E-Mail-Adresse"
+
+msgctxt "CWUser"
+msgid "primary_email"
+msgstr "primäre E-Mail-Adresse"
+
+msgid "primary_email_object"
+msgstr "Objekt der primären E-Mail-Adresse"
+
+msgctxt "EmailAddress"
+msgid "primary_email_object"
+msgstr "primäre E-Mail-Adresse von"
+
+msgid "profile"
+msgstr "Profil"
+
+msgid "progress"
+msgstr "Fortschritt"
+
+msgid "progress bar"
+msgstr "Fortschrittsbalken"
+
+msgid "project"
+msgstr "Projekt"
+
+msgid "rdef-description"
+msgstr "Beschreibung"
+
+msgid "rdef-permissions"
+msgstr "Rechte"
+
+msgid "read"
+msgstr "Lesen"
+
+msgid "read_permission"
+msgstr "Leseberechtigung"
+
+msgctxt "CWAttribute"
+msgid "read_permission"
+msgstr "Leseberechtigung"
+
+msgctxt "CWEType"
+msgid "read_permission"
+msgstr "Leseberechtigung"
+
+msgctxt "CWRelation"
+msgid "read_permission"
+msgstr "Leseberechtigung"
+
+msgid "read_permission_object"
+msgstr "hat eine Leseberechtigung"
+
+msgctxt "CWGroup"
+msgid "read_permission_object"
+msgstr "kann lesen"
+
+msgctxt "RQLExpression"
+msgid "read_permission_object"
+msgstr "kann lesen"
+
+msgid "regexp matching host(s) to which this config applies"
+msgstr ""
+
+msgid "registry"
+msgstr "Registratur"
+
+msgid "related entity has no state"
+msgstr "Verknüpfte Entität hat keinen Zustand"
+
+msgid "related entity has no workflow set"
+msgstr "Verknüpfte Entität hat keinen Workflow"
+
+msgid "relation"
+msgstr "Relation"
+
+#, python-format
+msgid "relation %(relname)s of %(ent)s"
+msgstr "Relation %(relname)s von %(ent)s"
+
+msgid "relation add"
+msgstr "Relation hinzufügen"
+
+msgid "relation removal"
+msgstr "Relation entfernen"
+
+msgid "relation_type"
+msgstr "Relationstyp"
+
+msgctxt "CWAttribute"
+msgid "relation_type"
+msgstr "Relationstyp"
+
+msgctxt "CWRelation"
+msgid "relation_type"
+msgstr "Relationstyp"
+
+msgid "relation_type_object"
+msgstr "Definition"
+
+msgctxt "CWRType"
+msgid "relation_type_object"
+msgstr "definition"
+
+msgid "relations"
+msgstr "Relationen"
+
+msgctxt "CWUniqueTogetherConstraint"
+msgid "relations"
+msgstr "Relationen"
+
+msgid "relations deleted"
+msgstr "Relationen entfernt"
+
+msgid "relations_object"
+msgstr "Relationen von"
+
+msgctxt "CWRType"
+msgid "relations_object"
+msgstr ""
+
+msgid "relative url of the bookmarked page"
+msgstr "URL relativ zu der Seite"
+
+msgid "remove-inlined-entity-form"
+msgstr "Entfernen"
+
+msgid "require_group"
+msgstr "benötigt die Gruppe"
+
+msgctxt "BaseTransition"
+msgid "require_group"
+msgstr "auf Gruppe beschränkt"
+
+msgctxt "CWPermission"
+msgid "require_group"
+msgstr "auf Gruppe beschränkt"
+
+msgctxt "Transition"
+msgid "require_group"
+msgstr "auf Gruppe beschränkt"
+
+msgctxt "WorkflowTransition"
+msgid "require_group"
+msgstr "auf Gruppe beschränkt"
+
+msgid "require_group_object"
+msgstr "hat die Rechte"
+
+msgctxt "CWGroup"
+msgid "require_group_object"
+msgstr "hat die Rechte"
+
+msgid "require_permission"
+msgstr "erfordert Berechtigung"
+
+msgid "require_permission_object"
+msgstr "Berechtigung von"
+
+msgid "required"
+msgstr "erforderlich"
+
+msgid "required attribute"
+msgstr "erforderliches Attribut"
+
+msgid "required field"
+msgstr "Pflichtfeld"
+
+msgid "resources usage"
+msgstr "genutzte Ressourcen"
+
+msgid ""
+"restriction part of a rql query. For entity rql expression, X and U are "
+"predefined respectivly to the current object and to the request user. For "
+"relation rql expression, S, O and U are predefined respectivly to the "
+"current relation'subject, object and to the request user. "
+msgstr ""
+"Restriktionsteil einer RQL-Abfrage. Für einen Ausdruck, der für eine Entität "
+"gilt,X und U sind jeweils für die Entität und den Nutzer vordefiniert."
+"respectivement prédéfinis au sujet/objet de la relation et à l'utilisateur "
+
+msgid "revert changes"
+msgstr "Änderungen rückgängig machen"
+
+msgid "right"
+msgstr "rechts"
+
+msgid "rql expressions"
+msgstr "RQL-Ausdrücke"
+
+msgid "rss"
+msgstr "RSS"
+
+msgid "same_as"
+msgstr "identisch mit"
+
+msgid "sample format"
+msgstr "Beispiel"
+
+msgid "saturday"
+msgstr "Samstag"
+
+msgid "schema entities"
+msgstr "Entitäten, die das Schema definieren"
+
+msgid "schema's permissions definitions"
+msgstr "Im Schema definierte Rechte"
+
+msgid "schema-diagram"
+msgstr "Diagramm"
+
+msgid "schema-entity-types"
+msgstr "Entitätstypen"
+
+msgid "schema-relation-types"
+msgstr "Relationstypen"
+
+msgid "schema-security"
+msgstr "Rechte"
+
+msgid "search"
+msgstr "suchen"
+
+msgid "search for association"
+msgstr "nach verwandten Ergebnissen suchen"
+
+msgid "searching for"
+msgstr "Suche nach"
+
+msgid "secondary"
+msgstr "sekundär"
+
+msgid "security"
+msgstr "Sicherheit"
+
+msgid "see more"
+msgstr ""
+
+msgid "see them all"
+msgstr "Alle ansehen"
+
+msgid "see_also"
+msgstr "Siehe auch"
+
+msgid "select"
+msgstr "auswählen"
+
+msgid "select a"
+msgstr "wählen Sie einen"
+
+msgid "select a key first"
+msgstr "Wählen Sie zuerst einen Schlüssel."
+
+msgid "select a relation"
+msgstr "Wählen Sie eine Relation."
+
+msgid "select this entity"
+msgstr "Wählen Sie diese Entität"
+
+msgid "selected"
+msgstr "ausgewählt"
+
+msgid "semantic description of this attribute"
+msgstr "Semantische Beschreibung dieses Attributs"
+
+msgid "semantic description of this entity type"
+msgstr "Semantische Beschreibung dieses Entitätstyps"
+
+msgid "semantic description of this relation"
+msgstr "Semantische Beschreibung dieser Relation"
+
+msgid "semantic description of this relation type"
+msgstr "Semantische Beschreibung dieses Relationstyps"
+
+msgid "semantic description of this state"
+msgstr "Semantische Beschreibung dieses Zustands"
+
+msgid "semantic description of this transition"
+msgstr "Semantische Beschreibung dieses Übergangs"
+
+msgid "semantic description of this workflow"
+msgstr "Semantische Beschreibung dieses Workflows"
+
+msgid "send email"
+msgstr "E-Mail senden"
+
+msgid "september"
+msgstr "September"
+
+msgid "server information"
+msgstr "Server-Informationen"
+
+msgid ""
+"should html fields being edited using fckeditor (a HTML WYSIWYG editor). "
+"You should also select text/html as default text format to actually get "
+"fckeditor."
+msgstr ""
+"Bestimmt, ob HTML-Felder mit fckeditor (ein WYSIWYG-HTML-Editor)\n"
+"bearbeitet werden müssen. Es wird auch empfohlen, Text/HTML\n"
+"als Standard-Textformat festzulegen, um Text mit fckeditor zu bearbeiten."
+
+#, python-format
+msgid "show %s results"
+msgstr "Zeige %s Ergebnisse"
+
+msgid "show advanced fields"
+msgstr "Zeige detaillierte Felder"
+
+msgid "show filter form"
+msgstr "Filter zeigen"
+
+msgid "sioc"
+msgstr "sioc"
+
+msgid "site configuration"
+msgstr "Konfiguration der Website"
+
+msgid "site documentation"
+msgstr "Dokumentation der Website"
+
+msgid "site schema"
+msgstr "Schema der Website"
+
+msgid "site title"
+msgstr "Titel der Website"
+
+msgid "site-wide property can't be set for user"
+msgstr ""
+"Eine Eigenschaft für die gesamte Website kann nicht für einen Nutzer gesetzt "
+"werden."
+
+msgid "some errors occurred:"
+msgstr "Einige Fehler sind aufgetreten"
+
+msgid "some later transaction(s) touch entity, undo them first"
+msgstr ""
+"Eine oder mehrere frühere Transaktion(en) betreffen die Tntität. Machen Sie "
+"sie zuerst rückgängig."
+
+msgid "sorry, the server is unable to handle this query"
+msgstr "Der Server kann diese Anfrage leider nicht bearbeiten."
+
+msgid ""
+"source's configuration. One key=value per line, authorized keys depending on "
+"the source's type"
+msgstr ""
+
+msgid "sparql xml"
+msgstr "Sparql XML"
+
+msgid "special transition allowing to go through a sub-workflow"
+msgstr "Spezieller Übergang, um in einen Subworkflow hineinzugehen"
+
+msgid "specializes"
+msgstr "leitet sich ab von"
+
+msgctxt "CWEType"
+msgid "specializes"
+msgstr "spezialisiert"
+
+msgid "specializes_object"
+msgstr "Vorgänger von"
+
+msgctxt "CWEType"
+msgid "specializes_object"
+msgstr "Vorgänger von"
+
+msgid "startup views"
+msgstr "Start-Ansichten"
+
+msgid "state"
+msgstr "Zustand"
+
+msgid "state and transition don't belong the the same workflow"
+msgstr "Zustand und Übergang gehören nicht zum selben Workflow."
+
+msgid "state doesn't apply to this entity's type"
+msgstr "Zustand gilt nicht für diesen Entitätstyp."
+
+msgid "state doesn't belong to entity's current workflow"
+msgstr "Der Zustand gehört nicht zum aktuellen Workflow der Entität."
+
+msgid "state doesn't belong to entity's workflow"
+msgstr "Der Zustand gehört nicht zum Workflow der Entität."
+
+msgid ""
+"state doesn't belong to entity's workflow. You may want to set a custom "
+"workflow for this entity first."
+msgstr ""
+"Der Zustand gehört nicht zum Workflow der Entität.Bitte bestimmen Sie zuerst "
+"einen Workflow für diese Entität."
+
+msgid "state doesn't belong to this workflow"
+msgstr "Zustand gehört nicht zu diesem Workflow."
+
+msgid "state_of"
+msgstr "Zustand von"
+
+msgctxt "State"
+msgid "state_of"
+msgstr "Zustand von"
+
+msgid "state_of_object"
+msgstr "hat als Zustand"
+
+msgctxt "Workflow"
+msgid "state_of_object"
+msgstr "enthält die Zustände"
+
+msgid "status change"
+msgstr "Zustand ändern"
+
+msgid "status changed"
+msgstr "Zustand geändert"
+
+#, python-format
+msgid "status will change from %(st1)s to %(st2)s"
+msgstr "Entität wird vom Zustand %(st1)s in zustand %(st2)s übergehen."
+
+msgid "subject"
+msgstr "Subjekt"
+
+msgid "subject type"
+msgstr "Subjekttyp"
+
+msgid "subject/object cardinality"
+msgstr "Subjekt/Objekt Kardinalität"
+
+msgid "subworkflow"
+msgstr "Subworkflow"
+
+msgctxt "WorkflowTransition"
+msgid "subworkflow"
+msgstr "Subworkflow"
+
+msgid ""
+"subworkflow isn't a workflow for the same types as the transition's workflow"
+msgstr ""
+"Dieser Subworkflow gilt nicht für dieselben Typen wie der Workflow dieses "
+"Übergangs."
+
+msgid "subworkflow state"
+msgstr "Zustand des Subworkflows"
+
+msgid "subworkflow_exit"
+msgstr "Ende des Subworkflows"
+
+msgctxt "WorkflowTransition"
+msgid "subworkflow_exit"
+msgstr "Ende des Subworkflows"
+
+msgid "subworkflow_exit_object"
+msgstr "Endzustand"
+
+msgctxt "SubWorkflowExitPoint"
+msgid "subworkflow_exit_object"
+msgstr "Endzustände"
+
+msgid "subworkflow_object"
+msgstr "verwendet vom Übergang"
+
+msgctxt "Workflow"
+msgid "subworkflow_object"
+msgstr "Subworkflow von"
+
+msgid "subworkflow_state"
+msgstr "Zustand des Subworkflows"
+
+msgctxt "SubWorkflowExitPoint"
+msgid "subworkflow_state"
+msgstr "Zustand"
+
+msgid "subworkflow_state_object"
+msgstr "Endzustand von"
+
+msgctxt "State"
+msgid "subworkflow_state_object"
+msgstr "Endzustand von"
+
+msgid "sunday"
+msgstr "Sonntag"
+
+msgid "surname"
+msgstr "Name"
+
+msgctxt "CWUser"
+msgid "surname"
+msgstr "Nachname"
+
+msgid "symmetric"
+msgstr "symmetrisch"
+
+msgctxt "CWRType"
+msgid "symmetric"
+msgstr "symmetrisch"
+
+msgid "system entities"
+msgstr "System-Entitäten"
+
+msgid "table"
+msgstr "Tabelle"
+
+msgid "tablefilter"
+msgstr "Tabellenfilter"
+
+msgid "task progression"
+msgstr "Fortschritt der Aufgabe"
+
+msgid "text"
+msgstr "Text"
+
+msgid "text/cubicweb-page-template"
+msgstr "dynamischer Inhalt"
+
+msgid "text/html"
+msgstr "html"
+
+msgid "text/plain"
+msgstr "Nur Text"
+
+msgid "text/rest"
+msgstr "reST"
+
+msgid "the URI of the object"
+msgstr "der URI des Objekts"
+
+msgid "the prefered email"
+msgstr "primäre E-Mail-Adresse"
+
+#, python-format
+msgid "the value \"%s\" is already used, use another one"
+msgstr ""
+"Der Wert \"%s\" wird bereits benutzt, bitte verwenden Sie einen anderen Wert"
+
+msgid "this action is not reversible!"
+msgstr "Achtung! Diese Aktion ist unumkehrbar."
+
+msgid "this entity is currently owned by"
+msgstr "Diese Entität gehört:"
+
+msgid "this resource does not exist"
+msgstr "cette ressource est introuvable"
+
+msgid "thursday"
+msgstr "Donnerstag"
+
+msgid "timeline"
+msgstr "Zeitleiste"
+
+msgid "timestamp"
+msgstr "Datum"
+
+msgctxt "CWCache"
+msgid "timestamp"
+msgstr "gültig seit"
+
+msgid "timestamp of the latest source synchronization."
+msgstr "Zeitstempel der letzten Synchronisierung mit der Quelle."
+
+msgid "timetable"
+msgstr "Zeitplan"
+
+msgid "title"
+msgstr "titel"
+
+msgctxt "Bookmark"
+msgid "title"
+msgstr "bezeichnet"
+
+msgid "to"
+msgstr "zu"
+
+#, python-format
+msgid "to %(date)s"
+msgstr "bis zum %(date)s"
+
+msgid "to associate with"
+msgstr "zu verknüpfen mit"
+
+msgid "to_entity"
+msgstr "zu der Entität"
+
+msgctxt "CWAttribute"
+msgid "to_entity"
+msgstr "für die Entität"
+
+msgctxt "CWRelation"
+msgid "to_entity"
+msgstr "für die Entität"
+
+msgid "to_entity_object"
+msgstr "Objekt der Relation"
+
+msgctxt "CWEType"
+msgid "to_entity_object"
+msgstr "Objekt der Relation"
+
+msgid "to_interval_end"
+msgstr "bis"
+
+msgid "to_state"
+msgstr "zum Zustand"
+
+msgctxt "TrInfo"
+msgid "to_state"
+msgstr "Zielstatus"
+
+msgid "to_state_object"
+msgstr "Übergänge zu dem Zustand"
+
+msgctxt "State"
+msgid "to_state_object"
+msgstr "Übergang zu diesem Zustand"
+
+msgid "todo_by"
+msgstr "zu erledigen bis"
+
+msgid "toggle check boxes"
+msgstr "Kontrollkästchen umkehren"
+
+msgid "tr_count"
+msgstr ""
+
+msgctxt "TrInfo"
+msgid "tr_count"
+msgstr ""
+
+msgid "transaction undoed"
+msgstr "Transaktion rückgängig gemacht"
+
+#, python-format
+msgid "transition %(tr)s isn't allowed from %(st)s"
+msgstr "Der Übergang %(tr)s ist aus dem Zustand %(st)s nicht erlaubt."
+
+msgid "transition doesn't belong to entity's workflow"
+msgstr "Übergang gehört nicht zum Workflow der Entität."
+
+msgid "transition isn't allowed"
+msgstr "Der Übergang ist nicht erleubt."
+
+msgid "transition may not be fired"
+msgstr "Der Übergang kann nicht ausgelöst werden."
+
+msgid "transition_of"
+msgstr "Übergang des/der"
+
+msgctxt "BaseTransition"
+msgid "transition_of"
+msgstr "Übergang des/der"
+
+msgctxt "Transition"
+msgid "transition_of"
+msgstr "Übergang des/der"
+
+msgctxt "WorkflowTransition"
+msgid "transition_of"
+msgstr "Übergang des/der"
+
+msgid "transition_of_object"
+msgstr "hat als Übergang"
+
+msgctxt "Workflow"
+msgid "transition_of_object"
+msgstr "hat als Übergang"
+
+msgid "tree view"
+msgstr "Baumansicht"
+
+msgid "tuesday"
+msgstr "Dienstag"
+
+msgid "type"
+msgstr "Typ"
+
+msgctxt "BaseTransition"
+msgid "type"
+msgstr "Typ"
+
+msgctxt "CWSource"
+msgid "type"
+msgstr ""
+
+msgctxt "Transition"
+msgid "type"
+msgstr "Typ"
+
+msgctxt "WorkflowTransition"
+msgid "type"
+msgstr "Typ"
+
+msgid "type here a sparql query"
+msgstr "Geben sie eine sparql-Anfrage ein"
+
+msgid "type of the source"
+msgstr ""
+
+msgid "ui"
+msgstr "Allgemeinen Eigenschaften der Nutzerschnittstelle"
+
+msgid "ui.date-format"
+msgstr "Datumsformat"
+
+msgid "ui.datetime-format"
+msgstr "Format von Datum und Zeit"
+
+msgid "ui.default-text-format"
+msgstr "Textformat"
+
+msgid "ui.encoding"
+msgstr "Kodierung"
+
+msgid "ui.fckeditor"
+msgstr "Editor"
+
+msgid "ui.float-format"
+msgstr "Format von Dezimalzahlen (float)"
+
+msgid "ui.language"
+msgstr "Sprache"
+
+msgid "ui.main-template"
+msgstr "Hauptvorlage"
+
+msgid "ui.site-title"
+msgstr "Titel der Website"
+
+msgid "ui.time-format"
+msgstr "Zeitformat"
+
+msgid "unable to check captcha, please try again"
+msgstr "Kann capcha nicht bestätigen. Bitte noch einmal versuchen."
+
+msgid "unaccessible"
+msgstr "nicnt zugänglich"
+
+msgid "unauthorized value"
+msgstr "ungültiger Wert"
+
+msgid "undo"
+msgstr "rückgängig machen"
+
+msgid "unique identifier used to connect to the application"
+msgstr "eindeutiger Bezeichner zur Verbindung mit der Anwendung"
+
+msgid "unknown external entity"
+msgstr "(Externe) Entität nicht gefunden"
+
+#, python-format
+msgid "unknown property key %s"
+msgstr "Unbekannter Eigentumsschlüssel %s"
+
+msgid "unknown vocabulary:"
+msgstr "Unbekanntes Wörterbuch : "
+
+msgid "up"
+msgstr "nach oben"
+
+msgid "upassword"
+msgstr "Passwort"
+
+msgctxt "CWUser"
+msgid "upassword"
+msgstr "Passwort"
+
+msgid "update"
+msgstr "Aktualisierung"
+
+msgid "update_permission"
+msgstr "Änderungsrecht"
+
+msgctxt "CWAttribute"
+msgid "update_permission"
+msgstr "Änderungsrecht"
+
+msgctxt "CWEType"
+msgid "update_permission"
+msgstr "Änderungsrecht"
+
+msgid "update_permission_object"
+msgstr "hat die Änderungsberechtigung"
+
+msgctxt "CWGroup"
+msgid "update_permission_object"
+msgstr "kann ändern"
+
+msgctxt "RQLExpression"
+msgid "update_permission_object"
+msgstr "kann ändern"
+
+msgid "update_relation"
+msgstr "aktualisieren"
+
+msgid "updated"
+msgstr "aktualisiert"
+
+#, python-format
+msgid "updated %(etype)s #%(eid)s (%(title)s)"
+msgstr "Entität %(etype)s #%(eid)s (%(title)s) aktualisiert"
+
+msgid "uri"
+msgstr "URI"
+
+msgctxt "ExternalUri"
+msgid "uri"
+msgstr "URI"
+
+msgid "use template languages"
+msgstr "Verwenden Sie Templating-Sprachen"
+
+msgid ""
+"use to define a transition from one or multiple states to a destination "
+"states in workflow's definitions. Transition without destination state will "
+"go back to the state from which we arrived to the current state."
+msgstr ""
+"verwendet, um einen Übergang von einem oder mehreren Zuständenin einen "
+"Zielzustand eines Workflows zu definieren.Ein Übergang ohne Zielzustand "
+"führt in den Zustand zurück, der dem aktuellen zustand vorausgeht."
+
+msgid "use_email"
+msgstr "E-Mail-Adresse"
+
+msgctxt "CWUser"
+msgid "use_email"
+msgstr "verwendet die E-Mail-Adresse"
+
+msgid "use_email_object"
+msgstr "Adresse verwendet von"
+
+msgctxt "EmailAddress"
+msgid "use_email_object"
+msgstr "verwendet von"
+
+msgid "use_template_format"
+msgstr "Benutzung des 'cubicweb template'-Formats"
+
+msgid ""
+"used for cubicweb configuration. Once a property has been created you can't "
+"change the key."
+msgstr ""
+"konfiguriert CubicWeb. Nachdem eine Eigenschafterstellt wurde, können Sie "
+"den Schlüssel nicht mehr ändern."
+
+msgid ""
+"used to associate simple states to an entity type and/or to define workflows"
+msgstr ""
+"assoziiert einfache Zustände mit einem Entitätstyp und/oder definiert "
+"Workflows"
+
+msgid "used to grant a permission to a group"
+msgstr "gibt einer Gruppe eine Berechtigung"
+
+msgid "user"
+msgstr "Nutzer"
+
+#, python-format
+msgid ""
+"user %s has made the following change(s):\n"
+"\n"
+msgstr ""
+"Nutzer %s hat die folgende(n) Änderung(en) vorgenommen:\n"
+"\n"
+
+msgid "user interface encoding"
+msgstr "Kodierung für die Nutzerschnittstelle"
+
+msgid "user preferences"
+msgstr "Nutzereinstellungen"
+
+msgid "users"
+msgstr "Nutzer"
+
+msgid "users using this bookmark"
+msgstr "Nutzer, die dieses Lesezeichen verwenden"
+
+msgid "validate modifications on selected items"
+msgstr "Überprüfen der Änderungen an den ausgewählten Elementen"
+
+msgid "validating..."
+msgstr "Überprüfung läuft..."
+
+msgid "value"
+msgstr "Wert"
+
+msgctxt "CWConstraint"
+msgid "value"
+msgstr "Einschränkung"
+
+msgctxt "CWProperty"
+msgid "value"
+msgstr "Wert"
+
+msgid "value associated to this key is not editable manually"
+msgstr ""
+"Der mit diesem Schlüssele verbundene Wert kann n icht manuell geändert "
+"werden."
+
+#, python-format
+msgid "value must be %(op)s %(boundary)s"
+msgstr "Der Wert muss %(op)s %(boundary)s sein."
+
+#, python-format
+msgid "value must be <= %(boundary)s"
+msgstr "Der Wert muss <= %(boundary)s sein."
+
+#, python-format
+msgid "value must be >= %(boundary)s"
+msgstr "Der Wert muss >= %(boundary)s sein."
+
+#, python-format
+msgid "value should have maximum size of %s"
+msgstr "Der Wert darf höchstens %s betragen."
+
+#, python-format
+msgid "value should have minimum size of %s"
+msgstr "Der Wert muss mindestens %s betragen."
+
+msgid "vcard"
+msgstr "VCard"
+
+msgid "versions configuration"
+msgstr "Versionskonfiguration"
+
+msgid "view"
+msgstr "ansehen"
+
+msgid "view all"
+msgstr "alle ansehen"
+
+msgid "view detail for this entity"
+msgstr "Details für diese Entität ansehen"
+
+msgid "view history"
+msgstr "Chronik ansehen"
+
+msgid "view identifier"
+msgstr "Nutzername"
+
+msgid "view title"
+msgstr "Titel"
+
+msgid "view workflow"
+msgstr "mögliche Zustände ansehen"
+
+msgid "view_index"
+msgstr "Index-Seite"
+
+#, python-format
+msgid "violates unique_together constraints (%s)"
+msgstr "Verletzung der unique_together-Einschränkung (%s)"
+
+msgid "visible"
+msgstr "sichtbar"
+
+msgid "we are not yet ready to handle this query"
+msgstr "Momentan können wir diese sparql-Anfrage noch nicht ausführen."
+
+msgid "wednesday"
+msgstr "Mittwoch"
+
+msgid "week"
+msgstr "Woche"
+
+#, python-format
+msgid "welcome %s !"
+msgstr "Willkommen %s !"
+
+msgid "wf_info_for"
+msgstr "Chronik von"
+
+msgid "wf_info_for_object"
+msgstr "Chronik der Übergänge"
+
+msgid "wf_tab_info"
+msgstr "Beschreibung"
+
+msgid "wfgraph"
+msgstr "Grafik des Workflows"
+
+msgid ""
+"when multiple addresses are equivalent (such as python-projects@logilab.org "
+"and python-projects@lists.logilab.org), set this to indicate which is the "
+"preferred form."
+msgstr ""
+"Wenn mehrere Adressen ähnlich sind (comme python-projects@logilab.org und "
+"python-projects@lists.logilab.org), bestimmen Sie die bevorzugte Form."
+
+msgid "workflow"
+msgstr "Workflow"
+
+msgid "workflow already have a state of that name"
+msgstr "Der Workflow hat bereits einen Zustand desselben Namens."
+
+msgid "workflow already have a transition of that name"
+msgstr "Der Workflow hat bereits einen Übergang desselben Namens."
+
+#, python-format
+msgid "workflow changed to \"%s\""
+msgstr "Workflow geändert in \"%s\""
+
+msgid "workflow has no initial state"
+msgstr "Workflow hat keinen Anfangszustand"
+
+msgid "workflow history item"
+msgstr "Beginn der Chronik des Workflows"
+
+msgid "workflow isn't a workflow for this type"
+msgstr "Der Workflow gilt nicht für diesen Entitätstyp."
+
+msgid "workflow to which this state belongs"
+msgstr "Workflow, zu dem dieser Zustand gehört"
+
+msgid "workflow to which this transition belongs"
+msgstr "Workflow, zu dem dieser Übergang gehört"
+
+msgid "workflow_of"
+msgstr "Workflow von"
+
+msgctxt "Workflow"
+msgid "workflow_of"
+msgstr "Workflow von"
+
+msgid "workflow_of_object"
+msgstr "hat als Workflow"
+
+msgctxt "CWEType"
+msgid "workflow_of_object"
+msgstr "hat als Workflow"
+
+#, python-format
+msgid "wrong query parameter line %s"
+msgstr "Falscher Anfrage-Parameter Zeile %s"
+
+msgid "xbel"
+msgstr "XBEL"
+
+msgid "xml"
+msgstr "XML"
+
+msgid "xml export"
+msgstr "XML-Export"
+
+msgid "yes"
+msgstr "Ja"
+
+msgid "you have been logged out"
+msgstr "Sie sind jetzt abgemeldet."
+
+msgid "you should probably delete that property"
+msgstr "Sie sollten diese Eigenschaft wahrscheinlich löschen."
+
+#~ msgid ""
+#~ "can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has "
+#~ "cardinality=%(card)s"
+#~ msgstr ""
+#~ "Kann 'inlined' = %(inlined)s nicht zuweisen, %(stype)s %(rtype)s "
+#~ "%(otype)s hat die Kardinalität %(card)s"
--- a/i18n/en.po Fri Dec 10 12:17:18 2010 +0100
+++ b/i18n/en.po Fri Mar 11 09:46:45 2011 +0100
@@ -47,6 +47,10 @@
msgstr ""
#, python-format
+msgid "%(etype)s by %(author)s"
+msgstr ""
+
+#, python-format
msgid "%(firstname)s %(surname)s"
msgstr ""
@@ -200,6 +204,9 @@
msgid "AND"
msgstr ""
+msgid "About this site"
+msgstr ""
+
msgid "Any"
msgstr ""
@@ -320,11 +327,23 @@
msgid "CWRelation_plural"
msgstr "Relations"
+msgid "CWSource"
+msgstr "Data source"
+
+msgid "CWSourceHostConfig"
+msgstr "Host configuration"
+
+msgid "CWSourceHostConfig_plural"
+msgstr "Host configurations"
+
+msgid "CWSource_plural"
+msgstr "Data sources"
+
msgid "CWUniqueTogetherConstraint"
-msgstr ""
+msgstr "Unicity constraint"
msgid "CWUniqueTogetherConstraint_plural"
-msgstr ""
+msgstr "Unicity constraints"
msgid "CWUser"
msgstr "User"
@@ -505,8 +524,14 @@
msgid "New CWRelation"
msgstr "New relation"
+msgid "New CWSource"
+msgstr "New source"
+
+msgid "New CWSourceHostConfig"
+msgstr "New host configuration"
+
msgid "New CWUniqueTogetherConstraint"
-msgstr ""
+msgstr "New unicity constraint"
msgid "New CWUser"
msgstr "New user"
@@ -569,6 +594,9 @@
msgid "Please note that this is only a shallow copy"
msgstr ""
+msgid "Powered by CubicWeb"
+msgstr ""
+
msgid "RQLConstraint"
msgstr "RQL constraint"
@@ -615,6 +643,12 @@
msgid "SizeConstraint"
msgstr "size constraint"
+msgid ""
+"Source's configuration for a particular host. One key=value per line, "
+"authorized keys depending on the source's type, overriding values defined on "
+"the source."
+msgstr ""
+
msgid "Startup views"
msgstr ""
@@ -698,8 +732,14 @@
msgid "This CWRelation"
msgstr "This relation"
+msgid "This CWSource"
+msgstr "This data source"
+
+msgid "This CWSourceHostConfig"
+msgstr "This host configuration"
+
msgid "This CWUniqueTogetherConstraint"
-msgstr ""
+msgstr "This unicity constraint"
msgid "This CWUser"
msgstr "This user"
@@ -829,9 +869,6 @@
"get_cache() method."
msgstr ""
-msgid "about this site"
-msgstr ""
-
msgid "abstract base class for transitions"
msgstr ""
@@ -895,8 +932,11 @@
msgid "add CWRelation relation_type CWRType object"
msgstr "relation definition"
+msgid "add CWSourceHostConfig cw_host_config_of CWSource object"
+msgstr "host configuration"
+
msgid "add CWUniqueTogetherConstraint constraint_of CWEType object"
-msgstr ""
+msgstr "unicity constraint"
msgid "add CWUser in_group CWGroup object"
msgstr "user"
@@ -1083,6 +1123,14 @@
msgstr ""
#, python-format
+msgid "archive for %(author)s"
+msgstr ""
+
+#, python-format
+msgid "archive for %(month)s/%(year)s"
+msgstr ""
+
+#, python-format
msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
msgstr ""
@@ -1139,54 +1187,6 @@
msgid "boxes"
msgstr ""
-msgid "boxes_bookmarks_box"
-msgstr "bookmarks box"
-
-msgid "boxes_bookmarks_box_description"
-msgstr "box listing the user's bookmarks"
-
-msgid "boxes_download_box"
-msgstr "download box"
-
-msgid "boxes_download_box_description"
-msgstr ""
-
-msgid "boxes_edit_box"
-msgstr "actions box"
-
-msgid "boxes_edit_box_description"
-msgstr "box listing the applicable actions on the displayed data"
-
-msgid "boxes_filter_box"
-msgstr "filter"
-
-msgid "boxes_filter_box_description"
-msgstr "box providing filter within current search results functionality"
-
-msgid "boxes_possible_views_box"
-msgstr "possible views box"
-
-msgid "boxes_possible_views_box_description"
-msgstr "box listing the possible views for the displayed data"
-
-msgid "boxes_rss"
-msgstr "rss box"
-
-msgid "boxes_rss_description"
-msgstr "RSS icon to get displayed data as a RSS thread"
-
-msgid "boxes_search_box"
-msgstr "search box"
-
-msgid "boxes_search_box_description"
-msgstr "search box"
-
-msgid "boxes_startup_views_box"
-msgstr "startup views box"
-
-msgid "boxes_startup_views_box_description"
-msgstr "box listing the possible start pages"
-
msgid "bug report sent"
msgstr ""
@@ -1275,10 +1275,13 @@
#, python-format
msgid ""
-"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
+"can't set inlined=True, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
msgstr ""
+msgid "cancel"
+msgstr ""
+
msgid "cancel select"
msgstr ""
@@ -1309,6 +1312,9 @@
msgid "click here to see created entity"
msgstr ""
+msgid "click here to see edited entity"
+msgstr ""
+
msgid "click on the box to cancel the deletion"
msgstr ""
@@ -1338,45 +1344,12 @@
msgid "components"
msgstr ""
-msgid "components_appliname"
-msgstr "application title"
-
-msgid "components_appliname_description"
-msgstr "display the application title in the page's header"
-
-msgid "components_breadcrumbs"
-msgstr "breadcrumbs"
-
-msgid "components_breadcrumbs_description"
-msgstr "breadcrumbs bar that display a path locating the page in the site"
-
msgid "components_etypenavigation"
msgstr "filtering by type"
msgid "components_etypenavigation_description"
msgstr "permit to filter search results by entity type"
-msgid "components_help"
-msgstr "help button"
-
-msgid "components_help_description"
-msgstr "the help button on the top right-hand corner"
-
-msgid "components_loggeduserlink"
-msgstr "user link"
-
-msgid "components_loggeduserlink_description"
-msgstr ""
-"for anonymous users, this is a link pointing to authentication form, for "
-"logged in users, this is a link that makes a box appear and listing some "
-"possible user actions"
-
-msgid "components_logo"
-msgstr "icon"
-
-msgid "components_logo_description"
-msgstr "the application's icon displayed in the page's header"
-
msgid "components_navigation"
msgstr "page navigation"
@@ -1421,6 +1394,17 @@
msgid "conditions"
msgstr ""
+msgid "config"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "config"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "config"
+msgstr ""
+
msgid "config mode"
msgstr ""
@@ -1452,18 +1436,18 @@
msgstr ""
msgid "constraint_of"
-msgstr ""
+msgstr "constraint of"
msgctxt "CWUniqueTogetherConstraint"
msgid "constraint_of"
-msgstr ""
+msgstr "constraint of"
msgid "constraint_of_object"
-msgstr ""
+msgstr "constrained by"
msgctxt "CWEType"
msgid "constraint_of_object"
-msgstr ""
+msgstr "constrained by"
msgid "constraints"
msgstr ""
@@ -1474,43 +1458,6 @@
msgid "content type"
msgstr ""
-msgid "contentnavigation"
-msgstr "contextual components"
-
-msgid "contentnavigation_breadcrumbs"
-msgstr "breadcrumb"
-
-msgid "contentnavigation_breadcrumbs_description"
-msgstr "breadcrumbs bar that display a path locating the page in the site"
-
-msgid "contentnavigation_metadata"
-msgstr "entity's metadata"
-
-msgid "contentnavigation_metadata_description"
-msgstr ""
-
-msgid "contentnavigation_prevnext"
-msgstr "previous / next entity"
-
-msgid "contentnavigation_prevnext_description"
-msgstr ""
-"display link to go from one entity to another on entities implementing the "
-"\"previous/next\" interface."
-
-msgid "contentnavigation_seealso"
-msgstr "see also"
-
-msgid "contentnavigation_seealso_description"
-msgstr ""
-"section containing entities related by the \"see also\" relation on entities "
-"supporting it."
-
-msgid "contentnavigation_wfhistory"
-msgstr "workflow history"
-
-msgid "contentnavigation_wfhistory_description"
-msgstr "show the workflow's history."
-
msgid "context"
msgstr ""
@@ -1589,9 +1536,14 @@
msgstr "creating relation %(linkto)s"
msgid ""
+"creating CWSourceHostConfig (CWSourceHostConfig cw_host_config_of CWSource "
+"%(linkto)s)"
+msgstr "creating host configuration for source %(linkto)s"
+
+msgid ""
"creating CWUniqueTogetherConstraint (CWUniqueTogetherConstraint "
"constraint_of CWEType %(linkto)s)"
-msgstr ""
+msgstr "creating unique together constraint for entity type %(linkto)s"
msgid "creating CWUser (CWUser in_group CWGroup %(linkto)s)"
msgstr "creating a new user in group %(linkto)s"
@@ -1710,6 +1662,110 @@
msgid "csv export"
msgstr ""
+msgid "ctxcomponents"
+msgstr "contextual components"
+
+msgid "ctxcomponents_anonuserlink"
+msgstr "user link"
+
+msgid "ctxcomponents_anonuserlink_description"
+msgstr ""
+"for anonymous users, this is a link pointing to authentication form, for "
+"logged in users, this is a link that makes a box appear and listing some "
+"possible user actions"
+
+msgid "ctxcomponents_appliname"
+msgstr "application title"
+
+msgid "ctxcomponents_appliname_description"
+msgstr "display the application title in the page's header"
+
+msgid "ctxcomponents_bookmarks_box"
+msgstr "bookmarks box"
+
+msgid "ctxcomponents_bookmarks_box_description"
+msgstr "box listing the user's bookmarks"
+
+msgid "ctxcomponents_breadcrumbs"
+msgstr "breadcrumb"
+
+msgid "ctxcomponents_breadcrumbs_description"
+msgstr "breadcrumbs bar that display a path locating the page in the site"
+
+msgid "ctxcomponents_download_box"
+msgstr "download box"
+
+msgid "ctxcomponents_download_box_description"
+msgstr ""
+
+msgid "ctxcomponents_edit_box"
+msgstr "actions box"
+
+msgid "ctxcomponents_edit_box_description"
+msgstr "box listing the applicable actions on the displayed data"
+
+msgid "ctxcomponents_facet.filters"
+msgstr "facets box"
+
+msgid "ctxcomponents_facet.filters_description"
+msgstr "box providing filter within current search results functionality"
+
+msgid "ctxcomponents_logo"
+msgstr "logo"
+
+msgid "ctxcomponents_logo_description"
+msgstr "the application's icon displayed in the page's header"
+
+msgid "ctxcomponents_metadata"
+msgstr "entity's metadata"
+
+msgid "ctxcomponents_metadata_description"
+msgstr ""
+
+msgid "ctxcomponents_possible_views_box"
+msgstr "possible views box"
+
+msgid "ctxcomponents_possible_views_box_description"
+msgstr "box listing the possible views for the displayed data"
+
+msgid "ctxcomponents_prevnext"
+msgstr "previous / next entity"
+
+msgid "ctxcomponents_prevnext_description"
+msgstr ""
+"display link to go from one entity to another on entities implementing the "
+"\"previous/next\" interface."
+
+msgid "ctxcomponents_rss"
+msgstr "rss box"
+
+msgid "ctxcomponents_rss_description"
+msgstr "RSS icon to get displayed data as a RSS thread"
+
+msgid "ctxcomponents_search_box"
+msgstr "search box"
+
+msgid "ctxcomponents_search_box_description"
+msgstr "search box"
+
+msgid "ctxcomponents_startup_views_box"
+msgstr "startup views box"
+
+msgid "ctxcomponents_startup_views_box_description"
+msgstr "box listing the possible start pages"
+
+msgid "ctxcomponents_userstatus"
+msgstr ""
+
+msgid "ctxcomponents_userstatus_description"
+msgstr ""
+
+msgid "ctxcomponents_wfhistory"
+msgstr "workflow history"
+
+msgid "ctxcomponents_wfhistory_description"
+msgstr "show the workflow's history."
+
msgid "ctxtoolbar"
msgstr "toolbar"
@@ -1719,6 +1775,72 @@
msgid "custom_workflow_object"
msgstr "custom workflow of"
+msgid "cw_dont_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_dont_cross"
+msgstr ""
+
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgid "cw_host_config_of"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "cw_host_config_of"
+msgstr ""
+
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgid "cw_may_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_may_cross"
+msgstr ""
+
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgid "cw_source"
+msgstr ""
+
+msgid "cw_source_object"
+msgstr ""
+
+msgid "cw_support"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_support"
+msgstr ""
+
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWEType"
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_support_object"
+msgstr ""
+
msgid "cwetype-box"
msgstr "\"box\" view"
@@ -2112,10 +2234,6 @@
msgid "error while embedding page"
msgstr ""
-#, python-format
-msgid "error while handling __method: %s"
-msgstr "error while handling method %s"
-
msgid "error while publishing ReST text"
msgstr ""
@@ -2159,8 +2277,11 @@
msgid "external page"
msgstr ""
+msgid "facet.filters"
+msgstr "filter"
+
msgid "facetbox"
-msgstr ""
+msgstr "facettes"
msgid "facets_created_by-facet"
msgstr "\"created by\" facet"
@@ -2168,6 +2289,12 @@
msgid "facets_created_by-facet_description"
msgstr ""
+msgid "facets_cw_source-facet"
+msgstr "data source"
+
+msgid "facets_cw_source-facet_description"
+msgstr ""
+
msgid "facets_cwfinal-facet"
msgstr "\"final entity or relation type\" facet"
@@ -2372,8 +2499,11 @@
msgid "has_text"
msgstr "has text"
-msgid "help"
-msgstr ""
+msgid "header-left"
+msgstr "header (left)"
+
+msgid "header-right"
+msgstr "header (right)"
msgid "hide filter form"
msgstr ""
@@ -2679,6 +2809,9 @@
msgid "login"
msgstr "login"
+msgid "login / password"
+msgstr ""
+
msgid "login or email"
msgstr ""
@@ -2695,6 +2828,9 @@
msgid "main informations"
msgstr ""
+msgid "main_tab"
+msgstr "description"
+
msgid "mainvars"
msgstr "main vars"
@@ -2723,6 +2859,13 @@
msgid "march"
msgstr ""
+msgid "match_host"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "match_host"
+msgstr ""
+
msgid "maximum number of characters in short description"
msgstr ""
@@ -2803,6 +2946,10 @@
msgid "name"
msgstr "name"
+msgctxt "CWSource"
+msgid "name"
+msgstr ""
+
msgctxt "State"
msgid "name"
msgstr "name"
@@ -2827,6 +2974,9 @@
"necessary (comma separated)"
msgstr ""
+msgid "name of the source"
+msgstr ""
+
msgid "name or identifier of the permission"
msgstr ""
@@ -3020,9 +3170,6 @@
msgid "possible views"
msgstr ""
-msgid "powered by CubicWeb"
-msgstr ""
-
msgid "prefered_form"
msgstr "prefered form"
@@ -3107,6 +3254,9 @@
msgid "read_permission_object"
msgstr "can be read by"
+msgid "regexp matching host(s) to which this config applies"
+msgstr ""
+
msgid "registry"
msgstr ""
@@ -3158,15 +3308,11 @@
msgstr ""
msgid "relations_object"
-msgstr ""
-
-msgctxt "CWAttribute"
+msgstr "constrained by"
+
+msgctxt "CWRType"
msgid "relations_object"
-msgstr ""
-
-msgctxt "CWRelation"
-msgid "relations_object"
-msgstr ""
+msgstr "constrained by"
msgid "relative url of the bookmarked page"
msgstr ""
@@ -3279,6 +3425,9 @@
msgid "security"
msgstr ""
+msgid "see more"
+msgstr ""
+
msgid "see them all"
msgstr ""
@@ -3376,6 +3525,11 @@
msgid "sorry, the server is unable to handle this query"
msgstr ""
+msgid ""
+"source's configuration. One key=value per line, authorized keys depending on "
+"the source's type"
+msgstr ""
+
msgid "sparql xml"
msgstr ""
@@ -3644,6 +3798,13 @@
msgid "toggle check boxes"
msgstr ""
+msgid "tr_count"
+msgstr "transition number"
+
+msgctxt "TrInfo"
+msgid "tr_count"
+msgstr "transition number"
+
msgid "transaction undoed"
msgstr ""
@@ -3695,6 +3856,10 @@
msgid "type"
msgstr "type"
+msgctxt "CWSource"
+msgid "type"
+msgstr ""
+
msgctxt "Transition"
msgid "type"
msgstr "type"
@@ -3706,6 +3871,9 @@
msgid "type here a sparql query"
msgstr ""
+msgid "type of the source"
+msgstr ""
+
msgid "ui"
msgstr ""
@@ -3757,7 +3925,8 @@
msgid "unknown external entity"
msgstr ""
-msgid "unknown property key"
+#, python-format
+msgid "unknown property key %s"
msgstr ""
msgid "unknown vocabulary:"
@@ -3971,10 +4140,10 @@
msgstr "workflow history"
msgid "wf_tab_info"
-msgstr ""
+msgstr "states and transitions"
msgid "wfgraph"
-msgstr ""
+msgstr "graph"
msgid ""
"when multiple addresses are equivalent (such as python-projects@logilab.org "
@@ -4045,15 +4214,3 @@
msgid "you should probably delete that property"
msgstr ""
-
-#~ msgid "add_perm"
-#~ msgstr "add permission"
-
-#~ msgid "delete_perm"
-#~ msgstr "delete permission"
-
-#~ msgid "read_perm"
-#~ msgstr "read permission"
-
-#~ msgid "update_perm"
-#~ msgstr "update permission"
--- a/i18n/es.po Fri Dec 10 12:17:18 2010 +0100
+++ b/i18n/es.po Fri Mar 11 09:46:45 2011 +0100
@@ -53,6 +53,10 @@
msgstr "el valor %(value)r no satisface la condición %(cstr)s"
#, python-format
+msgid "%(etype)s by %(author)s"
+msgstr ""
+
+#, python-format
msgid "%(firstname)s %(surname)s"
msgstr "%(firstname)s %(surname)s"
@@ -209,6 +213,9 @@
msgid "AND"
msgstr "Y"
+msgid "About this site"
+msgstr "Información del Sistema"
+
msgid "Any"
msgstr "Cualquiera"
@@ -329,6 +336,18 @@
msgid "CWRelation_plural"
msgstr "Relaciones"
+msgid "CWSource"
+msgstr ""
+
+msgid "CWSourceHostConfig"
+msgstr ""
+
+msgid "CWSourceHostConfig_plural"
+msgstr ""
+
+msgid "CWSource_plural"
+msgstr ""
+
msgid "CWUniqueTogetherConstraint"
msgstr ""
@@ -526,6 +545,12 @@
msgid "New CWRelation"
msgstr "Nueva definición de relación final"
+msgid "New CWSource"
+msgstr ""
+
+msgid "New CWSourceHostConfig"
+msgstr ""
+
msgid "New CWUniqueTogetherConstraint"
msgstr ""
@@ -590,6 +615,9 @@
msgid "Please note that this is only a shallow copy"
msgstr "Recuerde que sólo es una copia superficial"
+msgid "Powered by CubicWeb"
+msgstr "Potenciado en CubicWeb"
+
msgid "RQLConstraint"
msgstr "Restricción RQL"
@@ -636,6 +664,12 @@
msgid "SizeConstraint"
msgstr "Restricción de tamaño"
+msgid ""
+"Source's configuration for a particular host. One key=value per line, "
+"authorized keys depending on the source's type, overriding values defined on "
+"the source."
+msgstr ""
+
msgid "Startup views"
msgstr "Vistas de inicio"
@@ -719,6 +753,12 @@
msgid "This CWRelation"
msgstr "Esta definición de relación no final"
+msgid "This CWSource"
+msgstr ""
+
+msgid "This CWSourceHostConfig"
+msgstr ""
+
msgid "This CWUniqueTogetherConstraint"
msgstr ""
@@ -871,9 +911,6 @@
"Para recuperar un caché, hace falta utilizar el método\n"
"get_cache(cachename)."
-msgid "about this site"
-msgstr "Información del Sistema"
-
msgid "abstract base class for transitions"
msgstr "Clase de base abstracta para la transiciones"
@@ -937,6 +974,9 @@
msgid "add CWRelation relation_type CWRType object"
msgstr "Definición de relación"
+msgid "add CWSourceHostConfig cw_host_config_of CWSource object"
+msgstr ""
+
msgid "add CWUniqueTogetherConstraint constraint_of CWEType object"
msgstr ""
@@ -1127,6 +1167,14 @@
msgstr "Abril"
#, python-format
+msgid "archive for %(author)s"
+msgstr ""
+
+#, python-format
+msgid "archive for %(month)s/%(year)s"
+msgstr ""
+
+#, python-format
msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
msgstr ""
"La entidad #%(eid)s de tipo %(etype)s debe necesariamente tener almenos una "
@@ -1185,56 +1233,6 @@
msgid "boxes"
msgstr "Cajas"
-msgid "boxes_bookmarks_box"
-msgstr "Caja de Favoritos"
-
-msgid "boxes_bookmarks_box_description"
-msgstr "Muestra y permite administrar los favoritos del usuario"
-
-msgid "boxes_download_box"
-msgstr "Configuración de caja de descargas"
-
-msgid "boxes_download_box_description"
-msgstr "Caja que contiene los elementos descargados"
-
-msgid "boxes_edit_box"
-msgstr "Caja de Acciones"
-
-msgid "boxes_edit_box_description"
-msgstr "Muestra las acciones posibles a ejecutar para los datos seleccionados"
-
-msgid "boxes_filter_box"
-msgstr "Filtros"
-
-msgid "boxes_filter_box_description"
-msgstr "Muestra los filtros aplicables a una búsqueda realizada"
-
-msgid "boxes_possible_views_box"
-msgstr "Caja de Vistas Posibles"
-
-msgid "boxes_possible_views_box_description"
-msgstr "Muestra las vistas posibles a aplicar a los datos seleccionados"
-
-msgid "boxes_rss"
-msgstr "Ícono RSS"
-
-msgid "boxes_rss_description"
-msgstr "Muestra el ícono RSS para vistas RSS"
-
-msgid "boxes_search_box"
-msgstr "Caja de búsqueda"
-
-msgid "boxes_search_box_description"
-msgstr ""
-"Permite realizar una búsqueda simple para cualquier tipo de dato en la "
-"aplicación"
-
-msgid "boxes_startup_views_box"
-msgstr "Caja Vistas de inicio"
-
-msgid "boxes_startup_views_box_description"
-msgstr "Muestra las vistas de inicio de la aplicación"
-
msgid "bug report sent"
msgstr "Reporte de error enviado"
@@ -1323,11 +1321,14 @@
#, python-format
msgid ""
-"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
+"can't set inlined=True, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
msgstr ""
-"no puede poner 'inlined' = %(inlined)s, %(stype)s %(rtype)s %(otype)s tiene "
-"cardinalidad %(card)s"
+"no puede poner 'inlined' = True, %(stype)s %(rtype)s %(otype)s "
+ "tiene cardinalidad %(card)s"
+
+msgid "cancel"
+msgstr ""
msgid "cancel select"
msgstr "Cancelar la selección"
@@ -1359,6 +1360,9 @@
msgid "click here to see created entity"
msgstr "Ver la entidad creada"
+msgid "click here to see edited entity"
+msgstr ""
+
msgid "click on the box to cancel the deletion"
msgstr "Seleccione la zona de edición para cancelar la eliminación"
@@ -1388,44 +1392,12 @@
msgid "components"
msgstr "Componentes"
-msgid "components_appliname"
-msgstr "Nombre de la aplicación"
-
-msgid "components_appliname_description"
-msgstr "Muestra el nombre de la aplicación en el encabezado de la página"
-
-msgid "components_breadcrumbs"
-msgstr "Ruta de Navegación"
-
-msgid "components_breadcrumbs_description"
-msgstr "Muestra el lugar donde se encuentra la página actual en el Sistema"
-
msgid "components_etypenavigation"
msgstr "Filtar por tipo"
msgid "components_etypenavigation_description"
msgstr "Permite filtrar por tipo de entidad los resultados de una búsqueda"
-msgid "components_help"
-msgstr "Botón de ayuda"
-
-msgid "components_help_description"
-msgstr "El botón de ayuda, en el encabezado de la página"
-
-msgid "components_loggeduserlink"
-msgstr "Liga usuario"
-
-msgid "components_loggeduserlink_description"
-msgstr ""
-"Muestra un enlace hacia el formulario de conexión para los usuarios "
-"anónimos, o una caja que contiene los enlaces del usuario conectado. "
-
-msgid "components_logo"
-msgstr "logo"
-
-msgid "components_logo_description"
-msgstr "El logo de la aplicación, en el encabezado de página"
-
msgid "components_navigation"
msgstr "Navigación por página"
@@ -1472,6 +1444,17 @@
msgid "conditions"
msgstr "condiciones"
+msgid "config"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "config"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "config"
+msgstr ""
+
msgid "config mode"
msgstr "Modo de configuración"
@@ -1525,45 +1508,6 @@
msgid "content type"
msgstr "tipo MIME"
-msgid "contentnavigation"
-msgstr "Componentes contextuales"
-
-msgid "contentnavigation_breadcrumbs"
-msgstr "Ruta de Navegación"
-
-msgid "contentnavigation_breadcrumbs_description"
-msgstr "Muestra la ruta que permite localizar la página actual en el Sistema"
-
-msgid "contentnavigation_metadata"
-msgstr "Metadatos de la Entidad"
-
-msgid "contentnavigation_metadata_description"
-msgstr ""
-
-msgid "contentnavigation_prevnext"
-msgstr "Elemento anterior / siguiente"
-
-msgid "contentnavigation_prevnext_description"
-msgstr ""
-"Muestra las ligas que permiten pasar de una entidad a otra en las entidades "
-"que implementan la interface \"anterior/siguiente\"."
-
-msgid "contentnavigation_seealso"
-msgstr "Vea también"
-
-msgid "contentnavigation_seealso_description"
-msgstr ""
-"sección que muestra las entidades relacionadas por la relación \"vea también"
-"\" , si la entidad soporta esta relación."
-
-msgid "contentnavigation_wfhistory"
-msgstr "Histórico del workflow."
-
-msgid "contentnavigation_wfhistory_description"
-msgstr ""
-"Sección que muestra el reporte histórico de las transiciones del workflow. "
-"Aplica solo en entidades con workflow."
-
msgid "context"
msgstr "Contexto"
@@ -1649,6 +1593,11 @@
msgstr "Creación de la relación %(linkto)s"
msgid ""
+"creating CWSourceHostConfig (CWSourceHostConfig cw_host_config_of CWSource "
+"%(linkto)s)"
+msgstr ""
+
+msgid ""
"creating CWUniqueTogetherConstraint (CWUniqueTogetherConstraint "
"constraint_of CWEType %(linkto)s)"
msgstr ""
@@ -1774,6 +1723,113 @@
msgid "csv export"
msgstr "Exportar en CSV"
+msgid "ctxcomponents"
+msgstr "Componentes contextuales"
+
+msgid "ctxcomponents_anonuserlink"
+msgstr "Liga usuario"
+
+msgid "ctxcomponents_anonuserlink_description"
+msgstr ""
+"Muestra un enlace hacia el formulario de conexión para los usuarios "
+"anónimos, o una caja que contiene los enlaces del usuario conectado. "
+
+msgid "ctxcomponents_appliname"
+msgstr "Nombre de la aplicación"
+
+msgid "ctxcomponents_appliname_description"
+msgstr "Muestra el nombre de la aplicación en el encabezado de la página"
+
+msgid "ctxcomponents_bookmarks_box"
+msgstr "Caja de Favoritos"
+
+msgid "ctxcomponents_bookmarks_box_description"
+msgstr "Muestra y permite administrar los favoritos del usuario"
+
+msgid "ctxcomponents_breadcrumbs"
+msgstr "Ruta de Navegación"
+
+msgid "ctxcomponents_breadcrumbs_description"
+msgstr "Muestra la ruta que permite localizar la página actual en el Sistema"
+
+msgid "ctxcomponents_download_box"
+msgstr "Configuración de caja de descargas"
+
+msgid "ctxcomponents_download_box_description"
+msgstr "Caja que contiene los elementos descargados"
+
+msgid "ctxcomponents_edit_box"
+msgstr "Caja de Acciones"
+
+msgid "ctxcomponents_edit_box_description"
+msgstr "Muestra las acciones posibles a ejecutar para los datos seleccionados"
+
+msgid "ctxcomponents_facet.filters"
+msgstr "Filtros"
+
+msgid "ctxcomponents_facet.filters_description"
+msgstr "Muestra los filtros aplicables a una búsqueda realizada"
+
+msgid "ctxcomponents_logo"
+msgstr "logo"
+
+msgid "ctxcomponents_logo_description"
+msgstr "El logo de la aplicación, en el encabezado de página"
+
+msgid "ctxcomponents_metadata"
+msgstr "Metadatos de la Entidad"
+
+msgid "ctxcomponents_metadata_description"
+msgstr ""
+
+msgid "ctxcomponents_possible_views_box"
+msgstr "Caja de Vistas Posibles"
+
+msgid "ctxcomponents_possible_views_box_description"
+msgstr "Muestra las vistas posibles a aplicar a los datos seleccionados"
+
+msgid "ctxcomponents_prevnext"
+msgstr "Elemento anterior / siguiente"
+
+msgid "ctxcomponents_prevnext_description"
+msgstr ""
+"Muestra las ligas que permiten pasar de una entidad a otra en las entidades "
+"que implementan la interface \"anterior/siguiente\"."
+
+msgid "ctxcomponents_rss"
+msgstr "Ícono RSS"
+
+msgid "ctxcomponents_rss_description"
+msgstr "Muestra el ícono RSS para vistas RSS"
+
+msgid "ctxcomponents_search_box"
+msgstr "Caja de búsqueda"
+
+msgid "ctxcomponents_search_box_description"
+msgstr ""
+"Permite realizar una búsqueda simple para cualquier tipo de dato en la "
+"aplicación"
+
+msgid "ctxcomponents_startup_views_box"
+msgstr "Caja Vistas de inicio"
+
+msgid "ctxcomponents_startup_views_box_description"
+msgstr "Muestra las vistas de inicio de la aplicación"
+
+msgid "ctxcomponents_userstatus"
+msgstr ""
+
+msgid "ctxcomponents_userstatus_description"
+msgstr ""
+
+msgid "ctxcomponents_wfhistory"
+msgstr "Histórico del workflow."
+
+msgid "ctxcomponents_wfhistory_description"
+msgstr ""
+"Sección que muestra el reporte histórico de las transiciones del workflow. "
+"Aplica solo en entidades con workflow."
+
msgid "ctxtoolbar"
msgstr "Barra de herramientas"
@@ -1783,6 +1839,72 @@
msgid "custom_workflow_object"
msgstr "Workflow de"
+msgid "cw_dont_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_dont_cross"
+msgstr ""
+
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_dont_cross_object"
+msgstr ""
+
+msgid "cw_host_config_of"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "cw_host_config_of"
+msgstr ""
+
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_host_config_of_object"
+msgstr ""
+
+msgid "cw_may_cross"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_may_cross"
+msgstr ""
+
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_may_cross_object"
+msgstr ""
+
+msgid "cw_source"
+msgstr ""
+
+msgid "cw_source_object"
+msgstr ""
+
+msgid "cw_support"
+msgstr ""
+
+msgctxt "CWSource"
+msgid "cw_support"
+msgstr ""
+
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWEType"
+msgid "cw_support_object"
+msgstr ""
+
+msgctxt "CWRType"
+msgid "cw_support_object"
+msgstr ""
+
msgid "cwetype-box"
msgstr "Vista \"caja\""
@@ -2198,10 +2320,6 @@
msgid "error while embedding page"
msgstr "Error durante la inclusión de la página"
-#, python-format
-msgid "error while handling __method: %s"
-msgstr "Error ocurrido durante el tratamiento del formulario (%s)"
-
msgid "error while publishing ReST text"
msgstr ""
"Se ha producido un error durante la interpretación del texto en formato ReST"
@@ -2248,6 +2366,9 @@
msgid "external page"
msgstr "Página externa"
+msgid "facet.filters"
+msgstr "Filtros"
+
msgid "facetbox"
msgstr "Caja de facetas"
@@ -2257,6 +2378,12 @@
msgid "facets_created_by-facet_description"
msgstr "Faceta creada por"
+msgid "facets_cw_source-facet"
+msgstr ""
+
+msgid "facets_cw_source-facet_description"
+msgstr ""
+
msgid "facets_cwfinal-facet"
msgstr "Faceta \"final\""
@@ -2467,8 +2594,11 @@
msgid "has_text"
msgstr "Contiene el texto"
-msgid "help"
-msgstr "Ayuda"
+msgid "header-left"
+msgstr ""
+
+msgid "header-right"
+msgstr ""
msgid "hide filter form"
msgstr "Esconder el filtro"
@@ -2793,6 +2923,9 @@
msgid "login"
msgstr "Usuario"
+msgid "login / password"
+msgstr ""
+
msgid "login or email"
msgstr "Usuario o dirección de correo"
@@ -2809,6 +2942,9 @@
msgid "main informations"
msgstr "Informaciones Generales"
+msgid "main_tab"
+msgstr ""
+
msgid "mainvars"
msgstr "Variables principales"
@@ -2837,6 +2973,13 @@
msgid "march"
msgstr "Marzo"
+msgid "match_host"
+msgstr ""
+
+msgctxt "CWSourceHostConfig"
+msgid "match_host"
+msgstr ""
+
msgid "maximum number of characters in short description"
msgstr "Máximo de caracteres en las descripciones cortas"
@@ -2917,6 +3060,10 @@
msgid "name"
msgstr "Nombre"
+msgctxt "CWSource"
+msgid "name"
+msgstr ""
+
msgctxt "State"
msgid "name"
msgstr "Nombre"
@@ -2943,6 +3090,9 @@
"Nombre de las variables principales que deberían ser utilizadas en la "
"selección de ser necesario (separarlas con comas)"
+msgid "name of the source"
+msgstr ""
+
msgid "name or identifier of the permission"
msgstr "Nombre o identificador del permiso"
@@ -3135,9 +3285,6 @@
msgid "possible views"
msgstr "Vistas posibles"
-msgid "powered by CubicWeb"
-msgstr "Potenciado en CubicWeb"
-
msgid "prefered_form"
msgstr "Forma preferida"
@@ -3222,6 +3369,9 @@
msgid "read_permission_object"
msgstr "Puede leer"
+msgid "regexp matching host(s) to which this config applies"
+msgstr ""
+
msgid "registry"
msgstr "Registro"
@@ -3275,11 +3425,7 @@
msgid "relations_object"
msgstr ""
-msgctxt "CWAttribute"
-msgid "relations_object"
-msgstr ""
-
-msgctxt "CWRelation"
+msgctxt "CWRType"
msgid "relations_object"
msgstr ""
@@ -3398,6 +3544,9 @@
msgid "security"
msgstr "Seguridad"
+msgid "see more"
+msgstr ""
+
msgid "see them all"
msgstr "Ver todos"
@@ -3500,6 +3649,11 @@
msgid "sorry, the server is unable to handle this query"
msgstr "Lo sentimos, el servidor no puede manejar esta consulta"
+msgid ""
+"source's configuration. One key=value per line, authorized keys depending on "
+"the source's type"
+msgstr ""
+
msgid "sparql xml"
msgstr "XML Sparql"
@@ -3772,6 +3926,13 @@
msgid "toggle check boxes"
msgstr "Cambiar valor"
+msgid "tr_count"
+msgstr ""
+
+msgctxt "TrInfo"
+msgid "tr_count"
+msgstr ""
+
msgid "transaction undoed"
msgstr "Transacciones Anuladas"
@@ -3823,6 +3984,10 @@
msgid "type"
msgstr "Tipo"
+msgctxt "CWSource"
+msgid "type"
+msgstr ""
+
msgctxt "Transition"
msgid "type"
msgstr "Tipo"
@@ -3834,6 +3999,9 @@
msgid "type here a sparql query"
msgstr "Escriba aquí su consulta en Sparql"
+msgid "type of the source"
+msgstr ""
+
msgid "ui"
msgstr "Interfaz Genérica"
@@ -3885,8 +4053,9 @@
msgid "unknown external entity"
msgstr "Entidad externa desconocida"
-msgid "unknown property key"
-msgstr "Clave de Propiedad desconocida"
+#, python-format
+msgid "unknown property key %s"
+msgstr "Clave de Propiedad desconocida: %s"
msgid "unknown vocabulary:"
msgstr "Vocabulario desconocido: "
@@ -4185,24 +4354,3 @@
msgid "you should probably delete that property"
msgstr "Debería probablamente suprimir esta propriedad"
-
-#~ msgid "add_perm"
-#~ msgstr "Agregado"
-
-#~ msgid "delete_perm"
-#~ msgstr "Eliminar"
-
-#~ msgid "edition"
-#~ msgstr "Edición"
-
-#~ msgid "graphical workflow for %s"
-#~ msgstr "Gráfica del workflow por %s"
-
-#~ msgid "personnal informations"
-#~ msgstr "Información personal"
-
-#~ msgid "read_perm"
-#~ msgstr "Lectura"
-
-#~ msgid "update_perm"
-#~ msgstr "Permiso de Modificar"
--- a/i18n/fr.po Fri Dec 10 12:17:18 2010 +0100
+++ b/i18n/fr.po Fri Mar 11 09:46:45 2011 +0100
@@ -4,7 +4,7 @@
msgid ""
msgstr ""
"Project-Id-Version: cubicweb 2.46.0\n"
-"PO-Revision-Date: 2010-09-15 15:12+0200\n"
+"PO-Revision-Date: 2011-01-03 14:35+0100\n"
"Last-Translator: Logilab Team <contact@logilab.fr>\n"
"Language-Team: fr <contact@logilab.fr>\n"
"Language: \n"
@@ -52,6 +52,10 @@
msgstr "la valeur %(value)r ne satisfait pas la contrainte %(cstr)s"
#, python-format
+msgid "%(etype)s by %(author)s"
+msgstr "%(etype)s par %(author)s"
+
+#, python-format
msgid "%(firstname)s %(surname)s"
msgstr "%(firstname)s %(surname)s"
@@ -207,8 +211,11 @@
msgid "AND"
msgstr "ET"
+msgid "About this site"
+msgstr "À propos de ce site"
+
msgid "Any"
-msgstr "N'importe"
+msgstr "Tous"
msgid "Attributes permissions:"
msgstr "Permissions des attributs"
@@ -327,11 +334,23 @@
msgid "CWRelation_plural"
msgstr "Relations"
+msgid "CWSource"
+msgstr "Source de données"
+
+msgid "CWSourceHostConfig"
+msgstr "Configuration de source"
+
+msgid "CWSourceHostConfig_plural"
+msgstr "Configurations de source"
+
+msgid "CWSource_plural"
+msgstr "Source de données"
+
msgid "CWUniqueTogetherConstraint"
-msgstr "Contrainte unique_together"
+msgstr "Contrainte d'unicité"
msgid "CWUniqueTogetherConstraint_plural"
-msgstr "Contraintes unique_together"
+msgstr "Contraintes d'unicité"
msgid "CWUser"
msgstr "Utilisateur"
@@ -360,8 +379,8 @@
"Can't restore relation %(rtype)s of entity %(eid)s, this relation does not "
"exists anymore in the schema."
msgstr ""
-"Ne peut restaurer la relation %(rtype)s de l'entité %(eid)s, cette "
-"relationn'existe plus dans le schéma"
+"Ne peut restaurer la relation %(rtype)s de l'entité %(eid)s, cette relation "
+"n'existe plus dans le schéma"
#, python-format
msgid ""
@@ -524,6 +543,12 @@
msgid "New CWRelation"
msgstr "Nouvelle définition de relation non finale"
+msgid "New CWSource"
+msgstr "Nouvelle source"
+
+msgid "New CWSourceHostConfig"
+msgstr "Nouvelle configuration de source"
+
msgid "New CWUniqueTogetherConstraint"
msgstr "Nouvelle contrainte unique_together"
@@ -588,6 +613,9 @@
msgid "Please note that this is only a shallow copy"
msgstr "Attention, cela n'effectue qu'une copie de surface"
+msgid "Powered by CubicWeb"
+msgstr "Construit avec CubicWeb"
+
msgid "RQLConstraint"
msgstr "contrainte rql"
@@ -634,6 +662,15 @@
msgid "SizeConstraint"
msgstr "contrainte de taille"
+msgid ""
+"Source's configuration for a particular host. One key=value per line, "
+"authorized keys depending on the source's type, overriding values defined on "
+"the source."
+msgstr ""
+"Configuration de la source pour un hôte spécifique. Une clé=valeur par "
+"ligne, les clés autorisées dépendantes du type de source. Les valeur "
+"surchargent celles définies sur la source."
+
msgid "Startup views"
msgstr "Vues de départ"
@@ -717,6 +754,12 @@
msgid "This CWRelation"
msgstr "Cette définition de relation"
+msgid "This CWSource"
+msgstr "Cette source"
+
+msgid "This CWSourceHostConfig"
+msgstr "Cette configuration de source"
+
msgid "This CWUniqueTogetherConstraint"
msgstr "Cette contrainte unique_together"
@@ -869,9 +912,6 @@
"Pour récupérer un cache, il faut utiliser utiliser la méthode\n"
"get_cache(cachename)."
-msgid "about this site"
-msgstr "à propos de ce site"
-
msgid "abstract base class for transitions"
msgstr "classe de base abstraite pour les transitions"
@@ -935,6 +975,9 @@
msgid "add CWRelation relation_type CWRType object"
msgstr "définition de relation"
+msgid "add CWSourceHostConfig cw_host_config_of CWSource object"
+msgstr "configuration d'hôte"
+
msgid "add CWUniqueTogetherConstraint constraint_of CWEType object"
msgstr "contrainte unique_together"
@@ -1125,6 +1168,14 @@
msgstr "avril"
#, python-format
+msgid "archive for %(author)s"
+msgstr "archive pour l'auteur %(author)s"
+
+#, python-format
+msgid "archive for %(month)s/%(year)s"
+msgstr "archive pour le mois %(month)s/%(year)s"
+
+#, python-format
msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
msgstr ""
"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
@@ -1184,55 +1235,6 @@
msgid "boxes"
msgstr "boîtes"
-msgid "boxes_bookmarks_box"
-msgstr "boîte signets"
-
-msgid "boxes_bookmarks_box_description"
-msgstr "boîte contenant les signets de l'utilisateur"
-
-msgid "boxes_download_box"
-msgstr "boîte de téléchargement"
-
-msgid "boxes_download_box_description"
-msgstr "boîte contenant un lien permettant de télécharger la ressource"
-
-msgid "boxes_edit_box"
-msgstr "boîte d'actions"
-
-msgid "boxes_edit_box_description"
-msgstr ""
-"boîte affichant les différentes actions possibles sur les données affichées"
-
-msgid "boxes_filter_box"
-msgstr "filtrer"
-
-msgid "boxes_filter_box_description"
-msgstr "boîte permettant de filtrer parmi les résultats d'une recherche"
-
-msgid "boxes_possible_views_box"
-msgstr "boîte des vues possibles"
-
-msgid "boxes_possible_views_box_description"
-msgstr "boîte affichant les vues possibles pour les données courantes"
-
-msgid "boxes_rss"
-msgstr "icône RSS"
-
-msgid "boxes_rss_description"
-msgstr "l'icône RSS permettant de récupérer la vue RSS des données affichées"
-
-msgid "boxes_search_box"
-msgstr "boîte de recherche"
-
-msgid "boxes_search_box_description"
-msgstr "boîte avec un champ de recherche simple"
-
-msgid "boxes_startup_views_box"
-msgstr "boîte des vues de départs"
-
-msgid "boxes_startup_views_box_description"
-msgstr "boîte affichant les vues de départs de l'application"
-
msgid "bug report sent"
msgstr "rapport d'erreur envoyé"
@@ -1321,11 +1323,14 @@
#, python-format
msgid ""
-"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
+"can't set inlined=True, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
msgstr ""
-"ne peut mettre 'inlined' = %(inlined)s, %(stype)s %(rtype)s %(otype)s a pour "
-"cardinalité %(card)s"
+"ne peut mettre 'inlined'=Vrai, %(stype)s %(rtype)s %(otype)s a "
+"pour cardinalité %(card)s"
+
+msgid "cancel"
+msgstr "annuler"
msgid "cancel select"
msgstr "annuler la sélection"
@@ -1357,6 +1362,9 @@
msgid "click here to see created entity"
msgstr "cliquez ici pour voir l'entité créée"
+msgid "click here to see edited entity"
+msgstr "cliquez ici pour voir l'entité modifiée"
+
msgid "click on the box to cancel the deletion"
msgstr "cliquez dans la zone d'édition pour annuler la suppression"
@@ -1386,46 +1394,12 @@
msgid "components"
msgstr "composants"
-msgid "components_appliname"
-msgstr "titre de l'application"
-
-msgid "components_appliname_description"
-msgstr "affiche le titre de l'application dans l'en-tête de page"
-
-msgid "components_breadcrumbs"
-msgstr "fil d'ariane"
-
-msgid "components_breadcrumbs_description"
-msgstr ""
-"affiche un chemin permettant de localiser la page courante dans le site"
-
msgid "components_etypenavigation"
msgstr "filtrage par type"
msgid "components_etypenavigation_description"
msgstr "permet de filtrer par type d'entité les résultats d'une recherche"
-msgid "components_help"
-msgstr "bouton aide"
-
-msgid "components_help_description"
-msgstr "le bouton d'aide, dans l'en-tête de page"
-
-msgid "components_loggeduserlink"
-msgstr "lien utilisateur"
-
-msgid "components_loggeduserlink_description"
-msgstr ""
-"affiche un lien vers le formulaire d'authentification pour les utilisateurs "
-"anonymes, sinon une boite contenant notamment des liens propres à "
-"l'utilisateur connectés"
-
-msgid "components_logo"
-msgstr "logo"
-
-msgid "components_logo_description"
-msgstr "le logo de l'application, dans l'en-tête de page"
-
msgid "components_navigation"
msgstr "navigation par page"
@@ -1472,6 +1446,17 @@
msgid "conditions"
msgstr "conditions"
+msgid "config"
+msgstr "configuration"
+
+msgctxt "CWSource"
+msgid "config"
+msgstr "configuration"
+
+msgctxt "CWSourceHostConfig"
+msgid "config"
+msgstr "configuration"
+
msgid "config mode"
msgstr "mode de configuration"
@@ -1525,46 +1510,6 @@
msgid "content type"
msgstr "type MIME"
-msgid "contentnavigation"
-msgstr "composants contextuels"
-
-msgid "contentnavigation_breadcrumbs"
-msgstr "fil d'ariane"
-
-msgid "contentnavigation_breadcrumbs_description"
-msgstr ""
-"affiche un chemin permettant de localiser la page courante dans le site"
-
-msgid "contentnavigation_metadata"
-msgstr "méta-données de l'entité"
-
-msgid "contentnavigation_metadata_description"
-msgstr ""
-
-msgid "contentnavigation_prevnext"
-msgstr "élément précedent / suivant"
-
-msgid "contentnavigation_prevnext_description"
-msgstr ""
-"affiche des liens permettant de passer d'une entité à une autre sur les "
-"entités implémentant l'interface \"précédent/suivant\"."
-
-msgid "contentnavigation_seealso"
-msgstr "voir aussi"
-
-msgid "contentnavigation_seealso_description"
-msgstr ""
-"section affichant les entités liées par la relation \"voir aussi\" si "
-"l'entité supporte cette relation."
-
-msgid "contentnavigation_wfhistory"
-msgstr "historique du workflow."
-
-msgid "contentnavigation_wfhistory_description"
-msgstr ""
-"section affichant l'historique du workflow pour les entités ayant un "
-"workflow."
-
msgid "context"
msgstr "contexte"
@@ -1651,6 +1596,11 @@
msgstr "création relation %(linkto)s"
msgid ""
+"creating CWSourceHostConfig (CWSourceHostConfig cw_host_config_of CWSource "
+"%(linkto)s)"
+msgstr "création d'une configuration d'hôte pour la source %(linkto)s"
+
+msgid ""
"creating CWUniqueTogetherConstraint (CWUniqueTogetherConstraint "
"constraint_of CWEType %(linkto)s)"
msgstr "création d'une contrainte unique_together sur %(linkto)s"
@@ -1776,6 +1726,116 @@
msgid "csv export"
msgstr "export CSV"
+msgid "ctxcomponents"
+msgstr "composants contextuels"
+
+msgid "ctxcomponents_anonuserlink"
+msgstr "lien utilisateur"
+
+msgid "ctxcomponents_anonuserlink_description"
+msgstr ""
+"affiche un lien vers le formulaire d'authentification pour les utilisateurs "
+"anonymes, sinon une boite contenant notamment des liens propres à "
+"l'utilisateur connectés"
+
+msgid "ctxcomponents_appliname"
+msgstr "titre de l'application"
+
+msgid "ctxcomponents_appliname_description"
+msgstr "affiche le titre de l'application dans l'en-tête de page"
+
+msgid "ctxcomponents_bookmarks_box"
+msgstr "boîte signets"
+
+msgid "ctxcomponents_bookmarks_box_description"
+msgstr "boîte contenant les signets de l'utilisateur"
+
+msgid "ctxcomponents_breadcrumbs"
+msgstr "fil d'ariane"
+
+msgid "ctxcomponents_breadcrumbs_description"
+msgstr ""
+"affiche un chemin permettant de localiser la page courante dans le site"
+
+msgid "ctxcomponents_download_box"
+msgstr "boîte de téléchargement"
+
+msgid "ctxcomponents_download_box_description"
+msgstr "boîte contenant un lien permettant de télécharger la ressource"
+
+msgid "ctxcomponents_edit_box"
+msgstr "boîte d'actions"
+
+msgid "ctxcomponents_edit_box_description"
+msgstr ""
+"boîte affichant les différentes actions possibles sur les données affichées"
+
+msgid "ctxcomponents_facet.filters"
+msgstr "boîte à facettes"
+
+msgid "ctxcomponents_facet.filters_description"
+msgstr ""
+"boîte permettant de filtrer parmi les résultats d'une recherche à l'aide de "
+"facettes"
+
+msgid "ctxcomponents_logo"
+msgstr "logo"
+
+msgid "ctxcomponents_logo_description"
+msgstr "le logo de l'application, dans l'en-tête de page"
+
+msgid "ctxcomponents_metadata"
+msgstr "méta-données de l'entité"
+
+msgid "ctxcomponents_metadata_description"
+msgstr ""
+
+msgid "ctxcomponents_possible_views_box"
+msgstr "boîte des vues possibles"
+
+msgid "ctxcomponents_possible_views_box_description"
+msgstr "boîte affichant les vues possibles pour les données courantes"
+
+msgid "ctxcomponents_prevnext"
+msgstr "élément précedent / suivant"
+
+msgid "ctxcomponents_prevnext_description"
+msgstr ""
+"affiche des liens permettant de passer d'une entité à une autre sur les "
+"entités implémentant l'interface \"précédent/suivant\"."
+
+msgid "ctxcomponents_rss"
+msgstr "icône RSS"
+
+msgid "ctxcomponents_rss_description"
+msgstr "l'icône RSS permettant de récupérer la vue RSS des données affichées"
+
+msgid "ctxcomponents_search_box"
+msgstr "boîte de recherche"
+
+msgid "ctxcomponents_search_box_description"
+msgstr "boîte avec un champ de recherche simple"
+
+msgid "ctxcomponents_startup_views_box"
+msgstr "boîte des vues de départs"
+
+msgid "ctxcomponents_startup_views_box_description"
+msgstr "boîte affichant les vues de départs de l'application"
+
+msgid "ctxcomponents_userstatus"
+msgstr "état de l'utilisateur"
+
+msgid "ctxcomponents_userstatus_description"
+msgstr ""
+
+msgid "ctxcomponents_wfhistory"
+msgstr "historique du workflow."
+
+msgid "ctxcomponents_wfhistory_description"
+msgstr ""
+"section affichant l'historique du workflow pour les entités ayant un "
+"workflow."
+
msgid "ctxtoolbar"
msgstr "barre d'outils"
@@ -1785,6 +1845,72 @@
msgid "custom_workflow_object"
msgstr "workflow de"
+msgid "cw_dont_cross"
+msgstr "don't cross"
+
+msgctxt "CWSource"
+msgid "cw_dont_cross"
+msgstr "don't cross"
+
+msgid "cw_dont_cross_object"
+msgstr "can't be crossed with"
+
+msgctxt "CWRType"
+msgid "cw_dont_cross_object"
+msgstr "can't be crossed with"
+
+msgid "cw_host_config_of"
+msgstr "host configuration of"
+
+msgctxt "CWSourceHostConfig"
+msgid "cw_host_config_of"
+msgstr "host configuration of"
+
+msgid "cw_host_config_of_object"
+msgstr "has host configuration"
+
+msgctxt "CWSource"
+msgid "cw_host_config_of_object"
+msgstr "has host configuration"
+
+msgid "cw_may_cross"
+msgstr "may cross"
+
+msgctxt "CWSource"
+msgid "cw_may_cross"
+msgstr "may cross"
+
+msgid "cw_may_cross_object"
+msgstr "may be crossed with"
+
+msgctxt "CWRType"
+msgid "cw_may_cross_object"
+msgstr "may be crossed with"
+
+msgid "cw_source"
+msgstr "from data source"
+
+msgid "cw_source_object"
+msgstr "entities"
+
+msgid "cw_support"
+msgstr "support"
+
+msgctxt "CWSource"
+msgid "cw_support"
+msgstr "support"
+
+msgid "cw_support_object"
+msgstr "supported by"
+
+msgctxt "CWEType"
+msgid "cw_support_object"
+msgstr "supported by"
+
+msgctxt "CWRType"
+msgid "cw_support_object"
+msgstr "supported by"
+
msgid "cwetype-box"
msgstr "vue \"boîte\""
@@ -2195,10 +2321,6 @@
msgid "error while embedding page"
msgstr "erreur pendant l'inclusion de la page"
-#, python-format
-msgid "error while handling __method: %s"
-msgstr "erreur survenue lors du traitement de formulaire (%s)"
-
msgid "error while publishing ReST text"
msgstr ""
"une erreur s'est produite lors de l'interprétation du texte au format ReST"
@@ -2245,6 +2367,9 @@
msgid "external page"
msgstr "page externe"
+msgid "facet.filters"
+msgstr "facettes"
+
msgid "facetbox"
msgstr "boîte à facettes"
@@ -2254,6 +2379,12 @@
msgid "facets_created_by-facet_description"
msgstr ""
+msgid "facets_cw_source-facet"
+msgstr "facette \"source de données\""
+
+msgid "facets_cw_source-facet_description"
+msgstr ""
+
msgid "facets_cwfinal-facet"
msgstr "facette \"type d'entité ou de relation final\""
@@ -2465,8 +2596,11 @@
msgid "has_text"
msgstr "contient le texte"
-msgid "help"
-msgstr "aide"
+msgid "header-left"
+msgstr "en-tête (gauche)"
+
+msgid "header-right"
+msgstr "en-tête (droite)"
msgid "hide filter form"
msgstr "cacher le filtre"
@@ -2791,6 +2925,9 @@
msgid "login"
msgstr "identifiant"
+msgid "login / password"
+msgstr "identifiant / mot de passe"
+
msgid "login or email"
msgstr "identifiant ou email"
@@ -2807,6 +2944,9 @@
msgid "main informations"
msgstr "Informations générales"
+msgid "main_tab"
+msgstr "description"
+
msgid "mainvars"
msgstr "variables principales"
@@ -2835,6 +2975,13 @@
msgid "march"
msgstr "mars"
+msgid "match_host"
+msgstr "pour l'hôte"
+
+msgctxt "CWSourceHostConfig"
+msgid "match_host"
+msgstr "pour l'hôte"
+
msgid "maximum number of characters in short description"
msgstr "nombre maximum de caractères dans les descriptions courtes"
@@ -2915,6 +3062,10 @@
msgid "name"
msgstr "nom"
+msgctxt "CWSource"
+msgid "name"
+msgstr "nom"
+
msgctxt "State"
msgid "name"
msgstr "nom"
@@ -2941,6 +3092,9 @@
"nom des variables principales qui devrait être utilisées dans la sélection "
"si nécessaire (les séparer par des virgules)"
+msgid "name of the source"
+msgstr "nom de la source"
+
msgid "name or identifier of the permission"
msgstr "nom (identifiant) de la permission"
@@ -3135,9 +3289,6 @@
msgid "possible views"
msgstr "vues possibles"
-msgid "powered by CubicWeb"
-msgstr "construit avec CubicWeb"
-
msgid "prefered_form"
msgstr "forme préférée"
@@ -3222,6 +3373,10 @@
msgid "read_permission_object"
msgstr "peut lire"
+msgid "regexp matching host(s) to which this config applies"
+msgstr ""
+"expression régulière des noms d'hôtes auxquels cette configuration s'applique"
+
msgid "registry"
msgstr "registre"
@@ -3275,13 +3430,9 @@
msgid "relations_object"
msgstr "relations de"
-msgctxt "CWAttribute"
+msgctxt "CWRType"
msgid "relations_object"
-msgstr "contraint par"
-
-msgctxt "CWRelation"
-msgid "relations_object"
-msgstr "contraint par"
+msgstr "relations de"
msgid "relative url of the bookmarked page"
msgstr "url relative de la page"
@@ -3399,6 +3550,9 @@
msgid "security"
msgstr "sécurité"
+msgid "see more"
+msgstr "voir plus"
+
msgid "see them all"
msgstr "les voir toutes"
@@ -3500,6 +3654,14 @@
msgid "sorry, the server is unable to handle this query"
msgstr "désolé, le serveur ne peut traiter cette requête"
+msgid ""
+"source's configuration. One key=value per line, authorized keys depending on "
+"the source's type"
+msgstr ""
+"Configuration de la source. Une clé=valeur par ligne, les clés autorisées "
+"dépendantes du type de source. Les valeur surchargent celles définies sur la "
+"source."
+
msgid "sparql xml"
msgstr "XML Sparql"
@@ -3773,6 +3935,13 @@
msgid "toggle check boxes"
msgstr "inverser les cases à cocher"
+msgid "tr_count"
+msgstr "n° de transition"
+
+msgctxt "TrInfo"
+msgid "tr_count"
+msgstr "n° de transition"
+
msgid "transaction undoed"
msgstr "transaction annulées"
@@ -3824,6 +3993,10 @@
msgid "type"
msgstr "type"
+msgctxt "CWSource"
+msgid "type"
+msgstr "type"
+
msgctxt "Transition"
msgid "type"
msgstr "type"
@@ -3835,6 +4008,9 @@
msgid "type here a sparql query"
msgstr "Tapez une requête sparql"
+msgid "type of the source"
+msgstr "type de la source"
+
msgid "ui"
msgstr "propriétés génériques de l'interface"
@@ -3886,8 +4062,9 @@
msgid "unknown external entity"
msgstr "entité (externe) introuvable"
-msgid "unknown property key"
-msgstr "clé de propriété inconnue"
+#, python-format
+msgid "unknown property key %s"
+msgstr "clé de propriété inconnue : %s"
msgid "unknown vocabulary:"
msgstr "vocabulaire inconnu : "
@@ -4185,27 +4362,3 @@
msgid "you should probably delete that property"
msgstr "vous devriez probablement supprimer cette propriété"
-
-#~ msgid "add_perm"
-#~ msgstr "ajout"
-
-#~ msgid "delete_perm"
-#~ msgstr "suppression"
-
-#~ msgid "edition"
-#~ msgstr "édition"
-
-#~ msgid "graphical workflow for %s"
-#~ msgstr "graphique du workflow pour %s"
-
-#~ msgid "personnal informations"
-#~ msgstr "informations personnelles"
-
-#~ msgid "read_perm"
-#~ msgstr "lecture"
-
-#~ msgid "update_perm"
-#~ msgstr "modification"
-
-#~ msgid "yams type, rdf type or mime type of the object"
-#~ msgstr "type yams, vocabulaire rdf ou type mime de l'objet"
--- a/migration.py Fri Dec 10 12:17:18 2010 +0100
+++ b/migration.py Fri Mar 11 09:46:45 2011 +0100
@@ -334,14 +334,15 @@
if not self.execscript_confirm(migrscript):
return
scriptlocals = self._create_context().copy()
+ scriptlocals.update({'__file__': migrscript,
+ '__args__': kwargs.pop("scriptargs", [])})
self._context_stack.append(scriptlocals)
if script_mode == 'python':
if funcname is None:
pyname = '__main__'
else:
pyname = splitext(basename(migrscript))[0]
- scriptlocals.update({'__file__': migrscript, '__name__': pyname,
- '__args__': kwargs.pop("scriptargs", [])})
+ scriptlocals['__name__'] = pyname
execfile(migrscript, scriptlocals)
if funcname is not None:
try:
@@ -358,8 +359,13 @@
self.commit()
else: # script_mode == 'doctest'
import doctest
- doctest.testfile(migrscript, module_relative=False,
- optionflags=doctest.ELLIPSIS, globs=scriptlocals)
+ return doctest.testfile(migrscript, module_relative=False,
+ optionflags=doctest.ELLIPSIS,
+ # verbose mode when user input is expected
+ verbose=self.verbosity==2,
+ report=True,
+ encoding='utf-8',
+ globs=scriptlocals)
self._context_stack.pop()
def cmd_option_renamed(self, oldname, newname):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.0_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,38 @@
+from __future__ import with_statement
+
+from cubicweb.server.session import hooks_control
+
+for uri, cfg in config.sources().items():
+ if uri in ('system', 'admin'):
+ continue
+ repo.sources_by_uri[uri] = repo.get_source(cfg['adapter'], uri, cfg)
+
+add_entity_type('CWSource')
+add_relation_definition('CWSource', 'cw_source', 'CWSource')
+add_entity_type('CWSourceHostConfig')
+
+with hooks_control(session, session.HOOKS_ALLOW_ALL, 'cw.sources'):
+ create_entity('CWSource', type=u'native', name=u'system')
+commit()
+
+sql('INSERT INTO cw_source_relation(eid_from,eid_to) '
+ 'SELECT e.eid,s.cw_eid FROM entities as e, cw_CWSource as s '
+ 'WHERE s.cw_name=e.type')
+commit()
+
+for uri, cfg in config.sources().items():
+ if uri in ('system', 'admin'):
+ continue
+ repo.sources_by_uri.pop(uri)
+ config = u'\n'.join('%s=%s' % (key, value) for key, value in cfg.items()
+ if key != 'adapter' and value is not None)
+ create_entity('CWSource', name=unicode(uri), type=unicode(cfg['adapter']),
+ config=config)
+commit()
+
+# rename cwprops for boxes/contentnavigation
+for x in rql('Any X,XK WHERE X pkey XK, '
+ 'X pkey ~= "boxes.%s" OR '
+ 'X pkey ~= "contentnavigation.%s"').entities():
+ x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.0_common.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,1 @@
+option_group_changed('cleanup-session-time', 'web', 'main')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.4_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,8 @@
+for eschema in schema.entities():
+ if not (eschema.final or 'cw_source' in eschema.subjrels):
+ add_relation_definition(eschema.type, 'cw_source', 'CWSource', ask_confirm=False)
+
+sql('INSERT INTO cw_source_relation(eid_from, eid_to) '
+ 'SELECT e.eid,s.cw_eid FROM entities as e, cw_CWSource as s '
+ 'WHERE s.cw_name=e.source AND NOT EXISTS(SELECT 1 FROM cw_source_relation WHERE eid_from=e.eid AND eid_to=s.cw_eid)')
+commit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.5_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,6 @@
+sync_schema_props_perms('CWSourceHostConfig', syncperms=False)
+
+sql('INSERT INTO cw_source_relation(eid_from, eid_to) '
+ 'SELECT e.eid,s.cw_eid FROM entities as e, cw_CWSource as s '
+ 'WHERE s.cw_name=e.source AND NOT EXISTS(SELECT 1 FROM cw_source_relation WHERE eid_from=e.eid AND eid_to=s.cw_eid)')
+commit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.7_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,2 @@
+add_attribute('TrInfo', 'tr_count')
+sync_schema_props_perms('TrInfo')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.8_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,1 @@
+sync_schema_props_perms('CWSource', syncprops=False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.10.9_Any.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,32 @@
+from __future__ import with_statement
+import sys
+
+# fix some corrupted entities noticed on several instances
+rql('DELETE CWConstraint X WHERE NOT E constrained_by X')
+rql('SET X is_instance_of Y WHERE X is Y, NOT X is_instance_of Y')
+commit()
+
+if confirm('fix existing cwuri?'):
+ from logilab.common.shellutils import ProgressBar
+ from cubicweb.server.session import hooks_control
+ rset = rql('Any X, XC WHERE X cwuri XC, X cwuri ~= "%/eid/%"')
+ if sys.stdout.isatty():
+ pb = ProgressBar(nbops=rset.rowcount, size=70)
+ else:
+ pb = None
+ with hooks_control(session, session.HOOKS_DENY_ALL, 'integrity'):
+ for i, e in enumerate(rset.entities()):
+ e.set_attributes(cwuri=e.cwuri.replace('/eid', ''))
+ if i % 100: # commit every 100 entities to limit memory consumption
+ commit(ask_confirm=False)
+ if pb is not None:
+ pb.update()
+ commit(ask_confirm=False)
+
+try:
+ from cubicweb import devtools
+ option_group_changed('anonymous-user', 'main', 'web')
+ option_group_changed('anonymous-password', 'main', 'web')
+except ImportError:
+ # cubicweb-dev unavailable, nothing needed
+ pass
--- a/misc/migration/bootstrapmigration_repository.py Fri Dec 10 12:17:18 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py Fri Mar 11 09:46:45 2011 +0100
@@ -97,6 +97,14 @@
if applcubicwebversion < (3, 9, 6) and cubicwebversion >= (3, 9, 6):
add_entity_type('CWUniqueTogetherConstraint')
+if not ('CWUniqueTogetherConstraint', 'CWRType') in schema['relations'].rdefs:
+ add_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRType')
+ rql('SET C relations RT WHERE C relations RDEF, RDEF relation_type RT')
+ commit()
+ drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWAttribute')
+ drop_relation_definition('CWUniqueTogetherConstraint', 'relations', 'CWRelation')
+
+
if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
with hooks_control(session, session.HOOKS_ALLOW_ALL, 'integrity'):
--- a/misc/migration/postcreate.py Fri Dec 10 12:17:18 2010 +0100
+++ b/misc/migration/postcreate.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb post creation script, set user's workflow
+"""cubicweb post creation script, set user's workflow"""
-"""
# insert versions
create_entity('CWProperty', pkey=u'system.version.cubicweb',
value=unicode(config.cubicweb_version()))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/scripts/cwuser_ldap2system.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,40 @@
+import base64
+from cubicweb.server.utils import crypt_password
+
+dbdriver = config.sources()['system']['db-driver']
+from logilab.database import get_db_helper
+dbhelper = get_db_helper(driver)
+
+insert = ('INSERT INTO cw_cwuser (cw_creation_date,'
+ ' cw_eid,'
+ ' cw_modification_date,'
+ ' cw_login,'
+ ' cw_firstname,'
+ ' cw_surname,'
+ ' cw_last_login_time,'
+ ' cw_upassword,'
+ ' cw_cwuri) '
+ "VALUES (%(mtime)s, %(eid)s, %(mtime)s, %(login)s, "
+ " %(firstname)s, %(surname)s, %(mtime)s, %(pwd)s, 'foo');")
+update = "UPDATE entities SET source='system' WHERE eid=%(eid)s;"
+rset = sql("SELECT eid,type,source,extid,mtime FROM entities WHERE source!='system'", ask_confirm=False)
+for eid, type, source, extid, mtime in rset:
+ if type != 'CWUser':
+ print "don't know what to do with entity type", type
+ continue
+ if not source.lower().startswith('ldap'):
+ print "don't know what to do with source type", source
+ continue
+ extid = base64.decodestring(extid)
+ ldapinfos = [x.strip().split('=') for x in extid.split(',')]
+ login = ldapinfos[0][1]
+ firstname = login.capitalize()
+ surname = login.capitalize()
+ args = dict(eid=eid, type=type, source=source, login=login,
+ firstname=firstname, surname=surname, mtime=mtime,
+ pwd=dbhelper.binary_value(crypt_password('toto')))
+ print args
+ sql(insert, args)
+ sql(update, args)
+
+commit()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/scripts/drop_external_entities.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,23 @@
+from cubicweb import UnknownEid
+source, = __args__
+
+sql("DELETE FROM entities WHERE type='Int'")
+
+ecnx = session.pool.connection(source)
+for e in rql('Any X WHERE X cw_source S, S name %(name)s', {'name': source}).entities():
+ meta = e.cw_metainformation()
+ assert meta['source']['uri'] == source
+ try:
+ suri = ecnx.describe(meta['extid'])[1]
+ except UnknownEid:
+ print 'cant describe', e.__regid__, e.eid, meta
+ continue
+ 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)
+ except UnknownEid:
+ print ' cant delete', e.__regid__, e.eid, meta
+
+
+commit()
--- a/misc/scripts/repair_file_1-9_migration.py Fri Dec 10 12:17:18 2010 +0100
+++ b/misc/scripts/repair_file_1-9_migration.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,9 +19,9 @@
sourcescfg = repo.config.sources()
backupcfg = cwconfig.instance_configuration(backupinstance)
backupcfg.repairing = True
-backuprepo, backupcnx = dbapi.in_memory_cnx(backupcfg, sourcescfg['admin']['login'],
- password=sourcescfg['admin']['password'],
- host='localhost')
+backuprepo, backupcnx = dbapi.in_memory_repo_cnx(backupcfg, sourcescfg['admin']['login'],
+ password=sourcescfg['admin']['password'],
+ host='localhost')
backupcu = backupcnx.cursor()
with hooks_control(session, session.HOOKS_DENY_ALL):
--- a/mixins.py Fri Dec 10 12:17:18 2010 +0100
+++ b/mixins.py Fri Mar 11 09:46:45 2011 +0100
@@ -35,7 +35,7 @@
benefit from this default implementation
"""
__metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] TreeMixIn is deprecated, use/override ITreeAdapter instead'
+ __deprecation_warning__ = '[3.9] TreeMixIn is deprecated, use/override ITreeAdapter instead (%(cls)s)'
tree_attribute = None
# XXX misnamed
@@ -205,7 +205,7 @@
class TreeViewMixIn(object):
"""a recursive tree view"""
__metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] TreeViewMixIn is deprecated, use/override BaseTreeView instead'
+ __deprecation_warning__ = '[3.9] TreeViewMixIn is deprecated, use/override BaseTreeView instead (%(cls)s)'
__regid__ = 'tree'
__select__ = implements(ITree, warn=False)
@@ -244,7 +244,7 @@
class TreePathMixIn(object):
"""a recursive path view"""
__metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] TreePathMixIn is deprecated, use/override TreePathView instead'
+ __deprecation_warning__ = '[3.9] TreePathMixIn is deprecated, use/override TreePathView instead (%(cls)s)'
__regid__ = 'path'
item_vid = 'oneline'
separator = u' > '
@@ -270,7 +270,7 @@
class ProgressMixIn(object):
"""provide a default implementations for IProgress interface methods"""
__metaclass__ = class_deprecated
- __deprecation_warning__ = '[3.9] ProgressMixIn is deprecated, use/override IProgressAdapter instead'
+ __deprecation_warning__ = '[3.9] ProgressMixIn is deprecated, use/override IProgressAdapter instead (%(cls)s)'
@property
def cost(self):
--- a/req.py Fri Dec 10 12:17:18 2010 +0100
+++ b/req.py Fri Mar 11 09:46:45 2011 +0100
@@ -210,8 +210,7 @@
if not isinstance(values, (list, tuple)):
values = (values,)
for value in values:
- if value is None:
- raise ValueError(_('unauthorized value'))
+ assert value is not None
args.append(u'%s=%s' % (param, self.url_quote(value)))
return '&'.join(args)
--- a/rset.py Fri Dec 10 12:17:18 2010 +0100
+++ b/rset.py Fri Mar 11 09:46:45 2011 +0100
@@ -386,6 +386,19 @@
if self.rows[i][col] is not None:
yield self.get_entity(i, col)
+ def iter_rows_with_entities(self):
+ """ iterates over rows, and for each row
+ eids are converted to plain entities
+ """
+ for i, row in enumerate(self):
+ _row = []
+ for j, col in enumerate(row):
+ try:
+ _row.append(self.get_entity(i, j) if col is not None else col)
+ except NotAnEntity:
+ _row.append(col)
+ yield _row
+
def complete_entity(self, row, col=0, skip_bytes=True):
"""short cut to get an completed entity instance for a particular
row (all instance's attributes have been fetched)
@@ -401,9 +414,9 @@
.. warning::
- Due to the cache wrapping this function, you should NEVER
- give row as a named parameter (i.e. rset.get_entity(req, 0)
- is OK but rset.get_entity(row=0, req=req) isn't)
+ Due to the cache wrapping this function, you should NEVER give row as
+ a named parameter (i.e. `rset.get_entity(0, 1)` is OK but
+ `rset.get_entity(row=0, col=1)` isn't)
:type row,col: int, int
:param row,col:
@@ -421,11 +434,11 @@
return self._build_entity(row, col)
def _build_entity(self, row, col):
- """internal method to get a single entity, returns a
- partially initialized Entity instance.
+ """internal method to get a single entity, returns a partially
+ initialized Entity instance.
- partially means that only attributes selected in the RQL
- query will be directly assigned to the entity.
+ partially means that only attributes selected in the RQL query will be
+ directly assigned to the entity.
:type row,col: int, int
:param row,col:
@@ -474,24 +487,21 @@
select = rqlst
# take care, due to outer join support, we may find None
# values for non final relation
- for i, attr, role in attr_desc_iterator(select, col):
- outerselidx = rqlst.subquery_selection_index(select, i)
- if outerselidx is None:
- continue
+ for i, attr, role in attr_desc_iterator(select, col, entity.cw_col):
if role == 'subject':
rschema = eschema.subjrels[attr]
if rschema.final:
if attr == 'eid':
- entity.eid = rowvalues[outerselidx]
+ entity.eid = rowvalues[i]
else:
- entity[attr] = rowvalues[outerselidx]
+ entity.cw_attr_cache[attr] = rowvalues[i]
continue
else:
rschema = eschema.objrels[attr]
rdef = eschema.rdef(attr, role)
# only keep value if it can't be multivalued
if rdef.role_cardinality(role) in '1?':
- if rowvalues[outerselidx] is None:
+ if rowvalues[i] is None:
if role == 'subject':
rql = 'Any Y WHERE X %s Y, X eid %s'
else:
@@ -499,7 +509,7 @@
rrset = ResultSet([], rql % (attr, entity.eid))
rrset.req = req
else:
- rrset = self._build_entity(row, outerselidx).as_rset()
+ rrset = self._build_entity(row, i).as_rset()
entity.cw_set_relation_cache(attr, role, rrset)
return entity
@@ -637,8 +647,13 @@
return rhs.eval(self.args)
return None
+def _get_variable(term):
+ # XXX rewritten const
+ # use iget_nodes for (hack) case where we have things like MAX(V)
+ for vref in term.iget_nodes(nodes.VariableRef):
+ return vref.variable
-def attr_desc_iterator(rqlst, index=0):
+def attr_desc_iterator(select, selectidx, rootidx):
"""return an iterator on a list of 2-uple (index, attr_relation)
localizing attribute relations of the main variable in a result's row
@@ -649,25 +664,33 @@
a generator on (index, relation, target) describing column being
attribute of the main variable
"""
- main = rqlst.selection[index]
- for i, term in enumerate(rqlst.selection):
- if i == index:
+ rootselect = select
+ while rootselect.parent.parent is not None:
+ rootselect = rootselect.parent.parent.parent
+ rootmain = rootselect.selection[selectidx]
+ rootmainvar = _get_variable(rootmain)
+ assert rootmainvar
+ root = rootselect.parent
+ selectmain = select.selection[selectidx]
+ for i, term in enumerate(rootselect.selection):
+ rootvar = _get_variable(term)
+ if rootvar is None:
continue
- # XXX rewritten const
- # use iget_nodes for (hack) case where we have things like MAX(V)
- for vref in term.iget_nodes(nodes.VariableRef):
- var = vref.variable
- break
- else:
+ if rootvar.name == rootmainvar.name:
+ continue
+ if select is not rootselect:
+ term = select.selection[root.subquery_selection_index(select, i)]
+ var = _get_variable(term)
+ if var is None:
continue
for ref in var.references():
rel = ref.relation()
if rel is None or rel.is_types_restriction():
continue
lhs, rhs = rel.get_variable_parts()
- if main.is_equivalent(lhs):
+ if selectmain.is_equivalent(lhs):
if rhs.is_equivalent(term):
yield (i, rel.r_type, 'subject')
- elif main.is_equivalent(rhs):
+ elif selectmain.is_equivalent(rhs):
if lhs.is_equivalent(term):
yield (i, rel.r_type, 'object')
--- a/rtags.py Fri Dec 10 12:17:18 2010 +0100
+++ b/rtags.py Fri Mar 11 09:46:45 2011 +0100
@@ -105,6 +105,8 @@
def apply(self, schema, func):
for eschema in schema.entities():
+ if eschema.final:
+ continue
for rschema, tschemas, role in eschema.relation_definitions(True):
for tschema in tschemas:
if role == 'subject':
@@ -216,6 +218,9 @@
def name(self):
return self.__class__.name
+ # tag_subject_of / tag_object_of issue warning if '*' is not given as target
+ # type, while tag_relation handle it silently since it may be used during
+ # initialization
def tag_subject_of(self, key, tag):
subj, rtype, obj = key
if obj != '*':
@@ -232,5 +237,14 @@
self.name, rtype, obj, subj, rtype, obj)
super(NoTargetRelationTagsDict, self).tag_object_of(('*', rtype, obj), tag)
-
+ def tag_relation(self, key, tag):
+ if key[-1] == 'subject' and key[-2] != '*':
+ if isinstance(key, tuple):
+ key = list(key)
+ key[-2] = '*'
+ elif key[-1] == 'object' and key[0] != '*':
+ if isinstance(key, tuple):
+ key = list(key)
+ key[0] = '*'
+ super(NoTargetRelationTagsDict, self).tag_relation(key, tag)
set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags'))
--- a/schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -49,16 +49,28 @@
# set of meta-relations available for every entity types
META_RTYPES = set((
'owned_by', 'created_by', 'is', 'is_instance_of', 'identity',
- 'eid', 'creation_date', 'modification_date', 'has_text', 'cwuri',
+ 'eid', 'creation_date', 'cw_source', 'modification_date', 'has_text', 'cwuri',
))
WORKFLOW_RTYPES = set(('custom_workflow', 'in_state', 'wf_info_for'))
-SYSTEM_RTYPES = set(('require_permission',)) | WORKFLOW_RTYPES
+WORKFLOW_DEF_RTYPES = set(('workflow_of', 'state_of', 'transition_of',
+ 'initial_state', 'default_workflow',
+ 'allowed_transition', 'destination_state',
+ 'from_state', 'to_state', 'condition',
+ 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
+ ))
+SYSTEM_RTYPES = set(('in_group', 'require_group', 'require_permission',
+ # cwproperty
+ 'for_user',
+ )) | WORKFLOW_RTYPES
+NO_I18NCONTEXT = META_RTYPES | WORKFLOW_RTYPES
+NO_I18NCONTEXT.add('require_permission')
# set of entity and relation types used to build the schema
SCHEMA_TYPES = set((
'CWEType', 'CWRType', 'CWAttribute', 'CWRelation',
'CWConstraint', 'CWConstraintType', 'CWUniqueTogetherConstraint',
'RQLExpression',
+ 'specializes',
'relation_type', 'from_entity', 'to_entity',
'constrained_by', 'cstrtype',
'constraint_of', 'relations',
@@ -70,7 +82,9 @@
'WorkflowTransition', 'BaseTransition',
'SubWorkflowExitPoint'))
-INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri'))
+INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri',
+ 'CWSource', 'CWSourceHostConfig',
+))
_LOGGER = getLogger('cubicweb.schemaloader')
@@ -536,7 +550,11 @@
def add_entity_type(self, edef):
edef.name = edef.name.encode()
edef.name = bw_normalize_etype(edef.name)
- assert re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name), repr(edef.name)
+ if not re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name):
+ raise BadSchemaDefinition(
+ '%r is not a valid name for an entity type. It should start '
+ 'with an upper cased letter and be followed by at least a '
+ 'lower cased letter' % edef.name)
eschema = super(CubicWebSchema, self).add_entity_type(edef)
if not eschema.final:
# automatically add the eid relation to non final entity types
@@ -551,7 +569,11 @@
return eschema
def add_relation_type(self, rdef):
- rdef.name = rdef.name.lower().encode()
+ if not rdef.name.islower():
+ raise BadSchemaDefinition(
+ '%r is not a valid name for a relation type. It should be '
+ 'lower cased' % rdef.name)
+ rdef.name = rdef.name.encode()
rschema = super(CubicWebSchema, self).add_relation_type(rdef)
self._eid_index[rschema.eid] = rschema
return rschema
--- a/schemas/base.py Fri Dec 10 12:17:18 2010 +0100
+++ b/schemas/base.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,8 +20,8 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
- String, Datetime, Password)
+from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
+ SubjectRelation, String, Datetime, Password)
from cubicweb.schema import (
RQLConstraint, WorkflowableEntityType, ERQLExpression, RRQLExpression,
PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS)
@@ -62,7 +62,7 @@
}
alias = String(fulltextindexed=True, maxsize=56)
- address = String(required=True, fulltextindexed=True,
+ address = String(required=True, fulltextindexed=True,
indexed=True, unique=True, maxsize=128)
prefered_form = SubjectRelation('EmailAddress', cardinality='?*',
description=_('when multiple addresses are equivalent \
@@ -198,6 +198,7 @@
uri = String(required=True, unique=True, maxsize=256,
description=_('the URI of the object'))
+
class same_as(RelationType):
"""generic relation to specify that an external entity represent the same
object as a local one:
@@ -216,6 +217,7 @@
# in the cube's schema.
object = 'ExternalUri'
+
class CWCache(EntityType):
"""a simple cache entity characterized by a name and
a validity date.
@@ -234,12 +236,81 @@
'delete': ('managers',),
}
- name = String(required=True, unique=True, indexed=True, maxsize=128,
+ name = String(required=True, unique=True, maxsize=128,
description=_('name of the cache'))
timestamp = Datetime(default='NOW')
-# "abtract" relation types, not used in cubicweb itself
+class CWSource(EntityType):
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers',),
+ 'update': ('managers',),
+ 'delete': ('managers',),
+ }
+ name = String(required=True, unique=True, maxsize=128,
+ description=_('name of the source'))
+ type = String(required=True, maxsize=20, description=_('type of the source'))
+ config = String(description=_('source\'s configuration. One key=value per '
+ 'line, authorized keys depending on the '
+ 'source\'s type'),
+ __permissions__={
+ 'read': ('managers',),
+ 'update': ('managers',),
+ })
+
+
+class CWSourceHostConfig(EntityType):
+ __permissions__ = {
+ 'read': ('managers',),
+ 'add': ('managers',),
+ 'update': ('managers',),
+ 'delete': ('managers',),
+ }
+ __unique_together__ = [('match_host', 'cw_host_config_of')]
+ match_host = String(required=True, maxsize=128,
+ description=_('regexp matching host(s) to which this config applies'))
+ config = String(required=True,
+ description=_('Source\'s configuration for a particular host. '
+ 'One key=value per line, authorized keys '
+ 'depending on the source\'s type, overriding '
+ 'values defined on the source.'),
+ __permissions__={
+ 'read': ('managers',),
+ 'update': ('managers',),
+ })
+
+
+class cw_host_config_of(RelationDefinition):
+ subject = 'CWSourceHostConfig'
+ object = 'CWSource'
+ cardinality = '1*'
+ composite = 'object'
+ inlined = True
+
+class cw_source(RelationDefinition):
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': (),
+ 'delete': (),
+ }
+ subject = '*'
+ object = 'CWSource'
+ cardinality = '1*'
+
+class cw_support(RelationDefinition):
+ subject = 'CWSource'
+ object = ('CWEType', 'CWRType')
+
+class cw_dont_cross(RelationDefinition):
+ subject = 'CWSource'
+ object = 'CWRType'
+
+class cw_may_cross(RelationDefinition):
+ subject = 'CWSource'
+ object = 'CWRType'
+
+# "abtract" relation types, no definition in cubicweb itself ###################
class identical_to(RelationType):
"""identical to"""
--- a/schemas/bootstrap.py Fri Dec 10 12:17:18 2010 +0100
+++ b/schemas/bootstrap.py Fri Mar 11 09:46:45 2011 +0100
@@ -159,10 +159,10 @@
__permissions__ = PUB_SYSTEM_ENTITY_PERMS
constraint_of = SubjectRelation('CWEType', cardinality='1*', composite='object',
inlined=True)
- relations = SubjectRelation(('CWAttribute', 'CWRelation'), cardinality='+*',
- constraints=[RQLConstraint(
- 'O from_entity X, S constraint_of X, O relation_type T, '
- 'T final TRUE OR (T final FALSE AND T inlined TRUE)')])
+ relations = SubjectRelation('CWRType', cardinality='+*',
+ constraints=[RQLConstraint(
+ 'S constraint_of ET, RDEF relation_type O, RDEF from_entity ET, '
+ 'O final TRUE OR (O final FALSE AND O inlined TRUE)')])
class CWConstraintType(EntityType):
--- a/schemas/workflow.py Fri Dec 10 12:17:18 2010 +0100
+++ b/schemas/workflow.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,7 +22,7 @@
_ = unicode
from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
- RichString, String)
+ RichString, String, Int)
from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS,
HOOKS_RTYPE_PERMS)
@@ -159,13 +159,21 @@
'delete': (), # XXX should we allow managers to delete TrInfo?
'update': ('managers', 'owners',),
}
-
- from_state = SubjectRelation('State', cardinality='1*')
- to_state = SubjectRelation('State', cardinality='1*')
+ # The unique_together constraint ensures that 2 repositories
+ # sharing the db won't be able to fire a transition simultaneously
+ # on the same entity tr_count is filled in the FireTransitionHook
+ # to the number of TrInfo attached to the entity on which we
+ # attempt to fire a transition. In other word, it contains the
+ # rank of the TrInfo for that entity, and the constraint says we
+ # cannot have 2 TrInfo with the same rank.
+ __unique_together__ = [('tr_count', 'wf_info_for')]
+ from_state = SubjectRelation('State', cardinality='1*', inlined=True)
+ to_state = SubjectRelation('State', cardinality='1*', inlined=True)
# make by_transition optional because we want to allow managers to set
# entity into an arbitrary state without having to respect wf transition
by_transition = SubjectRelation('BaseTransition', cardinality='?*')
comment = RichString(fulltextindexed=True)
+ tr_count = Int(description='autocomputed attribute used to ensure transition coherency')
# get actor and date time using owned_by and creation_date
class from_state(RelationType):
--- a/selectors.py Fri Dec 10 12:17:18 2010 +0100
+++ b/selectors.py Fri Mar 11 09:46:45 2011 +0100
@@ -60,9 +60,9 @@
.. sourcecode:: python
- class RSSIconBox(ExtResourcesBoxTemplate):
+ class RSSIconBox(box.Box):
''' just display the RSS icon on uniform result set '''
- __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity()
+ __select__ = box.Box.__select__ & non_final_entity()
It takes into account:
@@ -479,6 +479,31 @@
return score + 0.5
return score
+
+class configuration_values(Selector):
+ """Return 1 if the instance has an option set to a given value(s) in its
+ configuration file.
+ """
+ # XXX this selector could be evaluated on startup
+ def __init__(self, key, values):
+ self._key = key
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ self._values = frozenset(values)
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ try:
+ return self._score
+ except AttributeError:
+ if req is None:
+ config = kwargs['repo'].config
+ else:
+ config = req.vreg.config
+ self._score = config[self._key] in self._values
+ return self._score
+
+
# rset selectors ##############################################################
@objectify_selector
@@ -526,6 +551,8 @@
"""Return 1 if the result set is of size 1, or greater but a specific row in
the result set is specified ('row' argument).
"""
+ if rset is None and 'entity' in kwargs:
+ return 1
if rset is not None and (row is not None or rset.rowcount == 1):
return 1
return 0
@@ -534,12 +561,12 @@
class multi_lines_rset(Selector):
"""Return 1 if the operator expression matches between `num` elements
in the result set and the `expected` value if defined.
-
+
By default, multi_lines_rset(expected) matches equality expression:
`nb` row(s) in result set equals to expected value
But, you can perform richer comparisons by overriding default operator:
multi_lines_rset(expected, operator.gt)
-
+
If `expected` is None, return 1 if the result set contains *at least*
two rows.
If rset is None, return 0.
@@ -605,7 +632,7 @@
@lltrace
def sorted_rset(cls, req, rset=None, **kwargs):
"""Return 1 for sorted result set (e.g. from an RQL query containing an
- :ref:ORDERBY clause), with exception that it will return 0 if the rset is
+ ORDERBY clause), with exception that it will return 0 if the rset is
'ORDERBY FTIRANK(VAR)' (eg sorted by rank value of the has_text index).
"""
if rset is None:
@@ -752,7 +779,11 @@
def score_class(self, eclass, req):
# cache on vreg to avoid reloading issues
- cache = req.vreg._is_instance_selector_cache
+ try:
+ cache = req.vreg._is_instance_selector_cache
+ except AttributeError:
+ # XXX 'before-registry-reset' not called for db-api connections
+ cache = req.vreg._is_instance_selector_cache = {}
try:
expected_eclasses = cache[self]
except KeyError:
@@ -788,6 +819,9 @@
This is a very useful selector that will usually interest you since it
allows a lot of things without having to write a specific selector.
+ The function can return arbitrary value which will be casted to an integer
+ value at the end.
+
See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
lookup / score rules according to the input context.
"""
@@ -802,21 +836,6 @@
return 1
self.score_entity = intscore
-class attribute_edited(EntitySelector):
- """Scores if the specified attribute has been edited
- This is useful for selection of forms by the edit controller.
- The initial use case is on a form, in conjunction with match_transition,
- which will not score at edit time::
-
- is_instance('Version') & (match_transition('ready') |
- attribute_edited('publication_date'))
- """
- def __init__(self, attribute, once_is_enough=False):
- super(attribute_edited, self).__init__(once_is_enough)
- self._attribute = attribute
-
- def score_entity(self, entity):
- return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
class has_mimetype(EntitySelector):
"""Return 1 if the entity adapt to IDownloadable and has the given MIME type.
@@ -1128,27 +1147,121 @@
must use 'X' variable to represent the context entity and may use 'U' to
represent the request's user.
+ .. warning::
+ If simply testing value of some attribute/relation of context entity (X),
+ you should rather use the :class:`score_entity` selector which will
+ benefit from the ORM's request entities cache.
+
See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
lookup / score rules according to the input context.
"""
def __init__(self, expression, once_is_enough=False):
super(rql_condition, self).__init__(once_is_enough)
if 'U' in frozenset(split_expression(expression)):
- rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
+ rql = 'Any COUNT(X) WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
else:
- rql = 'Any X WHERE X eid %%(x)s, %s' % expression
+ rql = 'Any COUNT(X) WHERE X eid %%(x)s, %s' % expression
self.rql = rql
- def __repr__(self):
- return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+ def __str__(self):
+ return '%s(%r)' % (self.__class__.__name__, self.rql)
def score(self, req, rset, row, col):
try:
- return len(req.execute(self.rql, {'x': rset[row][col],
- 'u': req.user.eid}))
+ return req.execute(self.rql, {'x': rset[row][col],
+ 'u': req.user.eid})[0][0]
except Unauthorized:
return 0
+
+class is_in_state(score_entity):
+ """Return 1 if entity is in one of the states given as argument list
+
+ You should use this instead of your own :class:`score_entity` selector to
+ avoid some gotchas:
+
+ * possible views gives a fake entity with no state
+ * you must use the latest tr info thru the workflow adapter for repository
+ side checking of the current state
+
+ In debug mode, this selector can raise:
+ :raises: :exc:`ValueError` for unknown states names
+ (etype workflow only not checked in custom workflow)
+
+ :rtype: int
+ """
+ def __init__(self, *expected):
+ assert expected, self
+ self.expected = frozenset(expected)
+ def score(entity, expected=self.expected):
+ adapted = entity.cw_adapt_to('IWorkflowable')
+ # in debug mode only (time consuming)
+ if entity._cw.vreg.config.debugmode:
+ # validation can only be done for generic etype workflow because
+ # expected transition list could have been changed for a custom
+ # workflow (for the current entity)
+ if not entity.custom_workflow:
+ self._validate(adapted)
+ return self._score(adapted)
+ super(is_in_state, self).__init__(score)
+
+ def _score(self, adapted):
+ trinfo = adapted.latest_trinfo()
+ if trinfo is None: # entity is probably in it's initial state
+ statename = adapted.state
+ else:
+ statename = trinfo.new_state.name
+ return statename in self.expected
+
+ def _validate(self, adapted):
+ wf = adapted.current_workflow
+ valid = [n.name for n in wf.reverse_state_of]
+ unknown = sorted(self.expected.difference(valid))
+ if unknown:
+ raise ValueError("%s: unknown state(s): %s"
+ % (wf.name, ",".join(unknown)))
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.expected))
+
+
+class on_transition(is_in_state):
+ """Return 1 if entity is in one of the transitions given as argument list
+
+ Especially useful to match passed transition to enable notifications when
+ your workflow allows several transition to the same states.
+
+ Note that if workflow `change_state` adapter method is used, this selector
+ will not be triggered.
+
+ You should use this instead of your own :class:`score_entity` selector to
+ avoid some gotchas:
+
+ * possible views gives a fake entity with no state
+ * you must use the latest tr info thru the workflow adapter for repository
+ side checking of the current state
+
+ In debug mode, this selector can raise:
+ :raises: :exc:`ValueError` for unknown transition names
+ (etype workflow only not checked in custom workflow)
+
+ :rtype: int
+ """
+ def _score(self, adapted):
+ trinfo = adapted.latest_trinfo()
+ if trinfo and trinfo.by_transition:
+ return trinfo.by_transition[0].name in self.expected
+
+ def _validate(self, adapted):
+ wf = adapted.current_workflow
+ valid = [n.name for n in wf.reverse_transition_of]
+ unknown = sorted(self.expected.difference(valid))
+ if unknown:
+ raise ValueError("%s: unknown transition(s): %s"
+ % (wf.name, ",".join(unknown)))
+
+
# logged user selectors ########################################################
@objectify_selector
@@ -1183,7 +1296,6 @@
"""
return ~ authenticated_user()
-
class match_user_groups(ExpectedValueSelector):
"""Return a non-zero score if request's user is in at least one of the
groups given as initializer argument. Returned score is the number of groups
@@ -1213,9 +1325,9 @@
score = all(user.owns(r[col]) for r in rset)
return score
-
# Web request selectors ########################################################
+# XXX deprecate
@objectify_selector
@lltrace
def primary_view(cls, req, view=None, **kwargs):
@@ -1233,6 +1345,15 @@
return 1
+@objectify_selector
+@lltrace
+def contextual(cls, req, view=None, **kwargs):
+ """Return 1 if view's contextual property is true"""
+ if view is not None and view.contextual:
+ return 1
+ return 0
+
+
class match_view(ExpectedValueSelector):
"""Return 1 if a view is specified an as its registry id is in one of the
expected view id given to the initializer.
@@ -1244,6 +1365,19 @@
return 1
+class match_context(ExpectedValueSelector):
+
+ @lltrace
+ def __call__(self, cls, req, context=None, **kwargs):
+ try:
+ if not context in self.expected:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
+
+
+# XXX deprecate
@objectify_selector
@lltrace
def match_context_prop(cls, req, context=None, **kwargs):
@@ -1264,8 +1398,6 @@
return 1
propval = req.property_value('%s.%s.context' % (cls.__registry__,
cls.__regid__))
- if not propval:
- propval = cls.context
if propval and context != propval:
return 0
return 1
@@ -1347,43 +1479,62 @@
return 0
-# Other selectors ##############################################################
+class attribute_edited(EntitySelector):
+ """Scores if the specified attribute has been edited This is useful for
+ selection of forms by the edit controller.
+
+ The initial use case is on a form, in conjunction with match_transition,
+ which will not score at edit time::
+
+ is_instance('Version') & (match_transition('ready') |
+ attribute_edited('publication_date'))
+ """
+ def __init__(self, attribute, once_is_enough=False):
+ super(attribute_edited, self).__init__(once_is_enough)
+ self._attribute = attribute
+
+ def score_entity(self, entity):
+ return eid_param(role_name(self._attribute, 'subject'), entity.eid) in entity._cw.form
+# Other selectors ##############################################################
+
+# XXX deprecated ? maybe use on_transition selector instead ?
class match_transition(ExpectedValueSelector):
- """Return 1 if `transition` argument is found in the input context
- which has a `.name` attribute matching one of the expected names
- given to the initializer
+ """Return 1 if `transition` argument is found in the input context which has
+ a `.name` attribute matching one of the expected names given to the
+ initializer.
"""
@lltrace
def __call__(self, cls, req, transition=None, **kwargs):
# XXX check this is a transition that apply to the object?
+ if transition is None:
+ treid = req.form.get('treid', None)
+ if treid:
+ transition = req.entity_from_eid(treid)
if transition is not None and getattr(transition, 'name', None) in self.expected:
return 1
return 0
-class is_in_state(score_entity):
- """return 1 if entity is in one of the states given as argument list
-
- you should use this instead of your own :class:`score_entity` selector to
- avoid some gotchas:
- * possible views gives a fake entity with no state
- * you must use the latest tr info, not entity.in_state for repository side
- checking of the current state
+class match_exception(ExpectedValueSelector):
+ """Return 1 if a view is specified an as its registry id is in one of the
+ expected view id given to the initializer.
"""
- def __init__(self, *states):
- def score(entity, states=set(states)):
- trinfo = entity.cw_adapt_to('IWorkflowable').latest_trinfo()
- try:
- return trinfo.new_state.name in states
- except AttributeError:
- return None
- super(is_in_state, self).__init__(score)
+ def __init__(self, *expected):
+ assert expected, self
+ self.expected = expected
+
+ @lltrace
+ def __call__(self, cls, req, exc=None, **kwargs):
+ if exc is not None and isinstance(exc, self.expected):
+ return 1
+ return 0
+
@objectify_selector
def debug_mode(cls, req, rset=None, **kwargs):
- """Return 1 if running in debug mode"""
+ """Return 1 if running in debug mode."""
return req.vreg.config.debugmode and 1 or 0
## deprecated stuff ############################################################
--- a/server/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,8 +19,8 @@
(repository) side
This module contains functions to initialize a new repository.
+"""
-"""
from __future__ import with_statement
__docformat__ = "restructuredtext en"
@@ -61,7 +61,6 @@
else:
DEBUG |= debugmode
-
class debugged(object):
"""repository debugging context manager / decorator
@@ -122,7 +121,7 @@
with the minimal set of entities (ie at least the schema, base groups and
a initial user)
"""
- from cubicweb.dbapi import in_memory_cnx
+ from cubicweb.dbapi import in_memory_repo_cnx
from cubicweb.server.repository import Repository
from cubicweb.server.utils import manager_userpasswd
from cubicweb.server.sqlutils import sqlexec, sqlschema, sqldropschema
@@ -132,7 +131,6 @@
config.consider_user_state = False
config.set_language = False
# only enable the system source at initialization time
- config.enabled_sources = ('system',)
repo = Repository(config, vreg=vreg)
schema = repo.schema
sourcescfg = config.sources()
@@ -162,6 +160,12 @@
sqlcnx.commit()
sqlcnx.close()
session = repo.internal_session()
+ # insert entity representing the system source
+ ssource = session.create_entity('CWSource', type=u'native', name=u'system')
+ repo.system_source.eid = ssource.eid
+ session.execute('SET X cw_source X WHERE X eid %(x)s', {'x': ssource.eid})
+ # insert base groups and default admin
+ print '-> inserting default user and default groups.'
try:
login = unicode(sourcescfg['admin']['login'])
pwd = sourcescfg['admin']['password']
@@ -171,17 +175,18 @@
login, pwd = manager_userpasswd(msg=msg, confirm=True)
else:
login, pwd = unicode(source['db-user']), source['db-password']
- print '-> inserting default user and default groups.'
# sort for eid predicatability as expected in some server tests
for group in sorted(BASE_GROUPS):
- session.execute('INSERT CWGroup X: X name %(name)s',
- {'name': unicode(group)})
- create_user(session, login, pwd, 'managers')
+ session.create_entity('CWGroup', name=unicode(group))
+ admin = create_user(session, login, pwd, 'managers')
+ session.execute('SET X owned_by U WHERE X is IN (CWGroup,CWSource), U eid %(u)s',
+ {'u': admin.eid})
session.commit()
repo.shutdown()
# reloging using the admin user
config._cubes = None # avoid assertion error
- repo, cnx = in_memory_cnx(config, login, password=pwd)
+ repo, cnx = in_memory_repo_cnx(config, login, password=pwd)
+ repo.system_source.eid = ssource.eid # redo this manually
# trigger vreg initialisation of entity classes
config.cubicweb_appobject_path = set(('entities',))
config.cube_appobject_path = set(('entities',))
@@ -197,13 +202,7 @@
initialize_schema(config, schema, handler)
# yoo !
cnx.commit()
- config.enabled_sources = None
- for uri, source_config in config.sources().items():
- if uri in ('admin', 'system'):
- # not an actual source or init_creating already called
- continue
- source = repo.get_source(uri, source_config)
- source.init_creating()
+ repo.system_source.init_creating()
cnx.commit()
cnx.close()
session.close()
--- a/server/checkintegrity.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/checkintegrity.py Fri Mar 11 09:46:45 2011 +0100
@@ -36,6 +36,12 @@
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.session import security_enabled
+def notify_fixed(fix):
+ if fix:
+ print >> sys.stderr, ' [FIXED]'
+ else:
+ print >> sys.stderr
+
def has_eid(session, sqlcursor, eid, eids):
"""return true if the eid is a valid eid"""
if eid in eids:
@@ -131,8 +137,8 @@
# attribute to their current value
source = repo.system_source
for eschema in etypes:
- for entity in session.execute('Any X WHERE X is %s' % eschema).entities():
- source.fti_index_entity(session, entity)
+ rset = session.execute('Any X WHERE X is %s' % eschema)
+ source.fti_index_entities(session, rset.entities())
if withpb:
pb.update()
@@ -169,9 +175,7 @@
print >> sys.stderr, msg % eid,
if fix:
session.system_sql('DELETE FROM appears WHERE uid=%s;' % eid)
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
def check_entities(schema, session, eids, fix=1):
@@ -185,9 +189,7 @@
print >> sys.stderr, msg % eid,
if fix:
session.system_sql('DELETE FROM entities WHERE eid=%s;' % eid)
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
print 'Checking entities tables'
for eschema in schema.entities():
if eschema.final:
@@ -204,22 +206,19 @@
print >> sys.stderr, msg % (eid, eschema.type),
if fix:
session.system_sql('DELETE FROM %s WHERE %s=%s;' % (table, column, eid))
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
def bad_related_msg(rtype, target, eid, fix):
msg = ' A relation %s with %s eid %s exists but no such entity in sources'
print >> sys.stderr, msg % (rtype, target, eid),
- if fix:
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
def check_relations(schema, session, eids, fix=1):
- """check all relations registered in the repo system table"""
+ """check that eids referenced by relations are registered in the repo system
+ table
+ """
print 'Checking relations'
for rschema in schema.relations():
if rschema.final or rschema in PURE_VIRTUAL_RTYPES:
@@ -265,6 +264,54 @@
session.system_sql(sql)
+def check_mandatory_relations(schema, session, eids, fix=1):
+ """check entities missing some mandatory relation"""
+ print 'Checking mandatory relations'
+ for rschema in schema.relations():
+ if rschema.final or rschema in PURE_VIRTUAL_RTYPES:
+ continue
+ smandatory = set()
+ omandatory = set()
+ for rdef in rschema.rdefs.values():
+ if rdef.cardinality[0] in '1+':
+ smandatory.add(rdef.subject)
+ if rdef.cardinality[1] in '1+':
+ omandatory.add(rdef.object)
+ for role, etypes in (('subject', smandatory), ('object', omandatory)):
+ for etype in etypes:
+ if role == 'subject':
+ rql = 'Any X WHERE NOT X %s Y, X is %s' % (rschema, etype)
+ else:
+ rql = 'Any X WHERE NOT Y %s X, X is %s' % (rschema, etype)
+ for entity in session.execute(rql).entities():
+ print >> sys.stderr, '%s #%s is missing mandatory %s relation %s' % (
+ entity.__regid__, entity.eid, role, rschema)
+ if fix:
+ #if entity.cw_describe()['source']['uri'] == 'system': XXX
+ entity.delete()
+ notify_fixed(fix)
+
+
+def check_mandatory_attributes(schema, session, eids, fix=1):
+ """check for entities stored in the system source missing some mandatory
+ attribute
+ """
+ print 'Checking mandatory attributes'
+ for rschema in schema.relations():
+ if not rschema.final or rschema in VIRTUAL_RTYPES:
+ continue
+ for rdef in rschema.rdefs.values():
+ if rdef.cardinality[0] in '1+':
+ rql = 'Any X WHERE X %s NULL, X is %s, X cw_source S, S name "system"' % (
+ rschema, rdef.subject)
+ for entity in session.execute(rql).entities():
+ print >> sys.stderr, '%s #%s is missing mandatory attribute %s' % (
+ entity.__regid__, entity.eid, rschema)
+ if fix:
+ entity.delete()
+ notify_fixed(fix)
+
+
def check_metadata(schema, session, eids, fix=1):
"""check entities has required metadata
@@ -287,9 +334,7 @@
session.system_sql("UPDATE %s SET %s=%%(v)s WHERE %s=%s ;"
% (table, column, eidcolumn, eid),
{'v': default})
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
cursor = session.system_sql('SELECT MIN(%s) FROM %sCWUser;' % (eidcolumn,
SQL_PREFIX))
default_user_eid = cursor.fetchone()[0]
@@ -305,9 +350,7 @@
if fix:
session.system_sql('INSERT INTO %s_relation VALUES (%s, %s) ;'
% (rel, eid, default))
- print >> sys.stderr, ' [FIXED]'
- else:
- print >> sys.stderr
+ notify_fixed(fix)
def check(repo, cnx, checks, reindex, fix, withpb=True):
@@ -336,6 +379,11 @@
cnx.commit()
+def info(msg, *args):
+ if args:
+ msg = msg % args
+ print 'INFO: %s' % msg
+
def warning(msg, *args):
if args:
msg = msg % args
@@ -374,13 +422,13 @@
# check relation in dont_cross_relations aren't in support_relations
for rschema in mapping['dont_cross_relations']:
if rschema in mapping['support_relations']:
- warning('relation %s is in dont_cross_relations and in support_relations',
- rschema)
+ info('relation %s is in dont_cross_relations and in support_relations',
+ rschema)
# check relation in cross_relations are in support_relations
for rschema in mapping['cross_relations']:
if rschema not in mapping['support_relations']:
- warning('relation %s is in cross_relations but not in support_relations',
- rschema)
+ info('relation %s is in cross_relations but not in support_relations',
+ rschema)
# check for relation in both cross_relations and dont_cross_relations
for rschema in mapping['cross_relations'] & mapping['dont_cross_relations']:
error('relation %s is in both cross_relations and dont_cross_relations',
@@ -410,7 +458,7 @@
if role == 'subject' and rschema.inlined:
error('inlined relation %s of %s should be supported',
rschema, eschema)
- elif not somethingprinted and rschema not in seen:
+ elif not somethingprinted and rschema not in seen and rschema not in mapping['cross_relations']:
print 'you may want to specify something for %s' % rschema
seen.add(rschema)
else:
--- a/server/hook.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/hook.py Fri Mar 11 09:46:45 2011 +0100
@@ -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,37 +15,233 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Hooks management
+"""
+Generalities
+------------
+
+Paraphrasing the `emacs`_ documentation, let us say that hooks are an important
+mechanism for customizing an application. A hook is basically a list of
+functions to be called on some well-defined occasion (this is called `running
+the hook`).
+
+.. _`emacs`: http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html
+
+Hooks
+~~~~~
+
+In |cubicweb|, hooks are subclasses of the :class:`~cubicweb.server.hook.Hook`
+class. They are selected over a set of pre-defined `events` (and possibly more
+conditions, hooks being selectable appobjects like views and components). They
+should implement a :meth:`~cubicweb.server.hook.Hook.__call__` method that will
+be called when the hook is triggered.
-This module defined the `Hook` class and registry and a set of abstract classes
-for operations.
+There are two families of events: data events (before / after any individual
+update of an entity / or a relation in the repository) and server events (such
+as server startup or shutdown). In a typical application, most of the hooks are
+defined over data events.
+
+Also, some :class:`~cubicweb.server.hook.Operation` may be registered by hooks,
+which will be fired when the transaction is commited or rollbacked.
+
+The purpose of data event hooks is usually to complement the data model as
+defined in the schema, which is static by nature and only provide a restricted
+builtin set of dynamic constraints, with dynamic or value driven behaviours.
+For instance they can serve the following purposes:
+
+* enforcing constraints that the static schema cannot express (spanning several
+ entities/relations, exotic value ranges and cardinalities, etc.)
+
+* implement computed attributes
+
+It is functionally equivalent to a `database trigger`_, except that database
+triggers definition languages are not standardized, hence not portable (for
+instance, PL/SQL works with Oracle and PostgreSQL but not SqlServer nor Sqlite).
+
+.. _`database trigger`: http://en.wikipedia.org/wiki/Database_trigger
-Hooks are called before / after any individual update of entities / relations
-in the repository and on special events such as server startup or shutdown.
+.. hint::
+
+ It is a good practice to write unit tests for each hook. See an example in
+ :ref:`hook_test`
+
+Operations
+~~~~~~~~~~
+
+Operations are subclasses of the :class:`~cubicweb.server.hook.Operation` class
+that may be created by hooks and scheduled to happen just before (or after) the
+`precommit`, `postcommit` or `rollback` event. Hooks are being fired immediately
+on data operations, and it is sometime necessary to delay the actual work down
+to a time where all other hooks have run. Also while the order of execution of
+hooks is data dependant (and thus hard to predict), it is possible to force an
+order on operations.
+
+Operations may be used to:
+
+* implements a validation check which needs that all relations be already set on
+ an entity
+
+* process various side effects associated with a transaction such as filesystem
+ udpates, mail notifications, etc.
-Operations may be registered by hooks during a transaction, which will be
-fired when the pool is commited or rollbacked.
+Events
+------
+
+Hooks are mostly defined and used to handle `dataflow`_ operations. It
+means as data gets in (entities added, updated, relations set or
+unset), specific events are issued and the Hooks matching these events
+are called.
+
+You can get the event that triggered a hook by accessing its :attr:event
+attribute.
+
+.. _`dataflow`: http://en.wikipedia.org/wiki/Dataflow
-Entity hooks (eg before_add_entity, after_add_entity, before_update_entity,
-after_update_entity, before_delete_entity, after_delete_entity) all have an
-`entity` attribute
+Entity modification related events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When called for one of these events, hook will have an `entity` attribute
+containing the entity instance.
+
+* `before_add_entity`, `before_update_entity`:
+
+ - on those events, you can check what attributes of the entity are modified in
+ `entity.cw_edited` (by definition the database is not yet updated in a before
+ event)
+
+ - you are allowed to further modify the entity before database
+ operations, using the dictionary notation on `cw_edited`. By doing
+ this, you'll avoid the need for a whole new rql query processing,
+ the only difference is that the underlying backend query (eg
+ usually sql) will contains the additional data. For example:
+
+ .. sourcecode:: python
+
+ self.entity.set_attributes(age=42)
+
+ will set the `age` attribute of the entity to 42. But to do so, it will
+ generate a rql query that will have to be processed, then trigger some
+ hooks, and so one (potentially leading to infinite hook loops or such
+ awkward situations..) You can avoid this by doing the modification that way:
+
+ .. sourcecode:: python
+
+ self.entity.cw_edited['age'] = 42
+
+ Here the attribute will simply be edited in the same query that the
+ one that triggered the hook.
-Relation (eg before_add_relation, after_add_relation, before_delete_relation,
-after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.
+ Similarly, removing an attribute from `cw_edited` will cancel its
+ modification.
+
+ - on `before_update_entity` event, you can access to old and new values in
+ this hook, by using `entity.cw_edited.oldnewvalue(attr)`
+
+
+* `after_add_entity`, `after_update_entity`
+
+ - on those events, you can still check what attributes of the entity are
+ modified in `entity.cw_edited` but you can't get anymore the old value, nor
+ modify it.
+
+* `before_delete_entity`, `after_delete_entity`
+
+ - on those events, the entity has no `cw_edited` set.
+
+
+Relation modification related events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When called for one of these events, hook will have `eidfrom`, `rtype`, `eidto`
+attributes containing respectivly the eid of the subject entity, the relation
+type and the eid of the object entity.
+
+* `before_add_relation`, `before_delete_relation`
+
+ - on those events, you can still get original relation by issuing a rql query
+
+* `after_add_relation`, `after_delete_relation`
+
+This is an occasion to remind us that relations support the add / delete
+operation, but no update.
+
+
+Non data events
+~~~~~~~~~~~~~~~
-Server start/maintenance/stop hooks (eg server_startup, server_maintenance,
-server_shutdown) have a `repo` attribute, but *their `_cw` attribute is None*.
-The `server_startup` is called on regular startup, while `server_maintenance`
-is called on cubicweb-ctl upgrade or shell commands. `server_shutdown` is
-called anyway.
+Hooks called on server start/maintenance/stop event (eg `server_startup`,
+`server_maintenance`, `server_shutdown`) have a `repo` attribute, but *their
+`_cw` attribute is None*. The `server_startup` is called on regular startup,
+while `server_maintenance` is called on cubicweb-ctl upgrade or shell
+commands. `server_shutdown` is called anyway.
+
+Hooks called on backup/restore event (eg 'server_backup', 'server_restore') have
+a `repo` and a `timestamp` attributes, but *their `_cw` attribute is None*.
+
+Hooks called on session event (eg `session_open`, `session_close`) have no
+special attribute.
+
+
+API
+---
+
+Hooks control
+~~~~~~~~~~~~~
+
+It is sometimes convenient to explicitly enable or disable some hooks. For
+instance if you want to disable some integrity checking hook. This can be
+controlled more finely through the `category` class attribute, which is a string
+giving a category name. One can then uses the
+:class:`~cubicweb.server.session.hooks_control` context manager to explicitly
+enable or disable some categories.
+
+.. autoclass:: cubicweb.server.session.hooks_control
+
+
+The existing categories are:
+
+* ``security``, security checking hooks
+
+* ``worfklow``, workflow handling hooks
-Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a
-`timestamp` attributes, but *their `_cw` attribute is None*.
+* ``metadata``, hooks setting meta-data on newly created entities
+
+* ``notification``, email notification hooks
+
+* ``integrity``, data integrity checking hooks
+
+* ``activeintegrity``, data integrity consistency hooks, that you should **never**
+ want to disable
+
+* ``syncsession``, hooks synchronizing existing sessions
+
+* ``syncschema``, hooks synchronizing instance schema (including the physical database)
+
+* ``email``, email address handling hooks
+
+* ``bookmark``, bookmark entities handling hooks
+
-Session hooks (eg session_open, session_close) have no special attribute.
+Nothing precludes one to invent new categories and use the
+:class:`~cubicweb.server.session.hooks_control` context manager to
+filter them in or out. Note that ending the transaction with commit()
+or rollback() will restore the hooks.
+
+
+Hooks specific selector
+~~~~~~~~~~~~~~~~~~~~~~~
+.. autoclass:: cubicweb.server.hook.match_rtype
+.. autoclass:: cubicweb.server.hook.match_rtype_sets
+
+
+Hooks and operations classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. autoclass:: cubicweb.server.hook.Hook
+.. autoclass:: cubicweb.server.hook.Operation
+.. autoclass:: cubicweb.server.hook.LateOperation
+.. autoclass:: cubicweb.server.hook.DataOperationMixIn
"""
from __future__ import with_statement
@@ -61,6 +257,7 @@
from logilab.common.logging_ext import set_log_methods
from cubicweb import RegistryNotFound
+from cubicweb.vregistry import classid
from cubicweb.cwvreg import CWRegistry, VRegistry
from cubicweb.selectors import (objectify_selector, lltrace, ExpectedValueSelector,
is_instance)
@@ -77,13 +274,21 @@
'session_open', 'session_close'))
ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
+def _iter_kwargs(entities, kwargs):
+ if not entities:
+ yield kwargs
+ else:
+ for entity in entities:
+ kwargs['entity'] = entity
+ yield kwargs
+
class HooksRegistry(CWRegistry):
def initialization_completed(self):
for appobjects in self.values():
for cls in appobjects:
if not cls.enabled:
- warn('[3.6] %s: enabled is deprecated' % cls)
+ warn('[3.6] %s: enabled is deprecated' % classid(cls))
self.unregister(cls)
def register(self, obj, **kwargs):
@@ -91,19 +296,30 @@
super(HooksRegistry, self).register(obj, **kwargs)
def call_hooks(self, event, session=None, **kwargs):
+ """call `event` hooks for an entity or a list of entities (passed
+ respectively as the `entity` or ``entities`` keyword argument).
+ """
kwargs['event'] = event
- if session is None:
+ if session is None: # True for events such as server_start
for hook in sorted(self.possible_objects(session, **kwargs),
key=lambda x: x.order):
hook()
else:
+ if 'entities' in kwargs:
+ assert 'entity' not in kwargs, \
+ 'can\'t pass "entities" and "entity" arguments simultaneously'
+ entities = kwargs.pop('entities')
+ else:
+ entities = []
# by default, hooks are executed with security turned off
with security_enabled(session, read=False):
- hooks = sorted(self.possible_objects(session, **kwargs),
- key=lambda x: x.order)
- with security_enabled(session, write=False):
- for hook in hooks:
- hook()
+ for _kwargs in _iter_kwargs(entities, kwargs):
+ hooks = sorted(self.possible_objects(session, **_kwargs),
+ key=lambda x: x.order)
+ with security_enabled(session, write=False):
+ for hook in hooks:
+ #print hook.category, hook.__regid__
+ hook()
class HooksManager(object):
def __init__(self, vreg):
@@ -111,29 +327,18 @@
def call_hooks(self, event, session=None, **kwargs):
try:
- self.vreg['%s_hooks' % event].call_hooks(event, session, **kwargs)
+ registry = self.vreg['%s_hooks' % event]
except RegistryNotFound:
- pass # no hooks for this event
+ return # no hooks for this event
+ registry.call_hooks(event, session, **kwargs)
for event in ALL_HOOKS:
VRegistry.REGISTRY_FACTORY['%s_hooks' % event] = HooksRegistry
-_MARKER = object()
+@deprecated('[3.10] use entity.cw_edited.oldnewvalue(attr)')
def entity_oldnewvalue(entity, attr):
- """returns the couple (old attr value, new attr value)
-
- NOTE: will only work in a before_update_entity hook
- """
- # get new value and remove from local dict to force a db query to
- # fetch old value
- newvalue = entity.pop(attr, _MARKER)
- oldvalue = getattr(entity, attr)
- if newvalue is not _MARKER:
- entity[attr] = newvalue
- else:
- newvalue = oldvalue
- return oldvalue, newvalue
+ return entity.cw_edited.oldnewvalue(attr)
# some hook specific selectors #################################################
@@ -170,6 +375,7 @@
self.expected = expected
self.frometypes = more.pop('frometypes', None)
self.toetypes = more.pop('toetypes', None)
+ assert not more, "unexpected kwargs in match_rtype: %s" % more
@lltrace
def __call__(self, cls, req, *args, **kwargs):
@@ -185,8 +391,23 @@
class match_rtype_sets(ExpectedValueSelector):
- """accept if parameters specified as initializer arguments are specified
- in named arguments given to the selector
+ """accept if the relation type is in one of the sets given as initializer
+ argument. The goal of this selector is that it keeps reference to original sets,
+ so modification to thoses sets are considered by the selector. For instance
+
+ MYSET = set()
+
+ class Hook1(Hook):
+ __regid__ = 'hook1'
+ __select__ = Hook.__select__ & match_rtype_sets(MYSET)
+ ...
+
+ class Hook2(Hook):
+ __regid__ = 'hook2'
+ __select__ = Hook.__select__ & match_rtype_sets(MYSET)
+
+ Client code can now change `MYSET`, this will changes the selection criteria
+ of :class:`Hook1` and :class:`Hook1`.
"""
def __init__(self, *expected):
@@ -203,6 +424,29 @@
# base class for hook ##########################################################
class Hook(AppObject):
+ """Base class for hook.
+
+ Hooks being appobjects like views, they have a `__regid__` and a `__select__`
+ class attribute. Like all appobjects, hooks have the `self._cw` attribute which
+ represents the current session. In entity hooks, a `self.entity` attribute is
+ also present.
+
+ The `events` tuple is used by the base class selector to dispatch the hook
+ on the right events. It is possible to dispatch on multiple events at once
+ if needed (though take care as hook attribute may vary as described above).
+
+ .. Note::
+
+ Do not forget to extend the base class selectors as in:
+
+ .. sourcecode:: python
+
+ class MyHook(Hook):
+ __regid__ = 'whatever'
+ __select__ = Hook.__select__ & is_instance('Person')
+
+ else your hooks will be called madly, whatever the event.
+ """
__select__ = enabled_category()
# set this in derivated classes
events = None
@@ -231,16 +475,16 @@
@classproperty
def __regid__(cls):
- warn('[3.6] %s.%s: please specify an id for your hook'
- % (cls.__module__, cls.__name__), DeprecationWarning)
+ warn('[3.6] %s: please specify an id for your hook' % classid(cls),
+ DeprecationWarning)
return str(id(cls))
@classmethod
def __registered__(cls, reg):
super(Hook, cls).__registered__(reg)
if getattr(cls, 'accepts', None):
- warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'
- % (cls.__module__, cls.__name__), DeprecationWarning)
+ warn('[3.6] %s: accepts is deprecated, define proper __select__'
+ % classid(cls), DeprecationWarning)
rtypes = []
for ertype in cls.accepts:
if ertype.islower():
@@ -261,9 +505,8 @@
def __call__(self):
if hasattr(self, 'call'):
- cls = self.__class__
- warn('[3.6] %s.%s: call is deprecated, implement __call__'
- % (cls.__module__, cls.__name__), DeprecationWarning)
+ warn('[3.6] %s: call is deprecated, implement __call__'
+ % classid(self.__class__), DeprecationWarning)
if self.event.endswith('_relation'):
self.call(self._cw, self.eidfrom, self.rtype, self.eidto)
elif 'delete' in self.event:
@@ -392,40 +635,53 @@
# abstract classes for operation ###############################################
class Operation(object):
- """an operation is triggered on connections pool events related to
+ """Base class for operations.
+
+ Operation may be instantiated in the hooks' `__call__` method. It always
+ takes a session object as first argument (accessible as `.session` from the
+ operation instance), and optionally all keyword arguments needed by the
+ operation. These keyword arguments will be accessible as attributes from the
+ operation instance.
+
+ An operation is triggered on connections pool events related to
commit / rollback transations. Possible events are:
- precommit:
- the pool is preparing to commit. You shouldn't do anything which
- has to be reverted if the commit fails at this point, but you can freely
- do any heavy computation or raise an exception if the commit can't go.
- You can add some new operations during this phase but their precommit
- event won't be triggered
+ * `precommit`:
- commit:
- the pool is preparing to commit. You should avoid to do to expensive
- stuff or something that may cause an exception in this event
+ the transaction is being prepared for commit. You can freely do any heavy
+ computation, raise an exception if the commit can't go. or even add some
+ new operations during this phase. If you do anything which has to be
+ reverted if the commit fails afterwards (eg altering the file system for
+ instance), you'll have to support the 'revertprecommit' event to revert
+ things by yourself
- revertcommit:
- if an operation failed while commited, this event is triggered for
- all operations which had their commit event already to let them
- revert things (including the operation which made fail the commit)
+ * `revertprecommit`:
+
+ if an operation failed while being pre-commited, this event is triggered
+ for all operations which had their 'precommit' event already fired to let
+ them revert things (including the operation which made the commit fail)
+
+ * `rollback`:
- rollback:
the transaction has been either rollbacked either:
+
* intentionaly
- * a precommit event failed, all operations are rollbacked
- * a commit event failed, all operations which are not been triggered for
- commit are rollbacked
+ * a 'precommit' event failed, in which case all operations are rollbacked
+ once 'revertprecommit'' has been called
+
+ * `postcommit`:
- postcommit:
- The transaction is over. All the ORM entities are
- invalid. If you need to work on the database, you need to stard
- a new transaction, for instance using a new internal_session,
- which you will need to commit (and close!).
+ the transaction is over. All the ORM entities accessed by the earlier
+ transaction are invalid. If you need to work on the database, you need to
+ start a new transaction, for instance using a new internal session, which
+ you will need to commit (and close!).
- order of operations may be important, and is controlled according to
- the insert_index's method output
+ For an operation to support an event, one has to implement the `<event
+ name>_event` method with no arguments.
+
+ The order of operations may be important, and is controlled according to
+ the insert_index's method output (whose implementation vary according to the
+ base hook class used).
"""
def __init__(self, session, **kwargs):
@@ -455,6 +711,10 @@
def handle_event(self, event):
"""delegate event handling to the opertaion"""
+ 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()
getattr(self, event)()
def precommit_event(self):
@@ -467,16 +727,6 @@
been all considered if it's this operation which failed
"""
- def commit_event(self):
- """the observed connections pool is commiting"""
-
- def revertcommit_event(self):
- """an error went when commiting this operation or a later one
-
- should revert commit's changes but take care, they may have not
- been all considered if it's this operation which failed
- """
-
def rollback_event(self):
"""the observed connections pool has been rollbacked
@@ -512,21 +762,153 @@
def _container_add(container, value):
{set: set.add, list: list.append}[container.__class__](container, value)
-def set_operation(session, datakey, value, opcls, containercls=set, **opkwargs):
- """Search for session.transaction_data[`datakey`] (expected to be a set):
+
+class DataOperationMixIn(object):
+ """Mix-in class to ease applying a single operation on a set of data,
+ avoiding to create as many as operation as they are individual modification.
+ The body of the operation must then iterate over the values that have been
+ stored in a single operation instance.
+
+ You should try to use this instead of creating on operation for each
+ `value`, since handling operations becomes costly on massive data import.
+
+ Usage looks like:
+
+ .. sourcecode:: python
+
+ class MyEntityHook(Hook):
+ __regid__ = 'my.entity.hook'
+ __select__ = Hook.__select__ & is_instance('MyEntity')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ MyOperation.get_instance(self._cw).add_data(self.entity)
+
+
+ class MyOperation(DataOperationMixIn, Operation):
+ def precommit_event(self):
+ for bucket in self.get_data():
+ process(bucket)
+
+ You can modify the `containercls` class attribute, which defines the
+ container class that should be instantiated to hold payloads. An instance is
+ created on instantiation, and then the :meth:`add_data` method will add the
+ given data to the existing container. Default to a `set`. Give `list` if you
+ want to keep arrival ordering. You can also use another kind of container
+ by redefining :meth:`_build_container` and :meth:`add_data`
- * if found, simply append `value`
+ More optional parameters can be given to the `get_instance` operation, that
+ will be given to the operation constructer (though those parameters should
+ not vary accross different calls to this method for a same operation for
+ obvious reason).
+
+ .. Note::
+ For sanity reason `get_data` will reset the operation, so that once
+ the operation has started its treatment, if some hook want to push
+ additional data to this same operation, a new instance will be created
+ (else that data has a great chance to be never treated). This implies:
+
+ * you should **always** call `get_data` when starting treatment
+
+ * you should **never** call `get_data` for another reason.
+ """
+ containercls = set
+
+ @classproperty
+ def data_key(cls):
+ return ('cw.dataops', cls.__name__)
+
+ @classmethod
+ def get_instance(cls, session, **kwargs):
+ # no need to lock: transaction_data already comes from thread's local storage
+ try:
+ return session.transaction_data[cls.data_key]
+ except KeyError:
+ op = session.transaction_data[cls.data_key] = cls(session, **kwargs)
+ return op
+
+ def __init__(self, *args, **kwargs):
+ super(DataOperationMixIn, self).__init__(*args, **kwargs)
+ self._container = self._build_container()
+ self._processed = False
- * else, initialize it to containercls([`value`]) and instantiate the given
- `opcls` operation class with additional keyword arguments. `containercls`
- is a set by default. Give `list` if you want to keep arrival ordering.
+ def __contains__(self, value):
+ return value in self._container
+
+ def _build_container(self):
+ return self.containercls()
+
+ def add_data(self, data):
+ assert not self._processed, """Trying to add data to a closed operation.
+Iterating over operation data closed it and should be reserved to precommit /
+postcommit method of the operation."""
+ _container_add(self._container, data)
+
+ def remove_data(self, data):
+ assert not self._processed, """Trying to add data to a closed operation.
+Iterating over operation data closed it and should be reserved to precommit /
+postcommit method of the operation."""
+ self._container.remove(data)
+
+ def get_data(self):
+ assert not self._processed, """Trying to get data from a closed operation.
+Iterating over operation data closed it and should be reserved to precommit /
+postcommit method of the operation."""
+ self._processed = True
+ op = self.session.transaction_data.pop(self.data_key)
+ assert op is self, "Bad handling of operation data, found %s instead of %s for key %s" % (
+ op, self, self.data_key)
+ return self._container
+
- You should use this instead of creating on operation for each `value`,
+@deprecated('[3.10] use opcls.get_instance(session, **opkwargs).add_data(value)')
+def set_operation(session, datakey, value, opcls, containercls=set, **opkwargs):
+ """Function to ease applying a single operation on a set of data, avoiding
+ to create as many as operation as they are individual modification. You
+ should try to use this instead of creating on operation for each `value`,
since handling operations becomes coslty on massive data import.
+
+ Arguments are:
+
+ * the `session` object
+
+ * `datakey`, a specially forged key that will be used as key in
+ session.transaction_data
+
+ * `value` that is the actual payload of an individual operation
+
+ * `opcls`, the class of the operation. An instance is created on the first
+ call for the given key, and then subsequent calls will simply add the
+ payload to the container (hence `opkwargs` is only used on that first
+ call)
+
+ * `containercls`, the container class that should be instantiated to hold
+ payloads. An instance is created on the first call for the given key, and
+ then subsequent calls will add the data to the existing container. Default
+ to a set. Give `list` if you want to keep arrival ordering.
+
+ * more optional parameters to give to the operation (here the rtype which do not
+ vary accross operations).
+
+ The body of the operation must then iterate over the values that have been mapped
+ in the transaction_data dictionary to the forged key, e.g.:
+
+ .. sourcecode:: python
+
+ for value in self._cw.transaction_data.pop(datakey):
+ ...
+
+ .. Note::
+ **poping** the key from `transaction_data` is not an option, else you may
+ get unexpected data loss in some case of nested hooks.
"""
try:
+ # Search for session.transaction_data[`datakey`] (expected to be a set):
+ # if found, simply append `value`
_container_add(session.transaction_data[datakey], value)
except KeyError:
+ # else, initialize it to containercls([`value`]) and instantiate the given
+ # `opcls` operation class with additional keyword arguments
opcls(session, **opkwargs)
session.transaction_data[datakey] = containercls()
_container_add(session.transaction_data[datakey], value)
@@ -551,8 +933,12 @@
return -(i + 1)
-class SingleOperation(Operation):
- """special operation which should be called once"""
+
+class SingleLastOperation(Operation):
+ """special operation which should be called once and after all other
+ operations
+ """
+
def register(self, session):
"""override register to handle cases where this operation has already
been added
@@ -573,11 +959,6 @@
return -(i+1)
return None
-
-class SingleLastOperation(SingleOperation):
- """special operation which should be called once and after all other
- operations
- """
def insert_index(self):
return None
@@ -599,7 +980,7 @@
if previous:
self.to_send = previous.to_send + self.to_send
- def commit_event(self):
+ def postcommit_event(self):
self.session.repo.threaded_task(self.sendmails)
def sendmails(self):
@@ -613,7 +994,7 @@
execute(*rql)
-class CleanupNewEidsCacheOp(SingleLastOperation):
+class CleanupNewEidsCacheOp(DataOperationMixIn, SingleLastOperation):
"""on rollback of a insert query we have to remove from repository's
type/source cache eids of entities added in that transaction.
@@ -623,28 +1004,27 @@
too expensive. Notice that there is no pb when using args to specify eids
instead of giving them into the rql string.
"""
+ data_key = 'neweids'
def rollback_event(self):
"""the observed connections pool has been rollbacked,
remove inserted eid from repository type/source cache
"""
try:
- self.session.repo.clear_caches(
- self.session.transaction_data['neweids'])
+ self.session.repo.clear_caches(self.get_data())
except KeyError:
pass
-class CleanupDeletedEidsCacheOp(SingleLastOperation):
+class CleanupDeletedEidsCacheOp(DataOperationMixIn, SingleLastOperation):
"""on commit of delete query, we have to remove from repository's
type/source cache eids of entities deleted in that transaction.
"""
-
- def commit_event(self):
+ data_key = 'pendingeids'
+ def postcommit_event(self):
"""the observed connections pool has been rollbacked,
remove inserted eid from repository type/source cache
"""
try:
- self.session.repo.clear_caches(
- self.session.transaction_data['pendingeids'])
+ self.session.repo.clear_caches(self.get_data())
except KeyError:
pass
--- a/server/migractions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/migractions.py Fri Mar 11 09:46:45 2011 +0100
@@ -41,12 +41,14 @@
from glob import glob
from copy import copy
from warnings import warn
+from contextlib import contextmanager
from logilab.common.deprecation import deprecated
from logilab.common.decorators import cached, clear_cache
from yams.constraints import SizeConstraint
from yams.schema2sql import eschema2sql, rschema2sql
+from yams.schema import RelationDefinitionSchema
from cubicweb import AuthenticationError, ExecutionError
from cubicweb.selectors import is_instance
@@ -60,7 +62,7 @@
from cubicweb.server import hook
try:
from cubicweb.server import SOURCE_TYPES, schemaserial as ss
- from cubicweb.server.utils import manager_userpasswd, ask_source_config
+ from cubicweb.server.utils import manager_userpasswd
from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
except ImportError: # LAX
pass
@@ -181,7 +183,7 @@
open(backupfile,'w').close() # kinda lock
os.chmod(backupfile, 0600)
# backup
- tmpdir = tempfile.mkdtemp(dir=instbkdir)
+ tmpdir = tempfile.mkdtemp()
try:
for source in repo.sources:
try:
@@ -534,38 +536,21 @@
unique_together = set([frozenset(ut)
for ut in eschema._unique_together])
for ut in repo_unique_together - unique_together:
- restrictions = ', '.join(['C relations R%(i)d, '
- 'R%(i)d relation_type T%(i)d, '
- 'R%(i)d from_entity X, '
- 'T%(i)d name %%(T%(i)d)s' % {'i': i,
- 'col':col}
- for (i, col) in enumerate(ut)])
- substs = {'etype': etype}
+ restrictions = []
+ substs = {'x': repoeschema.eid}
for i, col in enumerate(ut):
+ restrictions.append('C relations T%(i)d, '
+ 'T%(i)d name %%(T%(i)d)s' % {'i': i})
substs['T%d'%i] = col
self.rqlexec('DELETE CWUniqueTogetherConstraint C '
'WHERE C constraint_of E, '
- ' E name %%(etype)s,'
- ' %s' % restrictions,
+ ' E eid %%(x)s,'
+ ' %s' % ', '.join(restrictions),
substs)
for ut in unique_together - repo_unique_together:
- relations = ', '.join(['C relations R%d' % i
- for (i, col) in enumerate(ut)])
- restrictions = ', '.join(['R%(i)d relation_type T%(i)d, '
- 'R%(i)d from_entity E, '
- 'T%(i)d name %%(T%(i)d)s' % {'i': i,
- 'col':col}
- for (i, col) in enumerate(ut)])
- substs = {'etype': etype}
- for i, col in enumerate(ut):
- substs['T%d'%i] = col
- self.rqlexec('INSERT CWUniqueTogetherConstraint C:'
- ' C constraint_of E, '
- ' %s '
- 'WHERE '
- ' E name %%(etype)s,'
- ' %s' % (relations, restrictions),
- substs)
+ rql, substs = ss.uniquetogether2rql(eschema, ut)
+ substs['x'] = repoeschema.eid
+ self.rqlexec(rql, substs)
def _synchronize_rdef_schema(self, subjtype, rtype, objtype,
syncperms=True, syncprops=True):
@@ -643,13 +628,6 @@
for cube in newcubes:
self.cmd_set_property('system.version.'+cube,
self.config.cube_version(cube))
- if cube in SOURCE_TYPES:
- # don't use config.sources() in case some sources have been
- # disabled for migration
- sourcescfg = self.config.read_sources_file()
- sourcescfg[cube] = ask_source_config(cube)
- self.config.write_sources_file(sourcescfg)
- clear_cache(self.config, 'read_sources_file')
# ensure added cube is in config cubes
# XXX worth restoring on error?
if not cube in self.config._cubes:
@@ -961,8 +939,7 @@
# get some validation error on commit since integrity hooks
# may think some required relation is missing... This also ensure
# repository caches are properly cleanup
- hook.set_operation(session, 'pendingeids', eid,
- hook.CleanupDeletedEidsCacheOp)
+ hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(eid)
# and don't forget to remove record from system tables
self.repo.system_source.delete_info(
session, session.entity_from_eid(eid, rdeftype),
@@ -1119,11 +1096,20 @@
"""synchronize the persistent schema against the current definition
schema.
+ `ertype` can be :
+ - None, in that case everything will be synced ;
+ - a string, it should be an entity type or
+ a relation type. In that case, only the corresponding
+ entities / relations will be synced ;
+ - an rdef object to synchronize only this specific relation definition
+
It will synch common stuff between the definition schema and the
actual persistent schema, it won't add/remove any entity or relation.
"""
assert syncperms or syncprops, 'nothing to do'
if ertype is not None:
+ if isinstance(ertype, RelationDefinitionSchema):
+ ertype = ertype.as_triple()
if isinstance(ertype, (tuple, list)):
assert len(ertype) == 3, 'not a relation definition'
self._synchronize_rdef_schema(ertype[0], ertype[1], ertype[2],
@@ -1215,8 +1201,14 @@
# Workflows handling ######################################################
+ def cmd_make_workflowable(self, etype):
+ """add workflow relations to an entity type to make it workflowable"""
+ self.cmd_add_relation_definition(etype, 'in_state', 'State')
+ self.cmd_add_relation_definition(etype, 'custom_workflow', 'Workflow')
+ self.cmd_add_relation_definition('TrInfo', 'wf_info_for', etype)
+
def cmd_add_workflow(self, name, wfof, default=True, commit=False,
- **kwargs):
+ ensure_workflowable=True, **kwargs):
"""
create a new workflow and links it to entity types
:type name: unicode
@@ -1236,7 +1228,14 @@
**kwargs)
if not isinstance(wfof, (list, tuple)):
wfof = (wfof,)
+ def _missing_wf_rel(etype):
+ return 'missing workflow relations, see make_workflowable(%s)' % etype
for etype in wfof:
+ eschema = self.repo.schema[etype]
+ if ensure_workflowable:
+ assert 'in_state' in eschema.subjrels, _missing_wf_rel(etype)
+ assert 'custom_workflow' in eschema.subjrels, _missing_wf_rel(etype)
+ assert 'wf_info_for' in eschema.objrels, _missing_wf_rel(etype)
rset = self.rqlexec(
'SET X workflow_of ET WHERE X eid %(x)s, ET name %(et)s',
{'x': wf.eid, 'et': etype}, ask_confirm=False)
@@ -1380,6 +1379,40 @@
"""add a new entity of the given type"""
return self.cmd_create_entity(etype, *args, **kwargs).eid
+ @contextmanager
+ def cmd_dropped_constraints(self, etype, attrname, cstrtype,
+ droprequired=False):
+ """context manager to drop constraints temporarily on fs_schema
+
+ `cstrtype` should be a constraint class (or a tuple of classes)
+ and will be passed to isinstance directly
+
+ For instance::
+
+ >>> with dropped_constraints('MyType', 'myattr',
+ ... UniqueConstraint, droprequired=True):
+ ... add_attribute('MyType', 'myattr')
+ ... # + instructions to fill MyType.myattr column
+ ...
+ >>>
+
+ """
+ rdef = self.fs_schema.eschema(etype).rdef(attrname)
+ original_constraints = rdef.constraints
+ # remove constraints
+ rdef.constraints = [cstr for cstr in original_constraints
+ if not (cstrtype and isinstance(cstr, cstrtype))]
+ if droprequired:
+ original_cardinality = rdef.cardinality
+ rdef.cardinality = '?' + rdef.cardinality[1]
+ yield
+ # restore original constraints
+ rdef.constraints = original_constraints
+ if droprequired:
+ rdef.cardinality = original_cardinality
+ # update repository schema
+ self.cmd_sync_schema_props_perms(rdef, syncperms=False)
+
def sqlexec(self, sql, args=None, ask_confirm=True):
"""execute the given sql if confirmed
@@ -1424,7 +1457,7 @@
return res
def rqliter(self, rql, kwargs=None, ask_confirm=True):
- return ForRqlIterator(self, rql, None, ask_confirm)
+ return ForRqlIterator(self, rql, kwargs, ask_confirm)
# broken db commands ######################################################
--- a/server/msplanner.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/msplanner.py Fri Mar 11 09:46:45 2011 +0100
@@ -84,9 +84,8 @@
1. return the result of Any X WHERE X owned_by Y from system source, that's
enough (optimization of the sql querier will avoid join on CWUser, so we
will directly get local eids)
-
+"""
-"""
__docformat__ = "restructuredtext en"
from itertools import imap, ifilterfalse
@@ -94,6 +93,7 @@
from logilab.common.compat import any
from logilab.common.decorators import cached
+from rql import BadRQLQuery
from rql.stmts import Union, Select
from rql.nodes import (VariableRef, Comparison, Relation, Constant, Variable,
Not, Exists, SortTerm, Function)
@@ -434,10 +434,13 @@
# add source for relations
rschema = self._schema.rschema
termssources = {}
+ sourcerels = []
for rel in self.rqlst.iget_nodes(Relation):
# process non final relations only
# note: don't try to get schema for 'is' relation (not available
# during bootstrap)
+ if rel.r_type == 'cw_source':
+ sourcerels.append(rel)
if not (rel.is_types_restriction() or rschema(rel.r_type).final):
# nothing to do if relation is not supported by multiple sources
# or if some source has it listed in its cross_relations
@@ -469,6 +472,75 @@
self._handle_cross_relation(rel, relsources, termssources)
self._linkedterms.setdefault(lhsv, set()).add((rhsv, rel))
self._linkedterms.setdefault(rhsv, set()).add((lhsv, rel))
+ # extract information from cw_source relation
+ for srel in sourcerels:
+ vref = srel.children[1].children[0]
+ sourceeids, sourcenames = [], []
+ if isinstance(vref, Constant):
+ # simplified variable
+ sourceeids = None, (vref.eval(self.plan.args),)
+ var = vref
+ else:
+ var = vref.variable
+ for rel in var.stinfo['relations'] - var.stinfo['rhsrelations']:
+ if rel.r_type in ('eid', 'name'):
+ if rel.r_type == 'eid':
+ slist = sourceeids
+ else:
+ slist = sourcenames
+ sources = [cst.eval(self.plan.args)
+ for cst in rel.children[1].get_nodes(Constant)]
+ if sources:
+ if slist:
+ # don't attempt to do anything
+ sourcenames = sourceeids = None
+ break
+ slist[:] = (rel, sources)
+ if sourceeids:
+ rel, values = sourceeids
+ sourcesdict = self._repo.sources_by_eid
+ elif sourcenames:
+ rel, values = sourcenames
+ sourcesdict = self._repo.sources_by_uri
+ else:
+ sourcesdict = None
+ if sourcesdict is not None:
+ lhs = srel.children[0]
+ try:
+ sources = [sourcesdict[key] for key in values]
+ except KeyError:
+ raise BadRQLQuery('source conflict for term %s' % lhs.as_string())
+ if isinstance(lhs, Constant):
+ source = self._session.source_from_eid(lhs.eval(self.plan.args))
+ if not source in sources:
+ raise BadRQLQuery('source conflict for term %s' % lhs.as_string())
+ else:
+ lhs = getattr(lhs, 'variable', lhs)
+ invariant = getattr(lhs, '_q_invariant', False)
+ # XXX NOT NOT
+ neged = srel.neged(traverse_scope=True) or (rel and rel.neged(strict=True))
+ if neged:
+ for source in sources:
+ if invariant and source is self.system_source:
+ continue
+ self._remove_source_term(source, lhs)
+ usesys = self.system_source not in sources
+ else:
+ for source, terms in sourcesterms.items():
+ if lhs in terms and not source in sources:
+ if invariant and source is self.system_source:
+ continue
+ self._remove_source_term(source, lhs)
+ usesys = self.system_source in sources
+ if rel is None or (len(var.stinfo['relations']) == 2 and
+ not var.stinfo['selected']):
+ self._remove_source_term(self.system_source, var)
+ if not (len(sources) > 1 or usesys or invariant):
+ if rel is None:
+ srel.parent.remove(srel)
+ else:
+ self.rqlst.undefine_variable(var)
+ self._remove_source_term(self.system_source, srel)
return termssources
def _handle_cross_relation(self, rel, relsources, termssources):
@@ -673,6 +745,15 @@
and self._need_ext_source_access(term, rel):
self.needsplit = True
return
+ else:
+ # remove sources only accessing to constant nodes
+ for source, terms in self._sourcesterms.items():
+ if source is self.system_source:
+ continue
+ if not any(x for x in terms if not isinstance(x, Constant)):
+ del self._sourcesterms[source]
+ if len(self._sourcesterms) < 2:
+ self.needsplit = False
@cached
def _need_ext_source_access(self, var, rel):
@@ -713,9 +794,16 @@
assert isinstance(term, (rqlb.BaseNode, Variable)), repr(term)
continue # may occur with subquery column alias
if not sourcesterms[source][term]:
- del sourcesterms[source][term]
- if not sourcesterms[source]:
- del sourcesterms[source]
+ self._remove_source_term(source, term)
+
+ def _remove_source_term(self, source, term):
+ try:
+ poped = self._sourcesterms[source].pop(term, None)
+ except KeyError:
+ pass
+ else:
+ if not self._sourcesterms[source]:
+ del self._sourcesterms[source]
def crossed_relation(self, source, relation):
return relation in self._crossrelations.get(source, ())
@@ -736,7 +824,7 @@
while sourceterms:
# take a term randomly, and all terms supporting the
# same solutions
- term, solindices = self._choose_term(sourceterms)
+ term, solindices = self._choose_term(source, sourceterms)
if source.uri == 'system':
# ensure all variables are available for the latest step
# (missing one will be available from temporary tables
@@ -766,8 +854,24 @@
# set of terms which should be additionaly selected when
# possible
needsel = set()
- if not self._sourcesterms:
+ if not self._sourcesterms and scope is select:
terms += scope.defined_vars.values() + scope.aliases.values()
+ if isinstance(term, Relation) and len(sources) > 1:
+ variants = set()
+ partterms = [term]
+ for vref in term.get_nodes(VariableRef):
+ if not vref.variable._q_invariant:
+ variants.add(vref.name)
+ if len(variants) == 2:
+ # we need an extra-step to fetch relations from each source
+ # before a join with prefetched inputs
+ # (see test_crossed_relation_noeid_needattr in
+ # unittest_msplanner / unittest_multisources)
+ lhs, rhs = term.get_variable_parts()
+ steps.append( (sources, [term, getattr(lhs, 'variable', lhs),
+ getattr(rhs, 'variable', rhs)],
+ solindices, scope, variants, False) )
+ sources = [self.system_source]
final = True
else:
# suppose this is a final step until the contrary is proven
@@ -785,7 +889,7 @@
else:
needsel.add(var.name)
final = False
- # check where all relations are supported by the sources
+ # check all relations are supported by the sources
for rel in scope.iget_nodes(Relation):
if rel.is_types_restriction():
continue
@@ -799,7 +903,7 @@
break
else:
if not scope is select:
- self._exists_relation(rel, terms, needsel)
+ self._exists_relation(rel, terms, needsel, source)
# if relation is supported by all sources and some of
# its lhs/rhs variable isn't in "terms", and the
# other end *is* in "terms", mark it have to be
@@ -843,9 +947,14 @@
self._cleanup_sourcesterms(sources, solindices)
steps.append((sources, terms, solindices, scope, needsel, final)
)
+ if not steps[-1][-1]:
+ # add a final step
+ terms = select.defined_vars.values() + select.aliases.values()
+ steps.append( ([self.system_source], terms, set(self._solindices),
+ select, set(), True) )
return steps
- def _exists_relation(self, rel, terms, needsel):
+ def _exists_relation(self, rel, terms, needsel, source):
rschema = self._schema.rschema(rel.r_type)
lhs, rhs = rel.get_variable_parts()
try:
@@ -858,13 +967,24 @@
# variable is refed by an outer scope and should be substituted
# using an 'identity' relation (else we'll get a conflict of
# temporary tables)
- if rhsvar in terms and not lhsvar in terms and ms_scope(lhsvar) is lhsvar.stmt:
- self._identity_substitute(rel, lhsvar, terms, needsel)
- elif lhsvar in terms and not rhsvar in terms and ms_scope(rhsvar) is rhsvar.stmt:
- self._identity_substitute(rel, rhsvar, terms, needsel)
+ relscope = ms_scope(rel)
+ lhsscope = ms_scope(lhsvar)
+ rhsscope = ms_scope(rhsvar)
+ if rhsvar in terms and not lhsvar in terms and lhsscope is lhsvar.stmt:
+ self._identity_substitute(rel, lhsvar, terms, needsel, relscope)
+ elif lhsvar in terms and not rhsvar in terms and rhsscope is rhsvar.stmt:
+ self._identity_substitute(rel, rhsvar, terms, needsel, relscope)
+ elif self.crossed_relation(source, rel):
+ if lhsscope is not relscope:
+ self._identity_substitute(rel, lhsvar, terms, needsel,
+ relscope, lhsscope)
+ if rhsscope is not relscope:
+ self._identity_substitute(rel, rhsvar, terms, needsel,
+ relscope, rhsscope)
- def _identity_substitute(self, relation, var, terms, needsel):
- newvar = self._insert_identity_variable(ms_scope(relation), var)
+ def _identity_substitute(self, relation, var, terms, needsel, exist,
+ idrelscope=None):
+ newvar = self._insert_identity_variable(exist, var, idrelscope)
# ensure relation is using '=' operator, else we rely on a
# sqlgenerator side effect (it won't insert an inequality operator
# in this case)
@@ -872,12 +992,28 @@
terms.append(newvar)
needsel.add(newvar.name)
- def _choose_term(self, sourceterms):
+ def _choose_term(self, source, sourceterms):
"""pick one term among terms supported by a source, which will be used
as a base to generate an execution step
"""
secondchoice = None
if len(self._sourcesterms) > 1:
+ # first, return non invariant variable of crossed relation, then the
+ # crossed relation itself
+ for term in sourceterms:
+ if (isinstance(term, Relation)
+ and self.crossed_relation(source, term)
+ and not ms_scope(term) is self.rqlst):
+ for vref in term.get_variable_parts():
+ try:
+ var = vref.variable
+ except AttributeError:
+ # Constant
+ continue
+ if ((len(var.stinfo['relations']) > 1 or var.stinfo['selected'])
+ and var in sourceterms):
+ return var, sourceterms.pop(var)
+ return term, sourceterms.pop(term)
# priority to variable from subscopes
for term in sourceterms:
if not ms_scope(term) is self.rqlst:
@@ -962,7 +1098,7 @@
if isinstance(term, Relation) and term in cross_rels:
cross_terms = cross_rels.pop(term)
base_accept_term = accept_term
- accept_term = lambda x: (accept_term(x) or x in cross_terms)
+ accept_term = lambda x: (base_accept_term(x) or x in cross_terms)
for refed in cross_terms:
if not refed in candidates:
terms.append(refed)
@@ -1015,7 +1151,7 @@
if not sourceterms:
del self._sourcesterms[source]
- def merge_input_maps(self, allsolindices):
+ def merge_input_maps(self, allsolindices, complete=True):
"""inputmaps is a dictionary with tuple of solution indices as key with
an associated input map as value. This function compute for each
solution its necessary input map and return them grouped
@@ -1029,14 +1165,17 @@
"""
if not self._inputmaps:
return [(allsolindices, None)]
+ _allsolindices = allsolindices.copy()
mapbysol = {}
# compute a single map for each solution
for solindices, basemap in self._inputmaps.iteritems():
for solindex in solindices:
+ if not (complete or solindex in allsolindices):
+ continue
solmap = mapbysol.setdefault(solindex, {})
solmap.update(basemap)
try:
- allsolindices.remove(solindex)
+ _allsolindices.remove(solindex)
except KeyError:
continue # already removed
# group results by identical input map
@@ -1048,14 +1187,14 @@
break
else:
result.append( ([solindex], solmap) )
- if allsolindices:
- result.append( (list(allsolindices), None) )
+ if _allsolindices:
+ result.append( (list(_allsolindices), None) )
return result
def build_final_part(self, select, solindices, inputmap, sources,
insertedvars):
solutions = [self._solutions[i] for i in solindices]
- if self._conflicts:
+ if self._conflicts and inputmap:
for varname, mappedto in self._conflicts:
var = select.defined_vars[varname]
newvar = select.make_variable()
@@ -1080,7 +1219,7 @@
inputmapkey = tuple(sorted(solindices))
inputmap = self._inputmaps.setdefault(inputmapkey, {})
for varname, mapping in step.outputmap.iteritems():
- if varname in inputmap and \
+ if varname in inputmap and not '.' in varname and \
not (mapping == inputmap[varname] or
self._schema.eschema(solutions[0][varname]).final):
self._conflicts.append((varname, inputmap[varname]))
@@ -1212,13 +1351,15 @@
ppi.temptable = atemptable
vfilter = TermsFiltererVisitor(self.schema, ppi)
steps = []
+ multifinal = len([x for x in stepdefs if x[-1]]) >= 2
for sources, terms, solindices, scope, needsel, final in stepdefs:
# extract an executable query using only the specified terms
if sources[0].uri == 'system':
# in this case we have to merge input maps before call to
# filter so already processed restriction are correctly
# removed
- solsinputmaps = ppi.merge_input_maps(solindices)
+ solsinputmaps = ppi.merge_input_maps(
+ solindices, complete=not (final and multifinal))
for solindices, inputmap in solsinputmaps:
minrqlst, insertedvars = vfilter.filter(
sources, terms, scope, set(solindices), needsel, final)
@@ -1235,7 +1376,8 @@
minrqlst, insertedvars = vfilter.filter(
sources, terms, scope, solindices, needsel, final)
if final:
- solsinputmaps = ppi.merge_input_maps(solindices)
+ solsinputmaps = ppi.merge_input_maps(
+ solindices, complete=not (final and multifinal))
if len(solsinputmaps) > 1:
refrqlst = minrqlst
for solindices, inputmap in solsinputmaps:
@@ -1455,7 +1597,7 @@
def visit_relation(self, node, newroot, terms):
if not node.is_types_restriction():
- if node in self.skip and self.solindices.issubset(self.skip[node]):
+ if not node in terms and node in self.skip and self.solindices.issubset(self.skip[node]):
if not self.schema.rschema(node.r_type).final:
# can't really skip the relation if one variable is selected
# and only referenced by this relation
--- a/server/pool.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/pool.py Fri Mar 11 09:46:45 2011 +0100
@@ -34,7 +34,7 @@
# dictionnary of (source, connection), indexed by sources'uri
self.source_cnxs = {}
for source in sources:
- self.source_cnxs[source.uri] = (source, source.get_connection())
+ self.add_source(source)
if not 'system' in self.source_cnxs:
self.source_cnxs['system'] = self.source_cnxs[sources[0].uri]
self._cursors = {}
@@ -50,6 +50,15 @@
self._cursors[uri] = cursor
return cursor
+ def add_source(self, source):
+ assert not source.uri in self.source_cnxs
+ self.source_cnxs[source.uri] = (source, source.get_connection())
+
+ def remove_source(self, source):
+ source, cnx = self.source_cnxs.pop(source.uri)
+ cnx.close()
+ self._cursors.pop(source.uri, None)
+
def commit(self):
"""commit the current transaction for this user"""
# FIXME: what happends if a commit fail
@@ -144,11 +153,9 @@
self._cursors.pop(source.uri, None)
-from cubicweb.server.hook import (Operation, LateOperation, SingleOperation,
- SingleLastOperation)
+from cubicweb.server.hook import Operation, LateOperation, SingleLastOperation
from logilab.common.deprecation import class_moved, class_renamed
Operation = class_moved(Operation)
PreCommitOperation = class_renamed('PreCommitOperation', Operation)
LateOperation = class_moved(LateOperation)
-SingleOperation = class_moved(SingleOperation)
SingleLastOperation = class_moved(SingleLastOperation)
--- a/server/querier.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/querier.py Fri Mar 11 09:46:45 2011 +0100
@@ -38,7 +38,7 @@
from cubicweb.server.utils import cleanup_solutions
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
-from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction
+from cubicweb.server.ssplanner import READ_ONLY_RTYPES, add_types_restriction, EditedEntity
from cubicweb.server.session import security_enabled
def empty_rset(rql, args, rqlst=None):
@@ -450,7 +450,7 @@
# save originaly selected variable, we may modify this
# dictionary for substitution (query parameters)
self.selected = rqlst.selection
- # list of new or updated entities definition (utils.Entity)
+ # list of rows of entities definition (ssplanner.EditedEntity)
self.e_defs = [[]]
# list of new relation definition (3-uple (from_eid, r_type, to_eid)
self.r_defs = set()
@@ -461,7 +461,6 @@
def add_entity_def(self, edef):
"""add an entity definition to build"""
- edef.querier_pending_relations = {}
self.e_defs[-1].append(edef)
def add_relation_def(self, rdef):
@@ -493,8 +492,9 @@
self.e_defs[i][colidx] = edefs[0]
samplerow = self.e_defs[i]
for edef_ in edefs[1:]:
- row = samplerow[:]
- row[colidx] = edef_
+ row = [ed.clone() for i, ed in enumerate(samplerow)
+ if i != colidx]
+ row.insert(colidx, edef_)
self.e_defs.append(row)
# now, see if this entity def is referenced as subject in some relation
# definition
@@ -560,15 +560,16 @@
if isinstance(subj, basestring):
subj = typed_eid(subj)
elif not isinstance(subj, (int, long)):
- subj = subj.eid
+ subj = subj.entity.eid
if isinstance(obj, basestring):
obj = typed_eid(obj)
elif not isinstance(obj, (int, long)):
- obj = obj.eid
+ obj = obj.entity.eid
if repo.schema.rschema(rtype).inlined:
entity = session.entity_from_eid(subj)
- entity[rtype] = obj
- repo.glob_update_entity(session, entity, set((rtype,)))
+ edited = EditedEntity(entity)
+ edited.edited_attribute(rtype, obj)
+ repo.glob_update_entity(session, edited)
else:
repo.glob_add_relation(session, subj, rtype, obj)
@@ -585,12 +586,12 @@
def set_schema(self, schema):
self.schema = schema
repo = self._repo
- # rql st and solution cache. Don't bother using a Cache instance: we
- # should have a limited number of queries in there, since there are no
- # entries in this cache for user queries (which have no args)
- self._rql_cache = {}
- # rql cache key cache
- self._rql_ck_cache = Cache(repo.config['rql-cache-size'])
+ # rql st and solution cache.
+ self._rql_cache = Cache(repo.config['rql-cache-size'])
+ # rql cache key cache. Don't bother using a Cache instance: we should
+ # have a limited number of queries in there, since there are no entries
+ # in this cache for user queries (which have no args)
+ self._rql_ck_cache = {}
# some cache usage stats
self.cache_hit, self.cache_miss = 0, 0
# rql parsing / analysing helper
@@ -601,9 +602,7 @@
self._parse = rqlhelper.parse
self._annotate = rqlhelper.annotate
# rql planner
- # note: don't use repo.sources, may not be built yet, and also "admin"
- # isn't an actual source
- if len([uri for uri in repo.config.sources() if uri != 'admin']) < 2:
+ if len(repo.sources) < 2:
from cubicweb.server.ssplanner import SSPlanner
self._planner = SSPlanner(schema, rqlhelper)
else:
@@ -612,6 +611,14 @@
# sql generation annotator
self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
+ def set_planner(self):
+ if len(self._repo.sources) < 2:
+ from cubicweb.server.ssplanner import SSPlanner
+ self._planner = SSPlanner(self.schema, self._repo.vreg.rqlhelper)
+ else:
+ from cubicweb.server.msplanner import MSPlanner
+ self._planner = MSPlanner(self.schema, self._repo.vreg.rqlhelper)
+
def parse(self, rql, annotate=False):
"""return a rql syntax tree for the given rql"""
try:
@@ -649,11 +656,15 @@
print '*'*80
print 'querier input', rql, args
# parse the query and binds variables
+ cachekey = rql
try:
- cachekey = rql
if args:
+ # search for named args in query which are eids (hence
+ # influencing query's solutions)
eidkeys = self._rql_ck_cache[rql]
if eidkeys:
+ # if there are some, we need a better cache key, eg (rql +
+ # entity type of each eid)
try:
cachekey = self._repo.querier_cache_key(session, rql,
args, eidkeys)
@@ -667,15 +678,20 @@
self.cache_miss += 1
rqlst = self.parse(rql)
try:
+ # compute solutions for rqlst and return named args in query
+ # which are eids. Notice that if you may not need `eidkeys`, we
+ # have to compute solutions anyway (kept as annotation on the
+ # tree)
eidkeys = self.solutions(session, rqlst, args)
except UnknownEid:
# we want queries such as "Any X WHERE X eid 9999" return an
# empty result instead of raising UnknownEid
return empty_rset(rql, args, rqlst)
- self._rql_ck_cache[rql] = eidkeys
- if eidkeys:
- cachekey = self._repo.querier_cache_key(session, rql, args,
- eidkeys)
+ if args and not rql in self._rql_ck_cache:
+ self._rql_ck_cache[rql] = eidkeys
+ if eidkeys:
+ cachekey = self._repo.querier_cache_key(session, rql, args,
+ eidkeys)
self._rql_cache[cachekey] = rqlst
orig_rqlst = rqlst
if rqlst.TYPE != 'select':
--- a/server/repository.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/repository.py Fri Mar 11 09:46:45 2011 +0100
@@ -34,17 +34,19 @@
import sys
import threading
import Queue
+from itertools import chain
from os.path import join
from datetime import datetime
from time import time, localtime, strftime
-from logilab.common.decorators import cached
+from logilab.common.decorators import cached, clear_cache
from logilab.common.compat import any
from logilab.common import flatten
from yams import BadSchemaDefinition
from yams.schema import role_name
from rql import RQLSyntaxError
+from rql.utils import rqlvar_maker
from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, QueryError,
UnknownEid, AuthenticationError, ExecutionError,
@@ -55,7 +57,29 @@
from cubicweb.server import utils, hook, pool, querier, sources
from cubicweb.server.session import Session, InternalSession, InternalManager, \
security_enabled
-_ = unicode
+from cubicweb.server.ssplanner import EditedEntity
+
+def prefill_entity_caches(entity, relations):
+ session = entity._cw
+ # prefill entity relation caches
+ for rschema in entity.e_schema.subject_relations():
+ rtype = str(rschema)
+ if rtype in schema.VIRTUAL_RTYPES:
+ continue
+ if rschema.final:
+ entity.cw_attr_cache.setdefault(rtype, None)
+ else:
+ entity.cw_set_relation_cache(rtype, 'subject',
+ session.empty_rset())
+ for rschema in entity.e_schema.object_relations():
+ rtype = str(rschema)
+ if rtype in schema.VIRTUAL_RTYPES:
+ continue
+ entity.cw_set_relation_cache(rtype, 'object', session.empty_rset())
+ # set inlined relation cache before call to after_add_entity
+ for attr, value in relations:
+ session.update_rel_cache_add(entity.eid, attr, value)
+ del_existing_rel_if_needed(session, entity.eid, attr, value)
def del_existing_rel_if_needed(session, eidfrom, rtype, eidto):
"""delete existing relation when adding a new one if card is 1 or ?
@@ -120,27 +144,15 @@
# initial schema, should be build or replaced latter
self.schema = schema.CubicWebSchema(config.appid)
self.vreg.schema = self.schema # until actual schema is loaded...
- # querier helper, need to be created after sources initialization
- self.querier = querier.QuerierHelper(self, self.schema)
- # sources
- self.sources = []
- self.sources_by_uri = {}
# shutdown flag
self.shutting_down = False
- # FIXME: store additional sources info in the system database ?
- # FIXME: sources should be ordered (add_entity priority)
- for uri, source_config in config.sources().items():
- if uri == 'admin':
- # not an actual source
- continue
- source = self.get_source(uri, source_config)
- self.sources_by_uri[uri] = source
- if config.source_enabled(uri):
- self.sources.append(source)
- self.system_source = self.sources_by_uri['system']
- # ensure system source is the first one
- self.sources.remove(self.system_source)
- self.sources.insert(0, self.system_source)
+ # sources (additional sources info in the system database)
+ self.system_source = self.get_source('native', 'system',
+ config.sources()['system'])
+ self.sources = [self.system_source]
+ self.sources_by_uri = {'system': self.system_source}
+ # querier helper, need to be created after sources initialization
+ self.querier = querier.QuerierHelper(self, self.schema)
# cache eid -> type / source
self._type_source_cache = {}
# cache (extid, source uri) -> eid
@@ -192,6 +204,7 @@
config.bootstrap_cubes()
self.set_schema(config.load_schema())
if not config.creating:
+ self.init_sources_from_database()
if 'CWProperty' in self.schema:
self.vreg.init_properties(self.properties())
# call source's init method to complete their initialisation if
@@ -208,7 +221,7 @@
# close initialization pool and reopen fresh ones for proper
# initialization now that we know cubes
self._get_pool().close(True)
- # list of available pools (we can't iterated on Queue instance)
+ # list of available pools (we can't iterate on Queue instance)
self.pools = []
for i in xrange(config['connections-pool-size']):
self.pools.append(pool.ConnectionsPool(self.sources))
@@ -219,9 +232,60 @@
# internals ###############################################################
- def get_source(self, uri, source_config):
+ def init_sources_from_database(self):
+ self.sources_by_eid = {}
+ if self.config.quick_start \
+ or not 'CWSource' in self.schema: # # 3.10 migration
+ return
+ session = self.internal_session()
+ try:
+ # FIXME: sources should be ordered (add_entity priority)
+ for sourceent in session.execute(
+ 'Any S, SN, SA, SC WHERE S is CWSource, '
+ 'S name SN, S type SA, S config SC').entities():
+ if sourceent.name == 'system':
+ self.system_source.eid = sourceent.eid
+ self.sources_by_eid[sourceent.eid] = self.system_source
+ continue
+ self.add_source(sourceent, add_to_pools=False)
+ finally:
+ session.close()
+
+ def _clear_planning_caches(self):
+ for cache in ('source_defs', 'is_multi_sources_relation',
+ 'can_cross_relation', 'rel_type_sources'):
+ clear_cache(self, cache)
+
+ def add_source(self, sourceent, add_to_pools=True):
+ source = self.get_source(sourceent.type, sourceent.name,
+ sourceent.host_config)
+ source.eid = sourceent.eid
+ self.sources_by_eid[sourceent.eid] = source
+ self.sources_by_uri[sourceent.name] = source
+ if self.config.source_enabled(source):
+ self.sources.append(source)
+ self.querier.set_planner()
+ if add_to_pools:
+ for pool in self.pools:
+ pool.add_source(source)
+ self._clear_planning_caches()
+
+ def remove_source(self, uri):
+ source = self.sources_by_uri.pop(uri)
+ del self.sources_by_eid[source.eid]
+ if self.config.source_enabled(source):
+ self.sources.remove(source)
+ self.querier.set_planner()
+ for pool in self.pools:
+ pool.remove_source(source)
+ self._clear_planning_caches()
+
+ def get_source(self, type, uri, source_config):
+ # set uri and type in source config so it's available through
+ # source_defs()
source_config['uri'] = uri
- return sources.get_source(source_config, self.schema, self)
+ source_config['type'] = type
+ return sources.get_source(type, source_config, self)
def set_schema(self, schema, resetvreg=True, rebuildinfered=True):
if rebuildinfered:
@@ -270,7 +334,10 @@
# call instance level initialisation hooks
self.hm.call_hooks('server_startup', repo=self)
# register a task to cleanup expired session
- self.looping_task(self.config['session-time']/3., self.clean_sessions)
+ self.cleanup_session_time = self.config['cleanup-session-time'] or 60 * 60 * 24
+ assert self.cleanup_session_time > 0
+ cleanup_session_interval = min(60*60, self.cleanup_session_time / 3)
+ self.looping_task(cleanup_session_interval, self.clean_sessions)
assert isinstance(self._looping_tasks, list), 'already started'
for i, (interval, func, args) in enumerate(self._looping_tasks):
self._looping_tasks[i] = task = utils.LoopTask(interval, func, args)
@@ -400,7 +467,7 @@
rset = session.execute(rql, {'x': eid})
assert len(rset) == 1, rset
cwuser = rset.get_entity(0, 0)
- # pylint: disable-msg=W0104
+ # pylint: disable=W0104
# prefetch / cache cwuser's groups and properties. This is especially
# useful for internal sessions to avoid security insertions
cwuser.groups
@@ -476,7 +543,13 @@
return self.config[option]
pool = self._get_pool()
try:
- return pool.connection(sourceuri).get_option_value(option, extid)
+ cnx = pool.connection(sourceuri)
+ # needed to check connection is valid and usable by the current
+ # thread
+ newcnx = self.sources_by_uri[sourceuri].check_connection(cnx)
+ if newcnx is not None:
+ cnx = newcnx
+ return cnx.get_option_value(option, extid)
finally:
self._free_pool(pool)
@@ -520,14 +593,10 @@
This is a public method, not requiring a session id.
"""
- sources = self.config.sources().copy()
- # remove manager information
- sources.pop('admin', None)
+ sources = {}
# remove sensitive information
- for uri, sourcedef in sources.iteritems():
- sourcedef = sourcedef.copy()
- self.sources_by_uri[uri].remove_sensitive_information(sourcedef)
- sources[uri] = sourcedef
+ for uri, source in self.sources_by_uri.iteritems():
+ sources[uri] = source.public_config
return sources
def properties(self):
@@ -568,8 +637,7 @@
password = password.encode('UTF8')
kwargs['login'] = login
kwargs['upassword'] = password
- user.update(kwargs)
- self.glob_add_entity(session, user)
+ self.glob_add_entity(session, EditedEntity(user, **kwargs))
session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"',
{'x': user.eid})
if email or '@' in login:
@@ -586,6 +654,39 @@
session.close()
return True
+ def find_users(self, fetch_attrs, **query_attrs):
+ """yield user attributes for cwusers matching the given query_attrs
+ (the result set cannot survive this method call)
+
+ This can be used by low-privileges account (anonymous comes to
+ mind).
+
+ `fetch_attrs`: tuple of attributes to be fetched
+ `query_attrs`: dict of attr/values to restrict the query
+ """
+ assert query_attrs
+ if not hasattr(self, '_cwuser_attrs'):
+ cwuser = self.schema['CWUser']
+ self._cwuser_attrs = set(str(rschema)
+ for rschema, _eschema in cwuser.attribute_definitions()
+ if not rschema.meta)
+ cwuserattrs = self._cwuser_attrs
+ for k in chain(fetch_attrs, query_attrs.iterkeys()):
+ if k not in cwuserattrs:
+ raise Exception('bad input for find_user')
+ session = self.internal_session()
+ try:
+ varmaker = rqlvar_maker()
+ vars = [(attr, varmaker.next()) for attr in fetch_attrs]
+ rql = 'Any %s WHERE X is CWUser, ' % ','.join(var[1] for var in vars)
+ rql += ','.join('X %s %s' % (var[0], var[1]) for var in vars) + ','
+ rset = session.execute(rql + ','.join('X %s %%(%s)s' % (attr, attr)
+ for attr in query_attrs.iterkeys()),
+ query_attrs)
+ return rset.rows
+ finally:
+ session.close()
+
def connect(self, login, **kwargs):
"""open a connection for a given user
@@ -660,24 +761,32 @@
session.reset_pool()
def check_session(self, sessionid):
- """raise `BadConnectionId` if the connection is no more valid"""
- self._get_session(sessionid, setpool=False)
+ """raise `BadConnectionId` if the connection is no more valid, else
+ return its latest activity timestamp.
+ """
+ return self._get_session(sessionid, setpool=False).timestamp
+
+ def get_shared_data(self, sessionid, key, default=None, pop=False, txdata=False):
+ """return value associated to key in the session's data dictionary or
+ session's transaction's data if `txdata` is true.
- def get_shared_data(self, sessionid, key, default=None, pop=False):
- """return the session's data dictionary"""
+ If pop is True, value will be removed from the dictionnary.
+
+ If key isn't defined in the dictionnary, value specified by the
+ `default` argument will be returned.
+ """
session = self._get_session(sessionid, setpool=False)
- return session.get_shared_data(key, default, pop)
+ return session.get_shared_data(key, default, pop, txdata)
- def set_shared_data(self, sessionid, key, value, querydata=False):
+ def set_shared_data(self, sessionid, key, value, txdata=False):
"""set value associated to `key` in shared data
- if `querydata` is true, the value will be added to the repository
- session's query data which are cleared on commit/rollback of the current
- transaction, and won't be available through the connexion, only on the
- repository side.
+ if `txdata` is true, the value will be added to the repository session's
+ transaction's data which are cleared on commit/rollback of the current
+ transaction.
"""
session = self._get_session(sessionid, setpool=False)
- session.set_shared_data(key, value, querydata)
+ session.set_shared_data(key, value, txdata)
def commit(self, sessionid, txid=None):
"""commit transaction for the session with the given id"""
@@ -809,7 +918,7 @@
"""close sessions not used since an amount of time specified in the
configuration
"""
- mintime = time() - self.config['session-time']
+ mintime = time() - self.cleanup_session_time
self.debug('cleaning session unused since %s',
strftime('%T', localtime(mintime)))
nbclosed = 0
@@ -964,7 +1073,6 @@
self._extid_cache[cachekey] = eid
self._type_source_cache[eid] = (etype, source.uri, extid)
entity = source.before_entity_insertion(session, extid, etype, eid)
- entity.edited_attributes = set(entity.cw_attr_cache)
if source.should_call_hooks:
self.hm.call_hooks('before_add_entity', session, entity=entity)
# XXX call add_info with complete=False ?
@@ -972,10 +1080,6 @@
source.after_entity_insertion(session, extid, entity)
if source.should_call_hooks:
self.hm.call_hooks('after_add_entity', session, entity=entity)
- else:
- # minimal meta-data
- session.execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
- {'x': entity.eid, 'name': entity.__regid__})
session.commit(reset_pool)
return eid
except:
@@ -987,22 +1091,30 @@
and index the entity with the full text index
"""
# begin by inserting eid/type/source/extid into the entities table
- hook.set_operation(session, 'neweids', entity.eid,
- hook.CleanupNewEidsCacheOp)
+ 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):
+ def delete_info(self, session, entity, sourceuri, extid, scleanup=False):
"""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.set_operation(session, 'pendingeids', entity.eid,
- hook.CleanupDeletedEidsCacheOp)
- self._delete_info(session, entity, sourceuri, extid)
+ hook.CleanupDeletedEidsCacheOp.get_instance(session).add_data(entity.eid)
+ self._delete_info(session, entity, sourceuri, extid, scleanup)
- def _delete_info(self, session, entity, sourceuri, extid):
- # attributes=None, relations=None):
+ def delete_info_multi(self, session, entities, sourceuri, extids, scleanup=False):
+ """same as delete_info but accepts a list of entities and
+ extids with the same etype and belonging to the same source
+ """
+ # mark eid as being deleted in session info and setup cache update
+ # operation
+ op = hook.CleanupDeletedEidsCacheOp.get_instance(session)
+ for entity in entities:
+ op.add_data(entity.eid)
+ self._delete_info_multi(session, entities, sourceuri, extids, scleanup)
+
+ def _delete_info(self, session, entity, sourceuri, extid, scleanup=False):
"""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
@@ -1023,9 +1135,49 @@
rql = 'DELETE X %s Y WHERE X eid %%(x)s' % rtype
else:
rql = 'DELETE Y %s X WHERE X eid %%(x)s' % rtype
- session.execute(rql, {'x': eid}, build_descr=False)
+ if scleanup:
+ # source cleaning: only delete relations stored locally
+ rql += ', NOT (Y cw_source S, S name %(source)s)'
+ try:
+ session.execute(rql, {'x': eid, 'source': sourceuri},
+ build_descr=False)
+ except:
+ self.exception('error while cascading delete for entity %s '
+ 'from %s. RQL: %s', entity, sourceuri, rql)
self.system_source.delete_info(session, entity, sourceuri, extid)
+ def _delete_info_multi(self, session, entities, sourceuri, extids, scleanup=False):
+ """same as _delete_info but accepts a list of entities with
+ the same etype and belinging to the same source.
+ """
+ pendingrtypes = session.transaction_data.get('pendingrtypes', ())
+ # delete remaining relations: if user can delete the entity, he can
+ # delete all its relations without security checking
+ assert entities and len(entities) == len(extids)
+ with security_enabled(session, read=False, write=False):
+ eids = [_e.eid for _e in entities]
+ in_eids = ','.join((str(eid) for eid in eids))
+ for rschema, _, role in entities[0].e_schema.relation_definitions():
+ rtype = rschema.type
+ if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
+ continue
+ if role == 'subject':
+ # don't skip inlined relation so they are regularly
+ # deleted and so hooks are correctly called
+ rql = 'DELETE X %s Y WHERE X eid IN (%s)' % (rtype, in_eids)
+ else:
+ rql = 'DELETE Y %s X WHERE X eid IN (%s)' % (rtype, in_eids)
+ if scleanup:
+ # source cleaning: only delete relations stored locally
+ rql += ', NOT (Y cw_source S, S name %(source)s)'
+ try:
+ session.execute(rql, {'source': sourceuri},
+ build_descr=False)
+ except:
+ self.exception('error while cascading delete for entity %s '
+ 'from %s. RQL: %s', entities, sourceuri, rql)
+ self.system_source.delete_info_multi(session, entities, sourceuri, extids)
+
def locate_relation_source(self, session, subject, rtype, object):
subjsource = self.source_from_eid(subject, session)
objsource = self.source_from_eid(object, session)
@@ -1067,15 +1219,16 @@
self._type_source_cache[entity.eid] = (entity.__regid__, suri, extid)
return extid
- def glob_add_entity(self, session, entity):
+ def glob_add_entity(self, session, edited):
"""add an entity to the repository
the entity eid should originaly be None and a unique eid is assigned to
the entity instance
"""
- # init edited_attributes before calling before_add_entity hooks
+ entity = edited.entity
entity._cw_is_saved = False # entity has an eid but is not yet saved
- entity.edited_attributes = set(entity.cw_attr_cache) # XXX cw_edited_attributes
+ # init edited_attributes before calling before_add_entity hooks
+ entity.cw_edited = edited
eschema = entity.e_schema
source = self.locate_etype_source(entity.__regid__)
# allocate an eid to the entity before calling hooks
@@ -1083,49 +1236,26 @@
# set caches asap
extid = self.init_entity_caches(session, entity, source)
if server.DEBUG & server.DBG_REPO:
- print 'ADD entity', self, entity.__regid__, entity.eid, entity.cw_attr_cache
+ print 'ADD entity', self, entity.__regid__, entity.eid, edited
relations = []
if source.should_call_hooks:
self.hm.call_hooks('before_add_entity', session, entity=entity)
- # XXX use entity.keys here since edited_attributes is not updated for
- # inline relations XXX not true, right? (see edited_attributes
- # affectation above)
- for attr in entity.cw_attr_cache.iterkeys():
+ for attr in edited.iterkeys():
rschema = eschema.subjrels[attr]
if not rschema.final: # inlined relation
- relations.append((attr, entity[attr]))
- entity._cw_set_defaults()
+ relations.append((attr, edited[attr]))
+ edited.set_defaults()
if session.is_hook_category_activated('integrity'):
- entity._cw_check(creation=True)
+ edited.check(creation=True)
+ prefill_entity_caches(entity, relations)
try:
source.add_entity(session, entity)
except UniqueTogetherError, exc:
- etype, rtypes = exc.args
- problems = {}
- for col in rtypes:
- problems[col] = _('violates unique_together constraints (%s)') % (','.join(rtypes))
- raise ValidationError(entity.eid, problems)
+ userhdlr = session.vreg['adapters'].select(
+ 'IUserFriendlyError', session, entity=entity, exc=exc)
+ userhdlr.raise_user_exception()
self.add_info(session, entity, source, extid, complete=False)
- entity._cw_is_saved = True # entity has an eid and is saved
- # prefill entity relation caches
- for rschema in eschema.subject_relations():
- rtype = str(rschema)
- if rtype in schema.VIRTUAL_RTYPES:
- continue
- if rschema.final:
- entity.setdefault(rtype, None)
- else:
- entity.cw_set_relation_cache(rtype, 'subject',
- session.empty_rset())
- for rschema in eschema.object_relations():
- rtype = str(rschema)
- if rtype in schema.VIRTUAL_RTYPES:
- continue
- entity.cw_set_relation_cache(rtype, 'object', session.empty_rset())
- # set inlined relation cache before call to after_add_entity
- for attr, value in relations:
- session.update_rel_cache_add(entity.eid, attr, value)
- del_existing_rel_if_needed(session, entity.eid, attr, value)
+ edited.saved = entity._cw_is_saved = True
# trigger after_add_entity after after_add_relation
if source.should_call_hooks:
self.hm.call_hooks('after_add_entity', session, entity=entity)
@@ -1137,23 +1267,24 @@
eidfrom=entity.eid, rtype=attr, eidto=value)
return entity.eid
- def glob_update_entity(self, session, entity, edited_attributes):
+ def glob_update_entity(self, session, edited):
"""replace an entity in the repository
the type and the eid of an entity must not be changed
"""
+ entity = edited.entity
if server.DEBUG & server.DBG_REPO:
print 'UPDATE entity', entity.__regid__, entity.eid, \
- entity.cw_attr_cache, edited_attributes
+ entity.cw_attr_cache, edited
hm = self.hm
eschema = entity.e_schema
session.set_entity_cache(entity)
- orig_edited_attributes = getattr(entity, 'edited_attributes', None)
- entity.edited_attributes = edited_attributes
+ orig_edited = getattr(entity, 'cw_edited', None)
+ entity.cw_edited = edited
try:
only_inline_rels, need_fti_update = True, False
relations = []
source = self.source_from_eid(entity.eid, session)
- for attr in list(edited_attributes):
+ for attr in list(edited):
if attr == 'eid':
continue
rschema = eschema.subjrels[attr]
@@ -1166,13 +1297,13 @@
previous_value = entity.related(attr) or None
if previous_value is not None:
previous_value = previous_value[0][0] # got a result set
- if previous_value == entity[attr]:
+ if previous_value == entity.cw_attr_cache[attr]:
previous_value = None
elif source.should_call_hooks:
hm.call_hooks('before_delete_relation', session,
eidfrom=entity.eid, rtype=attr,
eidto=previous_value)
- relations.append((attr, entity[attr], previous_value))
+ relations.append((attr, edited[attr], previous_value))
if source.should_call_hooks:
# call hooks for inlined relations
for attr, value, _t in relations:
@@ -1181,16 +1312,16 @@
if not only_inline_rels:
hm.call_hooks('before_update_entity', session, entity=entity)
if session.is_hook_category_activated('integrity'):
- entity._cw_check()
+ edited.check()
try:
source.update_entity(session, entity)
+ edited.saved = True
except UniqueTogetherError, exc:
etype, rtypes = exc.args
problems = {}
for col in rtypes:
- problems[col] = _('violates unique_together constraints (%s)') % (','.join(rtypes))
+ problems[col] = session._('violates unique_together constraints (%s)') % (','.join(rtypes))
raise ValidationError(entity.eid, problems)
-
self.system_source.update_info(session, entity, need_fti_update)
if source.should_call_hooks:
if not only_inline_rels:
@@ -1212,22 +1343,43 @@
hm.call_hooks('after_add_relation', session,
eidfrom=entity.eid, rtype=attr, eidto=value)
finally:
- if orig_edited_attributes is not None:
- entity.edited_attributes = orig_edited_attributes
+ if orig_edited is not None:
+ entity.cw_edited = orig_edited
+
+
+ def glob_delete_entities(self, session, eids):
+ """delete a list of entities and all related entities from the repository"""
+ data_by_etype_source = {} # values are ([list of eids],
+ # [list of extid],
+ # [list of entities])
+ #
+ # WARNING: the way this dictionary is populated is heavily optimized
+ # and does not use setdefault on purpose. Unless a new release
+ # of the Python interpreter advertises large perf improvements
+ # in setdefault, this should not be changed without profiling.
- def glob_delete_entity(self, session, eid):
- """delete an entity and all related entities from the repository"""
- entity = session.entity_from_eid(eid)
- etype, sourceuri, extid = self.type_and_source_from_eid(eid, session)
- if server.DEBUG & server.DBG_REPO:
- print 'DELETE entity', etype, eid
- source = self.sources_by_uri[sourceuri]
- if source.should_call_hooks:
- self.hm.call_hooks('before_delete_entity', session, entity=entity)
- self._delete_info(session, entity, sourceuri, extid)
- source.delete_entity(session, entity)
- if source.should_call_hooks:
- self.hm.call_hooks('after_delete_entity', session, entity=entity)
+ for eid in eids:
+ etype, sourceuri, extid = self.type_and_source_from_eid(eid, session)
+ entity = session.entity_from_eid(eid, etype)
+ _key = (etype, sourceuri)
+ if _key not in data_by_etype_source:
+ data_by_etype_source[_key] = ([eid], [extid], [entity])
+ else:
+ _data = data_by_etype_source[_key]
+ _data[0].append(eid)
+ _data[1].append(extid)
+ _data[2].append(entity)
+ for (etype, sourceuri), (eids, extids, entities) in data_by_etype_source.iteritems():
+ if server.DEBUG & server.DBG_REPO:
+ print 'DELETE entities', etype, eids
+ #print 'DELETE entities', etype, len(eids)
+ source = self.sources_by_uri[sourceuri]
+ if source.should_call_hooks:
+ self.hm.call_hooks('before_delete_entity', session, entities=entities)
+ self._delete_info_multi(session, entities, sourceuri, extids) # xxx
+ source.delete_entities(session, entities)
+ if source.should_call_hooks:
+ self.hm.call_hooks('after_delete_entity', session, entities=entities)
# don't clear cache here this is done in a hook on commit
def glob_add_relation(self, session, subject, rtype, object):
@@ -1268,8 +1420,9 @@
# pyro handling ###########################################################
- def pyro_register(self, host=''):
- """register the repository as a pyro object"""
+ @property
+ @cached
+ def pyro_appid(self):
from logilab.common import pyro_ext as pyro
config = self.config
appid = '%s.%s' % pyro.ns_group_and_id(
@@ -1277,13 +1430,27 @@
config['pyro-ns-group'])
# ensure config['pyro-instance-id'] is a full qualified pyro name
config['pyro-instance-id'] = appid
- daemon = pyro.register_object(self, appid,
- daemonhost=config['pyro-host'],
- nshost=config['pyro-ns-host'])
- self.info('repository registered as a pyro object %s', appid)
+ return appid
+
+ def pyro_register(self, host=''):
+ """register the repository as a pyro object"""
+ from logilab.common import pyro_ext as pyro
+ daemon = pyro.register_object(self, self.pyro_appid,
+ daemonhost=self.config['pyro-host'],
+ nshost=self.config['pyro-ns-host'])
+ self.info('repository registered as a pyro object %s', self.pyro_appid)
self.pyro_registered = True
+ # register a looping task to regularly ensure we're still registered
+ # into the pyro name server
+ self.looping_task(60*10, self._ensure_pyro_ns)
return daemon
+ def _ensure_pyro_ns(self):
+ from logilab.common import pyro_ext as pyro
+ pyro.ns_reregister(self.pyro_appid, nshost=self.config['pyro-ns-host'])
+ self.info('repository re-registered as a pyro object %s',
+ self.pyro_appid)
+
# multi-sources planner helpers ###########################################
@cached
--- a/server/rqlannotation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/rqlannotation.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -135,7 +135,12 @@
# priority should be given to relation which are not in inner queries
# (eg exists)
try:
- stinfo['principal'] = _select_principal(var.scope, joins)
+ stinfo['principal'] = principal = _select_principal(var.scope, joins)
+ if getrschema(principal.r_type).inlined:
+ # the scope of the lhs variable must be equal or outer to the
+ # rhs variable's scope (since it's retrieved from lhs's table)
+ sstinfo = principal.children[0].variable.stinfo
+ sstinfo['scope'] = common_parent(sstinfo['scope'], stinfo['scope']).scope
except CantSelectPrincipal:
stinfo['invariant'] = False
rqlst.need_distinct = need_distinct
@@ -195,7 +200,7 @@
for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)):
# only equality relation with a variable as rhs may be principal
if rel.operator() not in ('=', 'IS') \
- or not isinstance(rel.children[1].children[0], VariableRef):
+ or not isinstance(rel.children[1].children[0], VariableRef) or rel.neged(strict=True):
continue
if rel.scope is rel.stmt:
return rel
--- a/server/schemaserial.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/schemaserial.py Fri Mar 11 09:46:45 2011 +0100
@@ -235,7 +235,14 @@
uniquecstreid, eeid, releid = values
eschema = schema.schema_by_eid(eeid)
relations = unique_togethers.setdefault(uniquecstreid, (eschema, []))
- relations[1].append(ertidx[releid].rtype.type)
+ rel = ertidx[releid]
+ if isinstance(rel, schemamod.RelationDefinitionSchema):
+ # not yet migrated 3.9 database ('relations' target type changed
+ # to CWRType in 3.10)
+ rtype = rel.rtype.type
+ else:
+ rtype = str(rel)
+ relations[1].append(rtype)
for eschema, unique_together in unique_togethers.itervalues():
eschema._unique_together.append(tuple(sorted(unique_together)))
schema.infer_specialization_rules()
@@ -355,6 +362,7 @@
for eschema in eschemas:
for unique_together in eschema._unique_together:
execschemarql(execute, eschema, [uniquetogether2rql(eschema, unique_together)])
+ # serialize yams inheritance relationships
for rql, kwargs in specialize2rql(schema):
execute(rql, kwargs, build_descr=False)
if pb is not None:
@@ -417,23 +425,17 @@
restrictions = []
substs = {}
for i, name in enumerate(unique_together):
- rschema = eschema.rdef(name)
- var = 'R%d' % i
+ rschema = eschema.schema.rschema(name)
rtype = 'T%d' % i
- substs[rtype] = rschema.rtype.type
- relations.append('C relations %s' % var)
- restrictions.append('%(var)s from_entity X, '
- '%(var)s relation_type %(rtype)s, '
- '%(rtype)s name %%(%(rtype)s)s' \
- % {'var': var,
- 'rtype':rtype})
+ substs[rtype] = rschema.type
+ relations.append('C relations %s' % rtype)
+ restrictions.append('%(rtype)s name %%(%(rtype)s)s' % {'rtype': rtype})
relations = ', '.join(relations)
restrictions = ', '.join(restrictions)
rql = ('INSERT CWUniqueTogetherConstraint C: '
' C constraint_of X, %s '
'WHERE '
- ' X eid %%(x)s, %s' )
-
+ ' X eid %%(x)s, %s')
return rql % (relations, restrictions), substs
--- a/server/serverconfig.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/serverconfig.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,14 +19,15 @@
__docformat__ = "restructuredtext en"
+import sys
from os.path import join, exists
+from StringIO import StringIO
-from logilab.common.configuration import REQUIRED, Method, Configuration, \
- ini_format_section
+import logilab.common.configuration as lgconfig
from logilab.common.decorators import wproperty, cached
from cubicweb.toolsutils import read_config, restrict_perms_to_user
-from cubicweb.cwconfig import CubicWebConfiguration, merge_options
+from cubicweb.cwconfig import CONFIGURATIONS, CubicWebConfiguration, merge_options
from cubicweb.server import SOURCE_TYPES
@@ -38,13 +39,13 @@
'level': 0,
}),
('password', {'type' : 'password',
- 'default': REQUIRED,
+ 'default': lgconfig.REQUIRED,
'help': "cubicweb manager account's password",
'level': 0,
}),
)
-class SourceConfiguration(Configuration):
+class SourceConfiguration(lgconfig.Configuration):
def __init__(self, appconfig, options):
self.appconfig = appconfig # has to be done before super call
super(SourceConfiguration, self).__init__(options=options)
@@ -54,54 +55,36 @@
return self.appconfig.appid
def input_option(self, option, optdict, inputlevel):
- if self['db-driver'] == 'sqlite':
- if option in ('db-user', 'db-password'):
- return
- if option == 'db-name':
- optdict = optdict.copy()
- optdict['help'] = 'path to the sqlite database'
- optdict['default'] = join(self.appconfig.appdatahome,
- self.appconfig.appid + '.sqlite')
+ try:
+ dbdriver = self['db-driver']
+ except lgconfig.OptionError:
+ pass
+ else:
+ if dbdriver == 'sqlite':
+ if option in ('db-user', 'db-password'):
+ return
+ if option == 'db-name':
+ optdict = optdict.copy()
+ optdict['help'] = 'path to the sqlite database'
+ optdict['default'] = join(self.appconfig.appdatahome,
+ self.appconfig.appid + '.sqlite')
super(SourceConfiguration, self).input_option(option, optdict, inputlevel)
-def generate_sources_file(appconfig, sourcesfile, sourcescfg, keys=None):
- """serialize repository'sources configuration into a INI like file
+
+def ask_source_config(appconfig, type, inputlevel=0):
+ options = SOURCE_TYPES[type].options
+ sconfig = SourceConfiguration(appconfig, options=options)
+ sconfig.input_config(inputlevel=inputlevel)
+ return sconfig
- the `keys` parameter may be used to sort sections
- """
- if keys is None:
- keys = sourcescfg.keys()
- else:
- for key in sourcescfg:
- if not key in keys:
- keys.append(key)
- stream = open(sourcesfile, 'w')
- for uri in keys:
- sconfig = sourcescfg[uri]
- if isinstance(sconfig, dict):
- # get a Configuration object
- if uri == 'admin':
- options = USER_OPTIONS
- else:
- options = SOURCE_TYPES[sconfig['adapter']].options
- _sconfig = SourceConfiguration(appconfig, options=options)
- for attr, val in sconfig.items():
- if attr == 'uri':
- continue
- if attr == 'adapter':
- _sconfig.adapter = val
- else:
- _sconfig.set_option(attr, val)
- sconfig = _sconfig
- optsbysect = list(sconfig.options_by_section())
- assert len(optsbysect) == 1, 'all options for a source should be in the same group'
- ini_format_section(stream, uri, optsbysect[0][1])
- if hasattr(sconfig, 'adapter'):
- print >> stream
- print >> stream, '# adapter for this source (YOU SHOULD NOT CHANGE THIS)'
- print >> stream, 'adapter=%s' % sconfig.adapter
- print >> stream
+def generate_source_config(sconfig, encoding=sys.stdin.encoding):
+ """serialize a repository source configuration as text"""
+ stream = StringIO()
+ optsbysect = list(sconfig.options_by_section())
+ assert len(optsbysect) == 1, 'all options for a source should be in the same group'
+ lgconfig.ini_format(stream, optsbysect[0][1], encoding)
+ return stream.getvalue()
class ServerConfiguration(CubicWebConfiguration):
@@ -121,7 +104,7 @@
}),
('pid-file',
{'type' : 'string',
- 'default': Method('default_pid_file'),
+ 'default': lgconfig.Method('default_pid_file'),
'help': 'repository\'s pid file',
'group': 'main', 'level': 2,
}),
@@ -132,10 +115,16 @@
the repository rather than the user running the command',
'group': 'main', 'level': (CubicWebConfiguration.mode == 'installed') and 0 or 1,
}),
- ('session-time',
+ ('cleanup-session-time',
{'type' : 'time',
- 'default': '30min',
- 'help': 'session expiration time, default to 30 minutes',
+ 'default': '24h',
+ 'help': 'duration of inactivity after which a session '
+ 'will be closed, to limit memory consumption (avoid sessions that '
+ 'never expire and cause memory leak when http-session-time is 0, or '
+ 'because of bad client that never closes their connection). '
+ 'So notice that even if http-session-time is 0 and the user don\'t '
+ 'close his browser, he will have to reauthenticate after this time '
+ 'of inactivity. Default to 24h.',
'group': 'main', 'level': 3,
}),
('connections-pool-size',
@@ -276,16 +265,48 @@
"""
return self.read_sources_file()
- def source_enabled(self, uri):
- return not self.enabled_sources or uri in self.enabled_sources
+ def source_enabled(self, source):
+ if self.sources_mode is not None:
+ if 'migration' in self.sources_mode:
+ assert len(self.sources_mode) == 1
+ if source.connect_for_migration:
+ return True
+ print 'not connecting to source', source.uri, 'during migration'
+ return False
+ if 'all' in self.sources_mode:
+ assert len(self.sources_mode) == 1
+ return True
+ return source.uri in self.sources_mode
+ if self.quick_start:
+ return False
+ return (not source.disabled and (
+ not self.enabled_sources or source.uri in self.enabled_sources))
def write_sources_file(self, sourcescfg):
+ """serialize repository'sources configuration into a INI like file"""
sourcesfile = self.sources_file()
if exists(sourcesfile):
import shutil
shutil.copy(sourcesfile, sourcesfile + '.bak')
- generate_sources_file(self, sourcesfile, sourcescfg,
- ['admin', 'system'])
+ stream = open(sourcesfile, 'w')
+ for section in ('admin', 'system'):
+ sconfig = sourcescfg[section]
+ if isinstance(sconfig, dict):
+ # get a Configuration object
+ assert section == 'system'
+ _sconfig = SourceConfiguration(
+ self, options=SOURCE_TYPES['native'].options)
+ for attr, val in sconfig.items():
+ try:
+ _sconfig.set_option(attr, val)
+ except lgconfig.OptionError:
+ # skip adapter, may be present on pre 3.10 instances
+ if attr != 'adapter':
+ self.error('skip unknown option %s in sources file')
+ sconfig = _sconfig
+ print >> stream, '[%s]' % section
+ print >> stream, generate_source_config(sconfig)
+ print >> stream
restrict_perms_to_user(sourcesfile)
def pyro_enabled(self):
@@ -312,27 +333,9 @@
schema.name = 'bootstrap'
return schema
+ sources_mode = None
def set_sources_mode(self, sources):
- if 'migration' in sources:
- from cubicweb.server.sources import source_adapter
- assert len(sources) == 1
- enabled_sources = []
- for uri, config in self.sources().iteritems():
- if uri == 'admin':
- continue
- if source_adapter(config).connect_for_migration:
- enabled_sources.append(uri)
- else:
- print 'not connecting to source', uri, 'during migration'
- elif 'all' in sources:
- assert len(sources) == 1
- enabled_sources = None
- else:
- known_sources = self.sources()
- for uri in sources:
- assert uri in known_sources, uri
- enabled_sources = sources
- self.enabled_sources = enabled_sources
+ self.sources_mode = sources
def migration_handler(self, schema=None, interactive=True,
cnx=None, repo=None, connect=True, verbosity=None):
@@ -343,3 +346,6 @@
return ServerMigrationHelper(self, schema, interactive=interactive,
cnx=cnx, repo=repo, connect=connect,
verbosity=verbosity)
+
+
+CONFIGURATIONS.append(ServerConfiguration)
--- a/server/serverctl.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/serverctl.py Fri Mar 11 09:46:45 2011 +0100
@@ -25,6 +25,7 @@
import sys
import os
+from logilab.common import nullobject
from logilab.common.configuration import Configuration
from logilab.common.shellutils import ASK
@@ -32,8 +33,9 @@
from cubicweb.toolsutils import Command, CommandHandler, underline_title
from cubicweb.cwctl import CWCTL
from cubicweb.server import SOURCE_TYPES
-from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
- SourceConfiguration)
+from cubicweb.server.serverconfig import (
+ USER_OPTIONS, ServerConfiguration, SourceConfiguration,
+ ask_source_config, generate_source_config)
# utility functions ###########################################################
@@ -55,16 +57,14 @@
else:
print dbname,
if dbhelper.users_support:
- if not verbose or (not special_privs and source.get('db-user')):
+ if not special_privs and source.get('db-user'):
user = source['db-user']
if verbose:
print 'as', user
- if source.get('db-password'):
- password = source['db-password']
- else:
- password = getpass('password: ')
+ password = source.get('db-password')
else:
- print
+ if verbose:
+ print
if special_privs:
print 'WARNING'
print ('the user will need the following special access rights '
@@ -73,9 +73,9 @@
print
default_user = source.get('db-user', os.environ.get('USER', ''))
user = raw_input('Connect as user ? [%r]: ' % default_user)
- user = user or default_user
- if user == source.get('db-user') and source.get('db-password'):
- password = source['db-password']
+ user = user.strip() or default_user
+ if user == source.get('db-user'):
+ password = source.get('db-password')
else:
password = getpass('password: ')
else:
@@ -107,22 +107,18 @@
return source_cnx(source, system_db, special_privs=special_privs, verbose=verbose)
return source_cnx(source, special_privs=special_privs, verbose=verbose)
-def _db_sys_cnx(source, what, db=None, user=None, verbose=True):
- """return a connection on the RDMS system table (to create/drop a user
- or a database
+def _db_sys_cnx(source, special_privs, verbose=True):
+ """return a connection on the RDMS system table (to create/drop a user or a
+ database)
"""
import logilab.common as lgp
from logilab.database import get_db_helper
lgp.USE_MX_DATETIME = False
- special_privs = ''
driver = source['db-driver']
helper = get_db_helper(driver)
- if user is not None and helper.users_support:
- special_privs += '%s USER' % what
- if db is not None:
- special_privs += ' %s DATABASE' % what
# connect on the dbms system base to create our base
- cnx = system_source_cnx(source, True, special_privs=special_privs, verbose=verbose)
+ cnx = system_source_cnx(source, True, special_privs=special_privs,
+ verbose=verbose)
# disable autocommit (isolation_level(1)) because DROP and
# CREATE DATABASE can't be executed in a transaction
try:
@@ -134,7 +130,7 @@
def repo_cnx(config):
"""return a in-memory repository and a db api connection it"""
- from cubicweb.dbapi import in_memory_cnx
+ from cubicweb.dbapi import in_memory_repo_cnx
from cubicweb.server.utils import manager_userpasswd
try:
login = config.sources()['admin']['login']
@@ -143,7 +139,7 @@
login, pwd = manager_userpasswd()
while True:
try:
- return in_memory_cnx(config, login, password=pwd)
+ return in_memory_repo_cnx(config, login, password=pwd)
except AuthenticationError:
print '-> Error: wrong user/password.'
# reset cubes else we'll have an assertion error on next retry
@@ -161,7 +157,6 @@
"""create an instance by copying files from the given cube and by asking
information necessary to build required configuration files
"""
- from cubicweb.server.utils import ask_source_config
config = self.config
print underline_title('Configuring the repository')
config.input_config('email', inputlevel)
@@ -176,37 +171,9 @@
# defs (in native.py)
sconfig = SourceConfiguration(config,
options=SOURCE_TYPES['native'].options)
- sconfig.adapter = 'native'
sconfig.input_config(inputlevel=inputlevel)
sourcescfg = {'system': sconfig}
- for cube in cubes:
- # if a source is named as the cube containing it, we need the
- # source to use the cube, so add it.
- if cube in SOURCE_TYPES:
- sourcescfg[cube] = ask_source_config(cube, inputlevel)
print
- while ASK.confirm('Enter another source ?', default_is_yes=False):
- available = sorted(stype for stype in SOURCE_TYPES
- if not stype in cubes)
- while True:
- sourcetype = raw_input('source type (%s): ' % ', '.join(available))
- if sourcetype in available:
- break
- print '-> unknown source type, use one of the available types.'
- while True:
- sourceuri = raw_input('source identifier (a unique name used to tell sources apart): ').strip()
- if sourceuri != 'admin' and sourceuri not in sourcescfg:
- break
- print '-> uri already used, choose another one.'
- sourcescfg[sourceuri] = ask_source_config(sourcetype, inputlevel)
- sourcemodule = SOURCE_TYPES[sourcetype].module
- if not sourcemodule.startswith('cubicweb.'):
- # module names look like cubes.mycube.themodule
- sourcecube = SOURCE_TYPES[sourcetype].module.split('.', 2)[1]
- # if the source adapter is coming from an external component,
- # ensure it's specified in used cubes
- if not sourcecube in cubes:
- cubes.append(sourcecube)
sconfig = Configuration(options=USER_OPTIONS)
sconfig.input_config(inputlevel=inputlevel)
sourcescfg['admin'] = sconfig
@@ -222,6 +189,16 @@
print ('-> nevermind, you can do it later with '
'"cubicweb-ctl db-create %s".' % self.config.appid)
+ERROR = nullobject()
+
+def confirm_on_error_or_die(msg, func, *args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except Exception, ex:
+ print 'ERROR', ex
+ if not ASK.confirm('An error occurred while %s. Continue anyway?' % msg):
+ raise ExecutionError(str(ex))
+ return ERROR
class RepositoryDeleteHandler(CommandHandler):
cmdname = 'delete'
@@ -235,19 +212,29 @@
helper = get_db_helper(source['db-driver'])
if ASK.confirm('Delete database %s ?' % dbname):
if source['db-driver'] == 'sqlite':
- os.unlink(source['db-name'])
+ if confirm_on_error_or_die(
+ 'deleting database file %s' % dbname,
+ os.unlink, source['db-name']) is not ERROR:
+ print '-> database %s dropped.' % dbname
return
user = source['db-user'] or None
- cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
+ cnx = confirm_on_error_or_die('connecting to database %s' % dbname,
+ _db_sys_cnx, source, 'DROP DATABASE')
+ if cnx is ERROR:
+ return
cursor = cnx.cursor()
try:
- cursor.execute('DROP DATABASE %s' % dbname)
- print '-> database %s dropped.' % dbname
+ if confirm_on_error_or_die(
+ 'dropping database %s' % dbname,
+ cursor.execute, 'DROP DATABASE "%s"' % dbname) is not ERROR:
+ print '-> database %s dropped.' % dbname
# XXX should check we are not connected as user
if user and helper.users_support and \
ASK.confirm('Delete user %s ?' % user, default_is_yes=False):
- cursor.execute('DROP USER %s' % user)
- print '-> user %s dropped.' % user
+ if confirm_on_error_or_die(
+ 'dropping user %s' % user,
+ cursor.execute, 'DROP USER %s' % user) is not ERROR:
+ print '-> user %s dropped.' % user
cnx.commit()
except:
cnx.rollback()
@@ -265,6 +252,7 @@
command.append('--loglevel %s' % config['log-threshold'].lower())
command.append(config.appid)
os.system(' '.join(command))
+ return 1
class RepositoryStopHandler(CommandHandler):
@@ -294,7 +282,7 @@
You will be prompted for a login / password to use to connect to
the system database. The given user should have almost all rights
- on the database (ie a super user on the dbms allowed to create
+ on the database (ie a super user on the DBMS allowed to create
database, users, languages...).
<instance>
@@ -340,14 +328,15 @@
elif self.config.create_db:
print '\n'+underline_title('Creating the system database')
# connect on the dbms system base to create our base
- dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER', verbose=verbose)
+ dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER',
+ verbose=verbose)
cursor = dbcnx.cursor()
try:
if helper.users_support:
user = source['db-user']
if not helper.user_exists(cursor, user) and (automatic or \
ASK.confirm('Create db user %s ?' % user, default_is_yes=False)):
- helper.create_user(source['db-user'], source['db-password'])
+ helper.create_user(source['db-user'], source.get('db-password'))
print '-> user %s created.' % user
if dbname in helper.list_databases(cursor):
if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
@@ -360,7 +349,8 @@
except:
dbcnx.rollback()
raise
- cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=verbose)
+ cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE',
+ verbose=verbose)
cursor = cnx.cursor()
helper.init_fti_extensions(cursor)
# postgres specific stuff
@@ -383,9 +373,8 @@
class InitInstanceCommand(Command):
"""Initialize the system database of an instance (run after 'db-create').
- You will be prompted for a login / password to use to connect to
- the system database. The given user should have the create tables,
- and grant permissions.
+ Notice this will be done using user specified in the sources files, so this
+ user should have the create tables grant permissions on the database.
<instance>
the identifier of the instance to initialize.
@@ -399,6 +388,10 @@
'default': False,
'help': 'insert drop statements to remove previously existant \
tables, indexes... (no by default)'}),
+ ('config-level',
+ {'short': 'l', 'type': 'int', 'default': 1,
+ 'help': 'level threshold for questions asked when configuring another source'
+ }),
)
def run(self, args):
@@ -422,6 +415,68 @@
'the %s file. Resolve this first (error: %s).'
% (config.sources_file(), str(ex).strip()))
init_repository(config, drop=self.config.drop)
+ while ASK.confirm('Enter another source ?', default_is_yes=False):
+ CWCTL.run(['add-source', '--config-level', self.config.config_level, config.appid])
+
+
+class AddSourceCommand(Command):
+ """Add a data source to an instance.
+
+ <instance>
+ the identifier of the instance to initialize.
+ """
+ name = 'add-source'
+ arguments = '<instance>'
+ min_args = max_args = 1
+ options = (
+ ('config-level',
+ {'short': 'l', 'type': 'int', 'default': 1,
+ 'help': 'level threshold for questions asked when configuring another source'
+ }),
+ )
+
+ def run(self, args):
+ appid = args[0]
+ config = ServerConfiguration.config_for(appid)
+ config.quick_start = True
+ repo, cnx = repo_cnx(config)
+ req = cnx.request()
+ used = set(n for n, in req.execute('Any SN WHERE S is CWSource, S name SN'))
+ cubes = repo.get_cubes()
+ while True:
+ type = raw_input('source type (%s): '
+ % ', '.join(sorted(SOURCE_TYPES)))
+ if type not in SOURCE_TYPES:
+ print '-> unknown source type, use one of the available types.'
+ continue
+ sourcemodule = SOURCE_TYPES[type].module
+ if not sourcemodule.startswith('cubicweb.'):
+ # module names look like cubes.mycube.themodule
+ sourcecube = SOURCE_TYPES[type].module.split('.', 2)[1]
+ # if the source adapter is coming from an external component,
+ # ensure it's specified in used cubes
+ if not sourcecube in cubes:
+ print ('-> this source type require the %s cube which is '
+ 'not used by the instance.')
+ continue
+ break
+ while True:
+ sourceuri = raw_input('source identifier (a unique name used to '
+ 'tell sources apart): ').strip()
+ if not sourceuri:
+ print '-> mandatory.'
+ else:
+ sourceuri = unicode(sourceuri, sys.stdin.encoding)
+ if sourceuri in used:
+ print '-> uri already used, choose another one.'
+ else:
+ break
+ # XXX configurable inputlevel
+ sconfig = ask_source_config(config, type, inputlevel=self.config.config_level)
+ cfgstr = unicode(generate_source_config(sconfig), sys.stdin.encoding)
+ req.create_entity('CWSource', name=sourceuri,
+ type=unicode(type), config=cfgstr)
+ cnx.commit()
class GrantUserOnInstanceCommand(Command):
@@ -486,6 +541,9 @@
print '-> Error: could not get cubicweb administrator login.'
sys.exit(1)
cnx = source_cnx(sourcescfg['system'])
+ driver = sourcescfg['system']['db-driver']
+ from logilab.database import get_db_helper
+ dbhelper = get_db_helper(driver)
cursor = cnx.cursor()
# check admin exists
cursor.execute("SELECT * FROM cw_CWUser WHERE cw_login=%(l)s",
@@ -501,7 +559,7 @@
passwdmsg='new password for %s' % adminlogin)
try:
cursor.execute("UPDATE cw_CWUser SET cw_upassword=%(p)s WHERE cw_login=%(l)s",
- {'p': buffer(crypt_password(passwd)), 'l': adminlogin})
+ {'p': dbhelper.binary_value(crypt_password(passwd)), 'l': adminlogin})
sconfig = Configuration(options=USER_OPTIONS)
sconfig['login'] = adminlogin
sconfig['password'] = passwd
@@ -519,7 +577,7 @@
class StartRepositoryCommand(Command):
- """Start an CubicWeb RQL server for a given instance.
+ """Start a CubicWeb RQL server for a given instance.
The server will be accessible through pyro
@@ -562,7 +620,7 @@
# go ! (don't daemonize in debug mode)
if not os.path.exists(piddir):
os.makedirs(piddir)
- if not debug and daemonize(pidfile):
+ if not debug and daemonize(pidfile, umask=config['umask']):
return
uid = config['uid']
if uid is not None:
@@ -791,9 +849,12 @@
options = (
('checks',
{'short': 'c', 'type' : 'csv', 'metavar' : '<check list>',
- 'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
+ 'default' : ('entities', 'relations',
+ 'mandatory_relations', 'mandatory_attributes',
+ 'metadata', 'schema', 'text_index'),
'help': 'Comma separated list of check to run. By default run all \
-checks, i.e. entities, relations, text_index and metadata.'}
+checks, i.e. entities, relations, mandatory_relations, mandatory_attributes, \
+metadata, text_index and schema.'}
),
('autofix',
@@ -897,7 +958,7 @@
GrantUserOnInstanceCommand, ResetAdminPasswordCommand,
StartRepositoryCommand,
DBDumpCommand, DBRestoreCommand, DBCopyCommand,
- CheckRepositoryCommand, RebuildFTICommand,
+ AddSourceCommand, CheckRepositoryCommand, RebuildFTICommand,
SynchronizeInstanceSchemaCommand,
CheckMappingCommand,
):
--- a/server/session.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/session.py Fri Mar 11 09:46:45 2011 +0100
@@ -28,6 +28,7 @@
from warnings import warn
from logilab.common.deprecation import deprecated
+from rql import CoercionError
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
@@ -46,6 +47,7 @@
# anyway in the later case
NO_UNDO_TYPES.add('is')
NO_UNDO_TYPES.add('is_instance_of')
+NO_UNDO_TYPES.add('cw_source')
# XXX rememberme,forgotpwd,apycot,vcsfile
def _make_description(selected, args, solution):
@@ -64,6 +66,14 @@
If mode is session.`HOOKS_ALLOW_ALL`, given hooks categories will
be disabled.
+
+ .. sourcecode:: python
+
+ with hooks_control(self.session, self.session.HOOKS_ALLOW_ALL, 'integrity'):
+ # ... do stuff with all but 'integrity' hooks activated
+
+ with hooks_control(self.session, self.session.HOOKS_DENY_ALL, 'integrity'):
+ # ... do stuff with none but 'integrity' hooks activated
"""
def __init__(self, session, mode, *categories):
self.session = session
@@ -618,16 +628,20 @@
# shared data handling ###################################################
- def get_shared_data(self, key, default=None, pop=False):
+ def get_shared_data(self, key, default=None, pop=False, txdata=False):
"""return value associated to `key` in session data"""
- if pop:
- return self.data.pop(key, default)
+ if txdata:
+ data = self.transaction_data
else:
- return self.data.get(key, default)
+ data = self.data
+ if pop:
+ return data.pop(key, default)
+ else:
+ return data.get(key, default)
- def set_shared_data(self, key, value, querydata=False):
+ def set_shared_data(self, key, value, txdata=False):
"""set value associated to `key` in session data"""
- if querydata:
+ if txdata:
self.transaction_data[key] = value
else:
self.data[key] = value
@@ -738,51 +752,50 @@
try:
# by default, operations are executed with security turned off
with security_enabled(self, False, False):
- for trstate in ('precommit', 'commit'):
- processed = []
- self.commit_state = trstate
- try:
- while self.pending_operations:
- operation = self.pending_operations.pop(0)
- operation.processed = trstate
- processed.append(operation)
- operation.handle_event('%s_event' % trstate)
- self.pending_operations[:] = processed
- self.debug('%s session %s done', trstate, self.id)
- except:
- # if error on [pre]commit:
- #
- # * set .failed = True on the operation causing the failure
- # * call revert<event>_event on processed operations
- # * call rollback_event on *all* operations
- #
- # that seems more natural than not calling rollback_event
- # for processed operations, and allow generic rollback
- # instead of having to implements rollback, revertprecommit
- # and revertcommit, that will be enough in mont case.
- operation.failed = True
- for operation in reversed(processed):
- try:
- operation.handle_event('revert%s_event' % trstate)
- except:
- self.critical('error while reverting %sing', trstate,
- exc_info=True)
- # XXX use slice notation since self.pending_operations is a
- # read-only property.
- self.pending_operations[:] = processed + self.pending_operations
- self.rollback(reset_pool)
- raise
+ processed = []
+ self.commit_state = 'precommit'
+ try:
+ while self.pending_operations:
+ operation = self.pending_operations.pop(0)
+ operation.processed = 'precommit'
+ processed.append(operation)
+ operation.handle_event('precommit_event')
+ self.pending_operations[:] = processed
+ self.debug('precommit session %s done', self.id)
+ except:
+ # if error on [pre]commit:
+ #
+ # * set .failed = True on the operation causing the failure
+ # * call revert<event>_event on processed operations
+ # * call rollback_event on *all* operations
+ #
+ # that seems more natural than not calling rollback_event
+ # for processed operations, and allow generic rollback
+ # instead of having to implements rollback, revertprecommit
+ # and revertcommit, that will be enough in mont case.
+ operation.failed = True
+ for operation in reversed(processed):
+ try:
+ operation.handle_event('revertprecommit_event')
+ except:
+ self.critical('error while reverting precommit',
+ exc_info=True)
+ # XXX use slice notation since self.pending_operations is a
+ # read-only property.
+ self.pending_operations[:] = processed + self.pending_operations
+ self.rollback(reset_pool)
+ raise
self.pool.commit()
- self.commit_state = trstate = 'postcommit'
+ self.commit_state = 'postcommit'
while self.pending_operations:
operation = self.pending_operations.pop(0)
- operation.processed = trstate
+ operation.processed = 'postcommit'
try:
- operation.handle_event('%s_event' % trstate)
+ operation.handle_event('postcommit_event')
except:
- self.critical('error while %sing', trstate,
+ self.critical('error while postcommit',
exc_info=sys.exc_info())
- self.debug('%s session %s done', trstate, self.id)
+ self.debug('postcommit session %s done', self.id)
return self.transaction_uuid(set=False)
finally:
self._touch()
@@ -841,6 +854,10 @@
del self.__threaddata
del self._tx_data
+ @property
+ def closed(self):
+ return not hasattr(self, '_tx_data')
+
# transaction data/operations management ##################################
@property
--- a/server/sources/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,10 +22,12 @@
from os.path import join, splitext
from datetime import datetime, timedelta
from logging import getLogger
+import itertools
from cubicweb import set_log_methods, server
from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.ssplanner import EditedEntity
def dbg_st_search(uri, union, varmap, args, cachekey=None, prefix='rql for'):
@@ -54,7 +56,7 @@
def __init__(self, ttl):
# time to live in seconds
if ttl <= 0:
- raise ValueError('TimedCache initialized with a ttl of %ss' % self.ttl.seconds)
+ raise ValueError('TimedCache initialized with a ttl of %ss' % ttl.seconds)
self.ttl = timedelta(seconds=ttl)
def __setitem__(self, key, value):
@@ -98,13 +100,18 @@
dont_cross_relations = ()
cross_relations = ()
+ # force deactivation (configuration error for instance)
+ disabled = False
- def __init__(self, repo, appschema, source_config, *args, **kwargs):
+ def __init__(self, repo, source_config, *args, **kwargs):
self.repo = repo
self.uri = source_config['uri']
set_log_methods(self, getLogger('cubicweb.sources.'+self.uri))
- self.set_schema(appschema)
+ self.set_schema(repo.schema)
self.support_relations['identity'] = False
+ self.eid = None
+ self.public_config = source_config.copy()
+ self.remove_sensitive_information(self.public_config)
def init_creating(self):
"""method called by the repository once ready to create a new instance"""
@@ -218,7 +225,7 @@
def extid2eid(self, value, etype, session=None, **kwargs):
return self.repo.extid2eid(self, value, etype, session, **kwargs)
- PUBLIC_KEYS = ('adapter', 'uri')
+ PUBLIC_KEYS = ('type', 'uri')
def remove_sensitive_information(self, sourcedef):
"""remove sensitive information such as login / password from source
definition
@@ -343,6 +350,7 @@
"""
entity = self.repo.vreg['etypes'].etype_class(etype)(session)
entity.eid = eid
+ entity.cw_edited = EditedEntity(entity)
return entity
def after_entity_insertion(self, session, lid, entity):
@@ -365,6 +373,11 @@
"""update an entity in the source"""
raise NotImplementedError()
+ def delete_entities(self, session, entities):
+ """delete several entities from the source"""
+ for entity in entities:
+ self.delete_entity(session, entity)
+
def delete_entity(self, session, entity):
"""delete an entity from the source"""
raise NotImplementedError()
@@ -394,12 +407,19 @@
"""mark entity as being modified, fulltext reindex if needed"""
raise NotImplementedError()
- def delete_info(self, session, entity, uri, extid, attributes, relations):
+ def delete_info(self, session, entity, uri, extid):
"""delete system information on deletion of an entity by transfering
record from the entities table to the deleted_entities table
"""
raise NotImplementedError()
+ def delete_info_multi(self, session, entities, uri, extids):
+ """ame as delete_info but accepts a list of entities with
+ the same etype and belinging to the same source.
+ """
+ for entity, extid in itertools.izip(entities, extids):
+ self.delete_info(session, entity, uri, extid)
+
def modified_entities(self, session, etypes, mtime):
"""return a 2-uple:
* list of (etype, eid) of entities of the given types which have been
@@ -416,14 +436,13 @@
"""
raise NotImplementedError()
- def fti_unindex_entity(self, session, eid):
- """remove text content for entity with the given eid from the full text
- index
+ def fti_unindex_entities(self, session, entities):
+ """remove text content for entities from the full text index
"""
raise NotImplementedError()
- def fti_index_entity(self, session, entity):
- """add text content of a created/modified entity to the full text index
+ def fti_index_entities(self, session, entities):
+ """add text content of created/modified entities to the full text index
"""
raise NotImplementedError()
@@ -505,18 +524,20 @@
pass
def cursor(self):
return None # no actual cursor support
+ def close(self):
+ if hasattr(self.cnx, 'close'):
+ self.cnx.close()
from cubicweb.server import SOURCE_TYPES
-def source_adapter(source_config):
- adapter_type = source_config['adapter'].lower()
+def source_adapter(source_type):
try:
- return SOURCE_TYPES[adapter_type]
+ return SOURCE_TYPES[source_type]
except KeyError:
- raise RuntimeError('Unknown adapter %r' % adapter_type)
+ raise RuntimeError('Unknown source type %r' % source_type)
-def get_source(source_config, global_schema, repo):
+def get_source(type, source_config, repo):
"""return a source adapter according to the adapter field in the
source's configuration
"""
- return source_adapter(source_config)(repo, global_schema, source_config)
+ return source_adapter(type)(repo, source_config)
--- a/server/sources/ldapuser.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/ldapuser.py Fri Mar 11 09:46:45 2011 +0100
@@ -97,13 +97,13 @@
{'type' : 'string',
'default': '',
'help': 'user dn to use to open data connection to the ldap (eg used \
-to respond to rql queries).',
+to respond to rql queries). Leave empty for anonymous bind',
'group': 'ldap-source', 'level': 1,
}),
('data-cnx-password',
{'type' : 'string',
'default': '',
- 'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries).',
+ 'help': 'password to use to open data connection to the ldap (eg used to respond to rql queries). Leave empty for anonymous bind.',
'group': 'ldap-source', 'level': 1,
}),
@@ -111,19 +111,19 @@
{'type' : 'string',
'default': 'ou=People,dc=logilab,dc=fr',
'help': 'base DN to lookup for users',
- 'group': 'ldap-source', 'level': 0,
+ 'group': 'ldap-source', 'level': 1,
}),
('user-scope',
{'type' : 'choice',
'default': 'ONELEVEL',
'choices': ('BASE', 'ONELEVEL', 'SUBTREE'),
- 'help': 'user search scope',
+ 'help': 'user search scope (valid values: "BASE", "ONELEVEL", "SUBTREE")',
'group': 'ldap-source', 'level': 1,
}),
('user-classes',
{'type' : 'csv',
'default': ('top', 'posixAccount'),
- 'help': 'classes of user',
+ 'help': 'classes of user (with Active Directory, you want to say "user" here)',
'group': 'ldap-source', 'level': 1,
}),
('user-filter',
@@ -135,7 +135,7 @@
('user-login-attr',
{'type' : 'string',
'default': 'uid',
- 'help': 'attribute used as login on authentication',
+ 'help': 'attribute used as login on authentication (with Active Directory, you want to use "sAMAccountName" here)',
'group': 'ldap-source', 'level': 1,
}),
('user-default-group',
@@ -148,7 +148,7 @@
('user-attrs-map',
{'type' : 'named',
'default': {'uid': 'login', 'gecos': 'email'},
- 'help': 'map from ldap user attributes to cubicweb attributes',
+ 'help': 'map from ldap user attributes to cubicweb attributes (with Active Directory, you want to use sAMAccountName:login,mail:email,givenName:firstname,sn:surname)',
'group': 'ldap-source', 'level': 1,
}),
@@ -168,9 +168,8 @@
)
- def __init__(self, repo, appschema, source_config, *args, **kwargs):
- AbstractSource.__init__(self, repo, appschema, source_config,
- *args, **kwargs)
+ def __init__(self, repo, source_config, *args, **kwargs):
+ AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
self.host = source_config['host']
self.protocol = source_config.get('protocol', 'ldap')
self.authmode = source_config.get('auth-mode', 'simple')
@@ -178,7 +177,7 @@
self.cnx_dn = source_config.get('data-cnx-dn') or ''
self.cnx_pwd = source_config.get('data-cnx-password') or ''
self.user_base_scope = globals()[source_config['user-scope']]
- self.user_base_dn = source_config['user-base-dn']
+ self.user_base_dn = str(source_config['user-base-dn'])
self.user_base_scope = globals()[source_config['user-scope']]
self.user_classes = splitstrip(source_config['user-classes'])
self.user_login_attr = source_config['user-login-attr']
@@ -203,7 +202,7 @@
def _make_base_filters(self):
filters = [filter_format('(%s=%s)', ('objectClass', o))
- for o in self.user_classes]
+ for o in self.user_classes]
if self.user_filter:
filters += [self.user_filter]
return filters
@@ -280,7 +279,10 @@
def get_connection(self):
"""open and return a connection to the source"""
if self._conn is None:
- self._connect()
+ try:
+ self._connect()
+ except:
+ self.exception('unable to connect to ldap:')
return ConnectionWrapper(self._conn)
def authenticate(self, session, login, password=None, **kwargs):
@@ -325,7 +327,7 @@
return None
def prepare_columns(self, mainvars, rqlst):
- """return two list describin how to build the final results
+ """return two list describing how to build the final results
from the result of an ldap search (ie a list of dictionnary)
"""
columns = []
@@ -379,8 +381,14 @@
try:
results = self._query_cache[rqlkey]
except KeyError:
- results = self.rqlst_search(session, rqlst, args)
- self._query_cache[rqlkey] = results
+ try:
+ results = self.rqlst_search(session, rqlst, args)
+ self._query_cache[rqlkey] = results
+ except ldap.SERVER_DOWN:
+ # cant connect to server
+ msg = session._("can't connect to source %s, some data may be missing")
+ session.set_shared_data('sources_error', msg % self.uri)
+ return []
return results
def rqlst_search(self, session, rqlst, args):
@@ -523,6 +531,8 @@
searchstr='(objectClass=*)', attrs=()):
"""make an ldap query"""
self.debug('ldap search %s %s %s %s %s', self.uri, base, scope, searchstr, list(attrs))
+ # XXX for now, we do not have connection pool support for LDAP, so
+ # this is always self._conn
cnx = session.pool.connection(self.uri).cnx
try:
res = cnx.search_s(base, scope, searchstr, attrs)
@@ -586,15 +596,16 @@
entity = super(LDAPUserSource, self).before_entity_insertion(session, lid, etype, eid)
res = self._search(session, lid, BASE)[0]
for attr in entity.e_schema.indexable_attributes():
- entity[attr] = res[self.user_rev_attrs[attr]]
+ entity.cw_edited[attr] = res[self.user_rev_attrs[attr]]
return entity
- def after_entity_insertion(self, session, dn, entity):
+ def after_entity_insertion(self, session, lid, entity):
"""called by the repository after an entity stored here has been
inserted in the system table.
"""
self.debug('ldap after entity insertion')
- super(LDAPUserSource, self).after_entity_insertion(session, dn, entity)
+ super(LDAPUserSource, self).after_entity_insertion(session, lid, entity)
+ dn = lid
for group in self.user_default_groups:
session.execute('SET X in_group G WHERE X eid %(x)s, G name %(group)s',
{'x': entity.eid, 'group': group})
--- a/server/sources/native.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/native.py Fri Mar 11 09:46:45 2011 +0100
@@ -35,6 +35,7 @@
from contextlib import contextmanager
from os.path import abspath
import re
+import itertools
from logilab.common.compat import any
from logilab.common.cache import Cache
@@ -55,6 +56,7 @@
from cubicweb.server.rqlannotation import set_qdata
from cubicweb.server.hook import CleanupDeletedEidsCacheOp
from cubicweb.server.session import hooks_control, security_enabled
+from cubicweb.server.ssplanner import EditedEntity
from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
from cubicweb.server.sources.rql2sql import SQLGenerator
@@ -262,13 +264,12 @@
}),
)
- def __init__(self, repo, appschema, source_config, *args, **kwargs):
+ def __init__(self, repo, source_config, *args, **kwargs):
SQLAdapterMixIn.__init__(self, source_config)
self.authentifiers = [LoginPasswordAuthentifier(self)]
- AbstractSource.__init__(self, repo, appschema, source_config,
- *args, **kwargs)
+ AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
# sql generator
- self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
+ self._rql_sqlgen = self.sqlgen_class(self.schema, self.dbhelper,
ATTR_MAP.copy())
# full text index helper
self.do_fti = not repo.config['delay-full-text-indexation']
@@ -547,25 +548,30 @@
# on the filesystem. To make the entity.data usage absolutely
# transparent, we'll have to reset entity.data to its binary
# value once the SQL query will be executed
- restore_values = {}
- etype = entity.__regid__
+ restore_values = []
+ if isinstance(entity, list):
+ entities = entity
+ else:
+ entities = [entity]
+ etype = entities[0].__regid__
for attr, storage in self._storages.get(etype, {}).items():
- try:
- edited = entity.edited_attributes
- except AttributeError:
- assert event == 'deleted'
- getattr(storage, 'entity_deleted')(entity, attr)
- else:
- if attr in edited:
- handler = getattr(storage, 'entity_%s' % event)
- real_value = handler(entity, attr)
- restore_values[attr] = real_value
+ for entity in entities:
+ try:
+ edited = entity.cw_edited
+ except AttributeError:
+ assert event == 'deleted'
+ getattr(storage, 'entity_deleted')(entity, attr)
+ else:
+ if attr in edited:
+ handler = getattr(storage, 'entity_%s' % event)
+ to_restore = handler(entity, attr)
+ restore_values.append((entity, attr, to_restore))
try:
yield # 2/ execute the source's instructions
finally:
# 3/ restore original values
- for attr, value in restore_values.items():
- entity[attr] = value
+ for entity, attr, value in restore_values:
+ entity.cw_edited.edited_attribute(attr, value)
def add_entity(self, session, entity):
"""add a new entity to the source"""
@@ -844,8 +850,8 @@
if self._eid_creation_cnx is None:
self._eid_creation_cnx = self.get_connection()
cnx = self._eid_creation_cnx
- cursor = cnx.cursor()
try:
+ cursor = cnx.cursor()
for sql in self.dbhelper.sqls_increment_sequence('entities_id_seq'):
cursor.execute(sql)
eid = cursor.fetchone()[0]
@@ -880,6 +886,21 @@
attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
'source': source.uri, 'mtime': datetime.now()}
self.doexec(session, self.sqlgen.insert('entities', attrs), attrs)
+ # insert core relations: is, is_instance_of and cw_source
+ if not hasattr(entity, '_cw_recreating'):
+ try:
+ self.doexec(session, 'INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
+ % (entity.eid, eschema_eid(session, entity.e_schema)))
+ except IndexError:
+ # during schema serialization, skip
+ pass
+ else:
+ for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
+ self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
+ % (entity.eid, eschema_eid(session, eschema)))
+ if 'CWSource' in self.schema and source.eid is not None: # else, cw < 3.10
+ self.doexec(session, 'INSERT INTO cw_source_relation(eid_from,eid_to) '
+ 'VALUES (%s,%s)' % (entity.eid, source.eid))
# now we can update the full text index
if self.do_fti and self.need_fti_indexation(entity.__regid__):
if complete:
@@ -904,7 +925,7 @@
* transfer it to the deleted_entities table if the entity's type is
multi-sources
"""
- self.fti_unindex_entity(session, entity.eid)
+ self.fti_unindex_entities(session, [entity])
attrs = {'eid': entity.eid}
self.doexec(session, self.sqlgen.delete('entities', attrs), attrs)
if not entity.__regid__ in self.multisources_etypes:
@@ -916,6 +937,27 @@
'source': uri, 'dtime': datetime.now()}
self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs)
+ def delete_info_multi(self, session, entities, uri, extids):
+ """delete system information on deletion of an entity:
+ * update the fti
+ * remove record from the entities table
+ * transfer it to the deleted_entities table if the entity's type is
+ multi-sources
+ """
+ self.fti_unindex_entities(session, entities)
+ attrs = {'eid': '(%s)' % ','.join([str(_e.eid) for _e in entities])}
+ self.doexec(session, self.sqlgen.delete_many('entities', attrs), attrs)
+ if entities[0].__regid__ not in self.multisources_etypes:
+ return
+ attrs = {'type': entities[0].__regid__,
+ 'source': uri, 'dtime': datetime.now()}
+ for entity, extid in itertools.izip(entities, extids):
+ if extid is not None:
+ assert isinstance(extid, str), type(extid)
+ extid = b64encode(extid)
+ attrs.update({'eid': entity.eid, 'extid': extid})
+ self.doexec(session, self.sqlgen.insert('deleted_entities', attrs), attrs)
+
def modified_entities(self, session, etypes, mtime):
"""return a 2-uple:
* list of (etype, eid) of entities of the given types which have been
@@ -926,7 +968,7 @@
"""
for etype in etypes:
if not etype in self.multisources_etypes:
- self.critical('%s not listed as a multi-sources entity types. '
+ self.error('%s not listed as a multi-sources entity types. '
'Modify your configuration' % etype)
self.multisources_etypes.add(etype)
modsql = _modified_sql('entities', etypes)
@@ -1127,6 +1169,7 @@
err("can't restore entity %s of type %s, type no more supported"
% (eid, etype))
return errors
+ entity.cw_edited = edited = EditedEntity(entity)
# check for schema changes, entities linked through inlined relation
# still exists, rewrap binary values
eschema = entity.e_schema
@@ -1143,27 +1186,19 @@
assert value is None
elif eschema.destination(rtype) in ('Bytes', 'Password'):
action.changes[column] = self._binary(value)
- entity[rtype] = Binary(value)
+ edited[rtype] = Binary(value)
elif isinstance(value, str):
- entity[rtype] = unicode(value, session.encoding, 'replace')
+ edited[rtype] = unicode(value, session.encoding, 'replace')
else:
- entity[rtype] = value
+ edited[rtype] = value
entity.eid = eid
session.repo.init_entity_caches(session, entity, self)
- entity.edited_attributes = set(entity)
- entity._cw_check()
+ edited.check()
self.repo.hm.call_hooks('before_add_entity', session, entity=entity)
# restore the entity
action.changes['cw_eid'] = eid
sql = self.sqlgen.insert(SQL_PREFIX + etype, action.changes)
self.doexec(session, sql, action.changes)
- # add explicitly is / is_instance_of whose deletion is not recorded for
- # consistency with addition (done by sql in hooks)
- self.doexec(session, 'INSERT INTO is_relation(eid_from, eid_to) '
- 'VALUES(%s, %s)' % (eid, eschema_eid(session, eschema)))
- for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
- self.doexec(session, 'INSERT INTO is_instance_of_relation(eid_from,'
- 'eid_to) VALUES(%s, %s)' % (eid, eschema_eid(session, eschema)))
# restore record in entities (will update fti if needed)
self.add_info(session, entity, self, None, True)
# remove record from deleted_entities if entity's type is multi-sources
@@ -1220,13 +1255,13 @@
"no more supported" % {'eid': eid, 'etype': etype})]
entity.eid = eid
# for proper eid/type cache update
- hook.set_operation(session, 'pendingeids', eid,
- CleanupDeletedEidsCacheOp)
+ CleanupDeletedEidsCacheOp.get_instance(session).add_data(eid)
self.repo.hm.call_hooks('before_delete_entity', session, entity=entity)
# remove is / is_instance_of which are added using sql by hooks, hence
# unvisible as transaction action
self.doexec(session, 'DELETE FROM is_relation WHERE eid_from=%s' % eid)
self.doexec(session, 'DELETE FROM is_instance_of_relation WHERE eid_from=%s' % eid)
+ self.doexec(session, 'DELETE FROM cw_source_relation WHERE eid_from=%s' % self.eid)
# XXX check removal of inlined relation?
# delete the entity
attrs = {'cw_eid': eid}
@@ -1288,32 +1323,37 @@
"""create an operation to [re]index textual content of the given entity
on commit
"""
- hook.set_operation(session, 'ftindex', entity.eid, FTIndexEntityOp)
+ FTIndexEntityOp.get_instance(session).add_data(entity.eid)
- def fti_unindex_entity(self, session, eid):
- """remove text content for entity with the given eid from the full text
- index
+ def fti_unindex_entities(self, session, entities):
+ """remove text content for entities from the full text index
"""
+ cursor = session.pool['system']
+ cursor_unindex_object = self.dbhelper.cursor_unindex_object
try:
- self.dbhelper.cursor_unindex_object(eid, session.pool['system'])
+ for entity in entities:
+ cursor_unindex_object(entity.eid, cursor)
except Exception: # let KeyboardInterrupt / SystemExit propagate
- self.exception('error while unindexing %s', eid)
+ self.exception('error while unindexing %s', entity)
+
- def fti_index_entity(self, session, entity):
- """add text content of a created/modified entity to the full text index
+ def fti_index_entities(self, session, entities):
+ """add text content of created/modified entities to the full text index
"""
- self.debug('reindexing %r', entity.eid)
+ cursor_index_object = self.dbhelper.cursor_index_object
+ cursor = session.pool['system']
try:
# use cursor_index_object, not cursor_reindex_object since
# unindexing done in the FTIndexEntityOp
- self.dbhelper.cursor_index_object(entity.eid,
- entity.cw_adapt_to('IFTIndexable'),
- session.pool['system'])
+ for entity in entities:
+ cursor_index_object(entity.eid,
+ entity.cw_adapt_to('IFTIndexable'),
+ cursor)
except Exception: # let KeyboardInterrupt / SystemExit propagate
- self.exception('error while reindexing %s', entity)
+ self.exception('error while indexing %s', entity)
-class FTIndexEntityOp(hook.LateOperation):
+class FTIndexEntityOp(hook.DataOperationMixIn, hook.LateOperation):
"""operation to delay entity full text indexation to commit
since fti indexing may trigger discovery of other entities, it should be
@@ -1326,17 +1366,17 @@
source = session.repo.system_source
pendingeids = session.transaction_data.get('pendingeids', ())
done = session.transaction_data.setdefault('indexedeids', set())
- for eid in session.transaction_data.pop('ftindex', ()):
+ to_reindex = set()
+ for eid in self.get_data():
if eid in pendingeids or eid in done:
# entity added and deleted in the same transaction or already
# processed
- return
+ continue
done.add(eid)
iftindexable = session.entity_from_eid(eid).cw_adapt_to('IFTIndexable')
- for container in iftindexable.fti_containers():
- source.fti_unindex_entity(session, container.eid)
- source.fti_index_entity(session, container)
-
+ to_reindex |= set(iftindexable.fti_containers())
+ source.fti_unindex_entities(session, to_reindex)
+ source.fti_index_entities(session, to_reindex)
def sql_schema(driver):
helper = get_db_helper(driver)
--- a/server/sources/pyrorql.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/pyrorql.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""Source to query another RQL repository using pyro"""
__docformat__ = "restructuredtext en"
+_ = unicode
import threading
from os.path import join
@@ -28,6 +29,7 @@
from Pyro.errors import PyroError, ConnectionClosedError
from logilab.common.configuration import REQUIRED
+from logilab.common.optik_ext import check_yn
from rql.nodes import Constant
from rql.utils import rqlvar_maker
@@ -64,7 +66,7 @@
assert not unknown, 'unknown mapping attribute(s): %s' % unknown
# relations that are necessarily not crossed
mapping['dont_cross_relations'] |= set(('owned_by', 'created_by'))
- for rtype in ('is', 'is_instance_of'):
+ for rtype in ('is', 'is_instance_of', 'cw_source'):
assert rtype not in mapping['dont_cross_relations'], \
'%s relation should not be in dont_cross_relations' % rtype
assert rtype not in mapping['support_relations'], \
@@ -119,6 +121,12 @@
'to generate external link to entities from this repository',
'group': 'pyro-source', 'level': 1,
}),
+ ('skip-external-entities',
+ {'type' : 'yn',
+ 'default': False,
+ 'help': 'should entities not local to the source be considered or not',
+ 'group': 'pyro-source', 'level': 0,
+ }),
('pyro-ns-host',
{'type' : 'string',
'default': None,
@@ -131,7 +139,7 @@
'default': None,
'help': 'Pyro name server\'s group where the repository will be \
registered. If not set, default to the value from all_in_one.conf.',
- 'group': 'pyro-source', 'level': 1,
+ 'group': 'pyro-source', 'level': 2,
}),
('synchronization-interval',
{'type' : 'int',
@@ -146,17 +154,26 @@
PUBLIC_KEYS = AbstractSource.PUBLIC_KEYS + ('base-url',)
_conn = None
- def __init__(self, repo, appschema, source_config, *args, **kwargs):
- AbstractSource.__init__(self, repo, appschema, source_config,
- *args, **kwargs)
+ def __init__(self, repo, source_config, *args, **kwargs):
+ AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
mappingfile = source_config['mapping-file']
if not mappingfile[0] == '/':
mappingfile = join(repo.config.apphome, mappingfile)
- mapping = load_mapping_file(mappingfile)
- self.support_entities = mapping['support_entities']
- self.support_relations = mapping['support_relations']
- self.dont_cross_relations = mapping['dont_cross_relations']
- self.cross_relations = mapping['cross_relations']
+ try:
+ mapping = load_mapping_file(mappingfile)
+ except IOError:
+ self.disabled = True
+ self.error('cant read mapping file %s, source disabled',
+ mappingfile)
+ self.support_entities = {}
+ self.support_relations = {}
+ self.dont_cross_relations = set()
+ self.cross_relations = set()
+ else:
+ self.support_entities = mapping['support_entities']
+ self.support_relations = mapping['support_relations']
+ self.dont_cross_relations = mapping['dont_cross_relations']
+ self.cross_relations = mapping['cross_relations']
baseurl = source_config.get('base-url')
if baseurl and not baseurl.endswith('/'):
source_config['base-url'] += '/'
@@ -169,6 +186,8 @@
}),)
register_persistent_options(myoptions)
self._query_cache = TimedCache(1800)
+ self._skip_externals = check_yn(None, 'skip-external-entities',
+ source_config.get('skip-external-entities', False))
def reset_caches(self):
"""method called during test to reset potential source caches"""
@@ -176,10 +195,10 @@
def last_update_time(self):
pkey = u'sources.%s.latest-update-time' % self.uri
- rql = 'Any V WHERE X is CWProperty, X value V, X pkey %(k)s'
session = self.repo.internal_session()
try:
- rset = session.execute(rql, {'k': pkey})
+ rset = session.execute('Any V WHERE X is CWProperty, X value V, X pkey %(k)s',
+ {'k': pkey})
if not rset:
# insert it
session.execute('INSERT CWProperty X: X pkey %(k)s, X value %(v)s',
@@ -200,6 +219,18 @@
self.repo.looping_task(self._query_cache.ttl.seconds/10,
self._query_cache.clear_expired)
+ def local_eid(self, cnx, extid, session):
+ etype, dexturi, dextid = cnx.describe(extid)
+ if dexturi == 'system' or not (
+ dexturi in self.repo.sources_by_uri or self._skip_externals):
+ return self.repo.extid2eid(self, str(extid), etype, session), True
+ if dexturi in self.repo.sources_by_uri:
+ source = self.repo.sources_by_uri[dexturi]
+ cnx = session.pool.connection(source.uri)
+ eid = source.local_eid(cnx, dextid, session)[0]
+ return eid, False
+ return None, None
+
def synchronize(self, mtime=None):
"""synchronize content known by this repository with content in the
external repository
@@ -224,9 +255,8 @@
try:
for etype, extid in modified:
try:
- exturi = cnx.describe(extid)[1]
- if exturi == 'system' or not exturi in repo.sources_by_uri:
- eid = self.extid2eid(str(extid), etype, session)
+ eid = self.local_eid(cnx, extid, session)[0]
+ if eid is not None:
rset = session.eid_rset(eid, etype)
entity = rset.get_entity(0, 0)
entity.complete(entity.e_schema.indexable_attributes())
@@ -242,7 +272,8 @@
# 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, extid,
+ scleanup=True)
except:
self.exception('while updating %s with external id %s of source %s',
etype, extid, self.uri)
@@ -323,8 +354,9 @@
msg = session._("can't connect to source %s, some data may be missing")
session.set_shared_data('sources_error', msg % self.uri)
return []
+ translator = RQL2RQL(self)
try:
- rql = RQL2RQL(self).generate(session, union, args)
+ rql = translator.generate(session, union, args)
except UnknownEid, ex:
if server.DEBUG:
print ' unknown eid', ex, 'no results'
@@ -350,19 +382,27 @@
cnx = session.pool.connection(self.uri)
for rowindex in xrange(rset.rowcount - 1, -1, -1):
row = rows[rowindex]
+ localrow = False
for colindex in needtranslation:
if row[colindex] is not None: # optional variable
- etype = descr[rowindex][colindex]
- exttype, exturi, extid = cnx.describe(row[colindex])
- if exturi == 'system' or not exturi in self.repo.sources_by_uri:
- eid = self.extid2eid(str(row[colindex]), etype,
- session)
+ eid, local = self.local_eid(cnx, row[colindex], session)
+ if local:
+ localrow = True
+ if eid is not None:
row[colindex] = eid
else:
# skip this row
del rows[rowindex]
del descr[rowindex]
break
+ else:
+ # skip row if it only contains eids of entities which
+ # are actually from a source we also know locally,
+ # except if some args specified (XXX should actually
+ # check if there are some args local to the source)
+ if not (translator.has_local_eid or localrow):
+ del rows[rowindex]
+ del descr[rowindex]
results = rows
else:
results = []
@@ -371,7 +411,7 @@
def _entity_relations_and_kwargs(self, session, entity):
relations = []
kwargs = {'x': self.eid2extid(entity.eid, session)}
- for key, val in entity.iteritems():
+ for key, val in entity.cw_attr_cache.iteritems():
relations.append('X %s %%(%s)s' % (key, key))
kwargs[key] = val
return relations, kwargs
@@ -434,6 +474,7 @@
self._session = session
self.kwargs = args
self.need_translation = False
+ self.has_local_eid = False
return self.visit_union(rqlst)
def visit_union(self, node):
@@ -580,6 +621,7 @@
def visit_constant(self, node):
if self.need_translation or node.uidtype:
if node.type == 'Int':
+ self.has_local_eid = True
return str(self.eid2extid(node.value))
if node.type == 'Substitute':
key = node.value
@@ -587,6 +629,7 @@
if not key in self._const_var:
self.kwargs[key] = self.eid2extid(self.kwargs[key])
self._const_var[key] = None
+ self.has_local_eid = True
return node.as_string()
def visit_variableref(self, node):
--- a/server/sources/rql2sql.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/rql2sql.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -365,6 +365,11 @@
yield 1
return
thisexistssols, thisexistsvars = self.existssols[exists]
+ # when iterating other solutions inner to an EXISTS subquery, we should
+ # reset variables which have this exists node as scope at each iteration
+ for var in exists.stmt.defined_vars.itervalues():
+ if var.scope is exists:
+ thisexistsvars.add(var.name)
origsol = self.solution
origtables = self.tables
done = self.done
@@ -416,7 +421,7 @@
p = compnode.parent
oor = None
while not isinstance(p, Select):
- if isinstance(p, Or):
+ if isinstance(p, (Or, Not)):
oor = p
p = p.parent
if oor is not None:
@@ -434,7 +439,7 @@
while not isinstance(p, Select):
if p in ors or p is None: # p is None for nodes already in fakehaving
break
- if isinstance(p, Or):
+ if isinstance(p, (Or, Not)):
oor = p
p = p.parent
else:
@@ -508,7 +513,7 @@
select.need_distinct = True
return self.__union_sql(union, needalias)
- def union_sql(self, union, needalias=False): # pylint: disable-msg=E0202
+ def union_sql(self, union, needalias=False): # pylint: disable=E0202
if len(union.children) == 1:
return self.select_sql(union.children[0], needalias)
sqls = ('(%s)' % self.select_sql(select, needalias)
@@ -991,24 +996,21 @@
unification (eg X attr1 A, Y attr2 A). In case of selection,
nothing to do here.
"""
- contextrels = {}
for var in rhs_vars:
if var.name in self._varmap:
# ensure table is added
self._var_info(var.variable)
principal = var.variable.stinfo.get('principal')
if principal is not None and principal is not relation:
- contextrels[var.name] = relation
- if not contextrels:
- return ''
- # we have to generate unification expression
- lhssql = self._inlined_var_sql(relation.children[0].variable,
- relation.r_type)
- try:
- self._state.ignore_varmap = True
- return '%s%s' % (lhssql, relation.children[1].accept(self))
- finally:
- self._state.ignore_varmap = False
+ # we have to generate unification expression
+ lhssql = self._inlined_var_sql(relation.children[0].variable,
+ relation.r_type)
+ try:
+ self._state.ignore_varmap = True
+ return '%s%s' % (lhssql, relation.children[1].accept(self))
+ finally:
+ self._state.ignore_varmap = False
+ return ''
def _visit_attribute_relation(self, rel):
"""generate SQL for an attribute relation"""
@@ -1337,10 +1339,10 @@
# tables handling #########################################################
- def alias_and_add_table(self, tablename):
+ def alias_and_add_table(self, tablename, scope=-1):
alias = '%s%s' % (tablename, self._state.count)
self._state.count += 1
- self.add_table('%s AS %s' % (tablename, alias), alias)
+ self.add_table('%s AS %s' % (tablename, alias), alias, scope)
return alias
def add_table(self, table, key=None, scope=-1):
@@ -1437,6 +1439,7 @@
except AttributeError:
pass
self._state.done.add(relation)
- alias = self.alias_and_add_table(self.dbhelper.fti_table)
+ scope = self._state.scopes[relation.scope]
+ alias = self.alias_and_add_table(self.dbhelper.fti_table, scope=scope)
relation._q_sqltable = alias
return alias
--- a/server/sources/storages.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sources/storages.py Fri Mar 11 09:46:45 2011 +0100
@@ -24,6 +24,8 @@
from cubicweb import Binary, ValidationError
from cubicweb.server import hook
+from cubicweb.server.ssplanner import EditedEntity
+
def set_attribute_storage(repo, etype, attr, storage):
repo.system_source.set_storage(etype, attr, storage)
@@ -31,6 +33,7 @@
def unset_attribute_storage(repo, etype, attr):
repo.system_source.unset_storage(etype, attr)
+
class Storage(object):
"""abstract storage
@@ -126,14 +129,14 @@
def entity_added(self, entity, attr):
"""an entity using this storage for attr has been added"""
if entity._cw.transaction_data.get('fs_importing'):
- binary = Binary(file(entity[attr].getvalue(), 'rb').read())
+ binary = Binary(file(entity.cw_edited[attr].getvalue(), 'rb').read())
else:
- binary = entity.pop(attr)
+ binary = entity.cw_edited.pop(attr)
fpath = self.new_fs_path(entity, attr)
# bytes storage used to store file's path
- entity[attr] = Binary(fpath)
+ entity.cw_edited.edited_attribute(attr, Binary(fpath))
file(fpath, 'wb').write(binary.getvalue())
- hook.set_operation(entity._cw, 'bfss_added', fpath, AddFileOp)
+ AddFileOp.get_instance(entity._cw).add_data(fpath)
return binary
def entity_updated(self, entity, attr):
@@ -144,7 +147,7 @@
# If we are importing from the filesystem, the file already exists.
# We do not need to create it but we need to fetch the content of
# the file as the actual content of the attribute
- fpath = entity[attr].getvalue()
+ fpath = entity.cw_edited[attr].getvalue()
binary = Binary(file(fpath, 'rb').read())
else:
# We must store the content of the attributes
@@ -156,7 +159,7 @@
# went ok.
#
# fetch the current attribute value in memory
- binary = entity.pop(attr)
+ binary = entity.cw_edited.pop(attr)
# Get filename for it
fpath = self.new_fs_path(entity, attr)
assert not osp.exists(fpath)
@@ -164,20 +167,19 @@
file(fpath, 'wb').write(binary.getvalue())
# Mark the new file as added during the transaction.
# The file will be removed on rollback
- hook.set_operation(entity._cw, 'bfss_added', fpath, AddFileOp)
+ AddFileOp.get_instance(entity._cw).add_data(fpath)
if oldpath != fpath:
# register the new location for the file.
- entity[attr] = Binary(fpath)
+ entity.cw_edited.edited_attribute(attr, Binary(fpath))
# Mark the old file as useless so the file will be removed at
# commit.
- hook.set_operation(entity._cw, 'bfss_deleted', oldpath,
- DeleteFileOp)
+ DeleteFileOp.get_instance(entity._cw).add_data(oldpath)
return binary
def entity_deleted(self, entity, attr):
"""an entity using this storage for attr has been deleted"""
fpath = self.current_fs_path(entity, attr)
- hook.set_operation(entity._cw, 'bfss_deleted', fpath, DeleteFileOp)
+ DeleteFileOp.get_instance(entity._cw).add_data(fpath)
def new_fs_path(self, entity, attr):
# We try to get some hint about how to name the file using attribute's
@@ -209,7 +211,7 @@
def migrate_entity(self, entity, attribute):
"""migrate an entity attribute to the storage"""
- entity.edited_attributes = set()
+ entity.cw_edited = EditedEntity(entity, **entity.cw_attr_cache)
self.entity_added(entity, attribute)
session = entity._cw
source = session.repo.system_source
@@ -217,19 +219,20 @@
sql = source.sqlgen.update('cw_' + entity.__regid__, attrs,
['cw_eid'])
source.doexec(session, sql, attrs)
+ entity.cw_edited = None
-class AddFileOp(hook.Operation):
+class AddFileOp(hook.DataOperationMixIn, hook.Operation):
def rollback_event(self):
- for filepath in self.session.transaction_data.pop('bfss_added'):
+ for filepath in self.get_data():
try:
unlink(filepath)
except Exception, ex:
self.error('cant remove %s: %s' % (filepath, ex))
-class DeleteFileOp(hook.Operation):
- def commit_event(self):
- for filepath in self.session.transaction_data.pop('bfss_deleted'):
+class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
+ def postcommit_event(self):
+ for filepath in self.get_data():
try:
unlink(filepath)
except Exception, ex:
--- a/server/sqlutils.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/sqlutils.py Fri Mar 11 09:46:45 2011 +0100
@@ -165,7 +165,7 @@
self.OperationalError = dbapi_module.OperationalError
self.InterfaceError = dbapi_module.InterfaceError
self.DbapiError = dbapi_module.Error
- self._binary = dbapi_module.Binary
+ self._binary = self.dbhelper.binary_value
self._process_value = dbapi_module.process_value
self._dbencoding = dbencoding
@@ -260,8 +260,7 @@
"""
attrs = {}
eschema = entity.e_schema
- for attr in entity.edited_attributes:
- value = entity[attr]
+ for attr, value in entity.cw_edited.iteritems():
if value is not None and eschema.subjrels[attr].final:
atype = str(entity.e_schema.destination(attr))
if atype == 'Boolean':
--- a/server/ssplanner.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/ssplanner.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,6 +21,8 @@
__docformat__ = "restructuredtext en"
+from copy import copy
+
from rql.stmts import Union, Select
from rql.nodes import Constant, Relation
@@ -55,11 +57,11 @@
if isinstance(rhs, Constant) and not rhs.uid:
# add constant values to entity def
value = rhs.eval(plan.args)
- eschema = edef.e_schema
+ eschema = edef.entity.e_schema
attrtype = eschema.subjrels[rtype].objects(eschema)[0]
if attrtype == 'Password' and isinstance(value, unicode):
value = value.encode('UTF8')
- edef[rtype] = value
+ edef.edited_attribute(rtype, value)
elif to_build.has_key(str(rhs)):
# create a relation between two newly created variables
plan.add_relation_def((edef, rtype, to_build[rhs.name]))
@@ -126,6 +128,132 @@
return select
+_MARKER = object()
+
+class dict_protocol_catcher(object):
+ def __init__(self, entity):
+ self.__entity = entity
+ def __getitem__(self, attr):
+ return self.__entity.cw_edited[attr]
+ def __setitem__(self, attr, value):
+ self.__entity.cw_edited[attr] = value
+ def __getattr__(self, attr):
+ return getattr(self.__entity, attr)
+
+
+class EditedEntity(dict):
+ """encapsulate entities attributes being written by an RQL query"""
+ def __init__(self, entity, **kwargs):
+ dict.__init__(self, **kwargs)
+ self.entity = entity
+ self.skip_security = set()
+ self.querier_pending_relations = {}
+ self.saved = False
+
+ def __hash__(self):
+ # dict|set keyable
+ return hash(id(self))
+
+ def __cmp__(self, other):
+ # we don't want comparison by value inherited from dict
+ return cmp(id(self), id(other))
+
+ def __setitem__(self, attr, value):
+ assert attr != 'eid'
+ # don't add attribute into skip_security if already in edited
+ # attributes, else we may accidentaly skip a desired security check
+ if attr not in self:
+ self.skip_security.add(attr)
+ self.edited_attribute(attr, value)
+
+ def __delitem__(self, attr):
+ assert not self.saved, 'too late to modify edited attributes'
+ super(EditedEntity, self).__delitem__(attr)
+ self.entity.cw_attr_cache.pop(attr, None)
+
+ def pop(self, attr, *args):
+ # don't update skip_security by design (think to storage api)
+ assert not self.saved, 'too late to modify edited attributes'
+ value = super(EditedEntity, self).pop(attr, *args)
+ self.entity.cw_attr_cache.pop(attr, *args)
+ return value
+
+ def setdefault(self, attr, default):
+ assert attr != 'eid'
+ # don't add attribute into skip_security if already in edited
+ # attributes, else we may accidentaly skip a desired security check
+ if attr not in self:
+ self[attr] = default
+ return self[attr]
+
+ def update(self, values, skipsec=True):
+ if skipsec:
+ setitem = self.__setitem__
+ else:
+ setitem = self.edited_attribute
+ for attr, value in values.iteritems():
+ setitem(attr, value)
+
+ def edited_attribute(self, attr, value):
+ """attribute being edited by a rql query: should'nt be added to
+ skip_security
+ """
+ assert not self.saved, 'too late to modify edited attributes'
+ super(EditedEntity, self).__setitem__(attr, value)
+ self.entity.cw_attr_cache[attr] = value
+
+ def oldnewvalue(self, attr):
+ """returns the couple (old attr value, new attr value)
+
+ NOTE: will only work in a before_update_entity hook
+ """
+ assert not self.saved, 'too late to get the old value'
+ # get new value and remove from local dict to force a db query to
+ # fetch old value
+ newvalue = self.entity.cw_attr_cache.pop(attr, _MARKER)
+ oldvalue = getattr(self.entity, attr)
+ if newvalue is not _MARKER:
+ self.entity.cw_attr_cache[attr] = newvalue
+ else:
+ newvalue = oldvalue
+ return oldvalue, newvalue
+
+ def set_defaults(self):
+ """set default values according to the schema"""
+ for attr, value in self.entity.e_schema.defaults():
+ if not attr in self:
+ self[str(attr)] = value
+
+ def check(self, creation=False):
+ """check the entity edition against its schema. Only final relation
+ are checked here, constraint on actual relations are checked in hooks
+ """
+ entity = self.entity
+ if creation:
+ # on creations, we want to check all relations, especially
+ # required attributes
+ relations = [rschema for rschema in entity.e_schema.subject_relations()
+ if rschema.final and rschema.type != 'eid']
+ else:
+ relations = [entity._cw.vreg.schema.rschema(rtype)
+ for rtype in self]
+ from yams import ValidationError
+ try:
+ entity.e_schema.check(dict_protocol_catcher(entity),
+ creation=creation, _=entity._cw._,
+ relations=relations)
+ except ValidationError, ex:
+ ex.entity = self.entity
+ raise
+
+ def clone(self):
+ thecopy = EditedEntity(copy(self.entity))
+ thecopy.entity.cw_attr_cache = copy(self.entity.cw_attr_cache)
+ thecopy.entity._cw_related_cache = {}
+ thecopy.update(self, skipsec=False)
+ return thecopy
+
+
class SSPlanner(object):
"""SingleSourcePlanner: build execution plan for rql queries
@@ -162,7 +290,7 @@
etype_class = session.vreg['etypes'].etype_class
for etype, var in rqlst.main_variables:
# need to do this since entity class is shared w. web client code !
- to_build[var.name] = etype_class(etype)(session)
+ to_build[var.name] = EditedEntity(etype_class(etype)(session))
plan.add_entity_def(to_build[var.name])
# add constant values to entity def, mark variables to be selected
to_select = _extract_const_attributes(plan, rqlst, to_build)
@@ -177,7 +305,7 @@
for edef, rdefs in to_select.items():
# create a select rql st to fetch needed data
select = Select()
- eschema = edef.e_schema
+ eschema = edef.entity.e_schema
for i, (rtype, term, reverse) in enumerate(rdefs):
if getattr(term, 'variable', None) in eidconsts:
value = eidconsts[term.variable]
@@ -284,10 +412,8 @@
rhsinfo = selectedidx[rhskey][:-1] + (None,)
rschema = getrschema(relation.r_type)
updatedefs.append( (lhsinfo, rhsinfo, rschema) )
- if rschema.final or rschema.inlined:
- attributes.add(relation.r_type)
# the update step
- step = UpdateStep(plan, updatedefs, attributes)
+ step = UpdateStep(plan, updatedefs)
# when necessary add substep to fetch yet unknown values
select = _build_substep_query(select, rqlst)
if select is not None:
@@ -476,7 +602,7 @@
result = [[]]
for row in result:
# get a new entity definition for this row
- edef = base_edef.cw_copy()
+ edef = base_edef.clone()
# complete this entity def using row values
index = 0
for rtype, rorder, value in self.rdefs:
@@ -484,7 +610,7 @@
value = row[index]
index += 1
if rorder == InsertRelationsStep.FINAL:
- edef._cw_rql_set_value(rtype, value)
+ edef.edited_attribute(rtype, value)
elif rorder == InsertRelationsStep.RELATION:
self.plan.add_relation_def( (edef, rtype, value) )
edef.querier_pending_relations[(rtype, 'subject')] = value
@@ -495,6 +621,7 @@
self.plan.substitute_entity_def(base_edef, edefs)
return result
+
class InsertStep(Step):
"""step consisting in inserting new entities / relations"""
@@ -518,21 +645,16 @@
def execute(self):
"""execute this step"""
results = self.execute_child()
- todelete = frozenset(typed_eid(eid) for eid, in results)
- session = self.plan.session
- delete = session.repo.glob_delete_entity
- # mark eids as being deleted in session info and setup cache update
- # operation (register pending eids before actual deletion to avoid
- # multiple call to glob_delete_entity)
- try:
- pending = session.transaction_data['pendingeids']
- except KeyError:
- pending = session.transaction_data['pendingeids'] = set()
- CleanupDeletedEidsCacheOp(session)
- actual = todelete - pending
- pending |= actual
- for eid in actual:
- delete(session, eid)
+ if results:
+ todelete = frozenset(typed_eid(eid) for eid, in results)
+ session = self.plan.session
+ # mark eids as being deleted in session info and setup cache update
+ # operation (register pending eids before actual deletion to avoid
+ # multiple call to glob_delete_entities)
+ op = CleanupDeletedEidsCacheOp.get_instance(session)
+ actual = todelete - op._container
+ op._container |= actual
+ session.repo.glob_delete_entities(session, actual)
return results
class DeleteRelationsStep(Step):
@@ -555,10 +677,9 @@
definitions and from results fetched in previous step
"""
- def __init__(self, plan, updatedefs, attributes):
+ def __init__(self, plan, updatedefs):
Step.__init__(self, plan)
self.updatedefs = updatedefs
- self.attributes = attributes
def execute(self):
"""execute this step"""
@@ -578,16 +699,17 @@
if rschema.final or rschema.inlined:
eid = typed_eid(lhsval)
try:
- edef = edefs[eid]
+ edited = edefs[eid]
except KeyError:
- edefs[eid] = edef = session.entity_from_eid(eid)
- edef._cw_rql_set_value(str(rschema), rhsval)
+ edef = session.entity_from_eid(eid)
+ edefs[eid] = edited = EditedEntity(edef)
+ edited.edited_attribute(str(rschema), rhsval)
else:
repo.glob_add_relation(session, lhsval, str(rschema), rhsval)
result[i] = newrow
# update entities
- for eid, edef in edefs.iteritems():
- repo.glob_update_entity(session, edef, set(self.attributes))
+ for eid, edited in edefs.iteritems():
+ repo.glob_update_entity(session, edited)
return result
def _handle_relterm(info, row, newrow):
--- a/server/test/data/extern_mapping.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/data/extern_mapping.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
+"""mapping file for source used in unittest_multisources.py"""
-"""
support_entities = {'Card': True, 'Affaire': True, 'State': True}
support_relations = {'in_state': True, 'documented_by': True, 'multisource_inlined_rel': True}
+
+cross_relations = set( ('documented_by',) )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/ldap_test.ldif Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,55 @@
+dn: dc=cubicweb,dc=test
+structuralObjectClass: organization
+objectClass: dcObject
+objectClass: organization
+o: cubicweb
+dc: cubicweb
+
+dn: ou=People,dc=cubicweb,dc=test
+objectClass: organizationalUnit
+ou: People
+structuralObjectClass: organizationalUnit
+
+dn: uid=syt,ou=People,dc=cubicweb,dc=test
+loginShell: /bin/bash
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: top
+objectClass: shadowAccount
+structuralObjectClass: inetOrgPerson
+cn: Sylvain Thenault
+sn: Thenault
+shadowMax: 99999
+gidNumber: 1004
+uid: syt
+homeDirectory: /home/syt
+shadowFlag: 134538764
+uidNumber: 1004
+givenName: Sylvain
+telephoneNumber: 106
+displayName: sthenault
+gecos: Sylvain Thenault
+mail: sylvain.thenault@logilab.fr
+mail: syt@logilab.fr
+
+dn: uid=adim,ou=People,dc=cubicweb,dc=test
+loginShell: /bin/bash
+objectClass: inetOrgPerson
+objectClass: posixAccount
+objectClass: top
+objectClass: shadowAccount
+cn: Adrien Di Mascio
+sn: Di Mascio
+shadowMax: 99999
+gidNumber: 1006
+uid: adim
+homeDirectory: /home/adim
+uidNumber: 1006
+structuralObjectClass: inetOrgPerson
+givenName: Adrien
+telephoneNumber: 109
+displayName: adimascio
+gecos: Adrien Di Mascio
+mail: adim@logilab.fr
+mail: adrien.dimascio@logilab.fr
+
--- a/server/test/data/migratedapp/schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/data/migratedapp/schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
+"""cw.server.migraction test"""
from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
SubjectRelation,
RichString, String, Int, Boolean, Datetime, Date)
@@ -66,8 +64,9 @@
whatever = Int(default=2) # keep it before `date` for unittest_migraction.test_add_attribute_int
date = Datetime()
type = String(maxsize=1)
+ unique_id = String(maxsize=1, required=True, unique=True)
mydate = Date(default='TODAY')
- shortpara = String(maxsize=64)
+ shortpara = String(maxsize=64, default='hop')
ecrit_par = SubjectRelation('Personne', constraints=[RQLConstraint('S concerne A, O concerne A')])
attachment = SubjectRelation('File')
--- a/server/test/data/schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/data/schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,7 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
- SubjectRelation, RichString, String, Int, Boolean, Datetime)
+ SubjectRelation, RichString, String, Int, Float,
+ Boolean, Datetime)
from yams.constraints import SizeConstraint
from cubicweb.schema import (WorkflowableEntityType,
RQLConstraint, RQLUniqueConstraint,
@@ -40,7 +41,7 @@
description=_('more detailed description'))
duration = Int()
- invoiced = Int()
+ invoiced = Float()
depends_on = SubjectRelation('Affaire')
require_permission = SubjectRelation('CWPermission')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/slapd.conf.in Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,53 @@
+# This is the main slapd configuration file. See slapd.conf(5) for more
+# info on the configuration options.
+
+#######################################################################
+# Global Directives:
+
+# Features to permit
+#allow bind_v2
+
+# Schema and objectClass definitions
+include /etc/ldap/schema/core.schema
+include /etc/ldap/schema/cosine.schema
+include /etc/ldap/schema/nis.schema
+include /etc/ldap/schema/inetorgperson.schema
+include /etc/ldap/schema/openldap.schema
+include /etc/ldap/schema/misc.schema
+
+# Where the pid file is put. The init.d script
+# will not stop the server if you change this.
+pidfile %(apphome)s/test-slapd.pid
+
+# List of arguments that were passed to the server
+argsfile %(apphome)s/slapd.args
+
+# Read slapd.conf(5) for possible values
+loglevel sync
+# none
+
+# Where the dynamically loaded modules are stored
+modulepath /usr/lib/ldap
+moduleload back_hdb
+moduleload back_bdb
+moduleload back_monitor
+
+# The maximum number of entries that is returned for a search operation
+sizelimit 500
+
+# The tool-threads parameter sets the actual amount of cpu's that is used
+# for indexing.
+tool-threads 1
+
+database bdb
+
+# The base of your directory in database #1
+suffix "dc=cubicweb,dc=test"
+
+# rootdn directive for specifying a superuser on the database. This is needed
+# for syncrepl.
+#rootdn "cn=admin,dc=cubicweb,dc=test"
+#rootpw "cubicwebrocks"
+# Where the database file are physically stored for database #1
+directory "%(apphome)s/ldapdb"
+
--- a/server/test/data/sources Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-[system]
-
-db-driver = sqlite
-db-host =
-adapter = native
-db-name = tmpdb
-db-encoding = UTF-8
-db-user = admin
-db-password = gingkow
-
-[admin]
-login = admin
-password = gingkow
-
--- a/server/test/data/sources_extern Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/data/sources_extern Fri Mar 11 09:46:45 2011 +0100
@@ -1,13 +1,4 @@
[system]
-
db-driver = sqlite
-db-host =
-adapter = native
db-name = tmpdb-extern
db-encoding = UTF-8
-db-user = admin
-db-password = gingkow
-
-[admin]
-login = admin
-password = gingkow
--- a/server/test/data/sources_ldap1 Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-[system]
-adapter=native
-# database driver (postgres or sqlite)
-db-driver=sqlite
-# database host
-db-host=
-# database name
-db-name=tmpdb
-# database user
-db-user=admin
-# database password
-db-password=gingkow
-# database encoding
-db-encoding=utf8
-
-[admin]
-login = admin
-password = gingkow
-
-[ldapuser]
-adapter=ldapuser
-# ldap host
-host=ldap1
-# base DN to lookup for usres
-user-base-dn=ou=People,dc=logilab,dc=fr
-# user search scope
-user-scope=ONELEVEL
-# classes of user
-user-classes=top,posixAccount
-# attribute used as login on authentication
-user-login-attr=uid
-# name of a group in which ldap users will be by default
-user-default-group=users
-# map from ldap user attributes to cubicweb attributes
-user-attrs-map=gecos:email,uid:login
--- a/server/test/data/sources_ldap2 Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-[system]
-adapter=native
-# database driver (postgres or sqlite)
-db-driver=sqlite
-# database host
-db-host=
-# database name
-db-name=tmpdb
-# database user
-db-user=admin
-# database password
-db-password=gingkow
-# database encoding
-db-encoding=utf8
-
-[admin]
-login = admin
-password = gingkow
-
-[ldapuser]
-adapter=ldapuser
-# ldap host
-host=ldap1
-# base DN to lookup for usres
-user-base-dn=ou=People,dc=logilab,dc=net
-# user search scope
-user-scope=ONELEVEL
-# classes of user
-user-classes=top,OpenLDAPperson
-# attribute used as login on authentication
-user-login-attr=uid
-# name of a group in which ldap users will be by default
-user-default-group=users
-# map from ldap user attributes to cubicweb attributes
-user-attrs-map=mail:email,uid:login
--- a/server/test/data/sources_multi Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/data/sources_multi Fri Mar 11 09:46:45 2011 +0100
@@ -1,28 +1,5 @@
[system]
-
db-driver = sqlite
-db-host =
adapter = native
db-name = tmpdb-multi
db-encoding = UTF-8
-db-user = admin
-db-password = gingkow
-
-[extern]
-adapter = pyrorql
-pyro-ns-id = extern
-cubicweb-user = admin
-cubicweb-password = gingkow
-mapping-file = extern_mapping.py
-base-url=http://extern.org/
-
-[extern-multi]
-adapter = pyrorql
-pyro-ns-id = extern-multi
-cubicweb-user = admin
-cubicweb-password = gingkow
-mapping-file = extern_mapping.py
-
-[admin]
-login = admin
-password = gingkow
--- a/server/test/unittest_checkintegrity.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_checkintegrity.py Fri Mar 11 09:46:45 2011 +0100
@@ -26,7 +26,7 @@
class CheckIntegrityTC(TestCase):
def setUp(self):
- self.repo, self.cnx = init_test_database()
+ self.repo, self.cnx = init_test_database(apphome=self.datadir)
self.execute = self.cnx.cursor().execute
self.session = self.repo._sessions[self.cnx.sessionid]
sys.stderr = sys.stdout = StringIO()
--- a/server/test/unittest_fti.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_fti.py Fri Mar 11 09:46:45 2011 +0100
@@ -2,18 +2,23 @@
import socket
+from logilab.common.testlib import SkipTest
+
from cubicweb.devtools import ApptestConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.selectors import is_instance
from cubicweb.entities.adapters import IFTIndexableAdapter
+AT_LOGILAB = socket.gethostname().endswith('.logilab.fr')
+
+
class PostgresFTITC(CubicWebTC):
config = ApptestConfiguration('data', sourcefile='sources_fti')
- def setUp(self):
- if not socket.gethostname().endswith('.logilab.fr'):
- self.skipTest('XXX require logilab configuration')
- super(PostgresFTITC, self).setUp()
+ @classmethod
+ def setUpClass(cls):
+ if not AT_LOGILAB:
+ raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
def test_occurence_count(self):
req = self.request()
@@ -44,7 +49,6 @@
self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
[[c3.eid], [c1.eid], [c2.eid]])
-
def test_entity_weight(self):
class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
__select__ = is_instance('Personne')
@@ -58,6 +62,7 @@
self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
[[c1.eid], [c3.eid], [c2.eid]])
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_hook.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_hook.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""unit/functional tests for cubicweb.server.hook"""
+from __future__ import with_statement
+
from logilab.common.testlib import TestCase, unittest_main, mock_object
@@ -79,7 +81,7 @@
config.bootstrap_cubes()
schema = config.load_schema()
-def teardown_module(*args):
+def tearDownModule(*args):
global config, schema
del config, schema
@@ -101,20 +103,23 @@
def test_register_bad_hook1(self):
class _Hook(hook.Hook):
events = ('before_add_entiti',)
- ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEqual(str(ex), 'bad event before_add_entiti on %s._Hook' % __name__)
+ with self.assertRaises(Exception) as cm:
+ self.o.register(_Hook)
+ self.assertEqual(str(cm.exception), 'bad event before_add_entiti on %s._Hook' % __name__)
def test_register_bad_hook2(self):
class _Hook(hook.Hook):
events = None
- ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEqual(str(ex), 'bad .events attribute None on %s._Hook' % __name__)
+ with self.assertRaises(Exception) as cm:
+ self.o.register(_Hook)
+ self.assertEqual(str(cm.exception), 'bad .events attribute None on %s._Hook' % __name__)
def test_register_bad_hook3(self):
class _Hook(hook.Hook):
events = 'before_add_entity'
- ex = self.assertRaises(Exception, self.o.register, _Hook)
- self.assertEqual(str(ex), 'bad event b on %s._Hook' % __name__)
+ with self.assertRaises(Exception) as cm:
+ self.o.register(_Hook)
+ self.assertEqual(str(cm.exception), 'bad event b on %s._Hook' % __name__)
def test_call_hook(self):
self.o.register(AddAnyHook)
--- a/server/test/unittest_ldapuser.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_ldapuser.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,25 +17,31 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""cubicweb.server.sources.ldapusers unit and functional tests"""
-import socket
+import os
+import shutil
+import time
+from os.path import abspath, join, exists
+import subprocess
+from socket import socket, error as socketerror
from logilab.common.testlib import TestCase, unittest_main, mock_object
-from cubicweb.devtools import TestServerConfiguration
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import RQLGeneratorTC
+from cubicweb.devtools.httptest import get_available_port
from cubicweb.server.sources.ldapuser import *
-if '17.1' in socket.gethostbyname('ldap1'):
- SYT = 'syt'
- SYT_EMAIL = 'Sylvain Thenault'
- ADIM = 'adim'
- SOURCESFILE = 'data/sources_ldap1'
-else:
- SYT = 'sthenault'
- SYT_EMAIL = 'sylvain.thenault@logilab.fr'
- ADIM = 'adimascio'
- SOURCESFILE = 'data/sources_ldap2'
+SYT = 'syt'
+SYT_EMAIL = 'Sylvain Thenault'
+ADIM = 'adim'
+CONFIG = u'''host=%s
+user-base-dn=ou=People,dc=cubicweb,dc=test
+user-scope=ONELEVEL
+user-classes=top,posixAccount
+user-login-attr=uid
+user-default-group=users
+user-attrs-map=gecos:email,uid:login
+'''
def nopwd_authenticate(self, session, login, password):
@@ -57,25 +63,80 @@
# don't check upassword !
return self.extid2eid(user['dn'], 'CWUser', session)
+def setUpModule(*args):
+ create_slapd_configuration(LDAPUserSourceTC.config)
+ global repo
+ try:
+ LDAPUserSourceTC._init_repo()
+ repo = LDAPUserSourceTC.repo
+ add_ldap_source(LDAPUserSourceTC.cnx)
+ except:
+ terminate_slapd()
+ raise
+def tearDownModule(*args):
+ global repo
+ repo.shutdown()
+ del repo
+ terminate_slapd()
+
+def add_ldap_source(cnx):
+ cnx.request().create_entity('CWSource', name=u'ldapuser', type=u'ldapuser',
+ config=CONFIG)
+ cnx.commit()
+
+def create_slapd_configuration(config):
+ global slapd_process, CONFIG
+ basedir = join(config.apphome, "ldapdb")
+ slapdconf = join(config.apphome, "slapd.conf")
+ confin = file(join(config.apphome, "slapd.conf.in")).read()
+ confstream = file(slapdconf, 'w')
+ confstream.write(confin % {'apphome': config.apphome})
+ confstream.close()
+ if not exists(basedir):
+ os.makedirs(basedir)
+ # fill ldap server with some data
+ ldiffile = join(config.apphome, "ldap_test.ldif")
+ print "Initing ldap database"
+ cmdline = "/usr/sbin/slapadd -f %s -l %s -c" % (slapdconf, ldiffile)
+ subprocess.call(cmdline, shell=True)
+
+
+ #ldapuri = 'ldapi://' + join(basedir, "ldapi").replace('/', '%2f')
+ port = get_available_port(xrange(9000, 9100))
+ host = 'localhost:%s' % port
+ ldapuri = 'ldap://%s' % host
+ cmdline = ["/usr/sbin/slapd", "-f", slapdconf, "-h", ldapuri, "-d", "0"]
+ print "Starting slapd on", ldapuri
+ slapd_process = subprocess.Popen(cmdline)
+ time.sleep(0.2)
+ if slapd_process.poll() is None:
+ print "slapd started with pid %s" % slapd_process.pid
+ else:
+ raise EnvironmentError('Cannot start slapd with cmdline="%s" (from directory "%s")' %
+ (" ".join(cmdline), os.getcwd()))
+ CONFIG = CONFIG % host
+
+def terminate_slapd():
+ global slapd_process
+ if slapd_process.returncode is None:
+ print "terminating slapd"
+ if hasattr(slapd_process, 'terminate'):
+ slapd_process.terminate()
+ else:
+ import os, signal
+ os.kill(slapd_process.pid, signal.SIGTERM)
+ slapd_process.wait()
+ print "DONE"
+
+ del slapd_process
class LDAPUserSourceTC(CubicWebTC):
- config = TestServerConfiguration('data')
- config.sources_file = lambda: SOURCESFILE
def patch_authenticate(self):
self._orig_authenticate = LDAPUserSource.authenticate
LDAPUserSource.authenticate = nopwd_authenticate
- def setup_database(self):
- # XXX: need this first query else we get 'database is locked' from
- # sqlite since it doesn't support multiple connections on the same
- # database
- # so doing, ldap inserted users don't get removed between each test
- rset = self.sexecute('CWUser X')
- # check we get some users from ldap
- self.assert_(len(rset) > 1)
-
def tearDown(self):
if hasattr(self, '_orig_authenticate'):
LDAPUserSource.authenticate = self._orig_authenticate
@@ -93,7 +154,8 @@
def test_base(self):
# check a known one
- e = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
+ rset = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})
+ e = rset.get_entity(0, 0)
self.assertEqual(e.login, SYT)
e.complete()
self.assertEqual(e.creation_date, None)
@@ -382,19 +444,10 @@
res = trfunc.apply([[1, 2], [2, 4], [3, 6], [1, 5]])
self.assertEqual(res, [[1, 5], [2, 4], [3, 6]])
-# XXX
-LDAPUserSourceTC._init_repo()
-repo = LDAPUserSourceTC.repo
-
-def teardown_module(*args):
- global repo
- del repo
- del RQL2LDAPFilterTC.schema
-
class RQL2LDAPFilterTC(RQLGeneratorTC):
- schema = repo.schema
def setUp(self):
+ self.schema = repo.schema
RQLGeneratorTC.setUp(self)
ldapsource = repo.sources[-1]
self.pool = repo._get_pool()
--- a/server/test/unittest_migractions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_migractions.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,29 +15,35 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for module cubicweb.server.migractions
-"""
+"""unit tests for module cubicweb.server.migractions"""
+
+from __future__ import with_statement
from copy import deepcopy
from datetime import date
from os.path import join
-from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.testlib import TestCase, unittest_main, Tags, tag
-from cubicweb import ConfigurationError
+from yams.constraints import UniqueConstraint
+
+from cubicweb import ConfigurationError, ValidationError
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.schema import CubicWebSchemaLoader
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.migractions import *
migrschema = None
-def teardown_module(*args):
+def tearDownModule(*args):
global migrschema
del migrschema
- del MigrationCommandsTC.origschema
+ if hasattr(MigrationCommandsTC, 'origschema'):
+ del MigrationCommandsTC.origschema
class MigrationCommandsTC(CubicWebTC):
+ tags = CubicWebTC.tags | Tags(('server', 'migration', 'migractions'))
+
@classmethod
def init_config(cls, config):
super(MigrationCommandsTC, cls).init_config(config)
@@ -47,9 +53,11 @@
cls.origschema = deepcopy(cls.repo.schema)
# hack to read the schema from data/migrschema
config.appid = join('data', 'migratedapp')
+ config._apphome = cls.datapath('migratedapp')
global migrschema
migrschema = config.load_schema()
config.appid = 'data'
+ config._apphome = cls.datadir
assert 'Folder' in migrschema
@classmethod
@@ -72,6 +80,10 @@
assert self.cnx is self.mh._cnx
assert self.session is self.mh.session, (self.session.id, self.mh.session.id)
+ def tearDown(self):
+ CubicWebTC.tearDown(self)
+ self.repo.vreg['etypes'].clear_caches()
+
def test_add_attribute_int(self):
self.failIf('whatever' in self.schema)
self.request().create_entity('Note')
@@ -83,8 +95,12 @@
self.assertEqual(self.schema['whatever'].subjects(), ('Note',))
self.assertEqual(self.schema['whatever'].objects(), ('Int',))
self.assertEqual(self.schema['Note'].default('whatever'), 2)
+ # test default value set on existing entities
note = self.execute('Note X').get_entity(0, 0)
self.assertEqual(note.whatever, 2)
+ # test default value set for next entities
+ self.assertEqual(self.request().create_entity('Note').whatever, 2)
+ # test attribute order
orderdict2 = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, '
'RDEF relation_type RT, RDEF ordernum O, RT name RTN'))
whateverorder = migrschema['whatever'].rdef('Note', 'Int').order
@@ -103,6 +119,9 @@
self.mh.rollback()
def test_add_attribute_varchar(self):
+ self.failIf('whatever' in self.schema)
+ self.request().create_entity('Note')
+ self.commit()
self.failIf('shortpara' in self.schema)
self.mh.cmd_add_attribute('Note', 'shortpara')
self.failUnless('shortpara' in self.schema)
@@ -112,6 +131,11 @@
notesql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' and name='%sNote'" % SQL_PREFIX)[0][0]
fields = dict(x.strip().split()[:2] for x in notesql.split('(', 1)[1].rsplit(')', 1)[0].split(','))
self.assertEqual(fields['%sshortpara' % SQL_PREFIX], 'varchar(64)')
+ req = self.request()
+ # test default value set on existing entities
+ self.assertEqual(req.execute('Note X').get_entity(0, 0).shortpara, 'hop')
+ # test default value set for next entities
+ self.assertEqual(req.create_entity('Note').shortpara, 'hop')
self.mh.rollback()
def test_add_datetime_with_default_value_attribute(self):
@@ -130,6 +154,29 @@
self.assertEqual(d2, testdate)
self.mh.rollback()
+ def test_drop_chosen_constraints_ctxmanager(self):
+ with self.mh.cmd_dropped_constraints('Note', 'unique_id', UniqueConstraint):
+ self.mh.cmd_add_attribute('Note', 'unique_id')
+ # make sure the maxsize constraint is not dropped
+ self.assertRaises(ValidationError,
+ self.mh.rqlexec,
+ 'INSERT Note N: N unique_id "xyz"')
+ self.mh.rollback()
+ # make sure the unique constraint is dropped
+ self.mh.rqlexec('INSERT Note N: N unique_id "x"')
+ self.mh.rqlexec('INSERT Note N: N unique_id "x"')
+ self.mh.rqlexec('DELETE Note N')
+ self.mh.rollback()
+
+ def test_drop_required_ctxmanager(self):
+ with self.mh.cmd_dropped_constraints('Note', 'unique_id', cstrtype=None,
+ droprequired=True):
+ self.mh.cmd_add_attribute('Note', 'unique_id')
+ self.mh.rqlexec('INSERT Note N')
+ # make sure the required=True was restored
+ self.assertRaises(ValidationError, self.mh.rqlexec, 'INSERT Note N')
+ self.mh.rollback()
+
def test_rename_attribute(self):
self.failIf('civility' in self.schema)
eid1 = self.mh.rqlexec('INSERT Personne X: X nom "lui", X sexe "M"')[0][0]
@@ -145,7 +192,8 @@
def test_workflow_actions(self):
- wf = self.mh.cmd_add_workflow(u'foo', ('Personne', 'Email'))
+ wf = self.mh.cmd_add_workflow(u'foo', ('Personne', 'Email'),
+ ensure_workflowable=False)
for etype in ('Personne', 'Email'):
s1 = self.mh.rqlexec('Any N WHERE WF workflow_of ET, ET name "%s", WF name N' %
etype)[0][0]
@@ -164,7 +212,7 @@
self.failUnless(self.execute('CWRType X WHERE X name "filed_under2"'))
self.schema.rebuild_infered_relations()
self.assertEqual(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()),
- ['created_by', 'creation_date', 'cwuri',
+ ['created_by', 'creation_date', 'cw_source', 'cwuri',
'description', 'description_format',
'eid',
'filed_under2', 'has_text',
@@ -181,7 +229,8 @@
def test_add_drop_entity_type(self):
self.mh.cmd_add_entity_type('Folder2')
- wf = self.mh.cmd_add_workflow(u'folder2 wf', 'Folder2')
+ wf = self.mh.cmd_add_workflow(u'folder2 wf', 'Folder2',
+ ensure_workflowable=False)
todo = wf.add_state(u'todo', initial=True)
done = wf.add_state(u'done')
wf.add_transition(u'redoit', done, todo)
@@ -297,6 +346,7 @@
self.mh.cmd_change_relation_props('Personne', 'adel', 'String',
fulltextindexed=False)
+ @tag('longrun')
def test_sync_schema_props_perms(self):
cursor = self.mh.session
cursor.set_pool()
@@ -309,7 +359,7 @@
migrschema['titre'].rdefs[('Personne', 'String')].description = 'title for this person'
delete_concerne_rqlexpr = self._rrqlexpr_rset('delete', 'concerne')
add_concerne_rqlexpr = self._rrqlexpr_rset('add', 'concerne')
-
+
self.mh.cmd_sync_schema_props_perms(commit=False)
self.assertEqual(cursor.execute('Any D WHERE X name "Personne", X description D')[0][0],
@@ -386,9 +436,9 @@
self.assertEqual(len(self.schema.eschema('Personne')._unique_together), 1)
self.assertItemsEqual(self.schema.eschema('Personne')._unique_together[0],
('nom', 'prenom', 'datenaiss'))
- rset = cursor.execute('Any C WHERE C is CWUniqueTogetherConstraint')
+ rset = cursor.execute('Any C WHERE C is CWUniqueTogetherConstraint, C constraint_of ET, ET name "Personne"')
self.assertEqual(len(rset), 1)
- relations = [r.rtype.name for r in rset.get_entity(0,0).relations]
+ relations = [r.name for r in rset.get_entity(0, 0).relations]
self.assertItemsEqual(relations, ('nom', 'prenom', 'datenaiss'))
def _erqlexpr_rset(self, action, ertype):
@@ -418,6 +468,7 @@
finally:
self.mh.cmd_set_size_constraint('CWEType', 'description', None)
+ @tag('longrun')
def test_add_remove_cube_and_deps(self):
cubes = set(self.config.cubes())
schema = self.repo.schema
@@ -481,6 +532,7 @@
self.commit()
+ @tag('longrun')
def test_add_remove_cube_no_deps(self):
cubes = set(self.config.cubes())
schema = self.repo.schema
@@ -508,9 +560,11 @@
self.commit()
def test_remove_dep_cube(self):
- ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file')
- self.assertEqual(str(ex), "can't remove cube file, used as a dependency")
+ with self.assertRaises(ConfigurationError) as cm:
+ self.mh.cmd_remove_cube('file')
+ self.assertEqual(str(cm.exception), "can't remove cube file, used as a dependency")
+ @tag('longrun')
def test_introduce_base_class(self):
self.mh.cmd_add_entity_type('Para')
self.mh.repo.schema.rebuild_infered_relations()
@@ -524,7 +578,7 @@
self.assertEqual(self.schema['Text'].specializes().type, 'Para')
# test columns have been actually added
text = self.execute('INSERT Text X: X para "hip", X summary "hop", X newattr "momo"').get_entity(0, 0)
- note = self.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo"').get_entity(0, 0)
+ note = self.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo", X unique_id "x"').get_entity(0, 0)
aff = self.execute('INSERT Affaire X').get_entity(0, 0)
self.failUnless(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s',
{'x': text.eid, 'y': aff.eid}))
--- a/server/test/unittest_msplanner.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_msplanner.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,15 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+"""unit tests for module cubicweb.server.msplanner"""
+
+from __future__ import with_statement
from logilab.common.decorators import clear_cache
+from yams.buildobjs import RelationDefinition
+from rql import BadRQLQuery
+
from cubicweb.devtools import init_test_database
from cubicweb.devtools.repotest import BasePlannerTC, test_plan
@@ -59,8 +65,9 @@
{'X': 'Bookmark'}, {'X': 'CWAttribute'}, {'X': 'CWCache'},
{'X': 'CWConstraint'}, {'X': 'CWConstraintType'}, {'X': 'CWEType'},
{'X': 'CWGroup'}, {'X': 'CWPermission'}, {'X': 'CWProperty'},
- {'X': 'CWRType'}, {'X': 'CWRelation'}, {'X': 'CWUser'},
- {'X': 'CWUniqueTogetherConstraint'},
+ {'X': 'CWRType'}, {'X': 'CWRelation'},
+ {'X': 'CWSource'}, {'X': 'CWSourceHostConfig'},
+ {'X': 'CWUser'}, {'X': 'CWUniqueTogetherConstraint'},
{'X': 'Card'}, {'X': 'Comment'}, {'X': 'Division'},
{'X': 'Email'}, {'X': 'EmailAddress'}, {'X': 'EmailPart'},
{'X': 'EmailThread'}, {'X': 'ExternalUri'}, {'X': 'File'},
@@ -72,9 +79,11 @@
# keep cnx so it's not garbage collected and the associated session is closed
-repo, cnx = init_test_database()
+def setUpModule(*args):
+ global repo, cnx
+ repo, cnx = init_test_database(apphome=BaseMSPlannerTC.datadir)
-def teardown_module(*args):
+def tearDownModule(*args):
global repo, cnx
del repo, cnx
@@ -86,9 +95,9 @@
* ldap source supporting CWUser
* rql source supporting Card
"""
- repo = repo
def setUp(self):
+ self.__class__.repo = repo
#_QuerierTC.setUp(self)
self.setup()
# hijack Affaire security
@@ -537,7 +546,7 @@
[self.ldap, self.system], None,
{'AA': 'table0.C1', 'X': 'table0.C0', 'X.modification_date': 'table0.C1'}, []),
('OneFetchStep',
- [('Any X,AA ORDERBY AA WHERE 5 owned_by X, X modification_date AA, X is CWUser',
+ [('Any X,AA ORDERBY AA WHERE %s owned_by X, X modification_date AA, X is CWUser' % ueid,
[{'AA': 'Datetime', 'X': 'CWUser'}])],
None, None, [self.system],
{'AA': 'table0.C1', 'X': 'table0.C0', 'X.modification_date': 'table0.C1'}, []),
@@ -687,7 +696,7 @@
def test_complex_optional(self):
ueid = self.session.user.eid
self._test('Any U WHERE WF wf_info_for X, X eid %(x)s, WF owned_by U?, WF from_state FS',
- [('OneFetchStep', [('Any U WHERE WF wf_info_for 5, WF owned_by U?, WF from_state FS',
+ [('OneFetchStep', [('Any U WHERE WF wf_info_for %s, WF owned_by U?, WF from_state FS' % ueid,
[{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser'}])],
None, None, [self.system], {}, [])],
{'x': ueid})
@@ -695,7 +704,7 @@
def test_complex_optional(self):
ueid = self.session.user.eid
self._test('Any U WHERE WF wf_info_for X, X eid %(x)s, WF owned_by U?, WF from_state FS',
- [('OneFetchStep', [('Any U WHERE WF wf_info_for 5, WF owned_by U?, WF from_state FS',
+ [('OneFetchStep', [('Any U WHERE WF wf_info_for %s, WF owned_by U?, WF from_state FS' % ueid,
[{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser'}])],
None, None, [self.system], {}, [])],
{'x': ueid})
@@ -751,9 +760,10 @@
])
def test_not_identity(self):
- self._test('Any X WHERE NOT X identity U, U eid %s' % self.session.user.eid,
+ ueid = self.session.user.eid
+ self._test('Any X WHERE NOT X identity U, U eid %s' % ueid,
[('OneFetchStep',
- [('Any X WHERE NOT X identity 5, X is CWUser', [{'X': 'CWUser'}])],
+ [('Any X WHERE NOT X identity %s, X is CWUser' % ueid, [{'X': 'CWUser'}])],
None, None,
[self.ldap, self.system], {}, [])
])
@@ -777,18 +787,19 @@
def test_security_has_text(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X WHERE X has_text "bla"',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
[self.cards, self.system], None, {'E': 'table0.C0'}, []),
('UnionStep', None, None,
[('OneFetchStep',
- [(u'Any X WHERE X has_text "bla", (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
+ [(u'Any X WHERE X has_text "bla", (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), X is Affaire' % {'ueid': ueid},
[{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
None, None, [self.system], {'E': 'table0.C0'}, []),
('OneFetchStep',
- [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is Basket',
+ [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is Basket' % ueid,
[{'X': 'Basket'}]),
- ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser',
+ ('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid,
[{'X': 'CWUser'}]),
('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Note, Personne, Societe, SubDivision, Tag)',
[{'X': 'Card'}, {'X': 'Comment'},
@@ -803,18 +814,20 @@
def test_security_has_text_limit_offset(self):
# use a guest user
self.session = self.user_groups_session('guests')
- # note: same as the above query but because of the subquery usage, the display differs (not printing solutions for each union)
+ ueid = self.session.user.eid
+ # note: same as the above query but because of the subquery usage, the
+ # display differs (not printing solutions for each union)
self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla"',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
[self.cards, self.system], None, {'E': 'table1.C0'}, []),
('UnionFetchStep', [
- ('FetchStep', [('Any X WHERE X has_text "bla", (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
+ ('FetchStep', [('Any X WHERE X has_text "bla", (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), X is Affaire' % {'ueid': ueid},
[{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
[self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),
('FetchStep',
- [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is Basket',
+ [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is Basket' % ueid,
[{'X': 'Basket'}]),
- ('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser',
+ ('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid,
[{'X': 'CWUser'}]),
('Any X WHERE X has_text "bla", X is IN(Card, Comment, Division, Email, EmailThread, File, Folder, Note, Personne, Societe, SubDivision, Tag)',
[{'X': 'Card'}, {'X': 'Comment'},
@@ -839,22 +852,24 @@
"""a guest user trying to see another user: EXISTS(X owned_by U) is automatically inserted"""
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X WHERE X login "bla"',
[('FetchStep',
[('Any X WHERE X login "bla", X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table0.C0'}, []),
('OneFetchStep',
- [('Any X WHERE EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ [('Any X WHERE EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
None, None, [self.system], {'X': 'table0.C0'}, [])])
def test_security_complex_has_text(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X WHERE X has_text "bla", X firstname "bla"',
[('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table0.C0'}, []),
('UnionStep', None, None, [
- ('OneFetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ ('OneFetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
None, None, [self.system], {'X': 'table0.C0'}, []),
('OneFetchStep', [('Any X WHERE X has_text "bla", X firstname "bla", X is Personne', [{'X': 'Personne'}])],
None, None, [self.system], {}, []),
@@ -864,11 +879,12 @@
def test_security_complex_has_text_limit_offset(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla", X firstname "bla"',
[('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table1.C0'}, []),
('UnionFetchStep', [
- ('FetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ ('FetchStep', [('Any X WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
[self.system], {'X': 'table1.C0'}, {'X': 'table0.C0'}, []),
('FetchStep', [('Any X WHERE X has_text "bla", X firstname "bla", X is Personne', [{'X': 'Personne'}])],
[self.system], {}, {'X': 'table0.C0'}, []),
@@ -881,26 +897,30 @@
def test_security_complex_aggregat(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
+ ALL_SOLS = X_ALL_SOLS[:]
+ ALL_SOLS.remove({'X': 'CWSourceHostConfig'}) # not authorized
self._test('Any MAX(X)',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
[self.cards, self.system], None, {'E': 'table1.C0'}, []),
('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table2.C0'}, []),
('UnionFetchStep', [
- ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is Basket', [{'X': 'Basket'}])],
+ ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is Basket' % ueid, [{'X': 'Basket'}])],
[self.system], {}, {'X': 'table0.C0'}, []),
('UnionFetchStep',
[('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
[{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])],
[self.cards, self.system], {}, {'X': 'table0.C0'}, []),
('FetchStep',
- [('Any X WHERE X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
+ [('Any X WHERE X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
[{'X': 'BaseTransition'}, {'X': 'Bookmark'},
{'X': 'CWAttribute'}, {'X': 'CWCache'},
{'X': 'CWConstraint'}, {'X': 'CWConstraintType'},
{'X': 'CWEType'}, {'X': 'CWGroup'},
{'X': 'CWPermission'}, {'X': 'CWProperty'},
{'X': 'CWRType'}, {'X': 'CWRelation'},
+ {'X': 'CWSource'},
{'X': 'CWUniqueTogetherConstraint'},
{'X': 'Comment'}, {'X': 'Division'},
{'X': 'Email'}, {'X': 'EmailAddress'},
@@ -914,21 +934,24 @@
{'X': 'Workflow'}, {'X': 'WorkflowTransition'}])],
[self.system], {}, {'X': 'table0.C0'}, []),
]),
- ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
[self.system], {'X': 'table2.C0'}, {'X': 'table0.C0'}, []),
- ('FetchStep', [('Any X WHERE (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
+ ('FetchStep', [('Any X WHERE (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), X is Affaire' % {'ueid': ueid},
[{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
[self.system], {'E': 'table1.C0'}, {'X': 'table0.C0'}, []),
]),
- ('OneFetchStep', [('Any MAX(X)', X_ALL_SOLS)],
+ ('OneFetchStep', [('Any MAX(X)', ALL_SOLS)],
None, None, [self.system], {'X': 'table0.C0'}, [])
])
def test_security_complex_aggregat2(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
X_ET_ALL_SOLS = []
for s in X_ALL_SOLS:
+ if s == {'X': 'CWSourceHostConfig'}:
+ continue # not authorized
ets = {'ET': 'CWEType'}
ets.update(s)
X_ET_ALL_SOLS.append(ets)
@@ -941,28 +964,29 @@
('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table3.C0'}, []),
('UnionFetchStep',
- [('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is CWEType, X is Basket',
+ [('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is Basket' % ueid,
[{'ET': 'CWEType', 'X': 'Basket'}])],
[self.system], {}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
- ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), ET is CWEType, X is Affaire',
+ ('FetchStep', [('Any ET,X WHERE X is ET, (EXISTS(X owned_by %(ueid)s)) OR ((((EXISTS(D concerne C?, C owned_by %(ueid)s, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by %(ueid)s, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by %(ueid)s, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by %(ueid)s, X identity J, E is Note, J is Affaire))), ET is CWEType, X is Affaire' % {'ueid': ueid},
[{'C': 'Division', 'E': 'Note', 'D': 'Affaire',
'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire',
'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire',
'ET': 'CWEType'}])],
[self.system], {'E': 'table2.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'},
[]),
- ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by 5), ET is CWEType, X is CWUser',
+ ('FetchStep', [('Any ET,X WHERE X is ET, EXISTS(X owned_by %s), ET is CWEType, X is CWUser' % ueid,
[{'ET': 'CWEType', 'X': 'CWUser'}])],
[self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
# extra UnionFetchStep could be avoided but has no cost, so don't care
('UnionFetchStep',
- [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
+ [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
[{'X': 'BaseTransition', 'ET': 'CWEType'},
{'X': 'Bookmark', 'ET': 'CWEType'}, {'X': 'CWAttribute', 'ET': 'CWEType'},
{'X': 'CWCache', 'ET': 'CWEType'}, {'X': 'CWConstraint', 'ET': 'CWEType'},
{'X': 'CWConstraintType', 'ET': 'CWEType'}, {'X': 'CWEType', 'ET': 'CWEType'},
{'X': 'CWGroup', 'ET': 'CWEType'}, {'X': 'CWPermission', 'ET': 'CWEType'},
{'X': 'CWProperty', 'ET': 'CWEType'}, {'X': 'CWRType', 'ET': 'CWEType'},
+ {'X': 'CWSource', 'ET': 'CWEType'},
{'X': 'CWRelation', 'ET': 'CWEType'},
{'X': 'CWUniqueTogetherConstraint', 'ET': 'CWEType'},
{'X': 'Comment', 'ET': 'CWEType'},
@@ -993,6 +1017,7 @@
def test_security_3sources(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -1001,7 +1026,7 @@
[('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
[self.ldap, self.system], None, {'U': 'table1.C0'}, []),
('OneFetchStep',
- [('Any X,XT WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is CWUser, X is Card',
+ [('Any X,XT WHERE X owned_by U, X title XT, EXISTS(U owned_by %s), U is CWUser, X is Card' % ueid,
[{'X': 'Card', 'U': 'CWUser', 'XT': 'String'}])],
None, None, [self.system],
{'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1', 'U': 'table1.C0'}, [])
@@ -1011,12 +1036,13 @@
self.restore_orig_cwuser_security()
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
[self.cards, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
('OneFetchStep',
- [('Any X,XT WHERE X owned_by U, X title XT, U login "syt", EXISTS(U identity 5), U is CWUser, X is Card',
+ [('Any X,XT WHERE X owned_by U, X title XT, U login "syt", EXISTS(U identity %s), U is CWUser, X is Card' % ueid,
[{'U': 'CWUser', 'X': 'Card', 'XT': 'String'}])],
None, None, [self.system], {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, [])
])
@@ -1025,9 +1051,10 @@
self.restore_orig_cwuser_security()
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L',
[('FetchStep',
- [('Any U,L WHERE U login L, EXISTS(U identity 5), U is CWUser',
+ [('Any U,L WHERE U login L, EXISTS(U identity %s), U is CWUser' % ueid,
[{'L': 'String', u'U': 'CWUser'}])],
[self.system], {}, {'L': 'table0.C1', 'U': 'table0.C0', 'U.login': 'table0.C1'}, []),
('FetchStep',
@@ -1046,6 +1073,7 @@
def test_security_3sources_limit_offset(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X, XT LIMIT 10 OFFSET 10 WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -1054,7 +1082,7 @@
[('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
[self.ldap, self.system], None, {'U': 'table1.C0'}, []),
('OneFetchStep',
- [('Any X,XT LIMIT 10 OFFSET 10 WHERE X owned_by U, X title XT, EXISTS(U owned_by 5), U is CWUser, X is Card',
+ [('Any X,XT LIMIT 10 OFFSET 10 WHERE X owned_by U, X title XT, EXISTS(U owned_by %s), U is CWUser, X is Card' % ueid,
[{'X': 'Card', 'U': 'CWUser', 'XT': 'String'}])],
10, 10, [self.system],
{'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1', 'U': 'table1.C0'}, [])
@@ -1150,7 +1178,7 @@
'X.login': 'table0.C1',
'X.modification_date': 'table0.C4',
'X.surname': 'table0.C3'}, []),
- ('OneFetchStep', [('Any X,AA,AB,AC,AD ORDERBY AA WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, EXISTS(((X identity 5) OR (EXISTS(X in_group C, C name IN("managers", "staff"), C is CWGroup))) OR (EXISTS(X in_group D, 5 in_group D, NOT D name "users", D is CWGroup))), X is CWUser',
+ ('OneFetchStep', [('Any X,AA,AB,AC,AD ORDERBY AA WHERE X login AA, X firstname AB, X surname AC, X modification_date AD, EXISTS(((X identity %(ueid)s) OR (EXISTS(X in_group C, C name IN("managers", "staff"), C is CWGroup))) OR (EXISTS(X in_group D, %(ueid)s in_group D, NOT D name "users", D is CWGroup))), X is CWUser' % {'ueid': ueid},
[{'AA': 'String', 'AB': 'String', 'AC': 'String', 'AD': 'Datetime',
'C': 'CWGroup', 'D': 'CWGroup', 'X': 'CWUser'}])],
None, None, [self.system],
@@ -1227,7 +1255,7 @@
# in the source where %(x)s is not coming from and will be removed during rql
# generation for the external source
self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
- [('OneFetchStep', [('Any SN WHERE NOT EXISTS(5 in_state S), S name SN, S is State',
+ [('OneFetchStep', [('Any SN WHERE NOT EXISTS(%s in_state S), S name SN, S is State' % ueid,
[{'S': 'State', 'SN': 'String'}])],
None, None, [self.cards, self.system], {}, [])],
{'x': ueid})
@@ -1280,12 +1308,13 @@
def test_simplified_var(self):
+ ueid = self.session.user.eid
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
- [('OneFetchStep', [('Any 5 WHERE 5 in_group G, (G name IN("managers", "logilab")) OR (X require_permission P?, P name "bla", P require_group G), X eid 999999',
+ [('OneFetchStep', [('Any %s WHERE %s in_group G, (G name IN("managers", "logilab")) OR (X require_permission P?, P name "bla", P require_group G), X eid 999999' % (ueid, ueid),
[{'X': 'Note', 'G': 'CWGroup', 'P': 'CWPermission'}])],
None, None, [self.system], {}, [])],
- {'x': 999999, 'u': self.session.user.eid})
+ {'x': 999999, 'u': ueid})
def test_has_text(self):
self._test('Card X WHERE X has_text "toto"',
@@ -1325,13 +1354,14 @@
def test_security_has_text_orderby_rank(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X ORDERBY FTIRANK(X) WHERE X has_text "bla", X firstname "bla"',
[('FetchStep', [('Any X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table1.C0'}, []),
('UnionFetchStep',
[('FetchStep', [('Any X WHERE X firstname "bla", X is Personne', [{'X': 'Personne'}])],
[self.system], {}, {'X': 'table0.C0'}, []),
- ('FetchStep', [('Any X WHERE EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ ('FetchStep', [('Any X WHERE EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
[self.system], {'X': 'table1.C0'}, {'X': 'table0.C0'}, [])]),
('OneFetchStep', [('Any X ORDERBY FTIRANK(X) WHERE X has_text "bla"',
[{'X': 'CWUser'}, {'X': 'Personne'}])],
@@ -1354,11 +1384,12 @@
def test_security_has_text_select_rank(self):
# use a guest user
self.session = self.user_groups_session('guests')
+ ueid = self.session.user.eid
self._test('Any X, FTIRANK(X) WHERE X has_text "bla", X firstname "bla"',
[('FetchStep', [('Any X,X WHERE X firstname "bla", X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table0.C1'}, []),
('UnionStep', None, None, [
- ('OneFetchStep', [('Any X,FTIRANK(X) WHERE X has_text "bla", EXISTS(X owned_by 5), X is CWUser', [{'X': 'CWUser'}])],
+ ('OneFetchStep', [('Any X,FTIRANK(X) WHERE X has_text "bla", EXISTS(X owned_by %s), X is CWUser' % ueid, [{'X': 'CWUser'}])],
None, None, [self.system], {'X': 'table0.C1'}, []),
('OneFetchStep', [('Any X,FTIRANK(X) WHERE X has_text "bla", X firstname "bla", X is Personne', [{'X': 'Personne'}])],
None, None, [self.system], {}, []),
@@ -1436,6 +1467,7 @@
])
def test_subquery_1(self):
+ ueid = self.session.user.eid
self._test('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by D), D eid %(E)s '
'WITH A,N BEING ((Any X,N WHERE X is Tag, X name N) UNION (Any X,T WHERE X is Bookmark, X title T))',
[('FetchStep', [('Any X,N WHERE X is Tag, X name N', [{'N': 'String', 'X': 'Tag'}]),
@@ -1445,7 +1477,7 @@
('FetchStep',
[('Any B,C WHERE B login C, B is CWUser', [{'B': 'CWUser', 'C': 'String'}])],
[self.ldap, self.system], None, {'B': 'table1.C0', 'B.login': 'table1.C1', 'C': 'table1.C1'}, []),
- ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is CWUser, A is IN(Bookmark, Tag)',
+ ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by %s), B is CWUser, A is IN(Bookmark, Tag)' % ueid,
[{'A': 'Bookmark', 'B': 'CWUser', 'C': 'String'},
{'A': 'Tag', 'B': 'CWUser', 'C': 'String'}])],
None, None, [self.system],
@@ -1454,9 +1486,10 @@
'C': 'table1.C1',
'N': 'table0.C1'},
[])],
- {'E': self.session.user.eid})
+ {'E': ueid})
def test_subquery_2(self):
+ ueid = self.session.user.eid
self._test('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by D), D eid %(E)s '
'WITH A,N BEING ((Any X,N WHERE X is Tag, X name N) UNION (Any X,T WHERE X is Card, X title T))',
[('UnionFetchStep',
@@ -1479,7 +1512,7 @@
('FetchStep',
[('Any B,C WHERE B login C, B is CWUser', [{'B': 'CWUser', 'C': 'String'}])],
[self.ldap, self.system], None, {'B': 'table1.C0', 'B.login': 'table1.C1', 'C': 'table1.C1'}, []),
- ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by 5), B is CWUser, A is IN(Card, Tag)',
+ ('OneFetchStep', [('DISTINCT Any B,C ORDERBY C WHERE A created_by B, B login C, EXISTS(B owned_by %s), B is CWUser, A is IN(Card, Tag)' % ueid,
[{'A': 'Card', 'B': 'CWUser', 'C': 'String'},
{'A': 'Tag', 'B': 'CWUser', 'C': 'String'}])],
None, None, [self.system],
@@ -1488,7 +1521,7 @@
'C': 'table1.C1',
'N': 'table0.C1'},
[])],
- {'E': self.session.user.eid})
+ {'E': ueid})
def test_eid_dont_cross_relation_1(self):
repo._type_source_cache[999999] = ('Personne', 'system', 999999)
@@ -1578,20 +1611,84 @@
('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
[self.cards, self.system], None,
{'T': 'table1.C1', 'Y': 'table1.C0', 'Y.type': 'table1.C1'}, []),
- ('UnionStep', None, None,
- [('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
- [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
- None, None, [self.cards], None,
- []),
- ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
- [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
- None, None, [self.system],
- {'T': 'table1.C1', 'X': 'table0.C0', 'X.type': 'table0.C1',
- 'Y': 'table1.C0', 'Y.type': 'table1.C1'},
- [])]
- )],
+ ('FetchStep', [('Any X,Y WHERE X multisource_crossed_rel Y, X is Note, Y is Note',
+ [{'X': 'Note', 'Y': 'Note'}])],
+ [self.cards, self.system], None,
+ {'X': 'table2.C0', 'Y': 'table2.C1'},
+ []),
+ ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, '
+ 'X is Note, Y is Note, Y identity A, X identity B, A is Note, B is Note',
+ [{u'A': 'Note', u'B': 'Note', 'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
+ None, None,
+ [self.system],
+ {'A': 'table1.C0',
+ 'B': 'table0.C0',
+ 'T': 'table1.C1',
+ 'X': 'table2.C0',
+ 'X.type': 'table0.C1',
+ 'Y': 'table2.C1',
+ 'Y.type': 'table1.C1'},
+ []),
+ ],
{'x': 999999,})
+ def test_crossed_relation_noeid_needattr(self):
+ # http://www.cubicweb.org/ticket/1382452
+ self._test('DISTINCT Any DEP WHERE DEP is Note, P type "cubicweb-foo", P multisource_crossed_rel DEP, DEP type LIKE "cubicweb%"',
+ [('FetchStep', [(u'Any DEP WHERE DEP type LIKE "cubicweb%", DEP is Note',
+ [{'DEP': 'Note'}])],
+ [self.cards, self.system], None,
+ {'DEP': 'table0.C0'},
+ []),
+ ('FetchStep', [(u'Any P WHERE P type "cubicweb-foo", P is Note', [{'P': 'Note'}])],
+ [self.cards, self.system], None, {'P': 'table1.C0'},
+ []),
+ ('FetchStep', [('Any DEP,P WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note',
+ [{'DEP': 'Note', 'P': 'Note'}])],
+ [self.cards, self.system], None, {'DEP': 'table2.C0', 'P': 'table2.C1'},
+ []),
+ ('OneFetchStep',
+ [('DISTINCT Any DEP WHERE P multisource_crossed_rel DEP, DEP is Note, '
+ 'P is Note, DEP identity A, P identity B, A is Note, B is Note',
+ [{u'A': 'Note', u'B': 'Note', 'DEP': 'Note', 'P': 'Note'}])],
+ None, None, [self.system],
+ {'A': 'table0.C0', 'B': 'table1.C0', 'DEP': 'table2.C0', 'P': 'table2.C1'},
+ [])])
+
+ def test_crossed_relation_noeid_invariant(self):
+ # see comment in http://www.cubicweb.org/ticket/1382452
+ self.schema.add_relation_def(
+ RelationDefinition(subject='Note', name='multisource_crossed_rel', object='Affaire'))
+ self.repo.set_schema(self.schema)
+ try:
+ self._test('DISTINCT Any P,DEP WHERE P type "cubicweb-foo", P multisource_crossed_rel DEP',
+ [('FetchStep',
+ [('Any DEP WHERE DEP is Note', [{'DEP': 'Note'}])],
+ [self.cards, self.system], None, {'DEP': 'table0.C0'}, []),
+ ('FetchStep',
+ [(u'Any P WHERE P type "cubicweb-foo", P is Note', [{'P': 'Note'}])],
+ [self.cards, self.system], None, {'P': 'table1.C0'}, []),
+ ('UnionStep', None, None,
+ [('OneFetchStep',
+ [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note',
+ [{'DEP': 'Note', 'P': 'Note'}])],
+ None, None, [self.cards], None, []),
+ ('OneFetchStep',
+ [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note',
+ [{'DEP': 'Note', 'P': 'Note'}])],
+ None, None, [self.system],
+ {'DEP': 'table0.C0', 'P': 'table1.C0'},
+ []),
+ ('OneFetchStep',
+ [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Affaire, P is Note',
+ [{'DEP': 'Affaire', 'P': 'Note'}])],
+ None, None, [self.system], {'P': 'table1.C0'},
+ [])])
+ ])
+ finally:
+ self.schema.del_relation_def('Note', 'multisource_crossed_rel', 'Affaire')
+ self.repo.set_schema(self.schema)
+
# edition queries tests ###################################################
def test_insert_simplified_var_1(self):
@@ -1662,7 +1759,7 @@
ueid = self.session.user.eid
self._test('DELETE X created_by Y WHERE X eid %(x)s, NOT Y eid %(y)s',
[('DeleteRelationsStep', [
- ('OneFetchStep', [('Any 5,Y WHERE %s created_by Y, NOT Y eid %s, Y is CWUser'%(ueid, ueid),
+ ('OneFetchStep', [('Any %s,Y WHERE %s created_by Y, NOT Y eid %s, Y is CWUser' % (ueid, ueid, ueid),
[{'Y': 'CWUser'}])],
None, None, [self.system], {}, []),
]),
@@ -1681,6 +1778,18 @@
],
{'x': ueid, 'y': ueid})
+ def test_delete_relation3(self):
+ repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+ 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'})
+
def test_delete_entity1(self):
repo._type_source_cache[999999] = ('Note', 'system', 999999)
self._test('DELETE Note X WHERE X eid %(x)s, NOT Y multisource_rel X',
@@ -1805,6 +1914,156 @@
del self.cards.support_relations['see_also']
self.cards.cross_relations.remove('see_also')
+ def test_state_of_cross(self):
+ self._test('DELETE State X WHERE NOT X state_of Y',
+ [('DeleteEntitiesStep',
+ [('OneFetchStep',
+ [('Any X WHERE NOT X state_of Y, X is State, Y is Workflow',
+ [{'X': 'State', 'Y': 'Workflow'}])],
+ None, None, [self.system], {}, [])])]
+ )
+
+
+ def test_source_specified_0_0(self):
+ self._test('Card X WHERE X cw_source S, S eid 1',
+ [('OneFetchStep', [('Any X WHERE X cw_source 1, X is Card',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.system],{}, [])
+ ])
+
+ def test_source_specified_0_1(self):
+ self._test('Any X, S WHERE X is Card, X cw_source S, S eid 1',
+ [('OneFetchStep', [('Any X,1 WHERE X is Card, X cw_source 1',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.system],{}, [])
+ ])
+
+ def test_source_specified_1_0(self):
+ self._test('Card X WHERE X cw_source S, S name "system"',
+ [('OneFetchStep', [('Any X WHERE X cw_source S, S name "system", X is Card',
+ [{'X': 'Card', 'S': 'CWSource'}])],
+ None, None,
+ [self.system],{}, [])
+ ])
+
+ def test_source_specified_1_1(self):
+ self._test('Any X, SN WHERE X is Card, X cw_source S, S name "system", S name SN',
+ [('OneFetchStep', [('Any X,SN WHERE X is Card, X cw_source S, S name "system", '
+ 'S name SN',
+ [{'S': 'CWSource', 'SN': 'String', 'X': 'Card'}])],
+ None, None, [self.system], {}, [])
+ ])
+
+ def test_source_specified_1_2(self):
+ sols = []
+ for sol in X_ALL_SOLS:
+ sol = sol.copy()
+ sol['S'] = 'CWSource'
+ sols.append(sol)
+ self._test('Any X WHERE X cw_source S, S name "cards"',
+ [('OneFetchStep', [('Any X WHERE X cw_source S, S name "cards"',
+ sols)],
+ None, None,
+ [self.system],{}, [])
+ ])
+
+ def test_source_specified_2_0(self):
+ self._test('Card X WHERE X cw_source S, NOT S eid 1',
+ [('OneFetchStep', [('Any X WHERE X is Card',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.cards],{}, [])
+ ])
+ self._test('Card X WHERE NOT X cw_source S, S eid 1',
+ [('OneFetchStep', [('Any X WHERE X is Card',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.cards],{}, [])
+ ])
+
+ def test_source_specified_2_1(self):
+ self._test('Card X WHERE X cw_source S, NOT S name "system"',
+ [('OneFetchStep', [('Any X WHERE X is Card',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.cards],{}, [])
+ ])
+ self._test('Card X WHERE NOT X cw_source S, S name "system"',
+ [('OneFetchStep', [('Any X WHERE X is Card',
+ [{'X': 'Card'}])],
+ None, None,
+ [self.cards],{}, [])
+ ])
+
+ def test_source_specified_3_1(self):
+ self._test('Any X,XT WHERE X is Card, X title XT, X cw_source S, S name "cards"',
+ [('OneFetchStep',
+ [('Any X,XT WHERE X is Card, X title XT',
+ [{'X': 'Card', 'XT': 'String'}])],
+ None, None, [self.cards], {}, [])
+ ])
+
+ def test_source_specified_3_2(self):
+ self.skipTest('oops')
+ self._test('Any STN WHERE X is Note, X type XT, X in_state ST, ST name STN, X cw_source S, S name "cards"',
+ [('OneFetchStep',
+ [('Any X,XT WHERE X is Card, X title XT',
+ [{'X': 'Card', 'XT': 'String'}])],
+ None, None, [self.cards], {}, [])
+ ])
+
+ def test_source_conflict_1(self):
+ self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+ with self.assertRaises(BadRQLQuery) as cm:
+ self._test('Any X WHERE X cw_source S, S name "system", X eid %(x)s',
+ [], {'x': 999999})
+ self.assertEqual(str(cm.exception), 'source conflict for term %(x)s')
+
+ def test_source_conflict_2(self):
+ with self.assertRaises(BadRQLQuery) as cm:
+ self._test('Card X WHERE X cw_source S, S name "systeme"', [])
+ self.assertEqual(str(cm.exception), 'source conflict for term X')
+
+ def test_source_conflict_3(self):
+ self.skipTest('oops')
+ self._test('CWSource X WHERE X cw_source S, S name "cards"',
+ [('OneFetchStep',
+ [(u'Any X WHERE X cw_source S, S name "cards", X is CWSource',
+ [{'S': 'CWSource', 'X': 'CWSource'}])],
+ None, None,
+ [self.system],
+ {}, [])])
+
+
+ def test_ambigous_cross_relation_source_specified(self):
+ self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+ self.cards.support_relations['see_also'] = True
+ self.cards.cross_relations.add('see_also')
+ try:
+ self._test('Any X,AA ORDERBY AA WHERE E eid %(x)s, E see_also X, X modification_date AA',
+ [('AggrStep',
+ 'SELECT table0.C0, table0.C1 FROM table0 ORDER BY table0.C1',
+ None,
+ [('FetchStep',
+ [('Any X,AA WHERE 999999 see_also X, X modification_date AA, X is Note',
+ [{'AA': 'Datetime', 'X': 'Note'}])], [self.cards, self.system], {},
+ {'AA': 'table0.C1', 'X': 'table0.C0',
+ 'X.modification_date': 'table0.C1'},
+ []),
+ ('FetchStep',
+ [('Any X,AA WHERE 999999 see_also X, X modification_date AA, X is Bookmark',
+ [{'AA': 'Datetime', 'X': 'Bookmark'}])],
+ [self.system], {},
+ {'AA': 'table0.C1', 'X': 'table0.C0',
+ 'X.modification_date': 'table0.C1'},
+ [])])],
+ {'x': 999999})
+ finally:
+ del self.cards.support_relations['see_also']
+ self.cards.cross_relations.remove('see_also')
+
# non regression tests ####################################################
def test_nonregr1(self):
@@ -1864,15 +2123,16 @@
)
def test_nonregr4(self):
+ ueid = self.session.user.eid
self._test('Any U ORDERBY D DESC WHERE WF wf_info_for X, WF creation_date D, WF from_state FS, '
'WF owned_by U?, X eid %(x)s',
[#('FetchStep', [('Any U WHERE U is CWUser', [{'U': 'CWUser'}])],
# [self.ldap, self.system], None, {'U': 'table0.C0'}, []),
- ('OneFetchStep', [('Any U ORDERBY D DESC WHERE WF wf_info_for 5, WF creation_date D, WF from_state FS, WF owned_by U?',
+ ('OneFetchStep', [('Any U ORDERBY D DESC WHERE WF wf_info_for %s, WF creation_date D, WF from_state FS, WF owned_by U?' % ueid,
[{'WF': 'TrInfo', 'FS': 'State', 'U': 'CWUser', 'D': 'Datetime'}])],
None, None,
[self.system], {}, [])],
- {'x': self.session.user.eid})
+ {'x': ueid})
def test_nonregr5(self):
# original jpl query:
@@ -1914,7 +2174,7 @@
[('FetchStep', [('Any WP WHERE 999999 multisource_rel WP, WP is Note', [{'WP': 'Note'}])],
[self.cards], None, {'WP': u'table0.C0'}, []),
('OneFetchStep', [('Any S,SUM(DUR),SUM(I),(SUM(I) - SUM(DUR)),MIN(DI),MAX(DI) GROUPBY S ORDERBY S WHERE A duration DUR, A invoiced I, A modification_date DI, A in_state S, S name SN, (EXISTS(A concerne WP, WP is Note)) OR (EXISTS(A concerne 999999)), A is Affaire, S is State',
- [{'A': 'Affaire', 'DI': 'Datetime', 'DUR': 'Int', 'I': 'Int', 'S': 'State', 'SN': 'String', 'WP': 'Note'}])],
+ [{'A': 'Affaire', 'DI': 'Datetime', 'DUR': 'Int', 'I': 'Float', 'S': 'State', 'SN': 'String', 'WP': 'Note'}])],
None, None, [self.system], {'WP': u'table0.C0'}, [])],
{'n': 999999})
@@ -1997,6 +2257,7 @@
{'x': 999999})
def test_nonregr13_1(self):
+ ueid = self.session.user.eid
# identity wrapped into exists:
# should'nt propagate constraint that U is in the same source as ME
self._test('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
@@ -2008,7 +2269,7 @@
[self.ldap, self.system], None,
{'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
[]),
- ('FetchStep', [('Any U,UL WHERE ((EXISTS(U identity 5)) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser',
+ ('FetchStep', [('Any U,UL WHERE ((EXISTS(U identity %s)) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, %s in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser' % (ueid, ueid),
[{'G': 'CWGroup', 'H': 'CWGroup', 'U': 'CWUser', 'UL': 'String'}])],
[self.system],
{'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
@@ -2019,7 +2280,7 @@
None, None, [self.system],
{'U': 'table1.C0', 'UL': 'table1.C1'},
[])],
- {'x': self.session.user.eid})
+ {'x': ueid})
def test_nonregr13_2(self):
# identity *not* wrapped into exists.
@@ -2033,6 +2294,7 @@
# explain constraint propagation rules, and so why this should be
# wrapped in exists() if used in multi-source
self.skipTest('take a look at me if you wish')
+ ueid = self.session.user.eid
self._test('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (U identity ME '
'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
@@ -2042,7 +2304,7 @@
[self.ldap, self.system], None,
{'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
[]),
- ('FetchStep', [('Any U,UL WHERE ((U identity 5) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, 5 in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser',
+ ('FetchStep', [('Any U,UL WHERE ((U identity %s) OR (EXISTS(U in_group G, G name IN("managers", "staff"), G is CWGroup))) OR (EXISTS(U in_group H, %s in_group H, NOT H name "users", H is CWGroup)), U login UL, U is CWUser' % (ueid, ueid),
[{'G': 'CWGroup', 'H': 'CWGroup', 'U': 'CWUser', 'UL': 'String'}])],
[self.system],
{'U': 'table0.C0', 'U.login': 'table0.C1', 'UL': 'table0.C1'},
@@ -2094,23 +2356,15 @@
None, None, [self.system], {}, [])],
{'x': 999999, 'u': 999998})
- def test_state_of_cross(self):
- self._test('DELETE State X WHERE NOT X state_of Y',
- [('DeleteEntitiesStep',
- [('OneFetchStep',
- [('Any X WHERE NOT X state_of Y, X is State, Y is Workflow',
- [{'X': 'State', 'Y': 'Workflow'}])],
- None, None, [self.system], {}, [])])]
- )
class MSPlannerTwoSameExternalSourcesTC(BasePlannerTC):
"""test planner related feature on a 3-sources repository:
* 2 rql sources supporting Card
"""
- repo = repo
def setUp(self):
+ self.__class__.repo = repo
self.setup()
self.add_source(FakeCardSource, 'cards')
self.add_source(FakeCardSource, 'cards2')
@@ -2222,6 +2476,37 @@
)]
)
+ def test_version_crossed_depends_on_4(self):
+ self._test('Any X,AD,AE WHERE EXISTS(E multisource_crossed_rel X), X in_state AD, AD name AE, E is Note',
+ [('FetchStep',
+ [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
+ [{'X': 'Note', 'AD': 'State', 'AE': 'String'}])],
+ [self.cards, self.cards2, self.system], None,
+ {'X': 'table0.C0',
+ 'AD': 'table0.C1',
+ 'AD.name': 'table0.C2',
+ 'AE': 'table0.C2'},
+ []),
+ ('FetchStep',
+ [('Any A WHERE E multisource_crossed_rel A, A is Note, E is Note',
+ [{'A': 'Note', 'E': 'Note'}])],
+ [self.cards, self.cards2, self.system], None,
+ {'A': 'table1.C0'},
+ []),
+ ('OneFetchStep',
+ [('Any X,AD,AE WHERE EXISTS(X identity A), AD name AE, A is Note, AD is State, X is Note',
+ [{'A': 'Note', 'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
+ None, None,
+ [self.system],
+ {'A': 'table1.C0',
+ 'AD': 'table0.C1',
+ 'AD.name': 'table0.C2',
+ 'AE': 'table0.C2',
+ 'X': 'table0.C0'},
+ []
+ )]
+ )
+
def test_nonregr_dont_cross_rel_source_filtering_1(self):
self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any S WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
@@ -2257,7 +2542,7 @@
None, {'X': 'table0.C0'}, []),
('UnionStep', None, None,
[('OneFetchStep',
- [(u'Any X WHERE X owned_by U, U login "anon", U is CWUser, X is IN(Affaire, BaseTransition, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUniqueTogetherConstraint, CWUser, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
+ [(u'Any X WHERE X owned_by U, U login "anon", U is CWUser, X is IN(Affaire, BaseTransition, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWSourceHostConfig, CWUniqueTogetherConstraint, CWUser, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Personne, RQLExpression, Societe, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)',
[{'U': 'CWUser', 'X': 'Affaire'},
{'U': 'CWUser', 'X': 'BaseTransition'},
{'U': 'CWUser', 'X': 'Basket'},
@@ -2272,6 +2557,8 @@
{'U': 'CWUser', 'X': 'CWProperty'},
{'U': 'CWUser', 'X': 'CWRType'},
{'U': 'CWUser', 'X': 'CWRelation'},
+ {'U': 'CWUser', 'X': 'CWSource'},
+ {'U': 'CWUser', 'X': 'CWSourceHostConfig'},
{'U': 'CWUser', 'X': 'CWUniqueTogetherConstraint'},
{'U': 'CWUser', 'X': 'CWUser'},
{'U': 'CWUser', 'X': 'Division'},
@@ -2315,9 +2602,9 @@
return []
class MSPlannerVCSSource(BasePlannerTC):
- repo = repo
def setUp(self):
+ self.__class__.repo = repo
self.setup()
self.add_source(FakeVCSSource, 'vcs')
self.planner = MSPlanner(self.o.schema, self.repo.vreg.rqlhelper)
--- a/server/test/unittest_multisources.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_multisources.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ # 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.
@@ -15,29 +15,29 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-from os.path import dirname, join, abspath
+
from datetime import datetime, timedelta
-from logilab.common.decorators import cached
-
from cubicweb.devtools import TestServerConfiguration, init_test_database
from cubicweb.devtools.testlib import CubicWebTC, refresh_repo
from cubicweb.devtools.repotest import do_monkey_patch, undo_monkey_patch
-class TwoSourcesConfiguration(TestServerConfiguration):
- sourcefile = 'sources_multi'
-
-
class ExternalSource1Configuration(TestServerConfiguration):
sourcefile = 'sources_extern'
class ExternalSource2Configuration(TestServerConfiguration):
- sourcefile = 'sources_multi2'
+ sourcefile = 'sources_multi'
MTIME = datetime.now() - timedelta(0, 10)
-repo2, cnx2 = init_test_database(config=ExternalSource1Configuration('data'))
-repo3, cnx3 = init_test_database(config=ExternalSource2Configuration('data'))
+
+EXTERN_SOURCE_CFG = u'''
+pyro-ns-id = extern
+cubicweb-user = admin
+cubicweb-password = gingkow
+mapping-file = extern_mapping.py
+base-url=http://extern.org/
+'''
# hi-jacking
from cubicweb.server.sources.pyrorql import PyroRQLSource
@@ -46,7 +46,16 @@
PyroRQLSource_get_connection = PyroRQLSource.get_connection
Connection_close = Connection.close
-def setup_module(*args):
+def setUpModule(*args):
+ global repo2, cnx2, repo3, cnx3
+ cfg1 = ExternalSource1Configuration('data', apphome=TwoSourcesTC.datadir)
+ repo2, cnx2 = init_test_database(config=cfg1)
+ cfg2 = ExternalSource2Configuration('data', apphome=TwoSourcesTC.datadir)
+ repo3, cnx3 = init_test_database(config=cfg2)
+ cnx3.request().create_entity('CWSource', name=u'extern', type=u'pyrorql',
+ config=EXTERN_SOURCE_CFG)
+ cnx3.commit()
+
TestServerConfiguration.no_sqlite_wrap = True
# hi-jack PyroRQLSource.get_connection to access existing connection (no
# pyro connection)
@@ -55,7 +64,7 @@
# pool though we want to keep cnx2 valid
Connection.close = lambda x: None
-def teardown_module(*args):
+def tearDownModule(*args):
PyroRQLSource.get_connection = PyroRQLSource_get_connection
Connection.close = Connection_close
global repo2, cnx2, repo3, cnx3
@@ -67,8 +76,9 @@
TestServerConfiguration.no_sqlite_wrap = False
class TwoSourcesTC(CubicWebTC):
- config = TwoSourcesConfiguration('data')
-
+ """Main repo -> extern-multi -> extern
+ \-------------/
+ """
@classmethod
def _refresh_repo(cls):
super(TwoSourcesTC, cls)._refresh_repo()
@@ -82,6 +92,8 @@
do_monkey_patch()
def tearDown(self):
+ for source in self.repo.sources[1:]:
+ self.repo.remove_source(source.uri)
CubicWebTC.tearDown(self)
undo_monkey_patch()
@@ -91,6 +103,17 @@
cu.execute('INSERT Card X: X title "C4: Ze external card", X wikiid "zzz"')
self.aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF"')[0][0]
cnx2.commit()
+ for uri, config in [('extern', EXTERN_SOURCE_CFG),
+ ('extern-multi', '''
+pyro-ns-id = extern-multi
+cubicweb-user = admin
+cubicweb-password = gingkow
+mapping-file = extern_mapping.py
+''')]:
+ self.request().create_entity('CWSource', name=unicode(uri),
+ type=u'pyrorql',
+ config=unicode(config))
+ self.commit()
# trigger discovery
self.sexecute('Card X')
self.sexecute('Affaire X')
@@ -112,11 +135,11 @@
# since they are orderd by eid, we know the 3 first one is coming from the system source
# and the others from external source
self.assertEqual(rset.get_entity(0, 0).cw_metainformation(),
- {'source': {'adapter': 'native', 'uri': 'system'},
+ {'source': {'type': 'native', 'uri': 'system'},
'type': u'Card', 'extid': None})
externent = rset.get_entity(3, 0)
metainf = externent.cw_metainformation()
- self.assertEqual(metainf['source'], {'adapter': 'pyrorql', 'base-url': 'http://extern.org/', 'uri': 'extern'})
+ self.assertEqual(metainf['source'], {'type': 'pyrorql', 'base-url': 'http://extern.org/', 'uri': 'extern'})
self.assertEqual(metainf['type'], 'Card')
self.assert_(metainf['extid'])
etype = self.sexecute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
@@ -184,7 +207,7 @@
def test_simplifiable_var_2(self):
affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
rset = self.sexecute('Any E WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
- {'x': affeid, 'u': self.session.user.eid})
+ {'x': affeid, 'u': self.session.user.eid})
self.assertEqual(len(rset), 1)
def test_sort_func(self):
@@ -270,7 +293,6 @@
def test_not_relation(self):
states = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN'))
- self.session.user.clear_all_caches()
userstate = self.session.user.in_state[0]
states.remove((userstate.eid, userstate.name))
notstates = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
@@ -303,6 +325,19 @@
cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
cnx3.commit()
+ def test_crossed_relation_noeid_needattr(self):
+ """http://www.cubicweb.org/ticket/1382452"""
+ aff1 = self.sexecute('INSERT Affaire X: X ref "AFFREF"')[0][0]
+ # link within extern source
+ ec1 = self.sexecute('Card X WHERE X wikiid "zzz"')[0][0]
+ self.sexecute('SET A documented_by C WHERE E eid %(a)s, C eid %(c)s',
+ {'a': aff1, 'c': ec1})
+ # link from system to extern source
+ self.sexecute('SET A documented_by C WHERE E eid %(a)s, C eid %(c)s',
+ {'a': aff1, 'c': self.ic2})
+ rset = self.sexecute('DISTINCT Any DEP WHERE P ref "AFFREF", P documented_by DEP, DEP wikiid LIKE "z%"')
+ self.assertEqual(sorted(rset.rows), [[ec1], [self.ic2]])
+
def test_nonregr1(self):
ueid = self.session.user.eid
affaire = self.sexecute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
--- a/server/test/unittest_querier.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_querier.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,5 +1,5 @@
# -*- coding: iso-8859-1 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -62,9 +62,11 @@
('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
-repo, cnx = init_test_database()
+def setUpModule(*args):
+ global repo, cnx
+ repo, cnx = init_test_database(apphome=UtilsTC.datadir)
-def teardown_module(*args):
+def tearDownModule(*args):
global repo, cnx
cnx.close()
repo.shutdown()
@@ -72,7 +74,9 @@
class UtilsTC(BaseQuerierTC):
- repo = repo
+ def setUp(self):
+ self.__class__.repo = repo
+ super(UtilsTC, self).setUp()
def get_max_eid(self):
# no need for cleanup here
@@ -130,7 +134,7 @@
'X': 'Affaire',
'ET': 'CWEType', 'ETN': 'String'}])
rql, solutions = partrqls[1]
- self.assertEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUniqueTogetherConstraint, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Note, Personne, RQLExpression, Societe, State, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)')
+ self.assertEqual(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, X is IN(BaseTransition, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWSource, CWUniqueTogetherConstraint, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Note, Personne, RQLExpression, Societe, State, SubDivision, SubWorkflowExitPoint, Tag, TrInfo, Transition, Workflow, WorkflowTransition)')
self.assertListEqual(sorted(solutions),
sorted([{'X': 'BaseTransition', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Bookmark', 'ETN': 'String', 'ET': 'CWEType'},
@@ -147,6 +151,7 @@
{'X': 'CWPermission', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWProperty', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWRType', 'ETN': 'String', 'ET': 'CWEType'},
+ {'X': 'CWSource', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWUniqueTogetherConstraint', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWUser', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Email', 'ETN': 'String', 'ET': 'CWEType'},
@@ -224,7 +229,9 @@
class QuerierTC(BaseQuerierTC):
- repo = repo
+ def setUp(self):
+ self.__class__.repo = repo
+ super(QuerierTC, self).setUp()
def test_encoding_pb(self):
self.assertRaises(RQLSyntaxError, self.execute,
@@ -251,15 +258,15 @@
def test_select_1(self):
rset = self.execute('Any X ORDERBY X WHERE X is CWGroup')
result, descr = rset.rows, rset.description
- self.assertEqual(tuplify(result), [(1,), (2,), (3,), (4,)])
+ self.assertEqual(tuplify(result), [(2,), (3,), (4,), (5,)])
self.assertEqual(descr, [('CWGroup',), ('CWGroup',), ('CWGroup',), ('CWGroup',)])
def test_select_2(self):
rset = self.execute('Any X ORDERBY N WHERE X is CWGroup, X name N')
- self.assertEqual(tuplify(rset.rows), [(1,), (2,), (3,), (4,)])
+ self.assertEqual(tuplify(rset.rows), [(2,), (3,), (4,), (5,)])
self.assertEqual(rset.description, [('CWGroup',), ('CWGroup',), ('CWGroup',), ('CWGroup',)])
rset = self.execute('Any X ORDERBY N DESC WHERE X is CWGroup, X name N')
- self.assertEqual(tuplify(rset.rows), [(4,), (3,), (2,), (1,)])
+ self.assertEqual(tuplify(rset.rows), [(5,), (4,), (3,), (2,)])
def test_select_3(self):
rset = self.execute('Any N GROUPBY N WHERE X is CWGroup, X name N')
@@ -302,7 +309,7 @@
def test_select_5(self):
rset = self.execute('Any X, TMP ORDERBY TMP WHERE X name TMP, X is CWGroup')
- self.assertEqual(tuplify(rset.rows), [(1, 'guests',), (2, 'managers',), (3, 'owners',), (4, 'users',)])
+ self.assertEqual(tuplify(rset.rows), [(2, 'guests',), (3, 'managers',), (4, 'owners',), (5, 'users',)])
self.assertEqual(rset.description, [('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',)])
def test_select_6(self):
@@ -350,11 +357,11 @@
self.assertEqual(len(rset.rows), 0)
def test_select_nonregr_edition_not(self):
- groupeids = set((1, 2, 3))
- groupreadperms = set(r[0] for r in self.execute('Any Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), X read_permission Y'))
- rset = self.execute('DISTINCT Any Y WHERE X is CWEType, X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+ groupeids = set((2, 3, 4))
+ groupreadperms = set(r[0] for r in self.execute('Any Y WHERE X name "CWGroup", Y eid IN(2, 3, 4), X read_permission Y'))
+ rset = self.execute('DISTINCT Any Y WHERE X is CWEType, X name "CWGroup", Y eid IN(2, 3, 4), NOT X read_permission Y')
self.assertEqual(sorted(r[0] for r in rset.rows), sorted(groupeids - groupreadperms))
- rset = self.execute('DISTINCT Any Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), NOT X read_permission Y')
+ rset = self.execute('DISTINCT Any Y WHERE X name "CWGroup", Y eid IN(2, 3, 4), NOT X read_permission Y')
self.assertEqual(sorted(r[0] for r in rset.rows), sorted(groupeids - groupreadperms))
def test_select_outer_join(self):
@@ -493,15 +500,16 @@
self.assertListEqual(rset.rows,
[[u'description_format', 12],
[u'description', 13],
- [u'name', 14],
- [u'created_by', 38],
- [u'creation_date', 38],
- [u'cwuri', 38],
- [u'in_basket', 38],
- [u'is', 38],
- [u'is_instance_of', 38],
- [u'modification_date', 38],
- [u'owned_by', 38]])
+ [u'name', 15],
+ [u'created_by', 40],
+ [u'creation_date', 40],
+ [u'cw_source', 40],
+ [u'cwuri', 40],
+ [u'in_basket', 40],
+ [u'is', 40],
+ [u'is_instance_of', 40],
+ [u'modification_date', 40],
+ [u'owned_by', 40]])
def test_select_aggregat_having_dumb(self):
# dumb but should not raise an error
@@ -545,6 +553,26 @@
self.assertEqual(rset.rows[0][0], 'ADMIN')
self.assertEqual(rset.description, [('String',)])
+ def test_select_float_abs(self):
+ # test positive number
+ eid = self.execute('INSERT Affaire A: A invoiced %(i)s', {'i': 1.2})[0][0]
+ rset = self.execute('Any ABS(I) WHERE X eid %(x)s, X invoiced I', {'x': eid})
+ self.assertEqual(rset.rows[0][0], 1.2)
+ # test negative number
+ eid = self.execute('INSERT Affaire A: A invoiced %(i)s', {'i': -1.2})[0][0]
+ rset = self.execute('Any ABS(I) WHERE X eid %(x)s, X invoiced I', {'x': eid})
+ self.assertEqual(rset.rows[0][0], 1.2)
+
+ def test_select_int_abs(self):
+ # test positive number
+ eid = self.execute('INSERT Affaire A: A duration %(d)s', {'d': 12})[0][0]
+ rset = self.execute('Any ABS(D) WHERE X eid %(x)s, X duration D', {'x': eid})
+ self.assertEqual(rset.rows[0][0], 12)
+ # test negative number
+ eid = self.execute('INSERT Affaire A: A duration %(d)s', {'d': -12})[0][0]
+ rset = self.execute('Any ABS(D) WHERE X eid %(x)s, X duration D', {'x': eid})
+ self.assertEqual(rset.rows[0][0], 12)
+
## def test_select_simplified(self):
## ueid = self.session.user.eid
## rset = self.execute('Any L WHERE %s login L'%ueid)
@@ -597,15 +625,15 @@
def test_select_no_descr(self):
rset = self.execute('Any X WHERE X is CWGroup', build_descr=0)
rset.rows.sort()
- self.assertEqual(tuplify(rset.rows), [(1,), (2,), (3,), (4,)])
+ self.assertEqual(tuplify(rset.rows), [(2,), (3,), (4,), (5,)])
self.assertEqual(rset.description, ())
def test_select_limit_offset(self):
rset = self.execute('CWGroup X ORDERBY N LIMIT 2 WHERE X name N')
- self.assertEqual(tuplify(rset.rows), [(1,), (2,)])
+ self.assertEqual(tuplify(rset.rows), [(2,), (3,)])
self.assertEqual(rset.description, [('CWGroup',), ('CWGroup',)])
rset = self.execute('CWGroup X ORDERBY N LIMIT 2 OFFSET 2 WHERE X name N')
- self.assertEqual(tuplify(rset.rows), [(3,), (4,)])
+ self.assertEqual(tuplify(rset.rows), [(4,), (5,)])
def test_select_symmetric(self):
self.execute("INSERT Personne X: X nom 'machin'")
@@ -746,14 +774,14 @@
def test_select_constant(self):
rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup')
self.assertEqual(rset.rows,
- map(list, zip((1,2,3,4), ('toto','toto','toto','toto',))))
+ map(list, zip((2,3,4,5), ('toto','toto','toto','toto',))))
self.assertIsInstance(rset[0][1], unicode)
self.assertEqual(rset.description,
zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'),
('String', 'String', 'String', 'String',)))
rset = self.execute('Any X, %(value)s ORDERBY X WHERE X is CWGroup', {'value': 'toto'})
self.assertEqual(rset.rows,
- map(list, zip((1,2,3,4), ('toto','toto','toto','toto',))))
+ map(list, zip((2,3,4,5), ('toto','toto','toto','toto',))))
self.assertIsInstance(rset[0][1], unicode)
self.assertEqual(rset.description,
zip(('CWGroup', 'CWGroup', 'CWGroup', 'CWGroup'),
--- a/server/test/unittest_repository.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_repository.py Fri Mar 11 09:46:45 2011 +0100
@@ -95,15 +95,14 @@
self.assertItemsEqual(person._unique_together[0],
('nom', 'prenom', 'inline2'))
- def test_schema_has_owner(self):
- repo = self.repo
- cnxid = repo.connect(self.admlogin, password=self.admpassword)
- self.failIf(repo.execute(cnxid, 'CWEType X WHERE NOT X owned_by U'))
- self.failIf(repo.execute(cnxid, 'CWRType X WHERE NOT X owned_by U'))
- self.failIf(repo.execute(cnxid, 'CWAttribute X WHERE NOT X owned_by U'))
- self.failIf(repo.execute(cnxid, 'CWRelation X WHERE NOT X owned_by U'))
- self.failIf(repo.execute(cnxid, 'CWConstraint X WHERE NOT X owned_by U'))
- self.failIf(repo.execute(cnxid, 'CWConstraintType X WHERE NOT X owned_by U'))
+ def test_all_entities_have_owner(self):
+ self.failIf(self.execute('Any X WHERE NOT X owned_by U'))
+
+ def test_all_entities_have_is(self):
+ self.failIf(self.execute('Any X WHERE NOT X is ET'))
+
+ def test_all_entities_have_cw_source(self):
+ self.failIf(self.execute('Any X WHERE NOT X cw_source S'))
def test_connect(self):
self.assert_(self.repo.connect(self.admlogin, password=self.admpassword))
@@ -155,8 +154,9 @@
self.assertRaises(ValidationError,
self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"')
self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
- ex = self.assertRaises(QueryError, self.commit)
- self.assertEqual(str(ex), 'transaction must be rollbacked')
+ with self.assertRaises(QueryError) as cm:
+ self.commit()
+ self.assertEqual(str(cm.exception), 'transaction must be rollbacked')
self.rollback()
self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
@@ -171,8 +171,9 @@
self.assertRaises(Unauthorized,
self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"')
self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
- ex = self.assertRaises(QueryError, self.commit)
- self.assertEqual(str(ex), 'transaction must be rollbacked')
+ with self.assertRaises(QueryError) as cm:
+ self.commit()
+ self.assertEqual(str(cm.exception), 'transaction must be rollbacked')
self.rollback()
self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
@@ -211,7 +212,7 @@
def test_check_session(self):
repo = self.repo
cnxid = repo.connect(self.admlogin, password=self.admpassword)
- self.assertEqual(repo.check_session(cnxid), None)
+ self.assertIsInstance(repo.check_session(cnxid), float)
repo.close(cnxid)
self.assertRaises(BadConnectionId, repo.check_session, cnxid)
@@ -277,8 +278,9 @@
repo.execute(cnxid, 'DELETE CWUser X WHERE X login "toto"')
repo.commit(cnxid)
try:
- ex = self.assertRaises(Exception, run_transaction)
- self.assertEqual(str(ex), 'try to access pool on a closed session')
+ with self.assertRaises(Exception) as cm:
+ run_transaction()
+ self.assertEqual(str(cm.exception), 'try to access pool on a closed session')
finally:
t.join()
@@ -288,7 +290,7 @@
self.assertListEqual([r.type for r in schema.eschema('CWAttribute').ordered_relations()
if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
'creation_date', 'modification_date', 'cwuri',
- 'owned_by', 'created_by',
+ 'owned_by', 'created_by', 'cw_source',
'update_permission', 'read_permission',
'in_basket')],
['relation_type',
@@ -369,25 +371,25 @@
repo = self.repo
cnxid = repo.connect(self.admlogin, password=self.admpassword)
session = repo._get_session(cnxid, setpool=True)
- self.assertEqual(repo.type_and_source_from_eid(1, session),
- ('CWGroup', 'system', None))
- self.assertEqual(repo.type_from_eid(1, session), 'CWGroup')
- self.assertEqual(repo.source_from_eid(1, session).uri, 'system')
- self.assertEqual(repo.eid2extid(repo.system_source, 1, session), None)
+ self.assertEqual(repo.type_and_source_from_eid(2, session),
+ ('CWGroup', 'system', None))
+ self.assertEqual(repo.type_from_eid(2, session), 'CWGroup')
+ self.assertEqual(repo.source_from_eid(2, session).uri, 'system')
+ self.assertEqual(repo.eid2extid(repo.system_source, 2, session), None)
class dummysource: uri = 'toto'
- self.assertRaises(UnknownEid, repo.eid2extid, dummysource, 1, session)
+ self.assertRaises(UnknownEid, repo.eid2extid, dummysource, 2, session)
def test_public_api(self):
self.assertEqual(self.repo.get_schema(), self.repo.schema)
- self.assertEqual(self.repo.source_defs(), {'system': {'adapter': 'native', 'uri': 'system'}})
+ self.assertEqual(self.repo.source_defs(), {'system': {'type': 'native', 'uri': 'system'}})
# .properties() return a result set
self.assertEqual(self.repo.properties().rql, 'Any K,V WHERE P is CWProperty,P pkey K, P value V, NOT P for_user U')
def test_session_api(self):
repo = self.repo
cnxid = repo.connect(self.admlogin, password=self.admpassword)
- self.assertEqual(repo.user_info(cnxid), (5, 'admin', set([u'managers']), {}))
- self.assertEqual(repo.describe(cnxid, 1), (u'CWGroup', u'system', None))
+ self.assertEqual(repo.user_info(cnxid), (6, 'admin', set([u'managers']), {}))
+ self.assertEqual(repo.describe(cnxid, 2), (u'CWGroup', u'system', None))
repo.close(cnxid)
self.assertRaises(BadConnectionId, repo.user_info, cnxid)
self.assertRaises(BadConnectionId, repo.describe, cnxid, 1)
@@ -480,7 +482,7 @@
'EmailAddress', address=u'a@b.fr')
def test_multiple_edit_set_attributes(self):
- """make sure edited_attributes doesn't get cluttered
+ """make sure cw_edited doesn't get cluttered
by previous entities on multiple set
"""
# local hook
@@ -491,9 +493,9 @@
events = ('before_update_entity',)
def __call__(self):
# invoiced attribute shouldn't be considered "edited" before the hook
- self._test.failIf('invoiced' in self.entity.edited_attributes,
- 'edited_attributes cluttered by previous update')
- self.entity['invoiced'] = 10
+ self._test.failIf('invoiced' in self.entity.cw_edited,
+ 'cw_edited cluttered by previous update')
+ self.entity.cw_edited['invoiced'] = 10
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
req.create_entity('Affaire', ref=u'AFF01')
@@ -518,7 +520,7 @@
def test_type_from_eid(self):
self.session.set_pool()
- self.assertEqual(self.repo.type_from_eid(1, self.session), 'CWGroup')
+ self.assertEqual(self.repo.type_from_eid(2, self.session), 'CWGroup')
def test_type_from_eid_raise(self):
self.session.set_pool()
@@ -669,8 +671,9 @@
req.cnx.commit()
req = self.request()
req.create_entity('Note', type=u'todo', inline1=a01)
- ex = self.assertRaises(ValidationError, req.cnx.commit)
- self.assertEqual(ex.errors, {'inline1-subject': u'RQLUniqueConstraint S type T, S inline1 A1, A1 todo_by C, Y type T, Y inline1 A2, A2 todo_by C failed'})
+ with self.assertRaises(ValidationError) as cm:
+ req.cnx.commit()
+ self.assertEqual(cm.exception.errors, {'inline1-subject': u'RQLUniqueConstraint S type T, S inline1 A1, A1 todo_by C, Y type T, Y inline1 A2, A2 todo_by C failed'})
if __name__ == '__main__':
unittest_main()
--- a/server/test/unittest_rql2sql.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_rql2sql.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -38,14 +38,16 @@
pass # already registered
-config = TestServerConfiguration('data')
-config.bootstrap_cubes()
-schema = config.load_schema()
-schema['in_state'].inlined = True
-schema['state_of'].inlined = False
-schema['comments'].inlined = False
+def setUpModule():
+ global config, schema
+ config = TestServerConfiguration('data', apphome=CWRQLTC.datadir)
+ config.bootstrap_cubes()
+ schema = config.load_schema()
+ schema['in_state'].inlined = True
+ schema['state_of'].inlined = False
+ schema['comments'].inlined = False
-def teardown_module(*args):
+def tearDownModule():
global config, schema
del config, schema
@@ -178,10 +180,14 @@
FROM cw_Personne AS _X
WHERE _X.cw_prenom=lulu AND NOT (EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, in_group_relation AS rel_in_group1, cw_CWGroup AS _G WHERE rel_owned_by0.eid_from=_X.cw_eid AND rel_in_group1.eid_from=rel_owned_by0.eid_to AND rel_in_group1.eid_to=_G.cw_eid AND ((_G.cw_name=lulufanclub) OR (_G.cw_name=managers))))'''),
+ ('Any X WHERE X title V, NOT X wikiid V, NOT X title "parent", X is Card',
+ '''SELECT _X.cw_eid
+FROM cw_Card AS _X
+WHERE NOT (_X.cw_wikiid=_X.cw_title) AND NOT (_X.cw_title=parent)''')
]
-ADVANCED= [
+ADVANCED = [
("Societe S WHERE S nom 'Logilab' OR S nom 'Caesium'",
'''SELECT _S.cw_eid
FROM cw_Societe AS _S
@@ -571,7 +577,14 @@
'''SELECT 1
FROM in_group_relation AS rel_in_group0'''),
-
+ ('CWEType X WHERE X name CV, X description V HAVING NOT V=CV AND NOT V = "parent"',
+ '''SELECT _X.cw_eid
+FROM cw_CWEType AS _X
+WHERE NOT (EXISTS(SELECT 1 WHERE _X.cw_description=parent)) AND NOT (EXISTS(SELECT 1 WHERE _X.cw_description=_X.cw_name))'''),
+ ('CWEType X WHERE X name CV, X description V HAVING V!=CV AND V != "parent"',
+ '''SELECT _X.cw_eid
+FROM cw_CWEType AS _X
+WHERE _X.cw_description!=parent AND _X.cw_description!=_X.cw_name'''),
]
@@ -1078,8 +1091,12 @@
]
class CWRQLTC(RQLGeneratorTC):
- schema = schema
backend = 'sqlite'
+
+ def setUp(self):
+ self.__class__.schema = schema
+ super(CWRQLTC, self).setUp()
+
def test_nonregr_sol(self):
delete = self.rqlhelper.parse(
'DELETE X read_permission READ_PERMISSIONSUBJECT,X add_permission ADD_PERMISSIONSUBJECT,'
@@ -1107,9 +1124,12 @@
return '\n'.join(l.strip() for l in text.strip().splitlines())
class PostgresSQLGeneratorTC(RQLGeneratorTC):
- schema = schema
backend = 'postgres'
+ def setUp(self):
+ self.__class__.schema = schema
+ super(PostgresSQLGeneratorTC, self).setUp()
+
def _norm_sql(self, sql):
return sql.strip()
@@ -1415,6 +1435,13 @@
FROM appears AS appears0
WHERE appears0.words @@ to_tsquery('default', 'toto&tata')"""),
+
+ ('Any X WHERE NOT A tags X, X has_text "pouet"',
+ '''SELECT appears1.uid
+FROM appears AS appears1
+WHERE NOT (EXISTS(SELECT 1 FROM tags_relation AS rel_tags0 WHERE appears1.uid=rel_tags0.eid_to)) AND appears1.words @@ to_tsquery('default', 'pouet')
+'''),
+
)):
yield t
--- a/server/test/unittest_rqlannotation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_rqlannotation.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,21 +16,26 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for modules cubicweb.server.rqlannotation
-"""
+"""unit tests for modules cubicweb.server.rqlannotation"""
from cubicweb.devtools import init_test_database
from cubicweb.devtools.repotest import BaseQuerierTC
-repo, cnx = init_test_database()
-def teardown_module(*args):
+def setUpModule(*args):
+ global repo, cnx
+ repo, cnx = init_test_database(apphome=SQLGenAnnotatorTC.datadir)
+
+def tearDownModule(*args):
global repo, cnx
del repo, cnx
class SQLGenAnnotatorTC(BaseQuerierTC):
- repo = repo
+
+ def setUp(self):
+ self.__class__.repo = repo
+ super(SQLGenAnnotatorTC, self).setUp()
def get_max_eid(self):
# no need for cleanup here
--- a/server/test/unittest_schemaserial.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_schemaserial.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,8 +15,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for schema rql (de)serialization
-"""
+"""unit tests for schema rql (de)serialization"""
import sys
from cStringIO import StringIO
@@ -26,14 +25,16 @@
from cubicweb.schema import CubicWebSchemaLoader
from cubicweb.devtools import TestServerConfiguration
-loader = CubicWebSchemaLoader()
-config = TestServerConfiguration('data')
-config.bootstrap_cubes()
-schema = loader.load(config)
+def setUpModule(*args):
+ global schema, config
+ loader = CubicWebSchemaLoader()
+ config = TestServerConfiguration('data', apphome=Schema2RQLTC.datadir)
+ config.bootstrap_cubes()
+ schema = loader.load(config)
-def teardown_module(*args):
- global schema, config, loader
- del schema, config, loader
+def tearDownModule(*args):
+ global schema, config
+ del schema, config
from cubicweb.server.schemaserial import *
from cubicweb.server.schemaserial import _erperms2rql as erperms2rql
--- a/server/test/unittest_security.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_security.py Fri Mar 11 09:46:45 2011 +0100
@@ -27,8 +27,8 @@
class BaseSecurityTC(CubicWebTC):
- def setUp(self):
- CubicWebTC.setUp(self)
+ def setup_database(self):
+ super(BaseSecurityTC, self).setup_database()
self.create_user('iaminusersgrouponly')
self.readoriggroups = self.schema['Personne'].permissions['read']
self.addoriggroups = self.schema['Personne'].permissions['add']
@@ -75,7 +75,7 @@
def tearDown(self):
self.repo.system_source.__dict__.pop('syntax_tree_search', None)
- BaseSecurityTC.tearDown(self)
+ super(SecurityRewritingTC, self).tearDown()
def test_not_relation_read_security(self):
cnx = self.login('iaminusersgrouponly')
@@ -86,6 +86,7 @@
self.execute('Any U WHERE NOT EXISTS(A todo_by U), A is Affaire')
self.assertEqual(self.query[0][1].as_string(),
'Any U WHERE NOT EXISTS(A todo_by U), A is Affaire')
+ cnx.close()
class SecurityTC(BaseSecurityTC):
@@ -104,6 +105,7 @@
cu.execute("INSERT Personne X: X nom 'bidule'")
self.assertRaises(Unauthorized, cnx.commit)
self.assertEqual(cu.execute('Personne X').rowcount, 1)
+ cnx.close()
def test_insert_rql_permission(self):
# test user can only add une affaire related to a societe he owns
@@ -120,6 +122,7 @@
cu.execute("INSERT Societe X: X nom 'chouette'")
cu.execute("SET A concerne S WHERE A sujet 'cool', S nom 'chouette'")
cnx.commit()
+ cnx.close()
def test_update_security_1(self):
cnx = self.login('anon')
@@ -147,6 +150,7 @@
cu.execute("INSERT Personne X: X nom 'biduuule'")
cu.execute("INSERT Societe X: X nom 'looogilab'")
cu.execute("SET X travaille S WHERE X nom 'biduuule', S nom 'looogilab'")
+ cnx.close()
def test_update_rql_permission(self):
self.execute("SET A concerne S WHERE A is Affaire, S is Societe")
@@ -165,6 +169,7 @@
cu.execute("SET A concerne S WHERE A sujet 'pascool', S nom 'chouette'")
cu.execute("SET X sujet 'habahsicestcool' WHERE X sujet 'pascool'")
cnx.commit()
+ cnx.close()
def test_delete_security(self):
# FIXME: sample below fails because we don't detect "owner" can't delete
@@ -177,6 +182,7 @@
cnx = self.login('iaminusersgrouponly')
cu = cnx.cursor()
self.assertRaises(Unauthorized, cu.execute, "DELETE CWGroup Y WHERE Y name 'staff'")
+ cnx.close()
def test_delete_rql_permission(self):
self.execute("SET A concerne S WHERE A is Affaire, S is Societe")
@@ -200,6 +206,7 @@
## self.assertRaises(Unauthorized, cu.execute, "DELETE Affaire X")
cu.execute("DELETE Affaire X WHERE X sujet 'pascool'")
cnx.commit()
+ cnx.close()
def test_insert_relation_rql_permission(self):
@@ -225,6 +232,7 @@
cu.execute("INSERT Societe X: X nom 'chouette'")
cu.execute("SET A concerne S WHERE A is Affaire, S nom 'chouette'")
cnx.commit()
+ cnx.close()
def test_delete_relation_rql_permission(self):
self.execute("SET A concerne S WHERE A is Affaire, S is Societe")
@@ -249,6 +257,7 @@
cu.execute("SET A concerne S WHERE A is Affaire, S nom 'chouette'")
cnx.commit()
cu.execute("DELETE A concerne S WHERE S nom 'chouette'")
+ cnx.close()
def test_user_can_change_its_upassword(self):
@@ -260,6 +269,7 @@
cnx.commit()
cnx.close()
cnx = self.login('user', password='newpwd')
+ cnx.close()
def test_user_cant_change_other_upassword(self):
ueid = self.create_user('otheruser').eid
@@ -268,6 +278,7 @@
cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
{'x': ueid, 'passwd': 'newpwd'})
self.assertRaises(Unauthorized, cnx.commit)
+ cnx.close()
# read security test
@@ -277,6 +288,7 @@
cu = cnx.cursor()
self.assertRaises(Unauthorized,
cu.execute, 'Personne U where U nom "managers"')
+ cnx.close()
def test_read_erqlexpr_base(self):
eid = self.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
@@ -301,6 +313,7 @@
self.assertEqual(rset.rows, [])
# test can't update an attribute of an entity that can't be readen
self.assertRaises(Unauthorized, cu.execute, 'SET X sujet "hacked" WHERE X eid %(x)s', {'x': eid})
+ cnx.close()
def test_entity_created_in_transaction(self):
@@ -337,6 +350,7 @@
rset = cu.execute("Any X WHERE X has_text 'cool'")
self.assertEqual(sorted(eid for eid, in rset.rows),
[card1, aff2])
+ cnx.close()
def test_read_erqlexpr_has_text2(self):
self.execute("INSERT Personne X: X nom 'bidule'")
@@ -349,6 +363,7 @@
self.assertEqual(len(rset.rows), 1, rset.rows)
rset = cu.execute('Any N WITH N BEING (Any N WHERE N has_text "bidule")')
self.assertEqual(len(rset.rows), 1, rset.rows)
+ cnx.close()
def test_read_erqlexpr_optional_rel(self):
self.execute("INSERT Personne X: X nom 'bidule'")
@@ -359,6 +374,7 @@
cu = cnx.cursor()
rset = cu.execute('Any N,U WHERE N has_text "bidule", N owned_by U?')
self.assertEqual(len(rset.rows), 1, rset.rows)
+ cnx.close()
def test_read_erqlexpr_aggregat(self):
self.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
@@ -382,6 +398,7 @@
values = dict(rset)
self.assertEqual(values['Affaire'], 1)
self.assertEqual(values['Societe'], 2)
+ cnx.close()
def test_attribute_security(self):
@@ -429,6 +446,7 @@
cnx.commit()
cu.execute("SET X para 'chouette' WHERE X eid %(x)s", {'x': note2.eid})
cnx.commit()
+ cnx.close()
def test_attribute_read_security(self):
# anon not allowed to see users'login, but they can see users
@@ -446,6 +464,7 @@
self.assertEqual(x.login, None)
self.failUnless(x.creation_date)
cnx.rollback()
+ cnx.close()
class BaseSchemaSecurityTC(BaseSecurityTC):
"""tests related to the base schema permission configuration"""
@@ -472,6 +491,7 @@
cu.execute('DELETE Affaire X WHERE X ref "ARCT01"')
cnx.commit()
self.failIf(cu.execute('Affaire X'))
+ cnx.close()
def test_users_and_groups_non_readable_by_guests(self):
cnx = self.login('anon')
@@ -498,6 +518,7 @@
# but can't modify it
cu.execute('SET X login "toto" WHERE X eid %(x)s', {'x': anon.eid})
self.assertRaises(Unauthorized, cnx.commit)
+ cnx.close()
def test_in_group_relation(self):
cnx = self.login('iaminusersgrouponly')
@@ -506,6 +527,7 @@
self.assertRaises(Unauthorized, cu.execute, rql)
rql = u"SET U in_group G WHERE U login 'admin', G name 'users'"
self.assertRaises(Unauthorized, cu.execute, rql)
+ cnx.close()
def test_owned_by(self):
self.execute("INSERT Personne X: X nom 'bidule'")
@@ -514,6 +536,7 @@
cu = cnx.cursor()
rql = u"SET X owned_by U WHERE U login 'iaminusersgrouponly', X is Personne"
self.assertRaises(Unauthorized, cu.execute, rql)
+ cnx.close()
def test_bookmarked_by_guests_security(self):
beid1 = self.execute('INSERT Bookmark B: B path "?vid=manage", B title "manage"')[0][0]
@@ -535,6 +558,7 @@
self.assertRaises(Unauthorized,
cu.execute, 'SET B bookmarked_by U WHERE U eid %(x)s, B eid %(b)s',
{'x': anoneid, 'b': beid1})
+ cnx.close()
def test_ambigous_ordered(self):
@@ -542,6 +566,7 @@
cu = cnx.cursor()
names = [t for t, in cu.execute('Any N ORDERBY lower(N) WHERE X name N')]
self.assertEqual(names, sorted(names, key=lambda x: x.lower()))
+ cnx.close()
def test_in_state_without_update_perm(self):
"""check a user change in_state without having update permission on the
@@ -575,6 +600,7 @@
# restore orig perms
for action, perms in affaire_perms.iteritems():
self.schema['Affaire'].set_action_permissions(action, perms)
+ cnx.close()
def test_trinfo_security(self):
aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
--- a/server/test/unittest_ssplanner.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_ssplanner.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,25 +15,25 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-"""
from cubicweb.devtools import init_test_database
from cubicweb.devtools.repotest import BasePlannerTC, test_plan
from cubicweb.server.ssplanner import SSPlanner
# keep cnx so it's not garbage collected and the associated session closed
-repo, cnx = init_test_database()
+def setUpModule(*args):
+ global repo, cnx
+ repo, cnx = init_test_database(apphome=SSPlannerTC.datadir)
-def teardown_module(*args):
+def tearDownModule(*args):
global repo, cnx
del repo, cnx
class SSPlannerTC(BasePlannerTC):
- repo = repo
_test = test_plan
def setUp(self):
+ self.__class__.repo = repo
BasePlannerTC.setUp(self)
self.planner = SSPlanner(self.o.schema, self.repo.vreg.rqlhelper)
self.system = self.o._repo.system_source
--- a/server/test/unittest_storage.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_storage.py Fri Mar 11 09:46:45 2011 +0100
@@ -60,7 +60,7 @@
storages.set_attribute_storage(self.repo, 'File', 'data', bfs_storage)
def tearDown(self):
- super(CubicWebTC, self).tearDown()
+ super(StorageTC, self).tearDown()
storages.unset_attribute_storage(self.repo, 'File', 'data')
shutil.rmtree(self.tempdir)
@@ -75,6 +75,15 @@
{'f': entity.eid})[0][0]
return fspath.getvalue()
+ def test_bfss_wrong_fspath_usage(self):
+ f1 = self.create_file()
+ self.execute('Any fspath(D) WHERE F eid %(f)s, F data D', {'f': f1.eid})
+ with self.assertRaises(NotImplementedError) as cm:
+ self.execute('Any fspath(F) WHERE F eid %(f)s', {'f': f1.eid})
+ self.assertEqual(str(cm.exception),
+ 'This callback is only available for BytesFileSystemStorage '
+ 'managed attribute. Is FSPATH() argument BFSS managed?')
+
def test_bfss_storage(self):
f1 = self.create_file()
expected_filepath = osp.join(self.tempdir, '%s_data_%s' %
@@ -114,34 +123,34 @@
self.create_file()
def test_source_mapped_attribute_error_cases(self):
- ex = self.assertRaises(QueryError, self.execute,
- 'Any X WHERE X data ~= "hop", X is File')
- self.assertEqual(str(ex), 'can\'t use File.data (X data ILIKE "hop") in restriction')
- ex = self.assertRaises(QueryError, self.execute,
- 'Any X, Y WHERE X data D, Y data D, '
- 'NOT X identity Y, X is File, Y is File')
- self.assertEqual(str(ex), "can't use D as a restriction variable")
+ with self.assertRaises(QueryError) as cm:
+ self.execute('Any X WHERE X data ~= "hop", X is File')
+ self.assertEqual(str(cm.exception), 'can\'t use File.data (X data ILIKE "hop") in restriction')
+ with self.assertRaises(QueryError) as cm:
+ self.execute('Any X, Y WHERE X data D, Y data D, '
+ 'NOT X identity Y, X is File, Y is File')
+ self.assertEqual(str(cm.exception), "can't use D as a restriction variable")
# query returning mix of mapped / regular attributes (only file.data
# mapped, not image.data for instance)
- ex = self.assertRaises(QueryError, self.execute,
- 'Any X WITH X BEING ('
- ' (Any NULL)'
- ' UNION '
- ' (Any D WHERE X data D, X is File)'
- ')')
- self.assertEqual(str(ex), 'query fetch some source mapped attribute, some not')
- ex = self.assertRaises(QueryError, self.execute,
- '(Any D WHERE X data D, X is File)'
- ' UNION '
- '(Any D WHERE X title D, X is Bookmark)')
- self.assertEqual(str(ex), 'query fetch some source mapped attribute, some not')
+ with self.assertRaises(QueryError) as cm:
+ self.execute('Any X WITH X BEING ('
+ ' (Any NULL)'
+ ' UNION '
+ ' (Any D WHERE X data D, X is File)'
+ ')')
+ self.assertEqual(str(cm.exception), 'query fetch some source mapped attribute, some not')
+ with self.assertRaises(QueryError) as cm:
+ self.execute('(Any D WHERE X data D, X is File)'
+ ' UNION '
+ '(Any D WHERE X title D, X is Bookmark)')
+ self.assertEqual(str(cm.exception), 'query fetch some source mapped attribute, some not')
storages.set_attribute_storage(self.repo, 'State', 'name',
storages.BytesFileSystemStorage(self.tempdir))
try:
- ex = self.assertRaises(QueryError,
- self.execute, 'Any D WHERE X name D, X is IN (State, Transition)')
- self.assertEqual(str(ex), 'query fetch some source mapped attribute, some not')
+ with self.assertRaises(QueryError) as cm:
+ self.execute('Any D WHERE X name D, X is IN (State, Transition)')
+ self.assertEqual(str(cm.exception), 'query fetch some source mapped attribute, some not')
finally:
storages.unset_attribute_storage(self.repo, 'State', 'name')
@@ -172,10 +181,10 @@
self.assertEqual(rset[1][0], f1.eid)
self.assertEqual(rset[0][1], len('the-data'))
self.assertEqual(rset[1][1], len('the-data'))
- ex = self.assertRaises(QueryError, self.execute,
- 'Any X,UPPER(D) WHERE X eid %(x)s, X data D',
- {'x': f1.eid})
- self.assertEqual(str(ex), 'UPPER can not be called on mapped attribute')
+ with self.assertRaises(QueryError) as cm:
+ self.execute('Any X,UPPER(D) WHERE X eid %(x)s, X data D',
+ {'x': f1.eid})
+ self.assertEqual(str(cm.exception), 'UPPER can not be called on mapped attribute')
def test_bfss_fs_importing_transparency(self):
--- a/server/test/unittest_undo.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/test/unittest_undo.py Fri Mar 11 09:46:45 2011 +0100
@@ -212,9 +212,10 @@
self.assertEqual(errors,
[u"Can't restore relation in_group, object entity "
"%s doesn't exist anymore." % g.eid])
- ex = self.assertRaises(ValidationError, self.commit)
- self.assertEqual(ex.entity, self.toto.eid)
- self.assertEqual(ex.errors,
+ with self.assertRaises(ValidationError) as cm:
+ self.commit()
+ self.assertEqual(cm.exception.entity, self.toto.eid)
+ self.assertEqual(cm.exception.errors,
{'in_group-subject': u'at least one relation in_group is '
'required on CWUser (%s)' % self.toto.eid})
@@ -252,10 +253,10 @@
value=u'text/html')
tutu.set_relations(use_email=email, reverse_for_user=prop)
self.commit()
- ex = self.assertRaises(ValidationError,
- self.cnx.undo_transaction, txuuid)
- self.assertEqual(ex.entity, tutu.eid)
- self.assertEqual(ex.errors,
+ with self.assertRaises(ValidationError) as cm:
+ self.cnx.undo_transaction(txuuid)
+ self.assertEqual(cm.exception.entity, tutu.eid)
+ self.assertEqual(cm.exception.errors,
{None: 'some later transaction(s) touch entity, undo them first'})
def test_undo_creation_integrity_2(self):
@@ -265,17 +266,17 @@
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
self.toto.set_relations(in_group=g)
self.commit()
- ex = self.assertRaises(ValidationError,
- self.cnx.undo_transaction, txuuid)
- self.assertEqual(ex.entity, g.eid)
- self.assertEqual(ex.errors,
+ with self.assertRaises(ValidationError) as cm:
+ self.cnx.undo_transaction(txuuid)
+ self.assertEqual(cm.exception.entity, g.eid)
+ self.assertEqual(cm.exception.errors,
{None: 'some later transaction(s) touch entity, undo them first'})
# self.assertEqual(errors,
# [u"Can't restore relation in_group, object entity "
# "%s doesn't exist anymore." % g.eid])
- # ex = self.assertRaises(ValidationError, self.commit)
- # self.assertEqual(ex.entity, self.toto.eid)
- # self.assertEqual(ex.errors,
+ # with self.assertRaises(ValidationError) as cm: self.commit()
+ # self.assertEqual(cm.exception.entity, self.toto.eid)
+ # self.assertEqual(cm.exception.errors,
# {'in_group-subject': u'at least one relation in_group is '
# 'required on CWUser (%s)' % self.toto.eid})
--- a/server/utils.py Fri Dec 10 12:17:18 2010 +0100
+++ b/server/utils.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,12 +20,11 @@
import sys
import string
+import logging
from threading import Timer, Thread
from getpass import getpass
from random import choice
-from logilab.common.configuration import Configuration
-
from cubicweb.server import SOURCE_TYPES
try:
@@ -111,12 +110,6 @@
return user, passwd
-def ask_source_config(sourcetype, inputlevel=0):
- sconfig = Configuration(options=SOURCE_TYPES[sourcetype].options)
- sconfig.adapter = sourcetype
- sconfig.input_config(inputlevel=inputlevel)
- return sconfig
-
_MARKER=object()
def func_name(func):
name = getattr(func, '__name__', _MARKER)
@@ -137,6 +130,10 @@
def auto_restart_func(self=self, func=func, args=args):
try:
func(*args)
+ except:
+ logger = logging.getLogger('cubicweb.repository')
+ logger.exception('Unhandled exception in LoopTask %s', self.name)
+ raise
finally:
self.start()
self.func = auto_restart_func
@@ -166,6 +163,10 @@
def auto_remove_func(self=self, func=target):
try:
func()
+ except:
+ logger = logging.getLogger('cubicweb.repository')
+ logger.exception('Unhandled exception in RepoThread %s', self._name)
+ raise
finally:
self.running_threads.remove(self)
Thread.__init__(self, target=auto_remove_func)
--- a/setup.py Fri Dec 10 12:17:18 2010 +0100
+++ b/setup.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# pylint: disable-msg=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
+# pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
#
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -194,9 +194,10 @@
old_ok = DS._ok
def _ok(self, path):
"""Return True if ``path`` can be written during installation."""
- out = old_ok(self, path)
+ out = old_ok(self, path) # here for side effect from setuptools
realpath = os.path.normcase(os.path.realpath(path))
- if realpath.startswith(sys.prefix):
+ allowed_path = os.path.normcase(sys.prefix)
+ if realpath.startswith(allowed_path):
out = True
return out
DS._ok = _ok
--- a/skeleton/__pkginfo__.py.tmpl Fri Dec 10 12:17:18 2010 +0100
+++ b/skeleton/__pkginfo__.py.tmpl Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0622
+# pylint: disable=W0622
"""%(distname)s application packaging information"""
modname = '%(cubename)s'
@@ -8,13 +8,13 @@
version = '.'.join(str(num) for num in numversion)
license = '%(license)s'
-
author = '%(author)s'
author_email = '%(author-email)s'
+description = '%(shortdesc)s'
+web = 'http://www.cubicweb.org/project/%%s' %% distname
-description = '%(shortdesc)s'
-
-web = 'http://www.cubicweb.org/project/%%s' %% distname
+__depends__ = %(dependencies)s
+__recommends__ = {}
from os import listdir as _listdir
@@ -40,6 +40,3 @@
# Note: here, you'll need to add subdirectories if you want
# them to be included in the debian package
-__depends__ = %(dependencies)s
-__recommends__ = {}
-
--- a/skeleton/setup.py Fri Dec 10 12:17:18 2010 +0100
+++ b/skeleton/setup.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,8 +1,25 @@
#!/usr/bin/env python
-# pylint: disable=W0404,W0622,W0704,W0613
+# pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
+#
# 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 tag cube.
+#
+# 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 <http://www.gnu.org/licenses/>.
+"""Generic Setup script, takes package info from __pkginfo__.py file
+"""
__docformat__ = "restructuredtext en"
import os
@@ -12,49 +29,50 @@
try:
if os.environ.get('NO_SETUPTOOLS'):
- raise ImportError()
+ raise ImportError() # do as there is no setuptools
from setuptools import setup
from setuptools.command import install_lib
- USE_SETUPTOOLS = 1
+ USE_SETUPTOOLS = True
except ImportError:
from distutils.core import setup
from distutils.command import install_lib
- USE_SETUPTOOLS = 0
+ USE_SETUPTOOLS = False
+from distutils.command import install_data
-
-sys.modules.pop('__pkginfo__', None)
# import required features
-from __pkginfo__ import modname, version, license, description, \
- web, author, author_email
-# import optional features
-import __pkginfo__
-distname = getattr(__pkginfo__, 'distname', modname)
-scripts = getattr(__pkginfo__, 'scripts', [])
-data_files = getattr(__pkginfo__, 'data_files', None)
-include_dirs = getattr(__pkginfo__, 'include_dirs', [])
-ext_modules = getattr(__pkginfo__, 'ext_modules', None)
-dependency_links = getattr(__pkginfo__, 'dependency_links', [])
-
-STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
-
-IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
+from __pkginfo__ import modname, version, license, description, web, \
+ author, author_email
if exists('README'):
long_description = file('README').read()
else:
long_description = ''
+
+# import optional features
+import __pkginfo__
if USE_SETUPTOOLS:
- requires = {}
- for entry in ("__depends__", "__recommends__"):
- requires.update(getattr(__pkginfo__, entry, {}))
- install_requires = [("%s %s" % (d, v and v or "")).strip()
+ requires = {}
+ for entry in ("__depends__",): # "__recommends__"):
+ requires.update(getattr(__pkginfo__, entry, {}))
+ install_requires = [("%s %s" % (d, v and v or "")).strip()
for d, v in requires.iteritems()]
else:
- install_requires = []
+ install_requires = []
+
+distname = getattr(__pkginfo__, 'distname', modname)
+scripts = getattr(__pkginfo__, 'scripts', ())
+include_dirs = getattr(__pkginfo__, 'include_dirs', ())
+data_files = getattr(__pkginfo__, 'data_files', None)
+ext_modules = getattr(__pkginfo__, 'ext_modules', None)
+dependency_links = getattr(__pkginfo__, 'dependency_links', ())
+
+BASE_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
+IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
def ensure_scripts(linux_scripts):
- """Creates the proper script names required for each platform
+ """
+ Creates the proper script names required for each platform
(taken from 4Suite)
"""
from distutils import util
@@ -64,23 +82,8 @@
scripts_ = linux_scripts
return scripts_
-def get_packages(directory, prefix):
- """return a list of subpackages for the given directory"""
- result = []
- for package in os.listdir(directory):
- absfile = join(directory, package)
- if isdir(absfile):
- if exists(join(absfile, '__init__.py')) or \
- package in ('test', 'tests'):
- if prefix:
- result.append('%s.%s' % (prefix, package))
- else:
- result.append(package)
- result += get_packages(absfile, result[-1])
- return result
-
def export(from_dir, to_dir,
- blacklist=STD_BLACKLIST,
+ blacklist=BASE_BLACKLIST,
ignore_ext=IGNORED_EXTENSIONS,
verbose=True):
"""make a mirror of from_dir in to_dir, omitting directories and files
@@ -134,6 +137,35 @@
dest = join(self.install_dir, base, directory)
export(directory, dest, verbose=False)
+# re-enable copying data files in sys.prefix
+old_install_data = install_data.install_data
+if USE_SETUPTOOLS:
+ # overwrite InstallData to use sys.prefix instead of the egg directory
+ class MyInstallData(old_install_data):
+ """A class that manages data files installation"""
+ def run(self):
+ _old_install_dir = self.install_dir
+ if self.install_dir.endswith('egg'):
+ self.install_dir = sys.prefix
+ old_install_data.run(self)
+ self.install_dir = _old_install_dir
+ try:
+ import setuptools.command.easy_install # only if easy_install avaible
+ # monkey patch: Crack SandboxViolation verification
+ from setuptools.sandbox import DirectorySandbox as DS
+ old_ok = DS._ok
+ def _ok(self, path):
+ """Return True if ``path`` can be written during installation."""
+ out = old_ok(self, path) # here for side effect from setuptools
+ realpath = os.path.normcase(os.path.realpath(path))
+ allowed_path = os.path.normcase(sys.prefix)
+ if realpath.startswith(allowed_path):
+ out = True
+ return out
+ DS._ok = _ok
+ except ImportError:
+ pass
+
def install(**kwargs):
"""setup entry point"""
if USE_SETUPTOOLS:
@@ -142,9 +174,13 @@
# install-layout option was introduced in 2.5.3-1~exp1
elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv:
sys.argv.remove('--install-layout=deb')
- if USE_SETUPTOOLS and install_requires:
+ cmdclass = {'install_lib': MyInstallLib}
+ if USE_SETUPTOOLS:
kwargs['install_requires'] = install_requires
kwargs['dependency_links'] = dependency_links
+ kwargs['zip_safe'] = False
+ cmdclass['install_data'] = MyInstallData
+
return setup(name = distname,
version = version,
license = license,
@@ -156,7 +192,7 @@
scripts = ensure_scripts(scripts),
data_files = data_files,
ext_modules = ext_modules,
- cmdclass = {'install_lib': MyInstallLib},
+ cmdclass = cmdclass,
**kwargs
)
--- a/skeleton/test/realdb_test_CUBENAME.py Fri Dec 10 12:17:18 2010 +0100
+++ b/skeleton/test/realdb_test_CUBENAME.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,7 +21,7 @@
from cubicweb.devtools import buildconfig, loadconfig
from cubicweb.devtools.testlib import RealDBTest
-def setup_module(options):
+def setUpModule(options):
if options.source:
configcls = loadconfig(options.source)
elif options.dbname is None:
@@ -33,7 +33,7 @@
RealDatabaseTC.configcls = configcls
class RealDatabaseTC(RealDBTest):
- configcls = None # set by setup_module()
+ configcls = None # set by setUpModule()
def test_all_primaries(self):
for rset in self.iter_individual_rsets(limit=50):
--- a/skeleton/test/test_CUBENAME.py.tmpl Fri Dec 10 12:17:18 2010 +0100
+++ b/skeleton/test/test_CUBENAME.py.tmpl Fri Mar 11 09:46:45 2011 +0100
@@ -29,7 +29,7 @@
class DefaultTC(testlib.CubicWebTC):
def test_something(self):
- self.skip('this cube has no test')
+ self.skipTest('this cube has no test')
if __name__ == '__main__':
--- a/sobjects/notification.py Fri Dec 10 12:17:18 2010 +0100
+++ b/sobjects/notification.py Fri Mar 11 09:46:45 2011 +0100
@@ -27,7 +27,7 @@
from cubicweb.selectors import yes
from cubicweb.view import Component
-from cubicweb.mail import NotificationView, SkipEmail
+from cubicweb.mail import NotificationView as BaseNotificationView, SkipEmail
from cubicweb.server.hook import SendMailOp
@@ -59,7 +59,7 @@
# abstract or deactivated notification views and mixin ########################
-class NotificationView(NotificationView):
+class NotificationView(BaseNotificationView):
"""overriden to delay actual sending of mails to a commit operation by
default
"""
--- a/sobjects/supervising.py Fri Dec 10 12:17:18 2010 +0100
+++ b/sobjects/supervising.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,11 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""some hooks and views to handle supervising of any data changes
-
+"""some hooks and views to handle supervising of any data changes"""
-"""
__docformat__ = "restructuredtext en"
+_ = unicode
from cubicweb import UnknownEid
from cubicweb.selectors import none_rset
@@ -135,7 +134,8 @@
self.w(msg % locals())
def change_state(self, (entity, fromstate, tostate)):
- msg = self._cw._('changed state of %(etype)s #%(eid)s (%(title)s)')
+ _ = self._cw._
+ msg = _('changed state of %(etype)s #%(eid)s (%(title)s)')
self.w(u'%s\n' % (msg % self._entity_context(entity)))
self.w(_(' from state %(fromstate)s to state %(tostate)s\n' %
{'fromstate': _(fromstate.name), 'tostate': _(tostate.name)}))
@@ -185,6 +185,6 @@
msg = format_mail(uinfo, recipients, content, view.subject(), config=config)
self.to_send = [(msg, recipients)]
- def commit_event(self):
+ def postcommit_event(self):
self._prepare_email()
- SendMailOp.commit_event(self)
+ SendMailOp.postcommit_event(self)
--- a/sobjects/test/unittest_notification.py Fri Dec 10 12:17:18 2010 +0100
+++ b/sobjects/test/unittest_notification.py Fri Mar 11 09:46:45 2011 +0100
@@ -58,7 +58,7 @@
def test_nonregr_empty_message_id(self):
for eid in (1, 12, 123, 1234):
msgid1 = construct_message_id('testapp', eid, 12)
- self.assertNotEquals(msgid1, '<@testapp.%s>' % gethostname())
+ self.assertNotEqual(msgid1, '<@testapp.%s>' % gethostname())
class RecipientsFinderTC(CubicWebTC):
--- a/sobjects/textparsers.py Fri Dec 10 12:17:18 2010 +0100
+++ b/sobjects/textparsers.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,13 +15,13 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""hooks triggered on email entities creation:
+"""Some parsers to detect action to do from text
-* look for state change instruction (XXX security)
-* set email content as a comment on an entity when comments are supported and
- linking information are found
+Currently only a parser to look for state change instruction is provided.
+Take care to security when you're using it, think about the user that
+will provide the text to analyze...
+"""
-"""
__docformat__ = "restructuredtext en"
import re
@@ -29,7 +29,6 @@
from cubicweb import UnknownEid, typed_eid
from cubicweb.view import Component
- # XXX use user session if gpg signature validated
class TextAnalyzer(Component):
"""analyze and extract information from plain text by calling registered
--- a/tags.py Fri Dec 10 12:17:18 2010 +0100
+++ b/tags.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""helper classes to generate simple (X)HTML tags
+"""helper classes to generate simple (X)HTML tags"""
-"""
__docformat__ = "restructuredtext en"
from cubicweb.uilib import simple_sgml_tag, sgml_attributes
--- a/test/data/cubes/comment/__pkginfo__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/cubes/comment/__pkginfo__.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0622
+# pylint: disable=W0622
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
--- a/test/data/cubes/email/__pkginfo__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/cubes/email/__pkginfo__.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0622
+# pylint: disable=W0622
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
--- a/test/data/cubes/file/__pkginfo__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/cubes/file/__pkginfo__.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0622
+# pylint: disable=W0622
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
--- a/test/data/cubes/forge/__pkginfo__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/cubes/forge/__pkginfo__.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0622
+# pylint: disable=W0622
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/lowered_etype.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,5 @@
+
+from yams.buildobjs import EntityType
+
+class my_etype(EntityType):
+ pass
--- a/test/data/schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,7 +19,9 @@
"""
-from yams.buildobjs import EntityType, String, SubjectRelation, RelationDefinition
+from yams.buildobjs import (EntityType, String, SubjectRelation,
+ RelationDefinition)
+from cubicweb.schema import WorkflowableEntityType
class Personne(EntityType):
nom = String(required=True)
@@ -48,3 +50,9 @@
class evaluee(RelationDefinition):
subject = 'CWUser'
object = 'Note'
+
+
+class StateFull(WorkflowableEntityType):
+ name = String()
+
+
--- a/test/data/scripts/script1.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/scripts/script1.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,4 @@
-assert 'data/scripts/script1.py' == __file__
-assert '__main__' == __name__
+from os.path import join
+assert __file__.endswith(join('scripts', 'script1.py')), __file__
+assert '__main__' == __name__, __name__
assert [] == __args__, __args__
--- a/test/data/scripts/script2.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/scripts/script2.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,4 @@
-assert 'data/scripts/script2.py' == __file__
-assert '__main__' == __name__
+from os.path import join
+assert __file__.endswith(join('scripts', 'script2.py')), __file__
+assert '__main__' == __name__, __name__
assert ['-v'] == __args__, __args__
--- a/test/data/scripts/script3.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/data/scripts/script3.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,4 @@
-assert 'data/scripts/script3.py' == __file__
-assert '__main__' == __name__
+from os.path import join
+assert __file__.endswith(join('scripts', 'script3.py')), __file__
+assert '__main__' == __name__, __name__
assert ['-vd', '-f', 'FILE.TXT'] == __args__, __args__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/uppered_rtype.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,6 @@
+
+from yams.buildobjs import RelationDefinition
+
+class ARelation(RelationDefinition):
+ subject = 'CWUser'
+ object = 'CWGroup'
--- a/test/unittest_cwconfig.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_cwconfig.py Fri Mar 11 09:46:45 2011 +0100
@@ -43,7 +43,7 @@
class CubicWebConfigurationTC(TestCase):
def setUp(self):
cleanup_sys_modules([CUSTOM_CUBES_DIR, ApptestConfiguration.CUBES_DIR])
- self.config = ApptestConfiguration('data')
+ self.config = ApptestConfiguration('data', apphome=self.datadir)
self.config._cubes = ('email', 'file')
def tearDown(self):
--- a/test/unittest_cwctl.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_cwctl.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
import sys
import os
+from os.path import join
from cStringIO import StringIO
from logilab.common.testlib import TestCase, unittest_main
@@ -54,9 +52,10 @@
'script2.py': ['-v'],
'script3.py': ['-vd', '-f', 'FILE.TXT'],
}
- mih.cmd_process_script('data/scripts/script1.py', funcname=None)
+ mih.cmd_process_script(join(self.datadir, 'scripts', 'script1.py'),
+ funcname=None)
for script, args in scripts.items():
- scriptname = os.path.join('data/scripts/', script)
+ scriptname = os.path.join(self.datadir, 'scripts', script)
self.assert_(os.path.exists(scriptname))
mih.cmd_process_script(scriptname, None, scriptargs=args)
--- a/test/unittest_dbapi.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_dbapi.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,13 +15,15 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
+"""unittest for cubicweb.dbapi"""
-"""
from __future__ import with_statement
+
from copy import copy
-from cubicweb import ConnectionError
+from logilab.common import tempattr
+
+from cubicweb import ConnectionError, cwconfig
from cubicweb.dbapi import ProgrammingError
from cubicweb.devtools.testlib import CubicWebTC
@@ -30,7 +32,7 @@
def test_public_repo_api(self):
cnx = self.login('anon')
self.assertEqual(cnx.get_schema(), self.repo.schema)
- self.assertEqual(cnx.source_defs(), {'system': {'adapter': 'native', 'uri': 'system'}})
+ self.assertEqual(cnx.source_defs(), {'system': {'type': 'native', 'uri': 'system'}})
self.restore_connection() # proper way to close cnx
self.assertRaises(ProgrammingError, cnx.get_schema)
self.assertRaises(ProgrammingError, cnx.source_defs)
@@ -48,7 +50,7 @@
def test_api(self):
cnx = self.login('anon')
self.assertEqual(cnx.user(None).login, 'anon')
- self.assertEqual(cnx.describe(1), (u'CWGroup', u'system', None))
+ self.assertEqual(cnx.describe(1), (u'CWSource', u'system', None))
self.restore_connection() # proper way to close cnx
self.assertRaises(ProgrammingError, cnx.user, None)
self.assertRaises(ProgrammingError, cnx.describe, 1)
@@ -68,6 +70,16 @@
self.assertRaises(ProgrammingError, cnx.set_shared_data, 'data', 0)
self.assertRaises(ProgrammingError, cnx.get_shared_data, 'data')
+ def test_web_compatible_request(self):
+ config = cwconfig.CubicWebNoAppConfiguration()
+ with tempattr(self.cnx.vreg, 'config', config):
+ self.cnx.use_web_compatible_requests('http://perdu.com')
+ req = self.cnx.request()
+ self.assertEqual(req.base_url(), 'http://perdu.com')
+ self.assertEqual(req.from_controller(), 'view')
+ self.assertEqual(req.relative_path(), '')
+ req.ajax_replace_url('domid') # don't crash
+ req.user.cw_adapt_to('IBreadCrumbs') # don't crash
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
--- a/test/unittest_entity.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_entity.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,9 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for cubicweb.web.views.entities module
-
-"""
+"""unit tests for cubicweb.web.views.entities module"""
from datetime import datetime
@@ -26,6 +24,8 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.mttransforms import HAS_TAL
from cubicweb.entities import fetch_config
+from cubicweb.uilib import soup2xhtml
+
class EntityTC(CubicWebTC):
@@ -319,33 +319,33 @@
def test_printable_value_string(self):
e = self.request().create_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
- content_format=u'text/rest')
+ content_format=u'text/rest')
self.assertEqual(e.printable_value('content'),
- '<p>du <a class="reference" href="http://testing.fr/cubicweb/cwgroup/guests">*ReST*</a></p>\n')
- e['content'] = 'du <em>html</em> <ref rql="CWUser X">users</ref>'
- e['content_format'] = 'text/html'
+ '<p>du <a class="reference" href="http://testing.fr/cubicweb/cwsource/system">*ReST*</a></p>\n')
+ e.cw_attr_cache['content'] = 'du <em>html</em> <ref rql="CWUser X">users</ref>'
+ e.cw_attr_cache['content_format'] = 'text/html'
self.assertEqual(e.printable_value('content'),
'du <em>html</em> <a href="http://testing.fr/cubicweb/view?rql=CWUser%20X">users</a>')
- e['content'] = 'du *texte*'
- e['content_format'] = 'text/plain'
+ e.cw_attr_cache['content'] = 'du *texte*'
+ e.cw_attr_cache['content_format'] = 'text/plain'
self.assertEqual(e.printable_value('content'),
'<p>\ndu *texte*<br/>\n</p>')
- e['title'] = 'zou'
- e['content'] = '''\
+ e.cw_attr_cache['title'] = 'zou'
+ e.cw_attr_cache['content'] = '''\
a title
=======
du :eid:`1:*ReST*`'''
- e['content_format'] = 'text/rest'
+ e.cw_attr_cache['content_format'] = 'text/rest'
self.assertEqual(e.printable_value('content', format='text/plain'),
- e['content'])
+ e.cw_attr_cache['content'])
- e['content'] = u'<b>yo (zou éà ;)</b>'
- e['content_format'] = 'text/html'
+ e.cw_attr_cache['content'] = u'<b>yo (zou éà ;)</b>'
+ e.cw_attr_cache['content_format'] = 'text/html'
self.assertEqual(e.printable_value('content', format='text/plain').strip(),
- u'**yo (zou éà ;)**')
+ u'**yo (zou éà ;)**')
if HAS_TAL:
- e['content'] = '<h1 tal:content="self/title">titre</h1>'
- e['content_format'] = 'text/cubicweb-page-template'
+ e.cw_attr_cache['content'] = '<h1 tal:content="self/title">titre</h1>'
+ e.cw_attr_cache['content_format'] = 'text/cubicweb-page-template'
self.assertEqual(e.printable_value('content'),
'<h1>zou</h1>')
@@ -387,63 +387,53 @@
tidy = lambda x: x.replace('\n', '')
self.assertEqual(tidy(e.printable_value('content')),
'<div>R&D<br/></div>')
- e['content'] = u'yo !! R&D <div> pas fermé'
- self.assertEqual(tidy(e.printable_value('content')),
- u'yo !! R&D <div> pas fermé</div>')
- e['content'] = u'R&D'
- self.assertEqual(tidy(e.printable_value('content')), u'R&D')
- e['content'] = u'R&D;'
- self.assertEqual(tidy(e.printable_value('content')), u'R&D;')
- e['content'] = u'yo !! R&D <div> pas fermé'
+ e.cw_attr_cache['content'] = u'yo !! R&D <div> pas fermé'
self.assertEqual(tidy(e.printable_value('content')),
u'yo !! R&D <div> pas fermé</div>')
- e['content'] = u'été <div> été'
+ e.cw_attr_cache['content'] = u'R&D'
+ self.assertEqual(tidy(e.printable_value('content')), u'R&D')
+ e.cw_attr_cache['content'] = u'R&D;'
+ self.assertEqual(tidy(e.printable_value('content')), u'R&D;')
+ e.cw_attr_cache['content'] = u'yo !! R&D <div> pas fermé'
self.assertEqual(tidy(e.printable_value('content')),
- u'été <div> été</div>')
- e['content'] = u'C'est un exemple sérieux'
+ u'yo !! R&D <div> pas fermé</div>')
+ e.cw_attr_cache['content'] = u'été <div> été'
self.assertEqual(tidy(e.printable_value('content')),
- u"C'est un exemple sérieux")
+ u'été <div> été</div>')
+ e.cw_attr_cache['content'] = u'C'est un exemple sérieux'
+ self.assertEqual(tidy(e.printable_value('content')),
+ u"C'est un exemple sérieux")
# make sure valid xhtml is left untouched
- e['content'] = u'<div>R&D<br/></div>'
- self.assertEqual(e.printable_value('content'), e['content'])
- e['content'] = u'<div>été</div>'
- self.assertEqual(e.printable_value('content'), e['content'])
- e['content'] = u'été'
- self.assertEqual(e.printable_value('content'), e['content'])
- e['content'] = u'hop\r\nhop\nhip\rmomo'
+ e.cw_attr_cache['content'] = u'<div>R&D<br/></div>'
+ self.assertEqual(e.printable_value('content'), e.cw_attr_cache['content'])
+ e.cw_attr_cache['content'] = u'<div>été</div>'
+ self.assertEqual(e.printable_value('content'), e.cw_attr_cache['content'])
+ e.cw_attr_cache['content'] = u'été'
+ self.assertEqual(e.printable_value('content'), e.cw_attr_cache['content'])
+ e.cw_attr_cache['content'] = u'hop\r\nhop\nhip\rmomo'
self.assertEqual(e.printable_value('content'), u'hop\nhop\nhip\nmomo')
def test_printable_value_bad_html_ms(self):
- self.skipTest('fix soup2xhtml to handle this test')
req = self.request()
e = req.create_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
content_format=u'text/html')
tidy = lambda x: x.replace('\n', '')
- e['content'] = u'<div x:foo="bar">ms orifice produces weird html</div>'
- self.assertEqual(tidy(e.printable_value('content')),
- u'<div>ms orifice produces weird html</div>')
- import tidy as tidymod # apt-get install python-tidy
- tidy = lambda x: str(tidymod.parseString(x.encode('utf-8'),
- **{'drop_proprietary_attributes': True,
- 'output_xhtml': True,
- 'show_body_only' : True,
- 'quote-nbsp' : False,
- 'char_encoding' : 'utf8'})).decode('utf-8').strip()
- self.assertEqual(tidy(e.printable_value('content')),
- u'<div>ms orifice produces weird html</div>')
-
+ e.cw_attr_cache['content'] = u'<div x:foo="bar">ms orifice produces weird html</div>'
+ # Caution! current implementation of soup2xhtml strips first div element
+ content = soup2xhtml(e.printable_value('content'), 'utf-8')
+ self.assertMultiLineEqual(content, u'<div>ms orifice produces weird html</div>')
def test_fulltextindex(self):
e = self.vreg['etypes'].etype_class('File')(self.request())
- e['description'] = 'du <em>html</em>'
- e['description_format'] = 'text/html'
- e['data'] = Binary('some <em>data</em>')
- e['data_name'] = 'an html file'
- e['data_format'] = 'text/html'
- e['data_encoding'] = 'ascii'
+ e.cw_attr_cache['description'] = 'du <em>html</em>'
+ e.cw_attr_cache['description_format'] = 'text/html'
+ e.cw_attr_cache['data'] = Binary('some <em>data</em>')
+ e.cw_attr_cache['data_name'] = 'an html file'
+ e.cw_attr_cache['data_format'] = 'text/html'
+ e.cw_attr_cache['data_encoding'] = 'ascii'
e._cw.transaction_data = {} # XXX req should be a session
self.assertEqual(e.cw_adapt_to('IFTIndexable').get_words(),
- {'C': [u'du', u'html', 'an', 'html', 'file', u'some', u'data']})
+ {'C': ['an', 'html', 'file', 'du', 'html', 'some', 'data']})
def test_nonregr_relation_cache(self):
@@ -461,7 +451,7 @@
'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
trinfo = self.execute('Any X WHERE X eid %(x)s', {'x': eid}).get_entity(0, 0)
trinfo.complete()
- self.failUnless(isinstance(trinfo['creation_date'], datetime))
+ self.failUnless(isinstance(trinfo.cw_attr_cache['creation_date'], datetime))
self.failUnless(trinfo.cw_relation_cached('from_state', 'subject'))
self.failUnless(trinfo.cw_relation_cached('to_state', 'subject'))
self.failUnless(trinfo.cw_relation_cached('wf_info_for', 'subject'))
@@ -499,7 +489,7 @@
self.assertEqual(card3.rest_path(), 'card/eid/%d' % card3.eid)
card4 = req.create_entity('Card', title=u'pod', wikiid=u'zo?bi')
self.assertEqual(card4.rest_path(), 'card/eid/%d' % card4.eid)
-
+
def test_set_attributes(self):
req = self.request()
@@ -515,7 +505,7 @@
req = self.request()
note = req.create_entity('Note', type=u'z')
metainf = note.cw_metainformation()
- self.assertEqual(metainf, {'source': {'adapter': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None})
+ self.assertEqual(metainf, {'source': {'type': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None})
self.assertEqual(note.absolute_url(), 'http://testing.fr/cubicweb/note/%s' % note.eid)
metainf['source'] = metainf['source'].copy()
metainf['source']['base-url'] = 'http://cubicweb2.com/'
--- a/test/unittest_migration.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_migration.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,11 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""cubicweb.migration unit tests
+"""cubicweb.migration unit tests"""
-"""
-
-from os.path import abspath
+from os.path import abspath, dirname, join
from logilab.common.testlib import TestCase, unittest_main
from cubicweb.devtools import TestServerConfiguration
@@ -32,8 +30,8 @@
def has_entity(self, e_type):
return self.has_key(e_type)
-SMIGRDIR = abspath('data/server_migration') + '/'
-TMIGRDIR = abspath('data/migration') + '/'
+SMIGRDIR = join(dirname(__file__), 'data', 'server_migration') + '/'
+TMIGRDIR = join(dirname(__file__), 'data', 'migration') + '/'
class MigrTestConfig(TestServerConfiguration):
verbosity = 0
@@ -105,7 +103,7 @@
def test_db_creation(self):
"""make sure database can be created"""
- config = ApptestConfiguration('data')
+ config = ApptestConfiguration('data', apphome=self.datadir)
source = config.sources()['system']
self.assertEqual(source['db-driver'], 'sqlite')
cleanup_sqlite(source['db-name'], removetemplate=True)
--- a/test/unittest_req.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_req.py Fri Mar 11 09:46:45 2011 +0100
@@ -40,7 +40,7 @@
self.assertEqual(req.build_url('one'), u'http://testing.fr/cubicweb/one')
self.assertEqual(req.build_url(param='ok'), u'http://testing.fr/cubicweb/view?param=ok')
self.assertRaises(AssertionError, req.build_url, 'one', 'two not allowed')
- self.assertRaises(ValueError, req.build_url, 'view', test=None)
+ self.assertRaises(AssertionError, req.build_url, 'view', test=None)
def test_ensure_no_rql(self):
req = RequestSessionBase(None)
--- a/test/unittest_rqlrewrite.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_rqlrewrite.py Fri Mar 11 09:46:45 2011 +0100
@@ -26,20 +26,23 @@
from cubicweb.rqlrewrite import RQLRewriter
from cubicweb.devtools import repotest, TestServerConfiguration
-config = TestServerConfiguration('data/rewrite')
-config.bootstrap_cubes()
-schema = config.load_schema()
-from yams.buildobjs import RelationDefinition
-schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*'))
-rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
- 'has_text': 'fti'})
+def setUpModule(*args):
+ global rqlhelper, schema
+ config = TestServerConfiguration(RQLRewriteTC.datapath('rewrite'))
+ config.bootstrap_cubes()
+ schema = config.load_schema()
+ from yams.buildobjs import RelationDefinition
+ schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*'))
-def setup_module(*args):
+ rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
+ 'has_text': 'fti'})
repotest.do_monkey_patch()
-def teardown_module(*args):
+def tearDownModule(*args):
repotest.undo_monkey_patch()
+ global rqlhelper, schema
+ del rqlhelper, schema
def eid_func_map(eid):
return {1: 'CWUser',
--- a/test/unittest_rset.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_rset.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,9 +16,7 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for module cubicweb.utils
-
-"""
+"""unit tests for module cubicweb.utils"""
from urlparse import urlsplit
import pickle
@@ -51,7 +49,7 @@
'Any C where C is Company, C employs P' : [],
}
for rql, relations in queries.items():
- result = list(attr_desc_iterator(parse(rql).children[0]))
+ result = list(attr_desc_iterator(parse(rql).children[0], 0, 0))
self.assertEqual((rql, result), (rql, relations))
def test_relations_description_indexed(self):
@@ -61,8 +59,8 @@
{0: [(2,'employs', 'subject')], 1: [(3,'login', 'subject'), (4,'mail', 'subject')]},
}
for rql, results in queries.items():
- for var_index, relations in results.items():
- result = list(attr_desc_iterator(parse(rql).children[0], var_index))
+ for idx, relations in results.items():
+ result = list(attr_desc_iterator(parse(rql).children[0], idx, idx))
self.assertEqual(result, relations)
@@ -157,13 +155,13 @@
rs.req = self.request()
rs.vreg = self.vreg
- rs2 = rs.sorted_rset(lambda e:e['login'])
+ rs2 = rs.sorted_rset(lambda e:e.cw_attr_cache['login'])
self.assertEqual(len(rs2), 3)
self.assertEqual([login for _, login in rs2], ['adim', 'nico', 'syt'])
# make sure rs is unchanged
self.assertEqual([login for _, login in rs], ['adim', 'syt', 'nico'])
- rs2 = rs.sorted_rset(lambda e:e['login'], reverse=True)
+ rs2 = rs.sorted_rset(lambda e:e.cw_attr_cache['login'], reverse=True)
self.assertEqual(len(rs2), 3)
self.assertEqual([login for _, login in rs2], ['syt', 'nico', 'adim'])
# make sure rs is unchanged
@@ -186,8 +184,7 @@
description=[['CWUser', 'String', 'String']] * 5)
rs.req = self.request()
rs.vreg = self.vreg
-
- rsets = rs.split_rset(lambda e:e['login'])
+ rsets = rs.split_rset(lambda e:e.cw_attr_cache['login'])
self.assertEqual(len(rsets), 3)
self.assertEqual([login for _, login,_ in rsets[0]], ['adim', 'adim'])
self.assertEqual([login for _, login,_ in rsets[1]], ['syt'])
@@ -195,7 +192,7 @@
# make sure rs is unchanged
self.assertEqual([login for _, login,_ in rs], ['adim', 'adim', 'syt', 'nico', 'nico'])
- rsets = rs.split_rset(lambda e:e['login'], return_dict=True)
+ rsets = rs.split_rset(lambda e:e.cw_attr_cache['login'], return_dict=True)
self.assertEqual(len(rsets), 3)
self.assertEqual([login for _, login,_ in rsets['nico']], ['nico', 'nico'])
self.assertEqual([login for _, login,_ in rsets['adim']], ['adim', 'adim'])
@@ -230,12 +227,12 @@
self.request().create_entity('CWUser', login=u'adim', upassword='adim',
surname=u'di mascio', firstname=u'adrien')
e = self.execute('Any X,T WHERE X login "adim", X surname T').get_entity(0, 0)
- self.assertEqual(e['surname'], 'di mascio')
- self.assertRaises(KeyError, e.__getitem__, 'firstname')
- self.assertRaises(KeyError, e.__getitem__, 'creation_date')
+ self.assertEqual(e.cw_attr_cache['surname'], 'di mascio')
+ self.assertRaises(KeyError, e.cw_attr_cache.__getitem__, 'firstname')
+ self.assertRaises(KeyError, e.cw_attr_cache.__getitem__, 'creation_date')
self.assertEqual(pprelcachedict(e._cw_related_cache), [])
e.complete()
- self.assertEqual(e['firstname'], 'adrien')
+ self.assertEqual(e.cw_attr_cache['firstname'], 'adrien')
self.assertEqual(pprelcachedict(e._cw_related_cache), [])
def test_get_entity_advanced(self):
@@ -246,20 +243,20 @@
e = rset.get_entity(0, 0)
self.assertEqual(e.cw_row, 0)
self.assertEqual(e.cw_col, 0)
- self.assertEqual(e['title'], 'zou')
- self.assertRaises(KeyError, e.__getitem__, 'path')
+ self.assertEqual(e.cw_attr_cache['title'], 'zou')
+ self.assertRaises(KeyError, e.cw_attr_cache.__getitem__, 'path')
self.assertEqual(e.view('text'), 'zou')
self.assertEqual(pprelcachedict(e._cw_related_cache), [])
e = rset.get_entity(0, 1)
self.assertEqual(e.cw_row, 0)
self.assertEqual(e.cw_col, 1)
- self.assertEqual(e['login'], 'anon')
- self.assertRaises(KeyError, e.__getitem__, 'firstname')
+ self.assertEqual(e.cw_attr_cache['login'], 'anon')
+ self.assertRaises(KeyError, e.cw_attr_cache.__getitem__, 'firstname')
self.assertEqual(pprelcachedict(e._cw_related_cache),
[])
e.complete()
- self.assertEqual(e['firstname'], None)
+ self.assertEqual(e.cw_attr_cache['firstname'], None)
self.assertEqual(e.view('text'), 'anon')
self.assertEqual(pprelcachedict(e._cw_related_cache),
[])
@@ -282,17 +279,17 @@
rset = self.execute('Any X,U,S,XT,UL,SN WHERE X created_by U, U in_state S, '
'X title XT, S name SN, U login UL, X eid %s' % e.eid)
e = rset.get_entity(0, 0)
- self.assertEqual(e['title'], 'zou')
+ self.assertEqual(e.cw_attr_cache['title'], 'zou')
self.assertEqual(pprelcachedict(e._cw_related_cache),
- [('created_by_subject', [5])])
+ [('created_by_subject', [self.user().eid])])
# first level of recursion
u = e.created_by[0]
- self.assertEqual(u['login'], 'admin')
- self.assertRaises(KeyError, u.__getitem__, 'firstname')
+ self.assertEqual(u.cw_attr_cache['login'], 'admin')
+ self.assertRaises(KeyError, u.cw_attr_cache.__getitem__, 'firstname')
# second level of recursion
s = u.in_state[0]
- self.assertEqual(s['name'], 'activated')
- self.assertRaises(KeyError, s.__getitem__, 'description')
+ self.assertEqual(s.cw_attr_cache['name'], 'activated')
+ self.assertRaises(KeyError, s.cw_attr_cache.__getitem__, 'description')
def test_get_entity_cache_with_left_outer_join(self):
@@ -322,7 +319,7 @@
etype, n = expected[entity.cw_row]
self.assertEqual(entity.__regid__, etype)
attr = etype == 'Bookmark' and 'title' or 'name'
- self.assertEqual(entity[attr], n)
+ self.assertEqual(entity.cw_attr_cache[attr], n)
def test_related_entity_optional(self):
e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
@@ -331,7 +328,7 @@
self.assertEqual(entity, None)
self.assertEqual(rtype, None)
- def test_related_entity_union_subquery(self):
+ def test_related_entity_union_subquery_1(self):
e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
'((Any X,N WHERE X is CWGroup, X name N)'
@@ -340,10 +337,14 @@
entity, rtype = rset.related_entity(0, 1)
self.assertEqual(entity.eid, e.eid)
self.assertEqual(rtype, 'title')
+ self.assertEqual(entity.title, 'aaaa')
entity, rtype = rset.related_entity(1, 1)
self.assertEqual(entity.__regid__, 'CWGroup')
self.assertEqual(rtype, 'name')
- #
+ self.assertEqual(entity.name, 'guests')
+
+ def test_related_entity_union_subquery_2(self):
+ e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
rset = self.execute('Any X,N ORDERBY N WHERE X is Bookmark WITH X,N BEING '
'((Any X,N WHERE X is CWGroup, X name N)'
' UNION '
@@ -351,7 +352,10 @@
entity, rtype = rset.related_entity(0, 1)
self.assertEqual(entity.eid, e.eid)
self.assertEqual(rtype, 'title')
- #
+ self.assertEqual(entity.title, 'aaaa')
+
+ def test_related_entity_union_subquery_3(self):
+ e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
rset = self.execute('Any X,N ORDERBY N WITH N,X BEING '
'((Any N,X WHERE X is CWGroup, X name N)'
' UNION '
@@ -359,6 +363,18 @@
entity, rtype = rset.related_entity(0, 1)
self.assertEqual(entity.eid, e.eid)
self.assertEqual(rtype, 'title')
+ self.assertEqual(entity.title, 'aaaa')
+
+ def test_related_entity_union_subquery_4(self):
+ e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
+ rset = self.execute('Any X,X, N ORDERBY N WITH X,N BEING '
+ '((Any X,N WHERE X is CWGroup, X name N)'
+ ' UNION '
+ ' (Any X,N WHERE X is Bookmark, X title N))')
+ entity, rtype = rset.related_entity(0, 2)
+ self.assertEqual(entity.eid, e.eid)
+ self.assertEqual(rtype, 'title')
+ self.assertEqual(entity.title, 'aaaa')
def test_related_entity_trap_subquery(self):
req = self.request()
@@ -385,6 +401,14 @@
self.assertEqual(set(e.e_schema.type for e in rset.entities(1)),
set(['CWGroup',]))
+ def test_iter_rows_with_entities(self):
+ rset = self.execute('Any U,UN,G,GN WHERE U in_group G, U login UN, G name GN')
+ # make sure we have at least one element
+ self.failUnless(rset)
+ out = list(rset.iter_rows_with_entities())[0]
+ self.assertEqual( out[0].login, out[1] )
+ self.assertEqual( out[2].name, out[3] )
+
def test_printable_rql(self):
rset = self.execute(u'CWEType X WHERE X final FALSE')
self.assertEqual(rset.printable_rql(),
--- a/test/unittest_schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,6 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for module cubicweb.schema"""
+from __future__ import with_statement
+
import sys
from os.path import join, isabs, basename, dirname
@@ -72,7 +74,6 @@
('Personne tel Int'),
('Personne fax Int'),
('Personne datenaiss Date'),
- ('Personne TEST Boolean'),
('Personne promo String'),
# real relations
('Personne travaille Societe'),
@@ -80,7 +81,7 @@
('Societe evaluee Note'),
('Personne concerne Affaire'),
('Personne concerne Societe'),
- ('Affaire Concerne Societe'),
+ ('Affaire concerne Societe'),
)
done = {}
for rel in RELS:
@@ -108,17 +109,6 @@
self.failIf(issubclass(RQLUniqueConstraint, RQLConstraint))
self.failUnless(issubclass(RQLConstraint, RQLVocabularyConstraint))
- def test_normalize(self):
- """test that entities, relations and attributes name are normalized
- """
- self.assertEqual(esociete.type, 'Societe')
- self.assertEqual(schema.has_relation('TEST'), 0)
- self.assertEqual(schema.has_relation('test'), 1)
- self.assertEqual(eperson.subjrels['test'].type, 'test')
- self.assertEqual(schema.has_relation('Concerne'), 0)
- self.assertEqual(schema.has_relation('concerne'), 1)
- self.assertEqual(schema.rschema('concerne').type, 'concerne')
-
def test_entity_perms(self):
self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests')))
self.assertEqual(eperson.get_groups('update'), set(('managers', 'owners',)))
@@ -151,7 +141,7 @@
self.assertEqual(str(expr), 'Any O,U WHERE U has_update_permission O, O eid %(o)s, U eid %(u)s')
loader = CubicWebSchemaLoader()
-config = TestConfiguration('data')
+config = TestConfiguration('data', apphome=DATADIR)
config.bootstrap_cubes()
class SchemaReaderClassTest(TestCase):
@@ -167,31 +157,32 @@
schema = loader.load(config)
self.assert_(isinstance(schema, CubicWebSchema))
self.assertEqual(schema.name, 'data')
- entities = [str(e) for e in schema.entities()]
- entities.sort()
+ entities = sorted([str(e) for e in schema.entities()])
expected_entities = ['BaseTransition', 'Bookmark', 'Boolean', 'Bytes', 'Card',
'Date', 'Datetime', 'Decimal',
'CWCache', 'CWConstraint', 'CWConstraintType', 'CWEType',
'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation',
'CWPermission', 'CWProperty', 'CWRType',
+ 'CWSource', 'CWSourceHostConfig',
'CWUniqueTogetherConstraint', 'CWUser',
'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note',
'Password', 'Personne',
'RQLExpression',
- 'Societe', 'State', 'String', 'SubNote', 'SubWorkflowExitPoint',
+ 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
'Tag', 'Time', 'Transition', 'TrInfo',
'Workflow', 'WorkflowTransition']
self.assertListEqual(entities, sorted(expected_entities))
- relations = [str(r) for r in schema.relations()]
- relations.sort()
+ relations = sorted([str(r) for r in schema.relations()])
expected_relations = ['add_permission', 'address', 'alias', 'allowed_transition',
'bookmarked_by', 'by_transition',
'cardinality', 'comment', 'comment_format',
- 'composite', 'condition', 'connait',
+ 'composite', 'condition', 'config', 'connait',
'constrained_by', 'constraint_of',
'content', 'content_format',
- 'created_by', 'creation_date', 'cstrtype', 'custom_workflow', 'cwuri',
+ 'created_by', 'creation_date', 'cstrtype', 'custom_workflow',
+ 'cwuri', 'cw_source', 'cw_host_config_of',
+ 'cw_support', 'cw_dont_cross', 'cw_may_cross',
'data', 'data_encoding', 'data_format', 'data_name', 'default_workflow', 'defaultval', 'delete_permission',
'description', 'description_format', 'destination_state',
@@ -207,7 +198,7 @@
'label', 'last_login_time', 'login',
- 'mainvars', 'modification_date',
+ 'mainvars', 'match_host', 'modification_date',
'name', 'nom',
@@ -225,13 +216,14 @@
'value',
- 'wf_info_for', 'wikiid', 'workflow_of']
+ 'wf_info_for', 'wikiid', 'workflow_of', 'tr_count']
- self.assertListEqual(relations, expected_relations)
+ self.assertListEqual(relations, sorted(expected_relations))
eschema = schema.eschema('CWUser')
rels = sorted(str(r) for r in eschema.subject_relations())
- self.assertListEqual(rels, ['created_by', 'creation_date', 'custom_workflow', 'cwuri', 'eid',
+ self.assertListEqual(rels, ['created_by', 'creation_date', 'custom_workflow',
+ 'cw_source', 'cwuri', 'eid',
'evaluee', 'firstname', 'has_text', 'identity',
'in_group', 'in_state', 'is',
'is_instance_of', 'last_login_time',
@@ -267,7 +259,7 @@
self.assertEqual([x.expression for x in aschema.get_rqlexprs('update')],
['U has_update_permission X'])
-class BadSchemaRQLExprTC(TestCase):
+class BadSchemaTC(TestCase):
def setUp(self):
self.loader = CubicWebSchemaLoader()
self.loader.defined = {}
@@ -277,9 +269,19 @@
def _test(self, schemafile, msg):
self.loader.handle_file(join(DATADIR, schemafile))
- ex = self.assertRaises(BadSchemaDefinition,
- self.loader._build_schema, 'toto', False)
- self.assertEqual(str(ex), msg)
+ with self.assertRaises(BadSchemaDefinition) as cm:
+ self.loader._build_schema('toto', False)
+ self.assertEqual(str(cm.exception), msg)
+
+ def test_lowered_etype(self):
+ self._test('lowered_etype.py',
+ "'my_etype' is not a valid name for an entity type. It should "
+ "start with an upper cased letter and be followed by at least "
+ "a lower cased letter")
+
+ def test_uppered_rtype(self):
+ self._test('uppered_rtype.py',
+ "'ARelation' is not a valid name for a relation type. It should be lower cased")
def test_rrqlexpr_on_etype(self):
self._test('rrqlexpr_on_eetype.py',
@@ -308,7 +310,7 @@
def test_comparison(self):
self.assertEqual(ERQLExpression('X is CWUser', 'X', 0),
ERQLExpression('X is CWUser', 'X', 0))
- self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0),
+ self.assertNotEqual(ERQLExpression('X is CWUser', 'X', 0),
ERQLExpression('X is CWGroup', 'X', 0))
class GuessRrqlExprMainVarsTC(TestCase):
--- a/test/unittest_selectors.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_selectors.py Fri Mar 11 09:46:45 2011 +0100
@@ -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.
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for selectors mechanism"""
+from __future__ import with_statement
from operator import eq, lt, le, gt
from logilab.common.testlib import TestCase, unittest_main
@@ -24,10 +25,11 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.appobject import Selector, AndSelector, OrSelector
from cubicweb.selectors import (is_instance, adaptable, match_user_groups,
- multi_lines_rset)
-from cubicweb.interfaces import IDownloadable
+ multi_lines_rset, score_entity, is_in_state,
+ on_transition)
from cubicweb.web import action
+
class _1_(Selector):
def __call__(self, *args, **kwargs):
return 1
@@ -138,6 +140,35 @@
self.assertEqual(selector(None), 0)
+class IsInStateSelectorTC(CubicWebTC):
+ def setup_database(self):
+ wf = self.shell().add_workflow("testwf", 'StateFull', default=True)
+ initial = wf.add_state(u'initial', initial=True)
+ final = wf.add_state(u'final')
+ wf.add_transition(u'forward', (initial,), final)
+
+ def test_initial_state(self):
+ req = self.request()
+ entity = req.create_entity('StateFull')
+ selector = is_in_state(u'initial')
+ self.commit()
+ score = selector(entity.__class__, None, entity=entity)
+ self.assertEqual(score, 1)
+
+ def test_final_state(self):
+ req = self.request()
+ entity = req.create_entity('StateFull')
+ selector = is_in_state(u'initial')
+ self.commit()
+ entity.cw_adapt_to('IWorkflowable').fire_transition(u'forward')
+ self.commit()
+ score = selector(entity.__class__, None, entity=entity)
+ self.assertEqual(score, 0)
+ selector = is_in_state(u'final')
+ score = selector(entity.__class__, None, entity=entity)
+ self.assertEqual(score, 1)
+
+
class ImplementsSelectorTC(CubicWebTC):
def test_etype_priority(self):
req = self.request()
@@ -159,6 +190,131 @@
3)
+class WorkflowSelectorTC(CubicWebTC):
+ def _commit(self):
+ self.commit()
+ self.wf_entity.clear_all_caches()
+
+ def setup_database(self):
+ wf = self.shell().add_workflow("wf_test", 'StateFull', default=True)
+ created = wf.add_state('created', initial=True)
+ validated = wf.add_state('validated')
+ abandoned = wf.add_state('abandoned')
+ wf.add_transition('validate', created, validated, ('managers',))
+ wf.add_transition('forsake', (created, validated,), abandoned, ('managers',))
+
+ def setUp(self):
+ super(WorkflowSelectorTC, self).setUp()
+ self.req = self.request()
+ self.wf_entity = self.req.create_entity('StateFull', name=u'')
+ self.rset = self.wf_entity.as_rset()
+ self.adapter = self.wf_entity.cw_adapt_to('IWorkflowable')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'created')
+ # enable debug mode to state/transition validation on the fly
+ self.vreg.config.debugmode = True
+
+ def tearDown(self):
+ self.vreg.config.debugmode = False
+ super(WorkflowSelectorTC, self).tearDown()
+
+ def test_is_in_state(self):
+ for state in ('created', 'validated', 'abandoned'):
+ selector = is_in_state(state)
+ self.assertEqual(selector(None, self.req, self.rset),
+ state=="created")
+
+ self.adapter.fire_transition('validate')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'validated')
+
+ selector = is_in_state('created')
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = is_in_state('validated')
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ selector = is_in_state('validated', 'abandoned')
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ selector = is_in_state('abandoned')
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+
+ self.adapter.fire_transition('forsake')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'abandoned')
+
+ selector = is_in_state('created')
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = is_in_state('validated')
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = is_in_state('validated', 'abandoned')
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ self.assertEqual(self.adapter.state, 'abandoned')
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+
+ def test_is_in_state_unvalid_names(self):
+ selector = is_in_state("unknown")
+ with self.assertRaises(ValueError) as cm:
+ selector(None, self.req, self.rset)
+ self.assertEqual(str(cm.exception),
+ "wf_test: unknown state(s): unknown")
+ selector = is_in_state("weird", "unknown", "created", "weird")
+ with self.assertRaises(ValueError) as cm:
+ selector(None, self.req, self.rset)
+ self.assertEqual(str(cm.exception),
+ "wf_test: unknown state(s): unknown,weird")
+
+ def test_on_transition(self):
+ for transition in ('validate', 'forsake'):
+ selector = on_transition(transition)
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+
+ self.adapter.fire_transition('validate')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'validated')
+
+ selector = on_transition("validate")
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ selector = on_transition("validate", "forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ selector = on_transition("forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+
+ self.adapter.fire_transition('forsake')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'abandoned')
+
+ selector = on_transition("validate")
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = on_transition("validate", "forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+ selector = on_transition("forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 1)
+
+ def test_on_transition_unvalid_names(self):
+ selector = on_transition("unknown")
+ with self.assertRaises(ValueError) as cm:
+ selector(None, self.req, self.rset)
+ self.assertEqual(str(cm.exception),
+ "wf_test: unknown transition(s): unknown")
+ selector = on_transition("weird", "unknown", "validate", "weird")
+ with self.assertRaises(ValueError) as cm:
+ selector(None, self.req, self.rset)
+ self.assertEqual(str(cm.exception),
+ "wf_test: unknown transition(s): unknown,weird")
+
+ def test_on_transition_with_no_effect(self):
+ """selector will not be triggered with `change_state()`"""
+ self.adapter.change_state('validated')
+ self._commit()
+ self.assertEqual(self.adapter.state, 'validated')
+
+ selector = on_transition("validate")
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = on_transition("validate", "forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+ selector = on_transition("forsake")
+ self.assertEqual(selector(None, self.req, self.rset), 0)
+
+
class MatchUserGroupsTC(CubicWebTC):
def test_owners_group(self):
"""tests usage of 'owners' group with match_user_group"""
@@ -245,6 +401,24 @@
yield self.assertEqual, selector(None, self.req, self.rset), assertion
+class ScoreEntitySelectorTC(CubicWebTC):
+
+ def test_intscore_entity_selector(self):
+ req = self.request()
+ rset = req.execute('Any E WHERE E eid 1')
+ selector = score_entity(lambda x: None)
+ self.assertEqual(selector(None, req, rset), 0)
+ selector = score_entity(lambda x: "something")
+ self.assertEqual(selector(None, req, rset), 1)
+ selector = score_entity(lambda x: object)
+ self.assertEqual(selector(None, req, rset), 1)
+ rset = req.execute('Any G LIMIT 2 WHERE G is CWGroup')
+ selector = score_entity(lambda x: 10)
+ self.assertEqual(selector(None, req, rset), 20)
+ selector = score_entity(lambda x: 10, once_is_enough=True)
+ self.assertEqual(selector(None, req, rset), 10)
+
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_uilib.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_uilib.py Fri Mar 11 09:46:45 2011 +0100
@@ -16,17 +16,20 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""unittests for cubicweb.uilib
-
-"""
+"""unittests for cubicweb.uilib"""
__docformat__ = "restructuredtext en"
+
+import pkg_resources
from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.tree import Node
+from unittest2 import skipIf
from cubicweb import uilib
+lxml_version = pkg_resources.get_distribution('lxml').version.split('.')
+
+
class UILIBTC(TestCase):
def test_remove_tags(self):
@@ -94,7 +97,15 @@
got = uilib.text_cut(text, 30)
self.assertEqual(got, expected)
+ def test_soup2xhtml_0(self):
+ self.assertEqual(uilib.soup2xhtml('hop\r\nhop', 'ascii'),
+ 'hop\nhop')
+
def test_soup2xhtml_1_1(self):
+ self.assertEqual(uilib.soup2xhtml('hop', 'ascii'),
+ 'hop')
+ self.assertEqual(uilib.soup2xhtml('hop<div>', 'ascii'),
+ 'hop<div/>')
self.assertEqual(uilib.soup2xhtml('hop <div>', 'ascii'),
'hop <div/>')
self.assertEqual(uilib.soup2xhtml('<div> hop', 'ascii'),
@@ -118,11 +129,14 @@
self.assertEqual(uilib.soup2xhtml('hop <body> hop', 'ascii'),
'hop hop')
- def test_soup2xhtml_2_2(self):
+ def test_soup2xhtml_2_2a(self):
self.assertEqual(uilib.soup2xhtml('hop </body>', 'ascii'),
'hop ')
self.assertEqual(uilib.soup2xhtml('</body> hop', 'ascii'),
' hop')
+
+ @skipIf(lxml_version < ['2', '2'], 'expected behaviour on recent version of lxml only')
+ def test_soup2xhtml_2_2b(self):
self.assertEqual(uilib.soup2xhtml('hop </body> hop', 'ascii'),
'hop hop')
@@ -142,6 +156,10 @@
self.assertEqual(uilib.soup2xhtml('hop </html> hop', 'ascii'),
'hop hop')
+ def test_soup2xhtml_3_3(self):
+ self.assertEqual(uilib.soup2xhtml('<script>test</script> hop ', 'ascii'),
+ ' hop ')
+
def test_js(self):
self.assertEqual(str(uilib.js.pouet(1, "2")),
'pouet(1,"2")')
@@ -150,6 +168,23 @@
self.assertEqual(str(uilib.js.cw.pouet(1, "2").pouet(None)),
'cw.pouet(1,"2").pouet(null)')
+ def test_embedded_css(self):
+ incoming = u"""voir le ticket <style type="text/css">@font-face { font-family: "Cambria"; }p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0cm 0cm 10pt; font-size: 12pt; font-family: "Times New Roman"; }a:link, span.MsoHyperlink { color: blue; text-decoration: underline; }a:visited, span.MsoHyperlinkFollowed { color: purple; text-decoration: underline; }div.Section1 { page: Section1; }</style></p><p class="MsoNormal">text</p>"""
+ expected = 'voir le ticket <p class="MsoNormal">text</p>'
+ self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected)
+
+ def test_unknown_namespace(self):
+ incoming = '''<table cellspacing="0" cellpadding="0" width="81" border="0" x:str="" style="width: 61pt; border-collapse: collapse">
+<colgroup><col width="81" style="width: 61pt; mso-width-source: userset; mso-width-alt: 2962"/></colgroup>
+<tbody><tr height="17" style="height: 12.75pt"><td width="81" height="17" style="border-right: #e0dfe3; border-top: #e0dfe3; border-left: #e0dfe3; width: 61pt; border-bottom: #e0dfe3; height: 12.75pt; background-color: transparent"><font size="2">XXXXXXX</font></td></tr></tbody>
+</table>'''
+ expected = '''<table cellspacing="0" cellpadding="0" width="81" border="0">\
+<colgroup><col width="81"/></colgroup>\
+<tbody><tr height="17"><td width="81" height="17">XXXXXXX</td></tr></tbody>\
+</table>'''
+ self.assertMultiLineEqual(uilib.soup2xhtml(incoming, 'ascii'), expected)
+
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_utils.py Fri Dec 10 12:17:18 2010 +0100
+++ b/test/unittest_utils.py Fri Mar 11 09:46:45 2011 +0100
@@ -33,8 +33,8 @@
class MakeUidTC(TestCase):
def test_1(self):
- self.assertNotEquals(make_uid('xyz'), make_uid('abcd'))
- self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
+ self.assertNotEqual(make_uid('xyz'), make_uid('abcd'))
+ self.assertNotEqual(make_uid('xyz'), make_uid('xyz'))
def test_2(self):
d = set()
@@ -140,14 +140,14 @@
def test_encoding_bare_entity(self):
e = Entity(None)
- e['pouet'] = 'hop'
+ e.cw_attr_cache['pouet'] = 'hop'
e.eid = 2
self.assertEqual(json.loads(self.encode(e)),
{'pouet': 'hop', 'eid': 2})
def test_encoding_entity_in_list(self):
e = Entity(None)
- e['pouet'] = 'hop'
+ e.cw_attr_cache['pouet'] = 'hop'
e.eid = 2
self.assertEqual(json.loads(self.encode([e])),
[{'pouet': 'hop', 'eid': 2}])
--- a/uilib.py Fri Dec 10 12:17:18 2010 +0100
+++ b/uilib.py Fri Mar 11 09:46:45 2011 +0100
@@ -47,9 +47,8 @@
return 'Any X WHERE X eid %s' % eid
def eid_param(name, eid):
+ assert name is not None
assert eid is not None
- if eid is None:
- eid = ''
return '%s:%s' % (name, eid)
def printable_value(req, attrtype, value, props=None, displaytime=True):
@@ -81,7 +80,7 @@
# text publishing #############################################################
try:
- from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
+ from cubicweb.ext.rest import rest_publish # pylint: disable=W0611
except ImportError:
def rest_publish(entity, data):
"""default behaviour if docutils was not found"""
@@ -109,12 +108,6 @@
return u''
return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text)
-# fallback implementation, nicer one defined below if lxml is available
-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())
-
# fallback implementation, nicer one defined below if lxml> 2.0 is available
def safe_cut(text, length):
"""returns a string of length <length> based on <text>, removing any html
@@ -132,27 +125,30 @@
fallback_safe_cut = safe_cut
REM_ROOT_HTML_TAGS = re.compile('</(body|html)>', re.U)
+
try:
- from lxml import etree
-except (ImportError, AttributeError):
- # gae environment: lxml not available
- pass
-else:
+ 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'))
+ )
+
+ 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 (at least try) html soup and return the result
-
- Note: the function considers a string with no surrounding tag as valid
- if <div>`data`</div> can be parsed by an XML parser
+ """tidy html soup by allowing some element tags and return the result
"""
# remove spurious </body> and </html> 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()))
- # XXX lxml 1.1 support still needed ?
- xmltree = etree.HTML('<div>%s</div>' % data)
- # NOTE: lxml 1.1 (etch platforms) doesn't recognize
- # the encoding=unicode parameter (lxml 2.0 does), this is
- # why we specify an encoding and re-decode to unicode later
+ xmltree = etree.HTML(CLEANER.clean_html('<div>%s</div>' % 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 <body> and </body> and decode to unicode
snippet = body[6:-7].decode(encoding)
@@ -163,7 +159,29 @@
snippet = snippet[5:-6]
return snippet
- if hasattr(etree.HTML('<div>test</div>'), 'iter'):
+ # 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 "<p>text1</p><p>text2</p>"...
+ # XXX drop these two snippets action and follow the lxml behaviour
+ # XXX (tests need to be updated)
+ # if snippet.startswith('<div>') and snippet.endswith('</div>'):
+ # snippet = snippet[5:-6]
+ # if snippet.startswith('<p>') and snippet.endswith('</p>'):
+ # 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('<div>test</div>'), 'iter'): # XXX still necessary?
def safe_cut(text, length):
"""returns an html document of length <length> based on <text>,
@@ -342,6 +360,16 @@
import traceback
+def exc_message(ex, encoding):
+ try:
+ return unicode(ex)
+ except:
+ try:
+ return unicode(str(ex), encoding, 'replace')
+ except:
+ return unicode(repr(ex), encoding, 'replace')
+
+
def rest_traceback(info, exception):
"""return a ReST formated traceback"""
res = [u'Traceback\n---------\n::\n']
--- a/utils.py Fri Dec 10 12:17:18 2010 +0100
+++ b/utils.py Fri Mar 11 09:46:45 2011 +0100
@@ -24,6 +24,7 @@
import decimal
import datetime
import random
+from inspect import getargspec
from itertools import repeat
from uuid import uuid4
from warnings import warn
@@ -64,6 +65,52 @@
'__doc__': cls.__doc__,
'__module__': cls.__module__})
+def support_args(callable, *argnames):
+ """return true if the callable support given argument names"""
+ if isinstance(callable, type):
+ callable = callable.__init__
+ argspec = getargspec(callable)
+ if argspec[2]:
+ return True
+ for argname in argnames:
+ if argname not in argspec[0]:
+ return False
+ return True
+
+
+class wrap_on_write(object):
+ """ Sometimes it is convenient to NOT write some container element
+ if it happens that there is nothing to be written within,
+ but this cannot be known beforehand.
+ Hence one can do this:
+
+ .. sourcecode:: python
+
+ with wrap_on_write(w, '<div class="foo">', '</div>') as wow:
+ component.render_stuff(wow)
+ """
+ def __init__(self, w, tag, closetag=None):
+ self.written = False
+ self.tag = unicode(tag)
+ self.closetag = closetag
+ self.w = w
+
+ def __enter__(self):
+ return self
+
+ def __call__(self, data):
+ if self.written is False:
+ self.w(self.tag)
+ self.written = True
+ self.w(data)
+
+ def __exit__(self, exctype, value, traceback):
+ if self.written is True:
+ if self.closetag:
+ self.w(unicode(self.closetag))
+ else:
+ self.w(self.tag.replace('<', '</', 1))
+
# use networkX instead ?
# http://networkx.lanl.gov/reference/algorithms.traversal.html#module-networkx.algorithms.traversal.astar
@@ -151,11 +198,12 @@
return other[:] + ([self._item] * self._size)
def __eq__(self, other):
if isinstance(other, RepeatList):
- return other._size == self.size and other._item == self.item
+ return other._size == self._size and other._item == self._item
return self[:] == other
def pop(self, i):
self._size -= 1
+
class UStringIO(list):
"""a file wrapper which automatically encode unicode string to an encoding
specifed in the constructor
@@ -220,7 +268,7 @@
if jsoncall is not _MARKER:
warn('[3.7] specifying jsoncall is not needed anymore',
DeprecationWarning, stacklevel=2)
- self.add_post_inline_script(u"""jQuery(CubicWeb).one('server-response', function(event) {
+ self.add_post_inline_script(u"""$(cw).one('server-response', function(event) {
%s});""" % jscode)
--- a/view.py Fri Dec 10 12:17:18 2010 +0100
+++ b/view.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,6 +20,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
+import types, new
from cStringIO import StringIO
from warnings import warn
@@ -163,6 +164,7 @@
the whole result set (which may be None in this case), `call` is
called
"""
+ # XXX use .cw_row/.cw_col
row = context.get('row')
if row is not None:
context.setdefault('col', 0)
@@ -170,8 +172,11 @@
else:
view_func = self.call
stream = self.set_stream(w)
- # stream = self.set_stream(context)
- view_func(**context)
+ try:
+ view_func(**context)
+ except:
+ self.debug('view call %s failed (context=%s)', view_func, context)
+ raise
# return stream content if we have created it
if stream is not None:
return self._stream.getvalue()
@@ -206,11 +211,21 @@
if rset is None:
raise NotImplementedError, (self, "an rset is required")
wrap = self.templatable and len(rset) > 1 and self.add_div_section
- # XXX propagate self.extra_kwars?
- for i in xrange(len(rset)):
+ # avoid re-selection if rset of size 1, we already have the most
+ # specific view
+ if rset.rowcount != 1:
+ kwargs.setdefault('initargs', self.cw_extra_kwargs)
+ for i in xrange(len(rset)):
+ if wrap:
+ self.w(u'<div class="section">')
+ self.wview(self.__regid__, rset, row=i, **kwargs)
+ if wrap:
+ self.w(u"</div>")
+ else:
if wrap:
self.w(u'<div class="section">')
- self.wview(self.__regid__, rset, row=i, **kwargs)
+ kwargs.setdefault('col', 0)
+ self.cell_call(row=0, **kwargs)
if wrap:
self.w(u"</div>")
@@ -319,21 +334,11 @@
clabel = vtitle
return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title'))
- def output_url_builder( self, name, url, args ):
- self.w(u'<script language="JavaScript"><!--\n' \
- u'function %s( %s ) {\n' % (name, ','.join(args) ) )
- url_parts = url.split("%s")
- self.w(u' url="%s"' % url_parts[0] )
- for arg, part in zip(args, url_parts[1:]):
- self.w(u'+str(%s)' % arg )
- if part:
- self.w(u'+"%s"' % part)
- self.w('\n document.window.href=url;\n')
- self.w('}\n-->\n</script>\n')
-
+ @deprecated('[3.10] use vreg["etypes"].etype_class(etype).cw_create_url(req)')
def create_url(self, etype, **kwargs):
""" return the url of the entity creation form for a given entity type"""
- return self._cw.build_url('add/%s' % etype, **kwargs)
+ return self._cw.vreg["etypes"].etype_class(etype).cw_create_url(
+ self._cw, **kwargs)
def field(self, label, value, row=True, show_label=True, w=None, tr=True,
table=False):
@@ -350,7 +355,7 @@
if table:
w(u'<th>%s</th>' % label)
else:
- w(u'<span>%s</span> ' % label)
+ w(u'<span class="label">%s</span> ' % label)
if table:
if not (show_label and label):
w(u'<td colspan="2">%s</td></tr>' % value)
@@ -380,6 +385,7 @@
def entity_call(self, entity, **kwargs):
raise NotImplementedError()
+
class StartupView(View):
"""base class for views which doesn't need a particular result set to be
displayed (so they can always be displayed !)
@@ -514,8 +520,13 @@
build_js = build_update_js_call # expect updatable component by default
+ @property
+ def domid(self):
+ return domid(self.__regid__)
+
+ @deprecated('[3.10] use .domid property')
def div_id(self):
- return ''
+ return self.domid
class Component(ReloadableMixIn, View):
@@ -523,14 +534,20 @@
__registry__ = 'components'
__select__ = yes()
- # XXX huummm, much probably useless
+ # XXX huummm, much probably useless (should be...)
htmlclass = 'mainRelated'
+ @property
+ def cssclass(self):
+ return '%s %s' % (self.htmlclass, domid(self.__regid__))
+
+ # XXX should rely on ReloadableMixIn.domid
+ @property
+ def domid(self):
+ return '%sComponent' % domid(self.__regid__)
+
+ @deprecated('[3.10] use .cssclass property')
def div_class(self):
- return '%s %s' % (self.htmlclass, self.__regid__)
-
- # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer
- def div_id(self):
- return '%sComponent' % self.__regid__
+ return self.cssclass
class Adapter(AppObject):
@@ -538,17 +555,6 @@
__registry__ = 'adapters'
-class EntityAdapter(Adapter):
- """base class for entity adapters (eg adapt an entity to an interface)"""
- def __init__(self, _cw, **kwargs):
- try:
- self.entity = kwargs.pop('entity')
- except KeyError:
- self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
- kwargs.get('col') or 0)
- Adapter.__init__(self, _cw, **kwargs)
-
-
def implements_adapter_compat(iface):
def _pre39_compat(func):
def decorated(self, *args, **kwargs):
@@ -563,5 +569,35 @@
return member(*args, **kwargs)
return member
return func(self, *args, **kwargs)
+ decorated.decorated = func
return decorated
return _pre39_compat
+
+
+def unwrap_adapter_compat(cls):
+ parent = cls.__bases__[0]
+ for member_name in dir(parent):
+ member = getattr(parent, member_name)
+ if isinstance(member, types.MethodType) and hasattr(member.im_func, 'decorated') and not member_name in cls.__dict__:
+ method = new.instancemethod(member.im_func.decorated, None, cls)
+ setattr(cls, member_name, method)
+
+
+class auto_unwrap_bw_compat(type):
+ def __new__(mcs, name, bases, classdict):
+ cls = type.__new__(mcs, name, bases, classdict)
+ if not classdict.get('__needs_bw_compat__'):
+ unwrap_adapter_compat(cls)
+ return cls
+
+
+class EntityAdapter(Adapter):
+ """base class for entity adapters (eg adapt an entity to an interface)"""
+ __metaclass__ = auto_unwrap_bw_compat
+ def __init__(self, _cw, **kwargs):
+ try:
+ self.entity = kwargs.pop('entity')
+ except KeyError:
+ self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
+ kwargs.get('col') or 0)
+ Adapter.__init__(self, _cw, **kwargs)
--- a/vregistry.py Fri Dec 10 12:17:18 2010 +0100
+++ b/vregistry.py Fri Mar 11 09:46:45 2011 +0100
@@ -129,6 +129,8 @@
# or simplify by calling unregister then register here
if not isinstance(replaced, basestring):
replaced = classid(replaced)
+ # prevent from misspelling
+ assert obj is not replaced, 'replacing an object by itself: %s' % obj
registered_objs = self.get(class_regid(obj), ())
for index, registered in enumerate(registered_objs):
if classid(registered) == replaced:
@@ -174,7 +176,7 @@
assert len(objects) == 1, objects
return objects[0](*args, **kwargs)
- def select(self, oid, *args, **kwargs):
+ def select(self, __oid, *args, **kwargs):
"""return the most specific object among those with the given oid
according to the given context.
@@ -182,14 +184,14 @@
raise :exc:`NoSelectableObject` if not object apply
"""
- return self._select_best(self[oid], *args, **kwargs)
+ return self._select_best(self[__oid], *args, **kwargs)
- def select_or_none(self, oid, *args, **kwargs):
+ def select_or_none(self, __oid, *args, **kwargs):
"""return the most specific object among those with the given oid
according to the given context, or None if no object applies.
"""
try:
- return self.select(oid, *args, **kwargs)
+ return self.select(__oid, *args, **kwargs)
except (NoSelectableObject, ObjectNotFound):
return None
select_object = deprecated('[3.6] use select_or_none instead of select_object'
@@ -465,7 +467,7 @@
self.load_module(module)
def load_module(self, module):
- self.info('loading %s', module)
+ self.info('loading %s from %s', module.__name__, module.__file__)
if hasattr(module, 'registration_callback'):
module.registration_callback(self)
else:
--- a/web/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -30,7 +30,9 @@
from cubicweb.utils import json_dumps
from cubicweb.uilib import eid_param
-dumps = deprecated('[3.9] use cubicweb.utils.json_dumps instead of dumps')(json_dumps)
+assert json_dumps is not None, 'no json module installed'
+dumps = deprecated('[3.9] use cubicweb.utils.json_dumps instead of dumps')(
+ json_dumps)
INTERNAL_FIELD_VALUE = '__cubicweb_internal_field__'
--- a/web/_exceptions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/_exceptions.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,4 @@
-# pylint: disable-msg=W0401,W0614
+# pylint: disable=W0401,W0614
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
@@ -62,7 +62,7 @@
"""raised when a json remote call fails
"""
def __init__(self, reason=''):
- super(RequestError, self).__init__()
+ super(RemoteCallFailed, self).__init__()
self.reason = reason
def dumps(self):
--- a/web/action.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/action.py Fri Mar 11 09:46:45 2011 +0100
@@ -43,37 +43,33 @@
def fill_menu(self, box, menu):
"""add action(s) to the given submenu of the given box"""
for action in self.actual_actions():
- menu.append(box.box_action(action))
+ menu.append(box.action_link(action))
+
+ def html_class(self):
+ if self._cw.selected(self.url()):
+ return 'selected'
+
+ def build_action(self, title, url, **kwargs):
+ return UnregisteredAction(self._cw, title, url, **kwargs)
def url(self):
"""return the url associated with this action"""
raise NotImplementedError
- def html_class(self):
- if self._cw.selected(self.url()):
- return 'selected'
- if self.category:
- return 'box' + self.category.capitalize()
-
- def build_action(self, title, path, **kwargs):
- return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
-
class UnregisteredAction(Action):
- """non registered action used to build boxes. Unless you set them
- explicitly, .vreg and .schema attributes at least are None.
- """
+ """non registered action, used to build boxes"""
category = None
id = None
- def __init__(self, req, rset, title, path, **kwargs):
- Action.__init__(self, req, rset=rset)
+ def __init__(self, req, title, url, **kwargs):
+ Action.__init__(self, req)
self.title = req._(title)
- self._path = path
+ self._url = url
self.__dict__.update(kwargs)
def url(self):
- return self._path
+ return self._url
class LinkToEntityAction(Action):
--- a/web/application.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/application.py Fri Mar 11 09:46:45 2011 +0100
@@ -31,7 +31,7 @@
from cubicweb import set_log_methods, cwvreg
from cubicweb import (
ValidationError, Unauthorized, AuthenticationError, NoSelectableObject,
- RepositoryError, CW_EVENT_MANAGER)
+ BadConnectionId, CW_EVENT_MANAGER)
from cubicweb.dbapi import DBAPISession
from cubicweb.web import LOGGER, component
from cubicweb.web import (
@@ -48,48 +48,43 @@
def __init__(self, vreg):
self.session_time = vreg.config['http-session-time'] or None
- if self.session_time is not None:
- assert self.session_time > 0
- self.cleanup_session_time = self.session_time
- else:
- self.cleanup_session_time = vreg.config['cleanup-session-time'] or 1440 * 60
- assert self.cleanup_session_time > 0
- self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60
- assert self.cleanup_anon_session_time > 0
self.authmanager = vreg['components'].select('authmanager', vreg=vreg)
+ interval = (self.session_time or 0) / 2.
if vreg.config.anonymous_user() is not None:
- self.clean_sessions_interval = max(
- 5 * 60, min(self.cleanup_session_time / 2.,
- self.cleanup_anon_session_time / 2.))
- else:
- self.clean_sessions_interval = max(
- 5 * 60,
- self.cleanup_session_time / 2.)
+ self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60
+ assert self.cleanup_anon_session_time > 0
+ if self.session_time is not None:
+ self.cleanup_anon_session_time = min(self.session_time,
+ self.cleanup_anon_session_time)
+ interval = self.cleanup_anon_session_time / 2.
+ # we don't want to check session more than once every 5 minutes
+ self.clean_sessions_interval = max(5 * 60, interval)
def clean_sessions(self):
"""cleanup sessions which has not been unused since a given amount of
time. Return the number of sessions which have been closed.
"""
self.debug('cleaning http sessions')
+ session_time = self.session_time
closed, total = 0, 0
for session in self.current_sessions():
- no_use_time = (time() - session.last_usage_time)
total += 1
- if session.anonymous_session:
- if no_use_time >= self.cleanup_anon_session_time:
+ try:
+ last_usage_time = session.cnx.check()
+ except BadConnectionId:
+ self.close_session(session)
+ closed += 1
+ else:
+ no_use_time = (time() - last_usage_time)
+ if session.anonymous_session:
+ if no_use_time >= self.cleanup_anon_session_time:
+ self.close_session(session)
+ closed += 1
+ elif session_time is not None and no_use_time >= session_time:
self.close_session(session)
closed += 1
- elif no_use_time >= self.cleanup_session_time:
- self.close_session(session)
- closed += 1
return closed, total - closed
- def has_expired(self, session):
- """return True if the web session associated to the session is expired
- """
- return not (self.session_time is None or
- time() < session.last_usage_time + self.session_time)
-
def current_sessions(self):
"""return currently open sessions"""
raise NotImplementedError()
@@ -145,13 +140,7 @@
class CookieSessionHandler(object):
- """a session handler using a cookie to store the session identifier
-
- :cvar SESSION_VAR:
- string giving the name of the variable used to store the session
- identifier
- """
- SESSION_VAR = '__session'
+ """a session handler using a cookie to store the session identifier"""
def __init__(self, appli):
self.vreg = appli.vreg
@@ -159,8 +148,6 @@
vreg=self.vreg)
global SESSION_MANAGER
SESSION_MANAGER = self.session_manager
- if not 'last_login_time' in self.vreg.schema:
- self._update_last_login_time = lambda x: None
if self.vreg.config.mode != 'test':
# don't try to reset session manager during test, this leads to
# weird failures when running multiple tests
@@ -185,6 +172,14 @@
"""
self.session_manager.clean_sessions()
+ def session_cookie(self, req):
+ """return a string giving the name of the cookie used to store the
+ session identifier.
+ """
+ if req.https:
+ return '__%s_https_session' % self.vreg.config.appid
+ return '__%s_session' % self.vreg.config.appid
+
def set_session(self, req):
"""associate a session to the request
@@ -198,8 +193,9 @@
:raise Redirect: if authentication has occurred and succeed
"""
cookie = req.get_cookie()
+ sessioncookie = self.session_cookie(req)
try:
- sessionid = str(cookie[self.SESSION_VAR].value)
+ sessionid = str(cookie[sessioncookie].value)
except KeyError: # no session cookie
session = self.open_session(req)
else:
@@ -211,10 +207,8 @@
try:
session = self.open_session(req)
except AuthenticationError:
- req.remove_cookie(cookie, self.SESSION_VAR)
+ req.remove_cookie(cookie, sessioncookie)
raise
- # remember last usage time for web session tracking
- session.last_usage_time = time()
def get_session(self, req, sessionid):
return self.session_manager.get_session(req, sessionid)
@@ -222,57 +216,22 @@
def open_session(self, req):
session = self.session_manager.open_session(req)
cookie = req.get_cookie()
- cookie[self.SESSION_VAR] = session.sessionid
- req.set_cookie(cookie, self.SESSION_VAR, maxage=None)
- # remember last usage time for web session tracking
- session.last_usage_time = time()
+ sessioncookie = self.session_cookie(req)
+ cookie[sessioncookie] = session.sessionid
+ if req.https and req.base_url().startswith('https://'):
+ cookie[sessioncookie]['secure'] = True
+ req.set_cookie(cookie, sessioncookie, maxage=None)
if not session.anonymous_session:
- self._postlogin(req)
+ self.session_manager.postlogin(req)
return session
- def _update_last_login_time(self, req):
- # XXX should properly detect missing permission / non writeable source
- # and avoid "except (RepositoryError, Unauthorized)" below
- if req.user.cw_metainformation()['source']['adapter'] == 'ldapuser':
- return
- try:
- req.execute('SET X last_login_time NOW WHERE X eid %(x)s',
- {'x' : req.user.eid})
- req.cnx.commit()
- except (RepositoryError, Unauthorized):
- req.cnx.rollback()
- except:
- req.cnx.rollback()
- raise
-
- def _postlogin(self, req):
- """postlogin: the user has been authenticated, redirect to the original
- page (index by default) with a welcome message
- """
- # Update last connection date
- # XXX: this should be in a post login hook in the repository, but there
- # we can't differentiate actual login of automatic session
- # reopening. Is it actually a problem?
- self._update_last_login_time(req)
- args = req.form
- for forminternal_key in ('__form_id', '__domid', '__errorurl'):
- args.pop(forminternal_key, None)
- args['__message'] = req._('welcome %s !') % req.user.login
- if 'vid' in req.form:
- args['vid'] = req.form['vid']
- if 'rql' in req.form:
- args['rql'] = req.form['rql']
- path = req.relative_path(False)
- if path == 'login':
- path = 'view'
- raise Redirect(req.build_url(path, **args))
-
def logout(self, req, goto_url):
"""logout from the instance by cleaning the session and raising
`AuthenticationError`
"""
self.session_manager.close_session(req.session)
- req.remove_cookie(req.get_cookie(), self.SESSION_VAR)
+ sessioncookie = self.session_cookie(req)
+ req.remove_cookie(req.get_cookie(), sessioncookie)
raise LogOut(url=goto_url)
--- a/web/box.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/box.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,27 +21,44 @@
_ = unicode
from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_deprecated, class_renamed
-from cubicweb import Unauthorized, role as get_role, target as get_target
+from cubicweb import Unauthorized, role as get_role
from cubicweb.schema import display_name
-from cubicweb.selectors import (no_cnx, one_line_rset, primary_view,
- match_context_prop, partial_relation_possible,
- partial_has_related_entities)
-from cubicweb.view import View, ReloadableMixIn
-from cubicweb.uilib import domid, js
+from cubicweb.selectors import no_cnx, one_line_rset
+from cubicweb.view import View
from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
RawBoxItem, BoxSeparator)
from cubicweb.web.action import UnregisteredAction
+def sort_by_category(actions, categories_in_order=None):
+ """return a list of (category, actions_sorted_by_title)"""
+ result = []
+ actions_by_cat = {}
+ for action in actions:
+ actions_by_cat.setdefault(action.category, []).append(
+ (action.title, action) )
+ for key, values in actions_by_cat.items():
+ actions_by_cat[key] = [act for title, act in sorted(values)]
+ if categories_in_order:
+ for cat in categories_in_order:
+ if cat in actions_by_cat:
+ result.append( (cat, actions_by_cat[cat]) )
+ for item in sorted(actions_by_cat.items()):
+ result.append(item)
+ return result
+
+
+# old box system, deprecated ###################################################
+
class BoxTemplate(View):
"""base template for boxes, usually a (contextual) list of possible
-
actions. Various classes attributes may be used to control the box
rendering.
- You may override on of the formatting callbacks is this is not necessary
+ You may override one of the formatting callbacks if this is not necessary
for your custom box.
Classes inheriting from this class usually only have to override call
@@ -49,8 +66,11 @@
box.render(self.w)
"""
- __registry__ = 'boxes'
- __select__ = ~no_cnx() & match_context_prop()
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)'
+
+ __registry__ = 'ctxcomponents'
+ __select__ = ~no_cnx()
categories_in_order = ()
cw_property_defs = {
@@ -64,34 +84,21 @@
help=_('context where this box should be displayed')),
}
context = 'left'
- htmlitemclass = 'boxItem'
def sort_actions(self, actions):
"""return a list of (category, actions_sorted_by_title)"""
- result = []
- actions_by_cat = {}
- for action in actions:
- actions_by_cat.setdefault(action.category, []).append(
- (action.title, action) )
- for key, values in actions_by_cat.items():
- actions_by_cat[key] = [act for title, act in sorted(values)]
- for cat in self.categories_in_order:
- if cat in actions_by_cat:
- result.append( (cat, actions_by_cat[cat]) )
- for item in sorted(actions_by_cat.items()):
- result.append(item)
- return result
+ return sort_by_category(actions, self.categories_in_order)
- def mk_action(self, title, path, escape=True, **kwargs):
+ def mk_action(self, title, url, escape=True, **kwargs):
"""factory function to create dummy actions compatible with the
.format_actions method
"""
if escape:
title = xml_escape(title)
- return self.box_action(self._action(title, path, **kwargs))
+ return self.box_action(self._action(title, url, **kwargs))
- def _action(self, title, path, **kwargs):
- return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
+ def _action(self, title, url, **kwargs):
+ return UnregisteredAction(self._cw, title, url, **kwargs)
# formating callbacks
@@ -101,18 +108,14 @@
return u''
def box_action(self, action):
- cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass
+ klass = getattr(action, 'html_class', lambda: None)()
return BoxLink(action.url(), self._cw._(action.title),
- cls, self.boxitem_link_tooltip(action))
+ klass, self.boxitem_link_tooltip(action))
class RQLBoxTemplate(BoxTemplate):
"""abstract box for boxes displaying the content of a rql query not
related to the current result set.
-
- It rely on etype, rtype (both optional, usable to control registration
- according to application schema and display according to connected
- user's rights) and rql attributes
"""
rql = None
@@ -148,29 +151,17 @@
class EntityBoxTemplate(BoxTemplate):
"""base class for boxes related to a single entity"""
- __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+ __select__ = BoxTemplate.__select__ & one_line_rset()
context = 'incontext'
def call(self, row=0, col=0, **kwargs):
"""classes inheriting from EntityBoxTemplate should define cell_call"""
self.cell_call(row, col, **kwargs)
-
-class RelatedEntityBoxTemplate(EntityBoxTemplate):
- __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
-
- def cell_call(self, row, col, **kwargs):
- entity = self.cw_rset.get_entity(row, col)
- limit = self._cw.property_value('navigation.related-limit') + 1
- role = get_role(self)
- self.w(u'<div class="sideBox">')
- self.wview('sidebox', entity.related(self.rtype, role, limit=limit),
- title=display_name(self._cw, self.rtype, role,
- context=entity.__regid__))
- self.w(u'</div>')
+from cubicweb.web.component import AjaxEditRelationCtxComponent, EditRelationMixIn
-class EditRelationBoxTemplate(ReloadableMixIn, EntityBoxTemplate):
+class EditRelationBoxTemplate(EditRelationMixIn, EntityBoxTemplate):
"""base class for boxes which let add or remove entities linked
by a given relation
@@ -181,7 +172,8 @@
def cell_call(self, row, col, view=None, **kwargs):
self._cw.add_js('cubicweb.ajax.js')
entity = self.cw_rset.get_entity(row, col)
- title = display_name(self._cw, self.rtype, get_role(self), context=entity.__regid__)
+ title = display_name(self._cw, self.rtype, get_role(self),
+ context=entity.__regid__)
box = SideBoxWidget(title, self.__regid__)
related = self.related_boxitems(entity)
unrelated = self.unrelated_boxitems(entity)
@@ -191,144 +183,13 @@
box.extend(unrelated)
box.render(self.w)
- def div_id(self):
- return self.__regid__
-
def box_item(self, entity, etarget, rql, label):
- """builds HTML link to edit relation between `entity` and `etarget`
- """
- role, target = get_role(self), get_target(self)
- args = {role[0] : entity.eid, target[0] : etarget.eid}
- url = self._cw.user_rql_callback((rql, args))
- # for each target, provide a link to edit the relation
- label = u'[<a href="%s">%s</a>] %s' % (xml_escape(url), label,
- etarget.view('incontext'))
+ label = super(EditRelationBoxTemplate, self).box_item(
+ entity, etarget, rql, label)
return RawBoxItem(label, liclass=u'invisible')
- def related_boxitems(self, entity):
- rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
- related = []
- for etarget in self.related_entities(entity):
- related.append(self.box_item(entity, etarget, rql, u'-'))
- return related
-
- def unrelated_boxitems(self, entity):
- rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
- unrelated = []
- for etarget in self.unrelated_entities(entity):
- unrelated.append(self.box_item(entity, etarget, rql, u'+'))
- return unrelated
-
- def related_entities(self, entity):
- return entity.related(self.rtype, get_role(self), entities=True)
-
- def unrelated_entities(self, entity):
- """returns the list of unrelated entities, using the entity's
- appropriate vocabulary function
- """
- skip = set(unicode(e.eid) for e in entity.related(self.rtype, get_role(self),
- entities=True))
- skip.add(None)
- skip.add(INTERNAL_FIELD_VALUE)
- filteretype = getattr(self, 'etype', None)
- entities = []
- form = self._cw.vreg['forms'].select('edition', self._cw,
- rset=self.cw_rset,
- row=self.cw_row or 0)
- field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
- for _, eid in field.vocabulary(form):
- if eid not in skip:
- entity = self._cw.entity_from_eid(eid)
- if filteretype is None or entity.__regid__ == filteretype:
- entities.append(entity)
- return entities
-
-class AjaxEditRelationBoxTemplate(EntityBoxTemplate):
- __select__ = EntityBoxTemplate.__select__ & partial_relation_possible()
-
- # view used to display related entties
- item_vid = 'incontext'
- # values separator when multiple values are allowed
- separator = ','
- # msgid of the message to display when some new relation has been added/removed
- added_msg = None
- removed_msg = None
-
- # class attributes below *must* be set in concret classes (additionaly to
- # rtype / role [/ target_etype]. They should correspond to js_* methods on
- # the json controller
-
- # function(eid)
- # -> expected to return a list of values to display as input selector
- # vocabulary
- fname_vocabulary = None
-
- # function(eid, value)
- # -> handle the selector's input (eg create necessary entities and/or
- # relations). If the relation is multiple, you'll get a list of value, else
- # a single string value.
- fname_validate = None
-
- # function(eid, linked entity eid)
- # -> remove the relation
- fname_remove = None
+AjaxEditRelationBoxTemplate = class_renamed(
+ 'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent,
+ '[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent (%(cls)s)')
- def cell_call(self, row, col, **kwargs):
- req = self._cw
- entity = self.cw_rset.get_entity(row, col)
- related = entity.related(self.rtype, self.role)
- rdef = entity.e_schema.rdef(self.rtype, self.role, self.target_etype)
- if self.role == 'subject':
- mayadd = rdef.has_perm(req, 'add', fromeid=entity.eid)
- maydel = rdef.has_perm(req, 'delete', fromeid=entity.eid)
- else:
- mayadd = rdef.has_perm(req, 'add', toeid=entity.eid)
- maydel = rdef.has_perm(req, 'delete', toeid=entity.eid)
- if not (related or mayadd):
- return
- if mayadd or maydel:
- req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js'))
- _ = req._
- w = self.w
- divid = domid(self.__regid__) + unicode(entity.eid)
- w(u'<div class="sideBox" id="%s%s">' % (domid(self.__regid__), entity.eid))
- w(u'<div class="sideBoxTitle"><span>%s</span></div>' %
- rdef.rtype.display_name(req, self.role, context=entity.__regid__))
- w(u'<div class="sideBox"><div class="sideBoxBody">')
- if related:
- w(u'<table>')
- for rentity in related.entities():
- # for each related entity, provide a link to remove the relation
- subview = rentity.view(self.item_vid)
- if maydel:
- jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
- self.__regid__, entity.eid, rentity.eid,
- self.fname_remove,
- self.removed_msg and _(self.removed_msg)))
- w(u'<tr><td>[<a href="javascript: %s">-</a>]</td>'
- '<td class="tagged">%s</td></tr>' % (xml_escape(jscall),
- subview))
- else:
- w(u'<tr><td class="tagged">%s</td></tr>' % (subview))
- w(u'</table>')
- else:
- w(_('no related entity'))
- if mayadd:
- req.add_js('jquery.autocomplete.js')
- req.add_css('jquery.autocomplete.css')
- multiple = rdef.role_cardinality(self.role) in '*+'
- w(u'<table><tr><td>')
- jscall = unicode(js.ajaxBoxShowSelector(
- self.__regid__, entity.eid, self.fname_vocabulary,
- self.fname_validate, self.added_msg and _(self.added_msg),
- _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
- multiple and self.separator))
- w('<a class="button sglink" href="javascript: %s">%s</a>' % (
- xml_escape(jscall),
- multiple and _('add_relation') or _('update_relation')))
- w(u'</td><td>')
- w(u'<div id="%sHolder"></div>' % divid)
- w(u'</td></tr></table>')
- w(u'</div>\n')
- w(u'</div></div>\n')
--- a/web/component.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/component.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,58 +22,24 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.common.deprecation import class_renamed
+from warnings import warn
+
+from logilab.common.deprecation import class_deprecated, class_renamed, deprecated
from logilab.mtconverter import xml_escape
-from cubicweb import role
+from cubicweb import Unauthorized, role, target, tags
+from cubicweb.schema import display_name
+from cubicweb.uilib import js, domid
from cubicweb.utils import json_dumps
-from cubicweb.uilib import js
-from cubicweb.view import Component
-from cubicweb.selectors import (
- paginated_rset, one_line_rset, primary_view, match_context_prop,
- partial_has_related_entities)
+from cubicweb.view import ReloadableMixIn, Component
+from cubicweb.selectors import (no_cnx, paginated_rset, one_line_rset,
+ non_final_entity, partial_relation_possible,
+ partial_has_related_entities)
+from cubicweb.appobject import AppObject
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs
-class EntityVComponent(Component):
- """abstract base class for additinal components displayed in content
- headers and footer according to:
-
- * the displayed entity's type
- * a context (currently 'header' or 'footer')
-
- it should be configured using .accepts, .etype, .rtype, .target and
- .context class attributes
- """
-
- __registry__ = 'contentnavigation'
- __select__ = one_line_rset() & primary_view() & match_context_prop()
-
- cw_property_defs = {
- _('visible'): dict(type='Boolean', default=True,
- help=_('display the component or not')),
- _('order'): dict(type='Int', default=99,
- help=_('display order of the component')),
- _('context'): dict(type='String', default='navtop',
- vocabulary=(_('navtop'), _('navbottom'),
- _('navcontenttop'), _('navcontentbottom'),
- _('ctxtoolbar')),
- help=_('context where this component should be displayed')),
- }
-
- context = 'navcontentbottom'
-
- def call(self, view=None):
- if self.cw_rset is None:
- self.entity_call(self.cw_extra_kwargs.pop('entity'))
- else:
- self.cell_call(0, 0, view=view)
-
- def cell_call(self, row, col, view=None):
- self.entity_call(self.cw_rset.get_entity(row, col), view=view)
-
- def entity_call(self, entity, view=None):
- raise NotImplementedError()
-
+# abstract base class for navigation components ################################
class NavigationComponent(Component):
"""abstract base class for navigation components"""
@@ -150,7 +116,7 @@
# XXX hack to avoid opening a new page containing the evaluation of the
# js expression on ajax call
if url.startswith('javascript:'):
- url += '; noop();'
+ url += '; $.noop();'
return url
def ajax_page_url(self, **params):
@@ -183,6 +149,529 @@
return self.next_page_link_templ % (url, title, content)
+# new contextual components system #############################################
+
+def override_ctx(cls, **kwargs):
+ cwpdefs = cls.cw_property_defs.copy()
+ cwpdefs['context'] = cwpdefs['context'].copy()
+ cwpdefs['context'].update(kwargs)
+ return cwpdefs
+
+
+class EmptyComponent(Exception):
+ """some selectable component has actually no content and should not be
+ rendered
+ """
+
+
+class Link(object):
+ """a link to a view or action in the ui.
+
+ Use this rather than `cw.web.htmlwidgets.BoxLink`.
+
+ Note this class could probably be avoided with a proper DOM on the server
+ side.
+ """
+ newstyle = True
+
+ def __init__(self, href, label, **attrs):
+ self.href = href
+ self.label = label
+ self.attrs = attrs
+
+ def __unicode__(self):
+ return tags.a(self.label, href=self.href, **self.attrs)
+
+ def render(self, w):
+ w(tags.a(self.label, href=self.href, **self.attrs))
+
+
+class Separator(object):
+ """a menu separator.
+
+ Use this rather than `cw.web.htmlwidgets.BoxSeparator`.
+ """
+ newstyle = True
+
+ def render(self, w):
+ w(u'<hr class="boxSeparator"/>')
+
+
+def _bwcompatible_render_item(w, item):
+ if hasattr(item, 'render'):
+ if getattr(item, 'newstyle', False):
+ if isinstance(item, Separator):
+ w(u'</ul>')
+ item.render(w)
+ w(u'<ul>')
+ else:
+ w(u'<li>')
+ item.render(w)
+ w(u'</li>')
+ else:
+ item.render(w) # XXX displays <li> by itself
+ else:
+ w(u'<li>%s</li>' % item)
+
+
+class Layout(Component):
+ __regid__ = 'layout'
+ __abstract__ = True
+
+ def init_rendering(self):
+ """init view for rendering. Return true if we should go on, false
+ if we should stop now.
+ """
+ view = self.cw_extra_kwargs['view']
+ try:
+ view.init_rendering()
+ except Unauthorized, ex:
+ self.warning("can't render %s: %s", view, ex)
+ return False
+ except EmptyComponent:
+ return False
+ return True
+
+
+class CtxComponent(AppObject):
+ """base class for contextual components. The following contexts are
+ predefined:
+
+ * boxes: 'left', 'incontext', 'right'
+ * section: 'navcontenttop', 'navcontentbottom', 'navtop', 'navbottom'
+ * other: 'ctxtoolbar'
+
+ The 'incontext', 'navcontenttop', 'navcontentbottom' and 'ctxtoolbar'
+ contexts are handled by the default primary view, others by the default main
+ template.
+
+ All subclasses may not support all those contexts (for instance if it can't
+ be displayed as box, or as a toolbar icon). You may restrict allowed context
+ as follows:
+
+ .. sourcecode:: python
+
+ class MyComponent(CtxComponent):
+ cw_property_defs = override_ctx(CtxComponent,
+ vocabulary=[list of contexts])
+ context = 'my default context'
+
+ You can configure a component's default context by simply giving an
+ appropriate value to the `context` class attribute, as seen above.
+ """
+ __registry__ = 'ctxcomponents'
+ __select__ = ~no_cnx()
+
+ categories_in_order = ()
+ cw_property_defs = {
+ _('visible'): dict(type='Boolean', default=True,
+ help=_('display the box or not')),
+ _('order'): dict(type='Int', default=99,
+ help=_('display order of the box')),
+ _('context'): dict(type='String', default='left',
+ vocabulary=(_('left'), _('incontext'), _('right'),
+ _('navtop'), _('navbottom'),
+ _('navcontenttop'), _('navcontentbottom'),
+ _('ctxtoolbar')),
+ help=_('context where this component should be displayed')),
+ }
+ visible = True
+ order = 0
+ context = 'left'
+ contextual = False
+ title = None
+
+ # XXX support kwargs for compat with old boxes which gets the view as
+ # argument
+ def render(self, w, **kwargs):
+ if hasattr(self, 'call'):
+ warn('[3.10] should not anymore implement call on %s, see new CtxComponent api'
+ % self.__class__, DeprecationWarning)
+ self.w = w
+ 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)
+ return
+ getlayout = self._cw.vreg['components'].select
+ layout = getlayout('layout', self._cw, **self.layout_select_args())
+ layout.render(w)
+
+ def layout_select_args(self):
+ try:
+ # XXX ensure context is given when the component is reloaded through
+ # ajax
+ context = self.cw_extra_kwargs['context']
+ except KeyError:
+ context = self.cw_propval('context')
+ return dict(rset=self.cw_rset, row=self.cw_row, col=self.cw_col,
+ view=self, context=context)
+
+ def init_rendering(self):
+ """init rendering callback: that's the good time to check your component
+ has some content to display. If not, you can still raise
+ :exc:`EmptyComponent` to inform it should be skipped.
+
+ Also, :exc:`Unauthorized` will be catched, logged, then the component
+ will be skipped.
+ """
+ self.items = []
+
+ @property
+ def domid(self):
+ """return the HTML DOM identifier for this component"""
+ return domid(self.__regid__)
+
+ @property
+ def cssclass(self):
+ """return the CSS class name for this component"""
+ return domid(self.__regid__)
+
+ def render_title(self, w):
+ """return the title for this component"""
+ if self.title:
+ w(self._cw._(self.title))
+
+ def render_body(self, w):
+ """return the body (content) for this component"""
+ raise NotImplementedError()
+
+ def render_items(self, w, items=None, klass=u'boxListing'):
+ if items is None:
+ items = self.items
+ assert items
+ w(u'<ul class="%s">' % klass)
+ for item in items:
+ _bwcompatible_render_item(w, item)
+ w(u'</ul>')
+
+ def append(self, item):
+ self.items.append(item)
+
+ def action_link(self, action):
+ return self.link(self._cw._(action.title), action.url())
+
+ def link(self, title, url, **kwargs):
+ if self._cw.selected(url):
+ try:
+ kwargs['klass'] += ' selected'
+ except KeyError:
+ kwargs['klass'] = 'selected'
+ return Link(url, title, **kwargs)
+
+ def separator(self):
+ return Separator()
+
+ @deprecated('[3.10] use action_link() / link()')
+ def box_action(self, action): # XXX action_link
+ return self.build_link(self._cw._(action.title), action.url())
+
+ @deprecated('[3.10] use action_link() / link()')
+ def build_link(self, title, url, **kwargs):
+ if self._cw.selected(url):
+ try:
+ kwargs['klass'] += ' selected'
+ except KeyError:
+ kwargs['klass'] = 'selected'
+ return tags.a(title, href=url, **kwargs)
+
+
+class EntityCtxComponent(CtxComponent):
+ """base class for boxes related to a single entity"""
+ __select__ = CtxComponent.__select__ & non_final_entity() & one_line_rset()
+ context = 'incontext'
+ contextual = True
+
+ def __init__(self, *args, **kwargs):
+ super(EntityCtxComponent, self).__init__(*args, **kwargs)
+ try:
+ entity = kwargs['entity']
+ except KeyError:
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ self.entity = entity
+
+ def layout_select_args(self):
+ args = super(EntityCtxComponent, self).layout_select_args()
+ args['entity'] = self.entity
+ return args
+
+ @property
+ def domid(self):
+ return domid(self.__regid__) + unicode(self.entity.eid)
+
+ def lazy_view_holder(self, w, entity, oid, registry='views'):
+ """add a holder and return an url that may be used to replace this
+ holder by the html generate by the view specified by registry and
+ identifier. Registry defaults to 'views'.
+ """
+ holderid = '%sHolder' % self.domid
+ w(u'<div id="%s"></div>' % holderid)
+ params = self.cw_extra_kwargs.copy()
+ params.pop('view', None)
+ params.pop('entity', None)
+ form = params.pop('formparams', {})
+ form['pageid'] = self._cw.pageid
+ if entity.has_eid():
+ eid = entity.eid
+ else:
+ eid = None
+ form['etype'] = entity.__regid__
+ form['tempEid'] = entity.eid
+ args = [json_dumps(x) for x in (registry, oid, eid, params)]
+ return self._cw.ajax_replace_url(
+ holderid, fname='render', arg=args, **form)
+
+
+# high level abstract classes ##################################################
+
+class RQLCtxComponent(CtxComponent):
+ """abstract box for boxes displaying the content of a rql query not related
+ to the current result set.
+
+ Notice that this class's init_rendering implemention is overwriting context
+ result set (eg `cw_rset`) with the result set returned by execution of
+ `to_display_rql()`.
+ """
+ rql = None
+
+ def to_display_rql(self):
+ """return arguments to give to self._cw.execute, as a tuple, to build
+ the result set to be displayed by this box.
+ """
+ assert self.rql is not None, self.__regid__
+ return (self.rql,)
+
+ def init_rendering(self):
+ super(RQLCtxComponent, self).init_rendering()
+ self.cw_rset = self._cw.execute(*self.to_display_rql())
+ if not self.cw_rset:
+ raise EmptyComponent()
+
+ def render_body(self, w):
+ rset = self.cw_rset
+ if len(rset[0]) == 2:
+ items = []
+ for i, (eid, label) in enumerate(rset):
+ entity = rset.get_entity(i, 0)
+ items.append(self.link(label, entity.absolute_url()))
+ else:
+ items = [self.link(e.dc_title(), e.absolute_url())
+ for e in rset.entities()]
+ self.render_items(w, items)
+
+
+class EditRelationMixIn(ReloadableMixIn):
+ def box_item(self, entity, etarget, rql, label):
+ """builds HTML link to edit relation between `entity` and `etarget`"""
+ args = {role(self)[0] : entity.eid, target(self)[0] : etarget.eid}
+ url = self._cw.user_rql_callback((rql, args))
+ # for each target, provide a link to edit the relation
+ return u'[<a href="%s" class="action">%s</a>] %s' % (
+ xml_escape(url), label, etarget.view('incontext'))
+
+ def related_boxitems(self, entity):
+ rql = 'DELETE S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+ return [self.box_item(entity, etarget, rql, u'-')
+ for etarget in self.related_entities(entity)]
+
+ def related_entities(self, entity):
+ return entity.related(self.rtype, role(self), entities=True)
+
+ def unrelated_boxitems(self, entity):
+ rql = 'SET S %s O WHERE S eid %%(s)s, O eid %%(o)s' % self.rtype
+ return [self.box_item(entity, etarget, rql, u'+')
+ for etarget in self.unrelated_entities(entity)]
+
+ def unrelated_entities(self, entity):
+ """returns the list of unrelated entities, using the entity's
+ appropriate vocabulary function
+ """
+ skip = set(unicode(e.eid) for e in entity.related(self.rtype, role(self),
+ entities=True))
+ skip.add(None)
+ skip.add(INTERNAL_FIELD_VALUE)
+ filteretype = getattr(self, 'etype', None)
+ entities = []
+ form = self._cw.vreg['forms'].select('edition', self._cw,
+ rset=self.cw_rset,
+ row=self.cw_row or 0)
+ field = form.field_by_name(self.rtype, role(self), entity.e_schema)
+ for _, eid in field.vocabulary(form):
+ if eid not in skip:
+ entity = self._cw.entity_from_eid(eid)
+ if filteretype is None or entity.__regid__ == filteretype:
+ entities.append(entity)
+ return entities
+
+# XXX should be a view usable using uicfg
+class EditRelationCtxComponent(EditRelationMixIn, EntityCtxComponent):
+ """base class for boxes which let add or remove entities linked by a given
+ relation
+
+ subclasses should define at least id, rtype and target class attributes.
+ """
+ def render_title(self, w):
+ w(display_name(self._cw, self.rtype, role(self),
+ context=self.entity.__regid__))
+
+ def render_body(self, w):
+ self._cw.add_js('cubicweb.ajax.js')
+ related = self.related_boxitems(self.entity)
+ unrelated = self.unrelated_boxitems(self.entity)
+ self.items.extend(related)
+ if related and unrelated:
+ self.items.append(u'<hr class="boxSeparator"/>')
+ self.items.extend(unrelated)
+ self.render_items(w)
+
+
+class AjaxEditRelationCtxComponent(EntityCtxComponent):
+ __select__ = EntityCtxComponent.__select__ & (
+ partial_relation_possible(action='add') | partial_has_related_entities())
+
+ # view used to display related entties
+ item_vid = 'incontext'
+ # values separator when multiple values are allowed
+ separator = ','
+ # msgid of the message to display when some new relation has been added/removed
+ added_msg = None
+ removed_msg = None
+
+ # class attributes below *must* be set in concret classes (additionaly to
+ # rtype / role [/ target_etype]. They should correspond to js_* methods on
+ # the json controller
+
+ # function(eid)
+ # -> expected to return a list of values to display as input selector
+ # vocabulary
+ fname_vocabulary = None
+
+ # function(eid, value)
+ # -> handle the selector's input (eg create necessary entities and/or
+ # relations). If the relation is multiple, you'll get a list of value, else
+ # a single string value.
+ fname_validate = None
+
+ # function(eid, linked entity eid)
+ # -> remove the relation
+ fname_remove = None
+
+ def __init__(self, *args, **kwargs):
+ super(AjaxEditRelationCtxComponent, self).__init__(*args, **kwargs)
+ self.rdef = self.entity.e_schema.rdef(self.rtype, self.role, self.target_etype)
+
+ def render_title(self, w):
+ w(self.rdef.rtype.display_name(self._cw, self.role,
+ context=self.entity.__regid__))
+
+ def render_body(self, w):
+ req = self._cw
+ entity = self.entity
+ related = entity.related(self.rtype, self.role)
+ if self.role == 'subject':
+ mayadd = self.rdef.has_perm(req, 'add', fromeid=entity.eid)
+ maydel = self.rdef.has_perm(req, 'delete', fromeid=entity.eid)
+ else:
+ mayadd = self.rdef.has_perm(req, 'add', toeid=entity.eid)
+ maydel = self.rdef.has_perm(req, 'delete', toeid=entity.eid)
+ if mayadd or maydel:
+ req.add_js(('jquery.ui.js', 'cubicweb.widgets.js'))
+ req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js'))
+ req.add_css('jquery.ui.css')
+ _ = req._
+ if related:
+ w(u'<table class="ajaxEditRelationTable">')
+ for rentity in related.entities():
+ # for each related entity, provide a link to remove the relation
+ subview = rentity.view(self.item_vid)
+ if maydel:
+ jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
+ self.__regid__, entity.eid, rentity.eid,
+ self.fname_remove,
+ self.removed_msg and _(self.removed_msg)))
+ w(u'<tr><td class="dellink">[<a href="javascript: %s">-</a>]</td>'
+ '<td class="entity"> %s</td></tr>' % (xml_escape(jscall),
+ subview))
+ else:
+ w(u'<tr><td class="entity">%s</td></tr>' % (subview))
+ w(u'</table>')
+ else:
+ w(_('no related entity'))
+ if mayadd:
+ multiple = self.rdef.role_cardinality(self.role) in '*+'
+ w(u'<table><tr><td>')
+ jscall = unicode(js.ajaxBoxShowSelector(
+ self.__regid__, entity.eid, self.fname_vocabulary,
+ self.fname_validate, self.added_msg and _(self.added_msg),
+ _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
+ multiple and self.separator))
+ w('<a class="button sglink" href="javascript: %s">%s</a>' % (
+ xml_escape(jscall),
+ multiple and _('add_relation') or _('update_relation')))
+ w(u'</td><td>')
+ w(u'<div id="%sHolder"></div>' % self.domid)
+ w(u'</td></tr></table>')
+
+
+class RelatedObjectsCtxComponent(EntityCtxComponent):
+ """a contextual component to display entities related to another"""
+ __select__ = EntityCtxComponent.__select__ & partial_has_related_entities()
+ context = 'navcontentbottom'
+ rtype = None
+ role = 'subject'
+
+ vid = 'list'
+
+ def render_body(self, w):
+ rset = self.entity.related(self.rtype, role(self))
+ self._cw.view(self.vid, rset, w=w)
+
+
+# old contextual components, deprecated ########################################
+
+class EntityVComponent(Component):
+ """abstract base class for additinal components displayed in content
+ headers and footer according to:
+
+ * the displayed entity's type
+ * a context (currently 'header' or 'footer')
+
+ it should be configured using .accepts, .etype, .rtype, .target and
+ .context class attributes
+ """
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] *VComponent classes are deprecated, use *CtxComponent instead (%(cls)s)'
+
+ __registry__ = 'ctxcomponents'
+ __select__ = one_line_rset()
+
+ cw_property_defs = {
+ _('visible'): dict(type='Boolean', default=True,
+ help=_('display the component or not')),
+ _('order'): dict(type='Int', default=99,
+ help=_('display order of the component')),
+ _('context'): dict(type='String', default='navtop',
+ vocabulary=(_('navtop'), _('navbottom'),
+ _('navcontenttop'), _('navcontentbottom'),
+ _('ctxtoolbar')),
+ help=_('context where this component should be displayed')),
+ }
+
+ context = 'navcontentbottom'
+
+ def call(self, view=None):
+ if self.cw_rset is None:
+ self.entity_call(self.cw_extra_kwargs.pop('entity'))
+ else:
+ self.cell_call(0, 0, view=view)
+
+ def cell_call(self, row, col, view=None):
+ self.entity_call(self.cw_rset.get_entity(row, col), view=view)
+
+ def entity_call(self, entity, view=None):
+ raise NotImplementedError()
+
+
class RelatedObjectsVComponent(EntityVComponent):
"""a section to display some related entities"""
__select__ = EntityVComponent.__select__ & partial_has_related_entities()
@@ -203,14 +692,15 @@
rset = self._cw.execute(self.rql(), {'x': eid})
if not rset.rowcount:
return
- self.w(u'<div class="%s">' % self.div_class())
+ self.w(u'<div class="%s">' % self.cssclass)
self.w(u'<h4>%s</h4>\n' % self._cw._(self.title).capitalize())
self.wview(self.vid, rset)
self.w(u'</div>')
+
VComponent = class_renamed('VComponent', Component,
- 'VComponent is deprecated, use Component')
+ '[3.2] VComponent is deprecated, use Component')
SingletonVComponent = class_renamed('SingletonVComponent', Component,
- 'SingletonVComponent is deprecated, use '
+ '[3.2] SingletonVComponent is deprecated, use '
'Component and explicit registration control')
--- a/web/controller.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/controller.py Fri Mar 11 09:46:45 2011 +0100
@@ -91,7 +91,7 @@
pp = req.vreg['components'].select_or_none('magicsearch', req)
if pp is not None:
return pp.process_query(rql)
- if 'eid' in req.form:
+ if 'eid' in req.form and not isinstance(req.form['eid'], list):
return req.eid_rset(req.form['eid'])
return None
@@ -148,10 +148,13 @@
path = self._cw.form['__redirectpath']
if (self._edited_entity and path != self._edited_entity.rest_path()
and '_cwmsgid' in newparams):
- # XXX may be here on modification?
- msg = u'(<a href="%s">%s</a>)' % (
- xml_escape(self._edited_entity.absolute_url()),
- self._cw._('click here to see created entity'))
+ # are we here on creation or modification?
+ if any(eid == self._edited_entity.eid
+ for eid in self._cw.data.get('eidmap', {}).values()):
+ msg = self._cw._('click here to see created entity')
+ else:
+ msg = self._cw._('click here to see edited entity')
+ msg = u'(<a href="%s">%s</a>)' % (xml_escape(self._edited_entity.absolute_url()), msg)
self._cw.append_to_redirect_message(msg)
elif self._after_deletion_path:
# else it should have been set during form processing
Binary file web/data/actionBoxHeader.png has changed
Binary file web/data/boxHeader.png has changed
Binary file web/data/contextFreeBoxHeader.png has changed
Binary file web/data/contextualBoxHeader.png has changed
--- a/web/data/cubicweb.ajax.box.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.ajax.box.js Fri Mar 11 09:46:45 2011 +0100
@@ -16,7 +16,7 @@
var d = loadRemote('json', ajaxFuncArgs(fname, null, eid, value));
d.addCallback(function() {
$('#' + holderid).empty();
- var formparams = ajaxFuncArgs('render', null, 'boxes', boxid, eid);
+ var formparams = ajaxFuncArgs('render', null, 'ctxcomponents', boxid, eid);
$('#' + cw.utils.domid(boxid) + eid).loadxhtml('json', formparams);
if (msg) {
document.location.hash = '#header';
@@ -28,7 +28,7 @@
function ajaxBoxRemoveLinkedEntity(boxid, eid, relatedeid, delfname, msg) {
var d = loadRemote('json', ajaxFuncArgs(delfname, null, eid, relatedeid));
d.addCallback(function() {
- var formparams = ajaxFuncArgs('render', null, 'boxes', boxid, eid);
+ var formparams = ajaxFuncArgs('render', null, 'ctxcomponents', boxid, eid);
$('#' + cw.utils.domid(boxid) + eid).loadxhtml('json', formparams);
if (msg) {
document.location.hash = '#header';
@@ -37,6 +37,26 @@
});
}
+/**
+ * .. function:: ajaxBoxShowSelector(boxid, eid, unrelfname,
+ * addfname, msg,
+ * oklabel, cancellabel,
+ * separator=None)
+ *
+ * Display an ajax selector within a box of regid `boxid`, for entity with eid
+ * `eid`.
+ *
+ * Other parameters are:
+ *
+ * * `addfname`, name of the json controller method to call to add a relation
+ *
+ * * `msg`, message to display to the user when a relation has been added
+ *
+ * * `oklabel`/`cancellabel`, OK/cancel buttons label
+ *
+ * * `separator`, items separator if the field is multi-valued (will be
+ * considered mono-valued when not specified)
+ */
function ajaxBoxShowSelector(boxid, eid,
unrelfname,
addfname, msg,
@@ -53,28 +73,23 @@
deferred.addCallback(function (unrelated) {
var input = INPUT({'type': 'text', 'id': inputid, 'size': 20});
holder.append(input).show();
- $input = $(input);
- $input.keypress(function (event) {
- if (event.keyCode == KEYS.KEY_ENTER) {
- // XXX not very user friendly: we should test that the suggestions
- // aren't visible anymore
+ var $input = $(input);
+ $input.keypress(function (evt) {
+ if (evt.keyCode == $.ui.keyCode.ENTER) {
ajaxBoxValidateSelectorInput(boxid, eid, separator, addfname, msg);
}
});
+ $input.cwautocomplete(unrelated, {multiple: Boolean(separator)});
var buttons = DIV({'class' : "sgformbuttons"},
- A({'href' : "javascript: noop();",
- 'onclick' : cw.utils.strFuncCall('ajaxBoxValidateSelectorInput',
- boxid, eid, separator, addfname, msg)},
- oklabel),
+ A({href : "javascript: noop();",
+ onclick : cw.utils.strFuncCall('ajaxBoxValidateSelectorInput',
+ boxid, eid, separator, addfname, msg)},
+ oklabel),
' / ',
A({'href' : "javascript: noop();",
'onclick' : '$("#' + holderid + '").empty()'},
cancellabel));
holder.append(buttons);
- $input.autocomplete(unrelated, {
- multiple: separator,
- max: 15
- });
$input.focus();
});
}
--- a/web/data/cubicweb.ajax.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.ajax.js Fri Mar 11 09:46:45 2011 +0100
@@ -184,7 +184,7 @@
_loadDynamicFragments(node);
// XXX [3.7] jQuery.one is now used instead jQuery.bind,
// jquery.treeview.js can be unpatched accordingly.
- jQuery(CubicWeb).trigger('server-response', [true, node]);
+ jQuery(cw).trigger('server-response', [true, node]);
jQuery(node).trigger('server-response', [true, node]);
}
@@ -283,7 +283,7 @@
* dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
*/
function loadRemote(url, form, reqtype, sync) {
- if (!url.startswith(baseuri())) {
+ if (!url.toLowerCase().startswith(baseuri())) {
url = baseuri() + url;
}
if (!sync) {
@@ -292,6 +292,7 @@
url: url,
type: (reqtype || 'POST').toUpperCase(),
data: form,
+ traditional: true,
async: true,
beforeSend: function(xhr) {
@@ -299,9 +300,6 @@
},
success: function(data, status) {
- if (deferred._req.getResponseHeader("content-type") == 'application/json') {
- data = cw.evalJSON(data);
- }
deferred.success(data);
},
@@ -320,18 +318,22 @@
});
return deferred;
} else {
- var result = jQuery.ajax({
+ var result;
+ // jQuery.ajax returns the XHR object, even for synchronous requests,
+ // but in that case, the success callback will be called before
+ // jQuery.ajax returns. The first argument of the callback will be
+ // the server result, interpreted by jQuery according to the reponse's
+ // content-type (i.e. json or xml)
+ jQuery.ajax({
url: url,
type: (reqtype || 'GET').toUpperCase(),
data: form,
- async: false
+ traditional: true,
+ async: false,
+ success: function(res) {
+ result = res;
+ }
});
- // check result.responseText instead of result to avoid error encountered with IE
- if (result.responseText) {
- // XXX no good reason to force json here,
- // it should depends on request content-type
- result = cw.evalJSON(result.responseText);
- }
return result;
}
}
@@ -417,7 +419,7 @@
var d = loadRemote('json', ajaxFuncArgs('delete_bookmark', null, beid));
d.addCallback(function(boxcontent) {
$('#bookmarks_box').loadxhtml('json',
- ajaxFuncArgs('render', null, 'boxes',
+ ajaxFuncArgs('render', null, 'ctxcomponents',
'bookmarks_box'));
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
@@ -561,6 +563,51 @@
);
}
+/* High-level functions *******************************************************/
+
+/**
+ * .. function:: reloadCtxComponentsSection(context, actualEid, creationEid=None)
+ *
+ * reload all components in the section for a given `context`.
+ *
+ * This is necessary for cases where the parent entity (on which the section
+ * apply) has been created during post, hence the section has to be reloaded to
+ * consider its new eid, hence the two additional arguments `actualEid` and
+ * `creationEid`: `actualEid` is the eid of newly created top level entity and
+ * `creationEid` the fake eid that was given as form creation marker (e.g. A).
+ *
+ * You can still call this function with only the actual eid if you're not in
+ * such creation case.
+ */
+function reloadCtxComponentsSection(context, actualEid, creationEid) {
+ // in this case, actualEid is the eid of newly created top level entity and
+ // creationEid the fake eid given as form creation marker (e.g. A)
+ if (!creationEid) { creationEid = actualEid ; }
+ var $compsholder = $('#' + context + creationEid);
+ // reload the whole components section
+ $compsholder.children().each(function (index) {
+ // XXX this.id[:-len(eid)]
+ var compid = this.id.replace("_", ".").rstrip(creationEid);
+ var params = ajaxFuncArgs('render', null, 'ctxcomponents',
+ compid, actualEid);
+ $(this).loadxhtml('json', params, null, 'swap', true);
+ });
+ $compsholder.attr('id', context + actualEid);
+}
+
+
+/**
+ * .. function:: reload(domid, registry, formparams, *render_args)
+ *
+ * `js_render` based reloading of views and components.
+ */
+function reload(domid, compid, registry, formparams /* ... */) {
+ var ajaxArgs = ['render', formparams, registry, compid];
+ ajaxArgs = ajaxArgs.concat(cw.utils.sliceList(arguments, 4));
+ var params = ajaxFuncArgs.apply(null, ajaxArgs);
+ $('#'+domid).loadxhtml('json', params, null, 'swap');
+}
+
/* DEPRECATED *****************************************************************/
preprocessAjaxLoad = cw.utils.deprecatedFunction(
@@ -586,7 +633,7 @@
reloadBox = cw.utils.deprecatedFunction(
'[3.9] reloadBox() is deprecated, use loadxhtml instead',
function(boxid, rql) {
- return reloadComponent(boxid, rql, 'boxes', boxid);
+ return reloadComponent(boxid, rql, 'ctxcomponents', boxid);
}
);
@@ -635,14 +682,15 @@
function(fname /* ... */) {
setProgressCursor();
var props = {
- 'fname': fname,
- 'pageid': pageid,
- 'arg': $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
+ fname: fname,
+ pageid: pageid,
+ arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
};
var result = jQuery.ajax({
url: JSON_BASE_URL,
data: props,
- async: false
+ async: false,
+ traditional: true
}).responseText;
if (result) {
result = cw.evalJSON(result);
@@ -657,9 +705,9 @@
function(fname /* ... */) {
setProgressCursor();
var props = {
- 'fname': fname,
- 'pageid': pageid,
- 'arg': $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
+ fname: fname,
+ pageid: pageid,
+ arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON)
};
// XXX we should inline the content of loadRemote here
var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
--- a/web/data/cubicweb.calendar.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.calendar.css Fri Mar 11 09:46:45 2011 +0100
@@ -230,7 +230,7 @@
.calendar th.month {
font-weight:bold;
padding-bottom:0.2em;
- background: %(actionBoxTitleBgColor)s;
+ background: %(incontextBoxBodyBgColor)s;
}
.calendar th.month a{
--- a/web/data/cubicweb.calendar.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.calendar.js Fri Mar 11 09:46:45 2011 +0100
@@ -16,12 +16,14 @@
* .. class:: Calendar
*
* Calendar (graphical) widget
+ *
* public methods are :
+ *
* __init__ :
- * @param containerId: the DOM node's ID where the calendar will be displayed
- * @param inputId: which input needs to be updated when a date is selected
- * @param year, @param month: year and month to be displayed
- * @param cssclass: CSS class of the calendar widget (default is commandCal)
+ * :attr:`containerId`: the DOM node's ID where the calendar will be displayed
+ * :attr:`inputId`: which input needs to be updated when a date is selected
+ * :attr:`year`, :attr:`month`: year and month to be displayed
+ * :attr:`cssclass`: CSS class of the calendar widget (default is 'commandCal')
*
* show() / hide():
* show or hide the calendar widget
--- a/web/data/cubicweb.compat.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.compat.js Fri Mar 11 09:46:45 2011 +0100
@@ -32,14 +32,15 @@
/**
* .. function:: cw.utils.deprecatedFunction(msg, function)
*
- * jQUery flattens arrays returned by the mapping function:
- * >>> y = ['a:b:c', 'd:e']
- * >>> jQuery.map(y, function(y) { return y.split(':');})
- * ["a", "b", "c", "d", "e"]
- * // where one would expect:
- * [ ["a", "b", "c"], ["d", "e"] ]
- * XXX why not the same argument order as $.map and forEach ?
+ * jQUery flattens arrays returned by the mapping function: ::
+ *
+ * >>> y = ['a:b:c', 'd:e']
+ * >>> jQuery.map(y, function(y) { return y.split(':');})
+ * ["a", "b", "c", "d", "e"]
+ * // where one would expect:
+ * [ ["a", "b", "c"], ["d", "e"] ]
*/
+ // XXX why not the same argument order as $.map and forEach ?
map = cw.utils.deprecatedFunction(
'[3.9] map() is deprecated, use $.map instead',
function(func, array) {
@@ -66,23 +67,23 @@
);
addElementClass = cw.utils.deprecatedFunction(
- '[3.9] addElementClass(node, cls) is depcreated, use $(node).addClass(cls) instead',
+ '[3.9] addElementClass(node, cls) is deprecated, use $(node).addClass(cls) instead',
function(node, klass) {
$(node).addClass(klass);
}
);
removeElementClass = cw.utils.deprecatedFunction(
- '[3.9] removeElementClass(node, cls) is depcreated, use $(node).removeClass(cls) instead',
+ '[3.9] removeElementClass(node, cls) is deprecated, use $(node).removeClass(cls) instead',
function(node, klass) {
$(node).removeClass(klass);
}
);
hasElementClass = cw.utils.deprecatedFunction(
- '[3.9] hasElementClass(node, cls) is depcreated, use $.className.has(node, cls)',
+ '[3.9] hasElementClass(node, cls) is deprecated, use $(node).hasClass(cls)',
function(node, klass) {
- return $.className.has(node, klass);
+ return $(node).hasClass(klass);
}
);
--- a/web/data/cubicweb.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.css Fri Mar 11 09:46:45 2011 +0100
@@ -31,19 +31,22 @@
/* h3 { font-size:1.30769em; } */
/* scale traditional */
-h1 { font-size: %(h1FontSize)s; }
+h1,
+.vtitle { font-size: %(h1FontSize)s; }
h2 { font-size: %(h2FontSize)s; }
h3 { font-size: %(h3FontSize)s; }
/* paddings */
-h1 {
+h1,
+.vtitle {
border-bottom: %(h1BorderBottomStyle)s;
padding: %(h1Padding)s;
margin: %(h1Margin)s;
color: %(h1Color)s;
}
-div.tabbedprimary + h1, h1.plain {
+div.tabbedprimary + h1,
+h1.plain {
border-bottom: none;
}
@@ -100,7 +103,7 @@
}
ol ol,
-ul ul{
+ul ul {
margin-left: 8px;
margin-bottom : 0px;
}
@@ -113,7 +116,7 @@
margin-left: 1.5em;
}
-img{
+img {
border: none;
}
@@ -139,7 +142,7 @@
border: 1px inset %(headerBgColor)s;
}
-hr{
+hr {
border: none;
border-bottom: 1px solid %(defaultColor)s;
height: 1px;
@@ -218,7 +221,7 @@
table#header {
background: %(headerBgColor)s url("banner.png") repeat-x top left;
- text-align: left;
+ width: 100%;
}
table#header td {
@@ -229,6 +232,11 @@
color: %(defaultColor)s;
}
+table#header td#header-right {
+ padding-top: 1em;
+ float: right;
+}
+
table#header img#logo{
vertical-align: middle;
}
@@ -239,23 +247,10 @@
white-space: nowrap;
}
-table#header td#headtext {
- width: 100%;
-}
-
/* Popup on login box and userActionBox */
-div.popupWrapper {
- position: relative;
- z-index: 100;
-}
-
div.popup {
position: absolute;
background: #fff;
- /* background-color: #f0eff0; */
- /* background-image: url(popup.png); */
- /* background-repeat: repeat-x; */
- /* background-positon: top left; */
border: 1px solid %(listingBorderColor)s;
border-top: none;
text-align: left;
@@ -273,12 +268,13 @@
margin: %(defaultLayoutMargin)s;
}
-table#mainLayout #navColumnLeft {
+table#mainLayout td#navColumnLeft {
width: 16em;
padding-right: %(defaultLayoutMargin)s;
+
}
-table#mainLayout #navColumnRight {
+table#mainLayout td#navColumnRight {
width: 16em;
padding-left: %(defaultLayoutMargin)s;
}
@@ -313,28 +309,15 @@
color: %(defaultColor)s;
}
-/* rql bar */
-
-div#rqlinput {
- margin-bottom: %(defaultLayoutMargin)s;
-}
-
-input#rql{
- padding: 0.25em 0.3em;
- width: 99%;
-}
-
-/* boxes */
+/* XXX old boxes, deprecated */
div.boxFrame {
width: 100%;
}
div.boxTitle {
- overflow: hidden;
- font-weight: bold;
color: #fff;
- background: %(boxTitleBg)s;
+ background: %(contextualBoxTitleBgColor)s;
}
div.boxTitle span,
@@ -343,14 +326,7 @@
white-space: nowrap;
}
-div.searchBoxFrame div.boxTitle,
-div.greyBoxFrame div.boxTitle {
- background: %(actionBoxTitleBg)s;
-}
-
-div.sideBoxTitle span,
-div.searchBoxFrame div.boxTitle span,
-div.greyBoxFrame div.boxTitle span {
+div.sideBoxTitle span {
color: %(defaultColor)s;
}
@@ -364,34 +340,13 @@
border-top: none;
}
-a.boxMenu {
- display: block;
- padding: 1px 9px 1px 3px;
- background: transparent %(bulletDownImg)s;
-}
-
-a.boxMenu:hover {
- background: %(sideBoxBodyBgColor)s %(bulletDownImg)s;
- cursor: pointer;
-}
-
-a.popupMenu {
- background: transparent url("puce_down_black.png") 2% 6px no-repeat;
- padding-left: 2em;
-}
-
-div.searchBoxFrame div.boxContent {
- padding: 4px 4px 3px;
- background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
-}
-
div.shadow{
height: 14px;
background: url("shadow.gif") no-repeat top right;
}
div.sideBoxTitle {
- background: %(actionBoxTitleBg)s;
+ background: %(incontextBoxBodyBg)s;
display: block;
font-weight: bold;
}
@@ -401,22 +356,22 @@
}
ul.sideBox,
-ul.sideBox ul{
+ul.sideBox ul {
margin-bottom: 0px;
}
-ul.sideBox li{
+ul.sideBox li {
padding: 0px 0px 1px 1px;
margin: 1px 0 1px 4px;
}
div.sideBoxBody {
padding: 0.2em 5px;
- background: %(sideBoxBodyBg)s;
+ background: %(incontextBoxBodyBg)s;
}
div.sideBoxBody a {
- color: %(sideBoxBodyColor)s;
+ color: %(incontextBoxBodyColor)s;
}
div.sideBoxBody a:hover {
@@ -427,6 +382,183 @@
padding-right: 1em;
}
+/* boxes */
+
+div.boxTitle {
+ overflow: hidden;
+ font-weight: bold;
+}
+
+div.boxTitle span {
+ padding: 0px 0.5em;
+ white-space: nowrap;
+}
+
+div.boxBody {
+ padding: 5px;
+ border-top: none;
+ background-color: %(leftrightBoxBodyBgColor)s;
+}
+
+div.boxBody a {
+ color: %(leftrightBoxBodyColor)s;
+}
+
+div.boxBody a:hover {
+ text-decoration: none;
+ cursor: pointer;
+ background-color: %(leftrightBoxBodyHoverBgColor)s;
+}
+
+hr.boxSeparator {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+/* boxes contextual customization */
+
+.contextFreeBox div.boxTitle {
+ background: %(contextFreeBoxTitleBg)s;
+ color: %(contextFreeBoxTitleColor)s;
+}
+
+.contextualBox div.boxTitle {
+ background: %(contextualBoxTitleBg)s;
+ color: %(contextualBoxTitleColor)s;
+}
+
+.primaryRight div.boxTitle {
+ background: %(incontextBoxTitleBg)s;
+ color: %(incontextBoxTitleColor)s;
+}
+
+.primaryRight div.boxBody {
+ padding: 0.2em 5px;
+ background: %(incontextBoxBodyBgColor)s;
+}
+
+.primaryRight div.boxBody a {
+ color: %(incontextBoxBodyColor)s;
+}
+
+.primaryRight div.boxBody a:hover {
+ background-color: %(incontextBoxBodyHoverBgColor)s;
+}
+
+.primaryRight div.boxFooter {
+ margin-bottom: 1em;
+}
+
+#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{
+ height: 14px;
+ background: url("shadow.gif") no-repeat top right;
+}
+
+/* boxes lists and menus */
+
+ul.boxListing {
+ margin: 0;
+ padding: 0;
+}
+
+ul.boxListing ul {
+ padding: 1px 3px;
+}
+
+ul.boxListing a {
+ color: %(defaultColor)s;
+ padding: 1px 9px 1px 3px;
+ display: block; /* necessary to get links across all width available (see on mouse over) */
+}
+
+ul.boxListing a.action {
+ padding: 0;
+ display: inline;
+}
+
+ul.boxListing a.action + a{
+ display: inline;
+}
+
+ul.boxListing li {
+ margin: 0px;
+ padding: 0px;
+ background-image: none;
+}
+
+ul.boxListing ul li {
+ margin: 0px;
+ padding-left: 8px;
+}
+
+ul.boxListing ul li a {
+ padding-left: 10px;
+ background-image: url("bullet_orange.png");
+ background-repeat: no-repeat;
+ background-position: 0 6px;
+}
+
+ul.boxListing .selected {
+ color: %(aColor)s;
+ font-weight: bold;
+}
+
+ul.boxListing a.boxMenu:hover {
+ border-top: medium none;
+ background: %(leftrightBoxBodyHoverBgColor)s;
+}
+
+a.boxMenu,
+ul.boxListing a.boxMenu {
+ display: block;
+ padding: 1px 3px;
+ background: transparent %(bulletDownImg)s;
+}
+
+ul.boxListing a.boxMenu:hover {
+ border-top: medium none;
+ background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s;
+}
+
+a.boxMenu:hover {
+ cursor: pointer;
+}
+
+a.popupMenu {
+ background: transparent url("puce_down_black.png") 2% 6px no-repeat;
+ padding-left: 2em;
+}
+
+/* custom boxes */
+
+.search_box div.boxBody {
+ padding: 4px 4px 3px;
+ background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
+}
+
+.bookmarks_box ul.boxListing div a:hover {
+ border-bottom: 1px solid #000;
+}
+
+.download_box div.boxTitle {
+ background : #8fbc8f !important;
+}
+
+.download_box div.boxBody {
+ background : #eefed9;
+}
+
+/* search box and rql bar */
+
+div#rqlinput {
+ margin-bottom: %(defaultLayoutMargin)s;
+}
+
+input#rql{
+ padding: 0.25em 0.3em;
+ width: 99%;
+}
+
input.rqlsubmit{
display: block;
width: 20px;
@@ -436,7 +568,7 @@
}
input#norql{
- width:13em;
+ width:155px;
margin-right: 2px;
}
@@ -447,7 +579,7 @@
}
div#userActionsBox {
- width: 14em;
+ width: 15em;
text-align: right;
}
@@ -457,20 +589,6 @@
padding-right: 2em;
}
-/* download box XXX move to its own file? */
-div.downloadBoxTitle{
- background : #8fbc8f;
- font-weight: bold;
-}
-
-div.downloadBox{
- font-weight: bold;
-}
-
-div.downloadBox div.sideBoxBody{
- background : #eefed9;
-}
-
/**************/
/* navigation */
/**************/
@@ -578,7 +696,7 @@
div#appMsg {
margin-bottom: %(defaultLayoutMargin)s;
- border: 1px solid %(actionBoxTitleBgColor)s;
+ border: 1px solid %(incontextBoxTitleBgColor)s;
}
.message {
@@ -591,7 +709,7 @@
padding-left: 25px;
background: %(msgBgColor)s url("critical.png") 2px center no-repeat;
color: %(errorMsgColor)s;
- border: 1px solid %(actionBoxTitleBgColor)s;
+ border: 1px solid %(incontextBoxTitleBgColor)s;
}
/* search-associate message */
@@ -701,6 +819,14 @@
margin-bottom: 0.2em; /* because vertical-align doesn't seems to have any effect */
}
+
+table.ajaxEditRelationTable{
+ margin-bottom: 0.5em;
+}
+table.ajaxEditRelationTable td.entity{
+ padding-left: 0.5em;
+}
+
/***************************************/
/* error view (views/management.py) */
/***************************************/
@@ -746,7 +872,7 @@
input.button{
margin: 1em 1em 0px 0px;
border: 1px solid %(buttonBorderColor)s;
- border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s;
+ border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s;
}
/* FileItemInnerView jquery.treeview.css */
@@ -766,74 +892,20 @@
ul.startup li,
ul.section li {
- margin-left:0px
-}
-
-ul.boxListing {
- margin: 0px;
- padding: 0px 3px;
-}
-
-ul.boxListing li,
-ul.boxListing ul li {
- margin: 0px;
- padding: 0px;
- background-image: none;
-}
-
-ul.boxListing ul {
- padding: 1px 3px;
-}
-
-ul.boxListing a {
- color: %(defaultColor)s;
- display:block;
- padding: 1px 9px 1px 3px;
-}
-
-ul.boxListing .selected {
- color: %(aColor)s;
- font-weight: bold;
-}
-
-ul.boxListing a.boxMenu:hover {
- border-top: medium none;
- background: %(sideBoxBodyBgColor)s %(bulletDownImg)s;
-}
-
-ul.boxListing a.boxBookmark {
- padding-left: 3px;
- background-image: none;
- background:#fff;
+ margin-left: 0px
}
ul.simple li,
-ul.boxListing ul li ,
.popupWrapper ul li {
background: transparent url("bullet_orange.png") no-repeat 0% 6px;
}
-ul.boxListing a.boxBookmark:hover,
-ul.boxListing a:hover,
-ul.boxListing ul li a:hover {
- text-decoration: none;
- background: %(sideBoxBodyBg)s;
-}
-
-ul.boxListing ul li a:hover{
- background-color: transparent;
-}
-
-ul.boxListing ul li a {
- padding: 1px 3px 0px 10px;
-}
-
ul.simple li {
padding-left: 8px;
}
.popupWrapper ul {
- padding:0.2em 0.3em;
+ padding: 0.2em 0.3em;
margin-bottom: 0px;
}
@@ -866,7 +938,7 @@
.validateButton {
margin: 1em 1em 0px 0px;
border: 1px solid %(buttonBorderColor)s;
- border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s;
+ border-color: %(buttonBorderColor)s %(incontextBoxTitleBgColor)s %(incontextBoxTitleBgColor)s %(buttonBorderColor)s;
background: %(buttonBgColor)s url("button.png") bottom left repeat-x;
}
@@ -906,6 +978,11 @@
/* overwite other css here */
/********************************/
+.ui-menu li.ui-menu-item {
+ /* remove background image (orange bullet) for autocomplete suggestions */
+ background-image: none;
+}
+
/* ui.tabs.css */
ul.ui-tabs-nav,
div.ui-tabs-panel {
--- a/web/data/cubicweb.edition.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.edition.js Fri Mar 11 09:46:45 2011 +0100
@@ -11,7 +11,7 @@
/**
* .. function:: setPropValueWidget(varname, tabindex)
*
- * called on Eproperty key selection:
+ * called on CWProperty key selection:
* - get the selected value
* - get a widget according to the key by a sync query to the server
* - fill associated div with the returned html
@@ -65,12 +65,12 @@
vid: 'unrelateddivs',
relation: selectedValue,
rql: rql_for_eid(eid),
- '__notemplate': 1,
- callback: function() {
- _showMatchingSelect(eid, jQuery('#' + divId));
- }
+ '__notemplate': 1
};
- jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append');
+ var d = jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append');
+ d.addCallback(function() {
+ _showMatchingSelect(eid, jQuery('#' + divId));
+ });
} else {
_showMatchingSelect(eid, divNode);
}
@@ -209,7 +209,7 @@
}
}
elementId = elementId.substring(2, elementId.length);
- loadRemote('json', ajaxFuncArgs('remove_pending_inserts', null,
+ loadRemote('json', ajaxFuncArgs('remove_pending_insert', null,
elementId.split(':')), 'GET', true);
}
@@ -265,7 +265,7 @@
*/
function togglePendingDelete(nodeId, eid) {
// node found means we should cancel deletion
- if (jQuery.className.has(cw.getNode('span' + nodeId), 'pendingDelete')) {
+ if (jQuery(cw.getNode('span' + nodeId)).hasClass('pendingDelete')) {
cancelPendingDelete(nodeId, eid);
} else {
addPendingDelete(nodeId, eid);
@@ -588,179 +588,11 @@
var args = ajaxFuncArgs('validate_form', null, action, zipped[0], zipped[1]);
var d = loadRemote('json', args, 'POST');
} catch(ex) {
- log('got exception', ex);
+ cw.log('got exception', ex);
return false;
}
- function _callback(result, req) {
+ d.addCallback(function(result, req) {
handleFormValidationResponse(formid, onsuccess, onfailure, result);
- }
- d.addCallback(_callback);
+ });
return false;
}
-
-
-
-// ======================= DEPRECATED FUNCTIONS ========================= //
-// (mostly reledit related)
-/**
- * .. function:: inlineValidateRelationFormOptions(rtype, eid, divid, options)
- *
- * called by reledit forms to submit changes
- * * `rtype`, the attribute being edited
- *
- * * `eid`, the eid of the entity being edited
- *
- * * `options`, a dictionnary of options used by the form validation handler such
- * as ``role``, ``onsuccess``, ``onfailure``, ``reload``, ``vid``, ``lzone``
- * and ``default_value``:
- *
- * * `onsucess`, javascript function to execute on success, default is noop
- *
- * * `onfailure`, javascript function to execute on failure, default is noop
- *
- * * `default_value`, value if the field is empty
- *
- * * `lzone`, html fragment (string) for a clic-zone triggering actual edition
- */
-
-
-showInlineEditionForm = cw.utils.deprecatedFunction(
- '[3.9] this is now unused by reledit (see cw.reledit.js)',
- function showInlineEditionForm(eid, rtype, divid) {
- jQuery('#' + divid).hide();
- jQuery('#' + divid + '-value').hide();
- jQuery('#' + divid + '-form').show();
- }
-);
-
-hideInlineEdit = cw.utils.deprecatedFunction(
- '[3.9] this is now unused by reledit (see cw.reledit.js)',
- function hideInlineEdit(eid, rtype, divid) {
- jQuery('#appMsg').hide();
- jQuery('div.errorMessage').remove();
- jQuery('#' + divid).show();
- jQuery('#' + divid + '-value').show();
- jQuery('#' + divid + '-form').hide();
- }
-);
-
-
-inlineValidateRelationFormOptions = cw.utils.deprecatedFunction(
- '[3.9] this is now unused by reledit (see cw.reledit.js)',
- function inlineValidateRelationFormOptions(rtype, eid, divid, options) {
- try {
- var form = cw.getNode(divid + '-form');
- var relname = rtype + ':' + eid;
- var newtarget = jQuery('[name=' + relname + ']').val();
- var zipped = cw.utils.formContents(form);
- var args = ajaxFuncArgs('validate_form', null, 'apply', zipped[0], zipped[1]);
- var d = loadRemote(JSON_BASE_URL, args, 'POST');
- } catch(ex) {
- return false;
- }
- d.addCallback(function(result, req) {
- execFormValidationResponse(rtype, eid, divid, options, result);
- });
- return false;
- });
-
-execFormValidationResponse = cw.utils.deprecatedFunction(
- '[3.9] this is now unused by reledit (see cw.reledit.js)',
- function execFormValidationResponse(rtype, eid, divid, options, result) {
- options = $.extend({onsuccess: noop,
- onfailure: noop
- }, options);
- if (handleFormValidationResponse(divid + '-form', options.onsucess , options.onfailure, result)) {
- if (options.reload) {
- document.location.reload();
- } else {
- var args = {
- fname: 'reledit_form',
- rtype: rtype,
- role: options.role,
- eid: eid,
- divid: divid,
- reload: options.reload,
- vid: options.vid,
- default_value: options.default_value,
- landing_zone: options.lzone
- };
- jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
- }
- }
-});
-
-
-/**
- * .. function:: loadInlineEditionFormOptions(eid, rtype, divid, options)
- *
- * inline edition
- */
-loadInlineEditionFormOptions = cw.utils.deprecatedFunction(
- '[3.9] this is now unused by reledit (see cw.reledit.js) ',
- function loadInlineEditionFormOptions(eid, rtype, divid, options) {
- var args = {
- fname: 'reledit_form',
- rtype: rtype,
- role: options.role,
- eid: eid,
- divid: divid,
- reload: options.reload,
- vid: options.vid,
- default_value: options.default_value,
- landing_zone: options.lzone,
- callback: function() {
- showInlineEditionForm(eid, rtype, divid);
- }
- };
- jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
-});
-
-
-inlineValidateRelationForm = cw.utils.deprecatedFunction(
- '[3.9] inlineValidateRelationForm() function is deprecated, use inlineValidateRelationFormOptions instead',
- function(rtype, role, eid, divid, reload, vid, default_value, lzone, onsucess, onfailure) {
- try {
- var form = cw.getNode(divid + '-form');
- var relname = rtype + ':' + eid;
- var newtarget = jQuery('[name=' + relname + ']').val();
- var zipped = cw.utils.formContents(form);
- var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]);
- } catch(ex) {
- return false;
- }
- d.addCallback(function(result, req) {
- var options = {role : role,
- reload: reload,
- vid: vid,
- default_value: default_value,
- lzone: lzone,
- onsucess: onsucess || $.noop,
- onfailure: onfailure || $.noop
- };
- execFormValidationResponse(rtype, eid, divid, options);
- });
- return false;
- }
-);
-
-loadInlineEditionForm = cw.utils.deprecatedFunction(
- '[3.9] loadInlineEditionForm() function is deprecated, use loadInlineEditionFormOptions instead',
- function(eid, rtype, role, divid, reload, vid, default_value, lzone) {
- var args = {
- fname: 'reledit_form',
- rtype: rtype,
- role: role,
- eid: eid,
- divid: divid,
- reload: reload,
- vid: vid,
- default_value: default_value,
- landing_zone: lzone,
- callback: function() {
- showInlineEditionForm(eid, rtype, divid);
- }
- };
- jQuery('#' + divid + '-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
- }
-);
--- a/web/data/cubicweb.facets.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.facets.js Fri Mar 11 09:46:45 2011 +0100
@@ -92,14 +92,14 @@
$node.loadxhtml('json', ajaxFuncArgs('render', {
'rql': rql
},
- 'boxes', 'edit_box'));
+ 'ctxcomponents', 'edit_box'));
}
$node = jQuery('#breadcrumbs')
if ($node.length) {
$node.loadxhtml('json', ajaxFuncArgs('render', {
'rql': rql
},
- 'components', 'breadcrumbs'));
+ 'ctxcomponents', 'breadcrumbs'));
}
}
var d = loadRemote('json', ajaxFuncArgs('filter_select_content', null, toupdate, rql));
--- a/web/data/cubicweb.form.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.form.css Fri Mar 11 09:46:45 2011 +0100
@@ -10,7 +10,6 @@
font-size : 160%;
font-weight: bold;
padding-bottom : 0.4em;
- text-transform: capitalize;
margin-bottom: 0.6em
}
@@ -229,6 +228,6 @@
margin: 1em 1em 0px 0px;
border-width: 1px;
border-style: solid;
- border-color: %(buttonBorderColor)s %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s %(buttonBorderColor)s;
+ border-color: %(buttonBorderColor)s %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s %(buttonBorderColor)s;
background: %(buttonBgColor)s %(buttonBgImg)s;
}
--- a/web/data/cubicweb.htmlhelpers.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.htmlhelpers.js Fri Mar 11 09:46:45 2011 +0100
@@ -1,3 +1,14 @@
+/* in CW 3.10, we should move these functions in this namespace */
+cw.htmlhelpers = new Namespace('cw.htmlhelpers');
+
+jQuery.extend(cw.htmlhelpers, {
+ popupLoginBox: function(loginboxid, focusid) {
+ $('#'+loginboxid).toggleClass('hidden');
+ jQuery('#' + focusid +':visible').focus();
+ }
+});
+
+
/**
* .. function:: baseuri()
*
@@ -7,9 +18,9 @@
function baseuri() {
var uri = document.baseURI;
if (uri) { // some browsers don't define baseURI
- return uri;
+ return uri.toLowerCase();
}
- return jQuery('base').attr('href');
+ return jQuery('base').attr('href').toLowerCase();
}
/**
@@ -97,10 +108,11 @@
* toggles visibility of login popup div
*/
// XXX used exactly ONCE in basecomponents
-function popupLoginBox() {
- $('#popupLoginBox').toggleClass('hidden');
- jQuery('#__login:visible').focus();
-}
+popupLoginBox = cw.utils.deprecatedFunction(
+ function() {
+ $('#popupLoginBox').toggleClass('hidden');
+ jQuery('#__login:visible').focus();
+});
/**
* .. function getElementsMatching(tagName, properties, \/* optional \*\/ parent)
--- a/web/data/cubicweb.ie.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.ie.css Fri Mar 11 09:46:45 2011 +0100
@@ -4,10 +4,20 @@
margin-top: 0px;
}
+table#header td#header-right div.popupWrapper {
+ position: relative;
+ z-index: 400;
+}
+
+table#header td#header-right{
+ text-align:right;
+}
+
/* quick and dirty solution for pop to be
correctly displayed on right edge of window */
div.popupWrapper{
direction:rtl;
+ text-align:right;
}
div#rqlinput input.rqlsubmit{
--- a/web/data/cubicweb.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.js Fri Mar 11 09:46:45 2011 +0100
@@ -6,6 +6,15 @@
cw = new Namespace('cw');
jQuery.extend(cw, {
+ cubes: new Namespace('cubes'),
+ /* provide a removeEventListener / detachEvent definition to
+ * to bypass a jQuery 1.4.2 bug when unbind() is called on a
+ * plain JS object and not a DOM node.
+ * see http://dev.jquery.com/ticket/6184 for more details
+ */
+ removeEventListener: function() {},
+ detachEvent: function() {},
+
log: function () {
var args = [];
for (var i = 0; i < arguments.length; i++) {
@@ -299,6 +308,17 @@
},
/**
+ * .. function:: difference(lst1, lst2)
+ *
+ * returns a list containing all elements in `lst1` that are not
+ * in `lst2`.
+ */
+ difference: function(lst1, lst2) {
+ return jQuery.grep(lst1, function(elt, i) {
+ return jQuery.inArray(elt, lst2) == -1;
+ });
+ },
+ /**
* .. function:: domid(string)
*
* return a valid DOM id from a string (should also be usable in jQuery
@@ -415,22 +435,17 @@
}
// XXX avoid crashes / backward compat
-CubicWeb = {
+CubicWeb = cw;
+
+jQuery.extend(cw, {
require: cw.utils.deprecatedFunction(
'[3.9] CubicWeb.require() is not used anymore',
function(module) {}),
provide: cw.utils.deprecatedFunction(
'[3.9] CubicWeb.provide() is not used anymore',
function(module) {})
-};
+});
jQuery(document).ready(function() {
- jQuery(CubicWeb).trigger('server-response', [false, document]);
- jQuery(cw).trigger('server-response', [false, document]);
+ $(cw).trigger('server-response', [false, document]);
});
-
-// XXX as of 2010-04-07, no known cube uses this
-jQuery(CubicWeb).bind('ajax-loaded', function() {
- log('[3.7] "ajax-loaded" event is deprecated, use "server-response" instead');
- jQuery(cw).trigger('server-response', [false, document]);
-});
--- a/web/data/cubicweb.login.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.login.css Fri Mar 11 09:46:45 2011 +0100
@@ -5,20 +5,20 @@
* :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
-div#popupLoginBox {
+div.popupLoginBox {
position: absolute;
z-index: 400;
right: 0px;
width: 26em;
padding: 0px 1px 1px;
- background: %(listingBorderColor)s;
+ background: %(listingBorderColor)s;
}
-div#popupLoginBox label{
+div.popupLoginBox label{
font-weight: bold;
}
-div#popupLoginBox div#loginContent {
+div.popupLoginBox div.loginContent {
background: #e6e4ce;
padding: 5px 3px 4px;
}
@@ -30,7 +30,7 @@
margin-left: -14em;
width: 28em;
background: #fff;
- border: 2px solid %(actionBoxTitleBgColor)s;
+ border: 2px solid %(incontextBoxBodyBgColor)s;
padding-bottom: 0.5em;
text-align: center;
}
@@ -40,7 +40,7 @@
font-size: 140%;
}
-div#loginTitle {
+div.loginTitle {
color: #fff;
font-weight: bold;
font-size: 140%;
@@ -49,18 +49,19 @@
background: %(headerBgColor)s url("banner.png") repeat-x top left;
}
-div#loginBox div#loginContent form {
+div#loginBox div.loginContent form {
padding-top: 1em;
width: 90%;
margin: auto;
}
-#popupLoginBox table td {
+
+.popupLoginBox table td {
padding: 0px 3px;
white-space: nowrap;
}
-#loginContent table {
+.loginContent table {
padding: 0px 0.5em;
margin: auto;
}
@@ -74,17 +75,20 @@
margin-top: 0.6em;
}
-#loginContent input.data {
+.loginContent input.data {
width: 12em;
}
+/* This is seriously debatable
+ These buttons render much more clearly as standard/default buttons
+ */
.loginButton {
border: 1px solid #edecd2;
- border-color: #edecd2 %(actionBoxTitleBgColor)s %(actionBoxTitleBgColor)s #edecd2;
+ border-color: #edecd2 %(incontextBoxBodyBgColor)s %(incontextBoxBodyBgColor)s #edecd2;
margin: 2px 0px 0px;
background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
}
-#loginContent .formButtonBar {
+.loginContent .formButtonBar {
float: right;
}
--- a/web/data/cubicweb.old.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.old.css Fri Mar 11 09:46:45 2011 +0100
@@ -22,7 +22,8 @@
font-family: Verdana, sans-serif;
}
-h1 {
+h1,
+.vtitle {
font-size: 188%;
margin: 0.2em 0px 0.3em;
border-bottom: 1px solid #000;
@@ -203,7 +204,7 @@
li.invisible { list-style: none; background: none; padding: 0px 0px
1px 1px; }
-li.invisible div{
+li.invisible div {
display: inline;
}
@@ -227,7 +228,7 @@
table#header {
background: #ff7700 url("banner.png") left top repeat-x;
- text-align: left;
+ width: 100%;
}
table#header td {
@@ -235,17 +236,18 @@
}
table#header a {
-color: #000;
+ color: #000;
+}
+
+table#header td#header-right {
+ padding-top: 1em;
+ float: right;
}
span#appliName {
- font-weight: bold;
- color: #000;
- white-space: nowrap;
-}
-
-table#header td#headtext {
- width: 100%;
+ font-weight: bold;
+ color: #000;
+ white-space: nowrap;
}
/* FIXME appear with 4px width in IE6 */
@@ -254,9 +256,9 @@
}
/* Popup on login box and userActionBox */
-div.popupWrapper{
- position:relative;
- z-index:100;
+
+.popupWrapper{
+ position:relative;
}
div.popup {
@@ -264,7 +266,7 @@
background: #fff;
border: 1px solid black;
text-align: left;
- z-index:400;
+ z-index: 400;
}
div.popup ul li a {
@@ -314,18 +316,23 @@
div#rqlinput {
border: 1px solid #cfceb7;
margin-bottom: 8px;
- padding: 3px;
+ padding: 1px;
background: #cfceb7;
+ width: 100%;
+}
+
+input#rql {
+ width: 99%;
}
-input#rql{
- width: 95%;
+input.rqlsubmit{
+ display: block;
+ width: 20px;
+ height: 20px;
+ background: %(buttonBgColor)s url("go.png") 50% 50% no-repeat;
+ vertical-align: bottom;
}
-
-/* boxes */
-div.navboxes {
- margin-top: 8px;
-}
+/* old boxes, deprecated */
div.boxFrame {
width: 100%;
@@ -335,25 +342,17 @@
padding-top: 0px;
padding-bottom: 0.2em;
font: bold 100% Georgia;
- overflow: hidden;
color: #fff;
background: #ff9900 url("search.png") left bottom repeat-x;
}
-div.searchBoxFrame div.boxTitle,
-div.greyBoxFrame div.boxTitle {
- background: #cfceb7;
-}
-
div.boxTitle span,
div.sideBoxTitle span {
padding: 0px 5px;
white-space: nowrap;
}
-div.sideBoxTitle span,
-div.searchBoxFrame div.boxTitle span,
-div.greyBoxFrame div.boxTitle span {
+div.sideBoxTitle span {
color: #222211;
}
@@ -367,85 +366,6 @@
border-top: none;
}
-ul.boxListing {
- margin: 0px;
- padding: 0px 3px;
-}
-
-ul.boxListing li,
-ul.boxListing ul li {
- display: inline;
- margin: 0px;
- padding: 0px;
- background-image: none;
-}
-
-ul.boxListing ul {
- margin: 0px 0px 0px 7px;
- padding: 1px 3px;
-}
-
-ul.boxListing a {
- color: #000;
- display: block;
- padding: 1px 9px 1px 3px;
-}
-
-ul.boxListing .selected {
- color: #FF4500;
- font-weight: bold;
-}
-
-ul.boxListing a.boxBookmark:hover,
-ul.boxListing a:hover,
-ul.boxListing ul li a:hover {
- text-decoration: none;
- background: #eeedd9;
- color: #111100;
-}
-
-ul.boxListing a.boxMenu:hover {
- background: #eeedd9 url(puce_down.png) no-repeat scroll 98% 6px;
- cursor:pointer;
- border-top:medium none;
- }
-a.boxMenu {
- background: transparent url("puce_down.png") 98% 6px no-repeat;
- display: block;
- padding: 1px 9px 1px 3px;
-}
-
-
-a.popupMenu {
- background: transparent url("puce_down_black.png") 2% 6px no-repeat;
- padding-left: 2em;
-}
-
-ul.boxListing ul li a:hover {
- background: #eeedd9 url("bullet_orange.png") 0% 6px no-repeat;
-}
-
-a.boxMenu:hover {
- background: #eeedd9 url("puce_down.png") 98% 6px no-repeat;
- cursor: pointer;
-}
-
-ul.boxListing a.boxBookmark {
- padding-left: 3px;
- background-image:none;
- background:#fff;
-}
-
-ul.boxListing ul li a {
- background: #fff url("bullet_orange.png") 0% 6px no-repeat;
- padding: 1px 3px 0px 10px;
-}
-
-div.searchBoxFrame div.boxContent {
- padding: 4px 4px 3px;
- background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
-}
-
div.shadow{
height: 14px;
background: url("shadow.gif") no-repeat top right;
@@ -462,7 +382,7 @@
margin-bottom: 0.5em;
}
-ul.sideBox li{
+ul.sideBox li {
list-style: none;
background: none;
padding: 0px 0px 1px 1px;
@@ -485,16 +405,176 @@
padding-right: 1em;
}
-input.rqlsubmit{
- background: #fffff8 url("go.png") 50% 50% no-repeat;
- width: 20px;
- height: 20px;
- margin: 0px;
+/* boxes */
+
+div.navboxes {
+ padding-top: 0.5em;
+}
+
+div.boxTitle {
+ overflow: hidden;
+ font-weight: bold;
+}
+
+div.boxTitle span {
+ padding: 0px 0.5em;
+ white-space: nowrap;
+}
+
+div.boxBody {
+ padding: 3px 3px;
+ border-top: none;
+ background-color: %(leftrightBoxBodyBgColor)s;
+}
+
+div.boxBody a {
+ color: %(leftrightBoxBodyColor)s;
+}
+
+div.boxBody a:hover {
+ text-decoration: none;
+ cursor: pointer;
+ background-color: %(leftrightBoxBodyHoverBgColor)s;
+}
+
+hr.boxSeparator {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+/* boxes contextual customization */
+
+.contextFreeBox div.boxTitle {
+ background: %(contextFreeBoxTitleBg)s;
+ color: %(contextFreeBoxTitleColor)s;
+}
+
+.contextualBox div.boxTitle {
+ background: %(contextualBoxTitleBg)s;
+ color: %(contextualBoxTitleColor)s;
+}
+
+.primaryRight div.boxTitle {
+ background: %(incontextBoxTitleBg)s;
+ color: %(incontextBoxTitleColor)s;
+}
+
+.primaryRight div.boxBody {
+ padding: 0.2em 5px;
+ background: %(incontextBoxBodyBgColor)s;
+}
+
+.primaryRight div.boxBody a {
+ color: %(incontextBoxBodyColor)s;
+}
+
+.primaryRight div.boxBody a:hover {
+ background-color: %(incontextBoxBodyHoverBgColor)s;
+}
+
+.primaryRight div.boxFooter {
+ margin-bottom: 1em;
+}
+
+#navColumnLeft div.boxFooter, #navColumnRight div.boxFooter{
+ height: 14px;
+ background: url("shadow.gif") no-repeat top right;
+}
+
+/* boxes lists and menus */
+
+ul.boxListing {
+ margin: 0;
+ padding: 0;
}
-input#norql{
- width:13em;
- margin-right: 2px;
+ul.boxListing ul {
+ padding: 1px 3px;
+}
+
+ul.boxListing a {
+ color: %(defaultColor)s;
+ padding: 1px 3px;
+ display: block; /* necessary to get links across all width available (see on mouse over) */
+}
+
+ul.boxListing a.action {
+ padding: 0;
+ display: inline;
+}
+
+ul.boxListing a.action + a{
+ display: inline;
+}
+
+ul.boxListing li {
+ margin: 0px;
+ padding: 0px;
+ background-image: none;
+}
+
+ul.boxListing ul li {
+ margin: 0px;
+ padding-left: 1em;
+}
+
+ul.boxListing ul li a {
+ padding-left: 10px;
+ background-image: url("bullet_orange.png");
+ background-repeat: no-repeat;
+ background-position: 0 6px;
+}
+
+ul.boxListing .selected {
+ color: %(aColor)s;
+ font-weight: bold;
+}
+
+ul.boxListing a.boxMenu:hover {
+ border-top: medium none;
+ background: %(leftrightBoxBodyHoverBgColor)s;
+}
+
+a.boxMenu,
+ul.boxListing a.boxMenu {
+ display: block;
+ padding: 1px 3px;
+ background: transparent %(bulletDownImg)s;
+}
+
+ul.boxListing a.boxMenu:hover {
+ border-top: medium none;
+ background: %(leftrightBoxBodyHoverBgColor)s %(bulletDownImg)s;
+}
+
+a.boxMenu:hover {
+ cursor: pointer;
+}
+
+a.popupMenu {
+ background: transparent url("puce_down_black.png") 2% 6px no-repeat;
+ padding-left: 2em;
+}
+
+
+/* custom boxes */
+
+.search_box div.boxBody {
+ padding: 4px 4px 3px;
+ background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
+}
+
+.bookmarks_box ul.boxListing div {
+ padding-bottom: 0.3em;
+}
+
+.download_box div.boxTitle {
+ background : #8fbc8f !important;
+}
+
+.download_box div.boxBody {
+ background : #eefed9;
+ vertical-align: center;
}
/* user actions menu */
@@ -514,20 +594,6 @@
padding-right: 2em;
}
-/* download box XXX move to its own file? */
-div.downloadBoxTitle{
- background : #8FBC8F;
- font-weight: bold;
-}
-
-div.downloadBox{
- font-weight: bold;
-}
-
-div.downloadBox div.sideBoxBody{
- background : #EEFED9;
-}
-
/**************/
/* navigation */
/**************/
@@ -770,6 +836,13 @@
margin-bottom: 0.2em; /* because vertical-align doesn't seems to have any effect */
}
+table.ajaxEditRelationTable{
+ margin-bottom: 0.5em;
+}
+table.ajaxEditRelationTable td.entity{
+ padding-left: 0.5em;
+}
+
/***************************************/
/* error view (views/management.py) */
/***************************************/
@@ -884,3 +957,12 @@
.releditForm {
display:none;
}
+
+/********************************/
+/* overwite other css here */
+/********************************/
+
+.ui-menu li.ui-menu-item {
+ /* remove background image (orange bullet) for autocomplete suggestions */
+ background-image: none;
+}
--- a/web/data/cubicweb.preferences.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.preferences.js Fri Mar 11 09:46:45 2011 +0100
@@ -1,8 +1,6 @@
/**
- * toggle visibility of an element by its id
- * & set current visibility status in a cookie
- * XXX whenever used outside of preferences, don't forget to
- * move me in a more appropriate place
+ * toggle visibility of an element by its id & set current visibility status in a cookie
+ *
*/
var prefsValues = {};
@@ -46,30 +44,27 @@
function validatePrefsForm(formid) {
clearPreviousMessages();
- clearPreviousErrors(formid);
+ _clearPreviousErrors(formid);
return validateForm(formid, null, submitSucces, submitFailure);
}
-function submitFailure(formid) {
- var form = jQuery('#' + formid);
- var dom = DIV({
- 'class': 'critical'
- },
- _("please correct errors below"));
- jQuery(form).find('div.formsg').empty().append(dom);
- // clearPreviousMessages()
- jQuery(form).find('span.error').next().focus();
+function submitFailure(result, formid, cbargs) {
+ var $form = jQuery('#' + formid);
+ var dom = DIV({'class': 'critical'}, _("please correct errors below"));
+ $form.find('div.formsg').empty().append(dom);
+ unfreezeFormButtons(formid);
+ var descr = result[1];
+ _displayValidationerrors(formid, descr[0], descr[1]);
+ $form.find('span.error').next().focus();
+ return false; // so handleFormValidationResponse doesn't try to display error
}
-function submitSucces(url, formid) {
- var form = jQuery('#' + formid);
- setCurrentValues(form);
- var dom = DIV({
- 'class': 'msg'
- },
- _("changes applied"));
- jQuery(form).find('div.formsg').empty().append(dom);
- jQuery(form).find('input').removeClass('changed');
+function submitSucces(result, formid, cbargs) {
+ var $form = jQuery('#' + formid);
+ setCurrentValues($form);
+ var dom = DIV({'class': 'msg'}, _("changes applied"));
+ $form.find('div.formsg').empty().append(dom);
+ $form.find('input').removeClass('changed');
checkValues(form, true);
return;
}
@@ -79,10 +74,6 @@
jQuery('div.formsg').empty();
}
-function clearPreviousErrors(formid) {
- jQuery('#err-value:' + formid).remove();
-}
-
function checkValues(form, success) {
var unfreezeButtons = false;
jQuery(form).find('select').each(function() {
@@ -101,7 +92,7 @@
if (!success) {
clearPreviousMessages();
}
- clearPreviousErrors(form.attr('id'));
+ _clearPreviousErrors(form.attr('id'));
freezeFormButtons(form.attr('id'));
}
}
--- a/web/data/cubicweb.python.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.python.js Fri Mar 11 09:46:45 2011 +0100
@@ -188,6 +188,16 @@
return this.replace(/^\s*(.*?)\s*$/, "$1");
};
+/**
+ * .. function:: String.prototype.rstrip()
+ *
+ * python-like rstrip method for js strings
+ */
+String.prototype.rstrip = function(str) {
+ if (!str) { str = '\s' ; }
+ return this.replace(new RegExp('^(.*?)' + str + '*$'), "$1");
+};
+
// ========= class factories ========= //
/**
@@ -248,9 +258,11 @@
* this is a js class factory. objects returned by this function behave
* more or less like a python class. The `class` function prototype is
* inspired by the python `type` builtin
- * Important notes :
- * -> methods are _STATICALLY_ attached when the class it created
- * -> multiple inheritance was never tested, which means it doesn't work ;-)
+ *
+ * .. Note::
+ *
+ * * methods are _STATICALLY_ attached when the class it created
+ * * multiple inheritance was never tested, which means it doesn't work ;-)
*/
function defclass(name, bases, classdict) {
var baseclasses = bases || [];
--- a/web/data/cubicweb.reledit.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.reledit.js Fri Mar 11 09:46:45 2011 +0100
@@ -52,7 +52,7 @@
return;
}
}
- jQuery('#'+params.divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, params, 'post');
+ jQuery('#'+params.divid+'-reledit').loadxhtml(JSON_BASE_URL, params, 'post');
jQuery(cw).trigger('reledit-reloaded', params);
},
@@ -68,7 +68,7 @@
pageid: pageid,
eid: eid, divid: divid, formid: formid,
reload: reload, vid: vid};
- var d = jQuery('#'+divid+'-reledit').parent().loadxhtml(JSON_BASE_URL, args, 'post');
+ var d = jQuery('#'+divid+'-reledit').loadxhtml(JSON_BASE_URL, args, 'post');
d.addCallback(function () {cw.reledit.showInlineEditionForm(divid);});
}
});
--- a/web/data/cubicweb.tableview.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.tableview.css Fri Mar 11 09:46:45 2011 +0100
@@ -6,7 +6,7 @@
font-weight: bold;
background: #ebe8d9 url("button.png") repeat-x;
padding: 0.3em;
- border-bottom: 1px solid %(actionBoxTitleBgColor)s;
+ border-bottom: 1px solid %(incontextBoxBodyBgColor)s;
text-align: left;
}
--- a/web/data/cubicweb.widgets.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/cubicweb.widgets.js Fri Mar 11 09:46:45 2011 +0100
@@ -56,67 +56,252 @@
return jQuery.get(url, data, callback, 'json');
}
+
+(function ($) {
+ var defaultSettings = {
+ initialvalue: '',
+ multiple: false,
+ mustMatch: false,
+ delay: 50,
+ limit: 50
+ };
+ function split(val) { return val.split( /\s*,\s*/ ); }
+ function extractLast(term) { return split(term).pop(); }
+ function allButLast(val) {
+ var terms = split(val);
+ terms.pop();
+ return terms;
+ }
+
+ var methods = {
+ __init__: function(suggestions, options) {
+ return this.each(function() {
+ // here, `this` refers to the DOM element (e.g. input) being wrapped
+ // by cwautomplete plugin
+ var instanceData = $(this).data('cwautocomplete');
+ if (instanceData) {
+ // already initialized
+ return;
+ }
+ var settings = $.extend({}, defaultSettings, options);
+ instanceData = {
+ initialvalue: settings.initialvalue,
+ userInput: this,
+ hiddenInput: null
+ };
+ var hiHandlers = methods.hiddenInputHandlers;
+ $(this).data('cwautocomplete', instanceData);
+ // in case of an existing value, the hidden input must be initialized even if
+ // the value is not changed
+ if (($(instanceData.userInput).attr('cubicweb:initialvalue') !== undefined) && !instanceData.hiddenInput){
+ hiHandlers.initializeHiddenInput(instanceData);
+ }
+ $.ui.autocomplete.prototype._search = methods.search;
+ if (settings.multiple) {
+ $.ui.autocomplete.filter = methods.multiple.makeFilter(this);
+ $(this).bind({
+ autocompleteselect: methods.multiple.select,
+ autocompletefocus: methods.multiple.focus,
+ keydown: methods.multiple.keydown
+ });
+ }
+ // XXX katia we dont need it if minLength == 0, but by setting minLength = 0
+ // we probably break the backward compatibility
+ $(this).bind('blur', methods.blur);
+ if ($.isArray(suggestions)) { // precomputed list of suggestions
+ settings.source = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions);
+ } else { // url to call each time something is typed
+ settings.source = function(request, response) {
+ var d = loadRemote(suggestions, {q: request.term, limit: settings.limit}, 'POST');
+ d.addCallback(function (suggestions) {
+ suggestions = hiHandlers.checkSuggestionsDataFormat(instanceData, suggestions);
+ response(suggestions);
+ if((suggestions.length) == 0){
+ methods.resetValues(instanceData);
+ }
+ });
+ };
+ }
+ $(this).autocomplete(settings);
+ if (settings.mustMatch) {
+ $(this).keypress(methods.ensureExactMatch);
+ }
+ });
+ },
+
+ multiple: {
+ focus: function() {
+ // prevent value inserted on focus
+ return false;
+ },
+ select: function(event, ui) {
+ var terms = allButLast(this.value);
+ // add the selected item
+ terms.push(ui.item.value);
+ // add placeholder to get the comma-and-space at the end
+ terms.push("");
+ this.value = terms.join( ", " );
+ return false;
+ },
+ keydown: function(evt) {
+ if ($(this).data('autocomplete').menu.active && evt.keyCode == $.ui.keyCode.TAB) {
+ evt.preventDefault();
+ }
+ },
+ makeFilter: function(userInput) {
+ return function(array, term) {
+ // remove already entered terms from suggestion list
+ array = cw.utils.difference(array, allButLast(userInput.value));
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
+ return $.grep( array, function(value) {
+ return matcher.test( value.label || value.value || value );
+ });
+ };
+ }
+ },
+ blur: function(evt){
+ var instanceData = $(this).data('cwautocomplete');
+ if($(instanceData.userInput).val().strip().length==0){
+ methods.resetValues(instanceData);
+ }
+ },
+ search: function(value) {
+ this.element.addClass("ui-autocomplete-loading");
+ if (this.options.multiple) {
+ value = extractLast(value);
+ }
+ this.source({term: value}, this.response);
+ },
+ ensureExactMatch: function(evt) {
+ var instanceData = $(this).data('cwautocomplete');
+ if (evt.keyCode == $.ui.keyCode.ENTER || evt.keyCode == $.ui.keyCode.TAB) {
+ var validChoices = $.map($('ul.ui-autocomplete li'),
+ function(li) {return $(li).text();});
+ if ($.inArray($(instanceData.userInput).val(), validChoices) == -1) {
+ $(instanceData.userInput).val('');
+ $(instanceData.hiddenInput).val(instanceData.initialvalue || '');
+ }
+ }
+ },
+ resetValues: function(instanceData){
+ $(instanceData.userInput).val('');
+ $(instanceData.hiddenInput).val('');
+ },
+
+
+ hiddenInputHandlers: {
+ /**
+ * `hiddenInputHandlers` defines all methods specific to handle the
+ * hidden input created along the standard text input.
+ * An hiddenInput is necessary when displayed suggestions are
+ * different from actual values to submit.
+ * Imagine an autocompletion widget to choose among a list of CWusers.
+ * Suggestions would be the list of logins, but actual values would
+ * be the corresponding eids.
+ * To handle such cases, suggestions list should be a list of JS objects
+ * with two `label` and `value` properties.
+ **/
+ suggestionSelected: function(evt, ui) {
+ var instanceData = $(this).data('cwautocomplete');
+ instanceData.hiddenInput.value = ui.item.value;
+ instanceData.value = ui.item.label;
+ return false; // stop propagation
+ },
+
+ suggestionFocusChanged: function(evt, ui) {
+ var instanceData = $(this).data('cwautocomplete');
+ instanceData.userInput.value = ui.item.label;
+ return false; // stop propagation
+ },
+
+ needsHiddenInput: function(suggestions) {
+ return suggestions[0].label !== undefined;
+ },
+ initializeHiddenInput: function(instanceData) {
+ var userInput = instanceData.userInput;
+ var hiddenInput = INPUT({
+ type: "hidden",
+ name: userInput.name,
+ // XXX katia : this must be handeled in .SuggestField widget, but
+ // it seems not to be used anymore
+ value: $(userInput).attr('cubicweb:initialvalue') || userInput.value
+ });
+ $(userInput).removeAttr('name').after(hiddenInput);
+ instanceData.hiddenInput = hiddenInput;
+ $(userInput).bind({
+ autocompleteselect: methods.hiddenInputHandlers.suggestionSelected,
+ autocompletefocus: methods.hiddenInputHandlers.suggestionFocusChanged
+ });
+ },
+
+ /*
+ * internal convenience function: old jquery plugin accepted to be fed
+ * with a list of couples (value, label). The new (jquery-ui) autocomplete
+ * plugin expects a list of objects with "value" and "label" properties.
+ *
+ * This function converts the old format to the new one.
+ */
+ checkSuggestionsDataFormat: function(instanceData, suggestions) {
+ // check for old (value, label) format
+ if ($.isArray(suggestions) && suggestions.length &&
+ $.isArray(suggestions[0])){
+ if (suggestions[0].length == 2) {
+ cw.log('[3.10] autocomplete init func should return {label,value} dicts instead of lists');
+ suggestions = $.map(suggestions, function(sugg) {
+ return {value: sugg[0], label: sugg[1]};
+ });
+ } else {
+ if(suggestions[0].length == 1){
+ suggestions = $.map(suggestions, function(sugg) {
+ return {value: sugg[0], label: sugg[0]};
+ });
+ }
+ }
+ }
+ var hiHandlers = methods.hiddenInputHandlers;
+ if (suggestions.length && hiHandlers.needsHiddenInput(suggestions)
+ && !instanceData.hiddenInput) {
+ hiHandlers.initializeHiddenInput(instanceData);
+ hiHandlers.fixUserInputInitialValue(instanceData, suggestions);
+ }
+ // otherwise, assume data shape is correct
+ return suggestions;
+ },
+
+ fixUserInputInitialValue: function(instanceData, suggestions) {
+ // called when the data is loaded to reset the correct displayed
+ // value in the visible input field (typically replacing an eid
+ // by a displayable value)
+ var curvalue = instanceData.userInput.value;
+ if (!curvalue) {
+ return;
+ }
+ for (var i=0, length=suggestions.length; i < length; i++) {
+ var sugg = suggestions[i];
+ if (sugg.value == curvalue) {
+ instanceData.userInput.value = sugg.label;
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ $.fn.cwautocomplete = function(data, options) {
+ return methods.__init__.apply(this, [data, options]);
+ };
+})(jQuery);
+
+
Widgets.SuggestField = defclass('SuggestField', null, {
__init__: function(node, options) {
- var multi = node.getAttribute('cubicweb:multi') || "no";
options = options || {};
+ var multi = node.getAttribute('cubicweb:multi');
options.multiple = (multi == "yes") ? true: false;
- var dataurl = node.getAttribute('cubicweb:dataurl');
- var method = postJSON;
- if (options.method == 'get') {
- method = function(url, data, callback) {
- // We can't rely on jQuery.getJSON because the server
- // might set the Content-Type's response header to 'text/plain'
- jQuery.get(url, data, function(response) {
- callback(cw.evalJSON(response));
- });
- };
- }
- var self = this; // closure
- method(dataurl, null, function(data) {
- // in case we received a list of couple, we assume that the first
- // element is the real value to be sent, and the second one is the
- // value to be displayed
- if (data.length && data[0].length == 2) {
- options.formatItem = function(row) {
- return row[1];
- };
- self.hideRealValue(node);
- self.setCurrentValue(node, data);
- }
- jQuery(node).autocomplete(data, options);
+ var d = loadRemote(node.getAttribute('cubicweb:dataurl'));
+ d.addCallback(function(data) {
+ $(node).cwautocomplete(data, options);
});
- },
-
- hideRealValue: function(node) {
- var hidden = INPUT({
- 'type': "hidden",
- 'name': node.name,
- 'value': node.value
- });
- node.parentNode.appendChild(hidden);
- // remove 'name' attribute from visible input so that it is not submitted
- // and set correct value in the corresponding hidden field
- jQuery(node).removeAttr('name').bind('result', function(_, row, _) {
- hidden.value = row[0];
- });
- },
-
- setCurrentValue: function(node, data) {
- // called when the data is loaded to reset the correct displayed
- // value in the visible input field (typically replacing an eid
- // by a displayable value)
- var curvalue = node.value;
- if (!node.value) {
- return;
- }
- for (var i = 0, length = data.length; i < length; i++) {
- var row = data[i];
- if (row[0] == curvalue) {
- node.value = row[1];
- return;
- }
- }
}
});
@@ -124,84 +309,35 @@
__init__: function(node) {
Widgets.SuggestField.__init__(this, node, {
- method: 'get'
+ method: 'get' // XXX
});
}
});
Widgets.RestrictedSuggestField = defclass('RestrictedSuggestField', [Widgets.SuggestField], {
-
__init__: function(node) {
Widgets.SuggestField.__init__(this, node, {
mustMatch: true
});
}
+});
-});
//remote version of RestrictedSuggestField
Widgets.LazySuggestField = defclass('LazySuggestField', [Widgets.SuggestField], {
__init__: function(node, options) {
var self = this;
- var multi = "no";
options = options || {};
- options.max = 50;
options.delay = 50;
- options.cacheLength = 0;
- options.mustMatch = true;
// multiple selection not supported yet (still need to formalize correctly
// initial values / display values)
var initialvalue = cw.evalJSON(node.getAttribute('cubicweb:initialvalue') || 'null');
if (!initialvalue) {
initialvalue = node.value;
}
- options = jQuery.extend({
- dataType: 'json',
- multiple: (multi == "yes") ? true: false,
- parse: this.parseResult
- },
- options);
- var dataurl = node.getAttribute('cubicweb:dataurl');
- // remove 'name' from original input and add the hidden one that will
- // store the actual value
- var hidden = INPUT({
- 'type': "hidden",
- 'name': node.name,
- 'value': initialvalue
- });
- node.parentNode.appendChild(hidden);
- jQuery(node).bind('result', {
- hinput: hidden,
- input: node
- },
- self.hideRealValue).removeAttr('name').autocomplete(dataurl, options);
- },
-
- hideRealValue: function(evt, data, value) {
- if (!value) {
- value = "";
- }
- evt.data.hinput.value = value;
- },
-
- /*
- * @param data: a list of couple (value, label) to fill the suggestion list,
- * (returned by CW through AJAX)
- */
- parseResult: function(data) {
- var parsed = [];
- for (var i = 0; i < data.length; i++) {
- var value = '' + data[i][0]; // a string is required later by jquery.autocomplete.js
- var label = data[i][1];
- parsed[parsed.length] = {
- data: [label],
- value: value,
- result: label
- };
- };
- return parsed;
+ options.initialvalue = initialvalue;
+ Widgets.SuggestField.__init__(this, node, options);
}
-
});
/**
@@ -406,3 +542,111 @@
}
}
};
+
+
+// InOutWidget This contains specific InOutnWidget javascript
+// IE things can not handle hide/show options on select, this cloned list solition (should propably have 2 widgets)
+
+(function ($) {
+ var defaultSettings = {
+ bindDblClick: true
+ };
+ var methods = {
+ __init__: function(fromSelect, toSelect, options) {
+ var settings = $.extend({}, defaultSettings, options);
+ var bindDblClick = settings['bindDblClick'];
+ var $fromNode = $(cw.jqNode(fromSelect));
+ var clonedSelect = $fromNode.clone();
+ var $toNode = $(cw.jqNode(toSelect));
+ var $addButton = $(this.find('.cwinoutadd')[0]);
+ var $removeButton = $(this.find('.cwinoutremove')[0]);
+ // bind buttons
+ var name = this.attr('id');
+ var instanceData = {'fromNode':fromSelect,
+ 'toNode':toSelect,
+ 'cloned':clonedSelect,
+ 'bindDblClick':bindDblClick,
+ 'name': name};
+ $addButton.bind('click', {'instanceData':instanceData}, methods.inOutWidgetAddValues);
+ $removeButton.bind('click', {'instanceData':instanceData}, methods.inOutWidgetRemoveValues);
+ if(bindDblClick){
+ $toNode.bind('dblclick', {'instanceData': instanceData}, methods.inOutWidgetRemoveValues);
+ }
+ methods.inOutWidgetRemplaceSelect($fromNode, $toNode, clonedSelect, bindDblClick, name);
+ },
+
+ inOutWidgetRemplaceSelect: function($fromNode, $toNode, clonedSelect, bindDblClick, name){
+ var $newSelect = clonedSelect.clone();
+ $toNode.find('option').each(function() {
+ $newSelect.find('$(this)[value='+$(this).val()+']').remove();
+ });
+ var fromparent = $fromNode.parent();
+ if (bindDblClick) {
+ //XXX jQuery live binding does not seem to work here
+ $newSelect.bind('dblclick', {'instanceData': {'fromNode':$fromNode.attr('id'),
+ 'toNode': $toNode.attr('id'),
+ 'cloned':clonedSelect,
+ 'bindDblClick':bindDblClick,
+ 'name': name}},
+ methods.inOutWidgetAddValues);
+ }
+ $fromNode.remove();
+ fromparent.append($newSelect);
+ },
+
+ inOutWidgetAddValues: function(event){
+ var $fromNode = $(cw.jqNode(event.data.instanceData.fromNode));
+ var $toNode = $(cw.jqNode(event.data.instanceData.toNode));
+ $fromNode.find('option:selected').each(function() {
+ var option = $(this);
+ var newoption = OPTION({'value':option.val()},
+ value=option.text());
+ $toNode.append(newoption);
+ var hiddenInput = INPUT({
+ type: "hidden", name: event.data.instanceData.name,
+ value:option.val()
+ });
+ $toNode.parent().append(hiddenInput);
+ });
+ methods.inOutWidgetRemplaceSelect($fromNode, $toNode, event.data.instanceData.cloned,
+ event.data.instanceData.bindDblClick,
+ event.data.instanceData.name);
+ // for ie 7 : ie does not resize correctly the select
+ if($.browser.msie && $.browser.version.substr(0,1) < 8){
+ var p = $toNode.parent();
+ var newtoNode = $toNode.clone();
+ if (event.data.instanceData.bindDblClick) {
+ newtoNode.bind('dblclick', {'fromNode': $fromNode.attr('id'),
+ 'toNode': $toNode.attr('id'),
+ 'cloned': event.data.instanceData.cloned,
+ 'bindDblClick': true,
+ 'name': event.data.instanceData.name},
+ methods.inOutWidgetRemoveValues);
+ }
+ $toNode.remove();
+ p.append(newtoNode);
+ }
+ },
+
+ inOutWidgetRemoveValues: function(event){
+ var $fromNode = $(cw.jqNode(event.data.instanceData.toNode));
+ var $toNode = $(cw.jqNode(event.data.instanceData.fromNode));
+ var name = event.data.instanceData.name.replace(':', '\\:');
+ $fromNode.find('option:selected').each(function(){
+ var option = $(this);
+ var newoption = OPTION({'value':option.val()},
+ value=option.text());
+ option.remove();
+ $fromNode.parent().find('input[name]='+ name).each(function() {
+ $(this).val()==option.val()?$(this).remove():null;
+ });
+ });
+ methods.inOutWidgetRemplaceSelect($toNode, $fromNode, event.data.instanceData.cloned,
+ event.data.instanceData.bindDblClick,
+ event.data.instanceData.name);
+ }
+ };
+ $.fn.cwinoutwidget = function(fromSelect, toSelect, options){
+ return methods.__init__.apply(this, [fromSelect, toSelect, options]);
+ };
+})(jQuery);
\ No newline at end of file
Binary file web/data/images/ui-icons_222222_256x240.png has changed
Binary file web/data/images/ui-icons_228ef1_256x240.png has changed
Binary file web/data/images/ui-icons_ef8c08_256x240.png has changed
Binary file web/data/images/ui-icons_ffd27a_256x240.png has changed
Binary file web/data/images/ui-icons_ffffff_256x240.png has changed
Binary file web/data/incontextBoxHeader.png has changed
--- a/web/data/jquery.autocomplete.css Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-.ac_results {
- padding: 0px;
- border: 1px solid black;
- background-color: white;
- overflow: hidden;
- z-index: 99999;
-}
-
-.ac_results ul {
- width: 100%;
- list-style-position: outside;
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-.ac_results li {
- margin: 0px;
- padding: 2px 5px;
- cursor: default;
- display: block;
- /*
- if width will be 100% horizontal scrollbar will apear
- when scroll mode will be used
- */
- /*width: 100%;*/
- font: menu;
- font-size: 12px;
- /*
- it is very important, if line-height not setted or setted
- in relative units scroll will be broken in firefox
- */
- line-height: 16px;
- overflow: hidden;
- background-image: none;
- padding: 0px 0px 1px 1px;
-}
-
-.ac_loading {
- background: white url('indicator.gif') right center no-repeat;
-}
-
-.ac_odd {
- background-color: #eee;
-}
-
-.ac_over {
- background-color: #0A246A;
- color: white;
-}
--- a/web/data/jquery.autocomplete.js Fri Dec 10 12:17:18 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-/*
- * jQuery Autocomplete plugin 1.1
- *
- * Copyright (c) 2010 Jörn Zaefferer
- *
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- * Revision: $Id: jquery.autocomplete.js 15 2010-08-22 10:30:27Z joern.zaefferer $
- */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){hasFocus=1;lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){var seperator=options.multipleSeparator.length;var cursorAt=$(input).selection().start;var wordAt,progress=0;$.each(words,function(i,word){progress+=word.length;if(cursorAt<=progress){wordAt=i;return false;}progress+=seperator;});words[wordAt]=v;v=words.join(options.multipleSeparator);}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value)return[""];if(!options.multiple)return[$.trim(value)];return $.map(value.split(options.multipleSeparator),function(word){return $.trim(value).length?$.trim(word):null;});}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);if(words.length==1)return words[0];var cursorAt=$(input).selection().start;if(cursorAt==value.length){words=trimWords(value)}else{words=trimWords(value.replace(value.substring(cursorAt),""));}return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$(input).selection(previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else{$input.val("");$input.trigger("result",null);}}});}};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(options.matchContains=="word"){i=s.toLowerCase().search("\\b"+sub.toLowerCase());}if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
-if(data[q]){return data[q];}else
-if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName.toUpperCase()!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.fn.selection=function(start,end){if(start!==undefined){return this.each(function(){if(this.createTextRange){var selRange=this.createTextRange();if(end===undefined||start==end){selRange.move("character",start);selRange.select();}else{selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}}else if(this.setSelectionRange){this.setSelectionRange(start,end);}else if(this.selectionStart){this.selectionStart=start;this.selectionEnd=end;}});}var field=this[0];if(field.createTextRange){var range=document.selection.createRange(),orig=field.value,teststring="<->",textLength=range.text.length;range.text=teststring;var caretAt=field.value.indexOf(teststring);field.value=orig;this.selection(caretAt,caretAt+textLength);return{start:caretAt,end:caretAt+textLength}}else if(field.selectionStart!==undefined){return{start:field.selectionStart,end:field.selectionEnd}}};})(jQuery);
\ No newline at end of file
--- a/web/data/jquery.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/jquery.js Fri Mar 11 09:46:45 2011 +0100
@@ -1,19 +1,6240 @@
-/*
- * jQuery JavaScript Library v1.3.2
+/*!
+ * jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
- * Copyright (c) 2010 John Resig
- * Dual licensed under the MIT and GPL licenses.
- * http://docs.jquery.com/License
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
*
- * Date: 2010-02-19 17:34:21 -0500 (Thu, 19 Feb 2010)
- * Revision: 6246
+ * Date: Sat Feb 13 22:33:48 2010 -0500
*/
-(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G<E;G++){L.call(K(this[G],H),this.length>1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){if(G&&/\S/.test(G)){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+"></"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/<tbody/i.test(S),N=!O.indexOf("<table")&&!R?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(G){var J=[],L=o(G);for(var K=0,H=L.length;K<H;K++){var I=(K>0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
+(function( window, undefined ) {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // (both of which we optimize for)
+ quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,
+
+ // Is it a simple selector
+ isSimple = /^.[^:#\[\.,]*$/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // Has the ready events already been bound?
+ readyBound = false,
+
+ // The functions to execute on DOM ready
+ readyList = [],
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwnProperty = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ indexOf = Array.prototype.indexOf;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = "body";
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ doc = (context ? context.ownerDocument || context : document);
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = buildFragment( [ match[1] ], [ doc ] );
+ selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ if ( elem ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $("TAG")
+ } else if ( !context && /^\w+$/.test( selector ) ) {
+ this.selector = selector;
+ this.context = document;
+ selector = document.getElementsByTagName( selector );
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return (context || rootjQuery).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return jQuery( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if (selector.selector !== undefined) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.4.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady ) {
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ } else if ( readyList ) {
+ // Add the function to the wait list
+ readyList.push( fn );
+ }
+
+ return this;
+ },
+
+ eq: function( i ) {
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, +i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || jQuery(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging object literal values or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
+ var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
+ : jQuery.isArray(copy) ? [] : {};
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 13 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( readyList ) {
+ // Execute all of them
+ var fn, i = 0;
+ while ( (fn = readyList[ i++ ]) ) {
+ fn.call( document, jQuery );
+ }
+
+ // Reset the list of functions
+ readyList = null;
+ }
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyBound ) {
+ return;
+ }
+
+ readyBound = true;
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ return jQuery.ready();
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent("onreadystatechange", DOMContentLoaded);
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return toString.call(obj) === "[object Function]";
+ },
+
+ isArray: function( obj ) {
+ return toString.call(obj) === "[object Array]";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) {
+ return false;
+ }
+
+ // Not own constructor property must be Object
+ if ( obj.constructor
+ && !hasOwnProperty.call(obj, "constructor")
+ && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwnProperty.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw msg;
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) {
+
+ // Try to use the native JSON parser first
+ return window.JSON && window.JSON.parse ?
+ window.JSON.parse( data ) :
+ (new Function("return " + data))();
+
+ } else {
+ jQuery.error( "Invalid JSON: " + data );
+ }
+ },
+
+ noop: function() {},
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test(data) ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+
+ if ( jQuery.support.scriptEval ) {
+ script.appendChild( document.createTextNode( data ) );
+ } else {
+ script.text = data;
+ }
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction(object);
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
+ }
+ }
+
+ return object;
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // The extra typeof function check is to prevent crashes
+ // in Safari 2 (See: #3039)
+ if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length, j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ if ( !inv !== !callback( elems[ i ], i ) ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var ret = [], value;
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ proxy: function( fn, proxy, thisObject ) {
+ if ( arguments.length === 2 ) {
+ if ( typeof proxy === "string" ) {
+ thisObject = fn;
+ fn = thisObject[ proxy ];
+ proxy = undefined;
+
+ } else if ( proxy && !jQuery.isFunction( proxy ) ) {
+ thisObject = proxy;
+ proxy = undefined;
+ }
+ }
+
+ if ( !proxy && fn ) {
+ proxy = function() {
+ return fn.apply( thisObject || this, arguments );
+ };
+ }
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ if ( fn ) {
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+ }
+
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ browser: {}
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+ jQuery.inArray = function( elem, array ) {
+ return indexOf.call( array, elem );
+ };
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+function evalScript( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+}
+
+// Mutifunctional method to get and set values to a collection
+// The value/s can be optionally by executed if its a function
+function access( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+}
+
+function now() {
+ return (new Date).getTime();
+}
+(function() {
+
+ jQuery.support = {};
+
+ var root = document.documentElement,
+ script = document.createElement("script"),
+ div = document.createElement("div"),
+ id = "script" + now();
+
+ div.style.display = "none";
+ div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ var all = div.getElementsByTagName("*"),
+ a = div.getElementsByTagName("a")[0];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return;
+ }
+
+ jQuery.support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: div.firstChild.nodeType === 3,
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText insted)
+ style: /red/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: a.getAttribute("href") === "/a",
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55$/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: div.getElementsByTagName("input")[0].value === "on",
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected,
+
+ parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null,
+
+ // Will be defined later
+ deleteExpando: true,
+ checkClone: false,
+ scriptEval: false,
+ noCloneEvent: true,
+ boxModel: null
+ };
+
+ script.type = "text/javascript";
+ try {
+ script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+ } catch(e) {}
+
+ root.insertBefore( script, root.firstChild );
+
+ // Make sure that the execution of code works by injecting a script
+ // tag with appendChild/createTextNode
+ // (IE doesn't support this, fails, and uses .text instead)
+ if ( window[ id ] ) {
+ jQuery.support.scriptEval = true;
+ delete window[ id ];
+ }
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete script.test;
+
+ } catch(e) {
+ jQuery.support.deleteExpando = false;
+ }
+
+ root.removeChild( script );
+
+ if ( div.attachEvent && div.fireEvent ) {
+ div.attachEvent("onclick", function click() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ jQuery.support.noCloneEvent = false;
+ div.detachEvent("onclick", click);
+ });
+ div.cloneNode(true).fireEvent("onclick");
+ }
+
+ div = document.createElement("div");
+ div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
+
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild( div.firstChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
+
+ // Figure out if the W3C box model works as expected
+ // document.body must exist before we can do this
+ jQuery(function() {
+ var div = document.createElement("div");
+ div.style.width = div.style.paddingLeft = "1px";
+
+ document.body.appendChild( div );
+ jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+ document.body.removeChild( div ).style.display = 'none';
+
+ div = null;
+ });
+
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+ var eventSupported = function( eventName ) {
+ var el = document.createElement("div");
+ eventName = "on" + eventName;
+
+ var isSupported = (eventName in el);
+ if ( !isSupported ) {
+ el.setAttribute(eventName, "return;");
+ isSupported = typeof el[eventName] === "function";
+ }
+ el = null;
+
+ return isSupported;
+ };
+
+ jQuery.support.submitBubbles = eventSupported("submit");
+ jQuery.support.changeBubbles = eventSupported("change");
+
+ // release memory in IE
+ root = script = div = all = a = null;
+})();
+
+jQuery.props = {
+ "for": "htmlFor",
+ "class": "className",
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ tabindex: "tabIndex",
+ usemap: "useMap",
+ frameborder: "frameBorder"
+};
+var expando = "jQuery" + now(), uuid = 0, windowData = {};
+
+jQuery.extend({
+ cache: {},
+
+ expando:expando,
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ "object": true,
+ "applet": true
+ },
+
+ data: function( elem, name, data ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ return;
+ }
+
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache, thisCache;
+
+ if ( !id && typeof name === "string" && data === undefined ) {
+ return null;
+ }
+
+ // Compute a unique ID for the element
+ if ( !id ) {
+ id = ++uuid;
+ }
+
+ // Avoid generating a new cache unless none exists and we
+ // want to manipulate it.
+ if ( typeof name === "object" ) {
+ elem[ expando ] = id;
+ thisCache = cache[ id ] = jQuery.extend(true, {}, name);
+
+ } else if ( !cache[ id ] ) {
+ elem[ expando ] = id;
+ cache[ id ] = {};
+ }
+
+ thisCache = cache[ id ];
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined ) {
+ thisCache[ name ] = data;
+ }
+
+ return typeof name === "string" ? thisCache[ name ] : thisCache;
+ },
+
+ removeData: function( elem, name ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ return;
+ }
+
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( thisCache ) {
+ // Remove the section of cache data
+ delete thisCache[ name ];
+
+ // If we've removed all the data, remove the element's cache
+ if ( jQuery.isEmptyObject(thisCache) ) {
+ jQuery.removeData( elem );
+ }
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ // Completely remove the data cache
+ delete cache[ id ];
+ }
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ if ( typeof key === "undefined" && this.length ) {
+ return jQuery.data( this[0] );
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ }
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else {
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() {
+ jQuery.data( this, key, value );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ if ( !elem ) {
+ return;
+ }
+
+ type = (type || "fx") + "queue";
+ var q = jQuery.data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( !data ) {
+ return q || [];
+ }
+
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery.data( elem, type, jQuery.makeArray(data) );
+
+ } else {
+ q.push( data );
+ }
+
+ return q;
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ), fn = queue.shift();
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift("inprogress");
+ }
+
+ fn.call(elem, function() {
+ jQuery.dequeue(elem, type);
+ });
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function( i, elem ) {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function() {
+ var elem = this;
+ setTimeout(function() {
+ jQuery.dequeue( elem, type );
+ }, time );
+ });
+ },
+
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ }
+});
+var rclass = /[\n\t]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rspecialurl = /href|src|style/,
+ rtype = /(button|input)/i,
+ rfocusable = /(button|input|object|select|textarea)/i,
+ rclickable = /^(a|area)$/i,
+ rradiocheck = /radio|checkbox/;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name, fn ) {
+ return this.each(function(){
+ jQuery.attr( this, name, "" );
+ if ( this.nodeType === 1 ) {
+ this.removeAttribute( name );
+ }
+ });
+ },
+
+ addClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.addClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ var classNames = (value || "").split( rspace );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className ) {
+ elem.className = value;
+
+ } else {
+ var className = " " + elem.className + " ", setClass = elem.className;
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
+ setClass += " " + classNames[c];
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.removeClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ var classNames = (value || "").split(rspace);
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ var className = (" " + elem.className + " ").replace(rclass, " ");
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[c] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value, isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className, i = 0, self = jQuery(this),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery.data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ";
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ if ( value === undefined ) {
+ var elem = this[0];
+
+ if ( elem ) {
+ if ( jQuery.nodeName( elem, "option" ) ) {
+ return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+ }
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery(option).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ }
+
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+
+
+ // Everything else, we just grab the value
+ return (elem.value || "").replace(rreturn, "");
+
+ }
+
+ return undefined;
+ }
+
+ var isFunction = jQuery.isFunction(value);
+
+ return this.each(function(i) {
+ var self = jQuery(this), val = value;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call(this, i, self.val());
+ }
+
+ // Typecast each time if the value is a Function and the appended
+ // value is therefore different each time.
+ if ( typeof val === "number" ) {
+ val += "";
+ }
+
+ if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
+ this.checked = jQuery.inArray( self.val(), val ) >= 0;
+
+ } else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(val);
+
+ jQuery( "option", this ).each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ this.selectedIndex = -1;
+ }
+
+ } else {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ // don't set attributes on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery(elem)[name](value);
+ }
+
+ var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ if ( elem.nodeType === 1 ) {
+ // These attributes require special treatment
+ var special = rspecialurl.test( name );
+
+ // Safari mis-reports the default selected property of an option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name === "selected" && !jQuery.support.optSelected ) {
+ var parent = elem.parentNode;
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ }
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ }
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
+ return elem.getAttributeNode( name ).nodeValue;
+ }
+
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ if ( name === "tabIndex" ) {
+ var attributeNode = elem.getAttributeNode( "tabIndex" );
+
+ return attributeNode && attributeNode.specified ?
+ attributeNode.value :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+
+ return elem[ name ];
+ }
+
+ if ( !jQuery.support.style && notxml && name === "style" ) {
+ if ( set ) {
+ elem.style.cssText = "" + value;
+ }
+
+ return elem.style.cssText;
+ }
+
+ if ( set ) {
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+ }
+
+ var attr = !jQuery.support.hrefNormalized && notxml && special ?
+ // Some attributes require a special call on IE
+ elem.getAttribute( name, 2 ) :
+ elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+ // Using attr for specific style information is now deprecated. Use style instead.
+ return jQuery.style( elem, name, value );
+ }
+});
+var rnamespaces = /\.(.*)$/,
+ fcleanup = function( nm ) {
+ return nm.replace(/[^\w\s\.\|`]/g, function( ch ) {
+ return "\\" + ch;
+ });
+ };
+
/*
- * Sizzle CSS Selector Engine - v0.9.3
- * Copyright 2010, The Dojo Foundation
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function( elem, types, handler, data ) {
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
+ elem = window;
+ }
+
+ var handleObjIn, handleObj;
+
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure
+ var elemData = jQuery.data( elem );
+
+ // If no elemData is found then we must be trying to bind to one of the
+ // banned noData elements
+ if ( !elemData ) {
+ return;
+ }
+
+ var events = elemData.events = elemData.events || {},
+ eventHandle = elemData.handle, eventHandle;
+
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function() {
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+ jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ }
+
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native events in IE.
+ eventHandle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ var type, i = 0, namespaces;
+
+ while ( (type = types[ i++ ]) ) {
+ handleObj = handleObjIn ?
+ jQuery.extend({}, handleObjIn) :
+ { handler: handler, data: data };
+
+ // Namespaced event handlers
+ if ( type.indexOf(".") > -1 ) {
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+ } else {
+ namespaces = [];
+ handleObj.namespace = "";
+ }
+
+ handleObj.type = type;
+ handleObj.guid = handler.guid;
+
+ // Get the current list of functions bound to this event
+ var handlers = events[ type ],
+ special = jQuery.event.special[ type ] || {};
+
+ // Init the event handler queue
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers.push( handleObj );
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, pos ) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+ elemData = jQuery.data( elem ),
+ events = elemData && elemData.events;
+
+ if ( !elemData || !events ) {
+ return;
+ }
+
+ // types is actually an event object here
+ if ( types && types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Unbind all events for the element
+ if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+ types = types || "";
+
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types );
+ }
+
+ return;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ while ( (type = types[ i++ ]) ) {
+ origType = type;
+ handleObj = null;
+ all = type.indexOf(".") < 0;
+ namespaces = [];
+
+ if ( !all ) {
+ // Namespaced event handlers
+ namespaces = type.split(".");
+ type = namespaces.shift();
+
+ namespace = new RegExp("(^|\\.)" +
+ jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)")
+ }
+
+ eventType = events[ type ];
+
+ if ( !eventType ) {
+ continue;
+ }
+
+ if ( !handler ) {
+ for ( var j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ jQuery.event.remove( elem, origType, handleObj.handler, j );
+ eventType.splice( j--, 1 );
+ }
+ }
+
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+
+ for ( var j = pos || 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( handler.guid === handleObj.guid ) {
+ // remove the given handler for the given type
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ if ( pos == null ) {
+ eventType.splice( j--, 1 );
+ }
+
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+
+ if ( pos != null ) {
+ break;
+ }
+ }
+ }
+
+ // remove generic event handler if no more handlers exist
+ if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ removeEvent( elem, type, elemData.handle );
+ }
+
+ ret = null;
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ var handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ delete elemData.events;
+ delete elemData.handle;
+
+ if ( jQuery.isEmptyObject( elemData ) ) {
+ jQuery.removeData( elem );
+ }
+ }
+ },
+
+ // bubbling is internal
+ trigger: function( event, data, elem /*, bubbling */ ) {
+ // Event object or event type
+ var type = event.type || event,
+ bubbling = arguments[3];
+
+ if ( !bubbling ) {
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[expando] ? event :
+ // Object literal
+ jQuery.extend( jQuery.Event(type), event ) :
+ // Just the event type (string)
+ jQuery.Event(type);
+
+ if ( type.indexOf("!") >= 0 ) {
+ event.type = type = type.slice(0, -1);
+ event.exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Don't bubble custom events when global (to avoid too much overhead)
+ event.stopPropagation();
+
+ // Only trigger if we've ever bound an event for it
+ if ( jQuery.event.global[ type ] ) {
+ jQuery.each( jQuery.cache, function() {
+ if ( this.events && this.events[type] ) {
+ jQuery.event.trigger( event, data, this.handle.elem );
+ }
+ });
+ }
+ }
+
+ // Handle triggering a single element
+
+ // don't do events on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ // Clean up in case it is reused
+ event.result = undefined;
+ event.target = elem;
+
+ // Clone the incoming data, if any
+ data = jQuery.makeArray( data );
+ data.unshift( event );
+ }
+
+ event.currentTarget = elem;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) {
+ handle.apply( elem, data );
+ }
+
+ var parent = elem.parentNode || elem.ownerDocument;
+
+ // Trigger an inline bound script
+ try {
+ if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+ if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
+ event.result = false;
+ }
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (e) {}
+
+ if ( !event.isPropagationStopped() && parent ) {
+ jQuery.event.trigger( event, data, parent, true );
+
+ } else if ( !event.isDefaultPrevented() ) {
+ var target = event.target, old,
+ isClick = jQuery.nodeName(target, "a") && type === "click",
+ special = jQuery.event.special[ type ] || {};
+
+ if ( (!special._default || special._default.call( elem, event ) === false) &&
+ !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
+
+ try {
+ if ( target[ type ] ) {
+ // Make sure that we don't accidentally re-trigger the onFOO events
+ old = target[ "on" + type ];
+
+ if ( old ) {
+ target[ "on" + type ] = null;
+ }
+
+ jQuery.event.triggered = true;
+ target[ type ]();
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (e) {}
+
+ if ( old ) {
+ target[ "on" + type ] = old;
+ }
+
+ jQuery.event.triggered = false;
+ }
+ }
+ },
+
+ handle: function( event ) {
+ var all, handlers, namespaces, namespace, events;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+ event.currentTarget = this;
+
+ // Namespaced event handlers
+ all = event.type.indexOf(".") < 0 && !event.exclusive;
+
+ if ( !all ) {
+ namespaces = event.type.split(".");
+ event.type = namespaces.shift();
+ namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ var events = jQuery.data(this, "events"), handlers = events[ event.type ];
+
+ if ( events && handlers ) {
+ // Clone the handlers to prevent manipulation
+ handlers = handlers.slice(0);
+
+ for ( var j = 0, l = handlers.length; j < l; j++ ) {
+ var handleObj = handlers[ j ];
+
+ // Filter the functions by class
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handleObj.handler;
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ var ret = handleObj.handler.apply( this, arguments );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+ fix: function( event ) {
+ if ( event[ expando ] ) {
+ return event;
+ }
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = jQuery.Event( originalEvent );
+
+ for ( var i = this.props.length, prop; i; ) {
+ prop = this.props[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary
+ if ( !event.target ) {
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+ }
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement ) {
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+ }
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
+ event.which = event.charCode || event.keyCode;
+ }
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button !== undefined ) {
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+ }
+
+ return event;
+ },
+
+ // Deprecated, use jQuery.guid instead
+ guid: 1E8,
+
+ // Deprecated, use jQuery.proxy instead
+ proxy: jQuery.proxy,
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady,
+ teardown: jQuery.noop
+ },
+
+ live: {
+ add: function( handleObj ) {
+ jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) );
+ },
+
+ remove: function( handleObj ) {
+ var remove = true,
+ type = handleObj.origType.replace(rnamespaces, "");
+
+ jQuery.each( jQuery.data(this, "events").live || [], function() {
+ if ( type === this.origType.replace(rnamespaces, "") ) {
+ remove = false;
+ return false;
+ }
+ });
+
+ if ( remove ) {
+ jQuery.event.remove( this, handleObj.origType, liveHandler );
+ }
+ }
+
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( this.setInterval ) {
+ this.onbeforeunload = eventHandle;
+ }
+
+ return false;
+ },
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ }
+};
+
+var removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ elem.removeEventListener( type, handle, false );
+ } :
+ function( elem, type, handle ) {
+ elem.detachEvent( "on" + type, handle );
+ };
+
+jQuery.Event = function( src ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !this.preventDefault ) {
+ return new jQuery.Event( src );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // timeStamp is buggy for some events on Firefox(#3843)
+ // So we won't rely on the native value
+ this.timeStamp = now();
+
+ // Mark it as fixed
+ this[ expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+ }
+ // otherwise set the returnValue property of the original event to false (IE)
+ e.returnValue = false;
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+
+ // Firefox sometimes assigns relatedTarget a XUL element
+ // which we cannot access the parentNode property of
+ try {
+ // Traverse up the tree
+ while ( parent && parent !== this ) {
+ parent = parent.parentNode;
+ }
+
+ if ( parent !== this ) {
+ // set the correct event type
+ event.type = event.data;
+
+ // handle event if we actually just moused on to a non sub-element
+ jQuery.event.handle.apply( this, arguments );
+ }
+
+ // assuming we've left the element since we most likely mousedover a xul element
+ } catch(e) { }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+ event.type = event.data;
+ jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ setup: function( data ) {
+ jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+ },
+ teardown: function( data ) {
+ jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+ }
+ };
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function( data, namespaces ) {
+ if ( this.nodeName.toLowerCase() !== "form" ) {
+ jQuery.event.add(this, "click.specialSubmit", function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+ return trigger( "submit", this, arguments );
+ }
+ });
+
+ jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+ return trigger( "submit", this, arguments );
+ }
+ });
+
+ } else {
+ return false;
+ }
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialSubmit" );
+ }
+ };
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+ var formElems = /textarea|input|select/i,
+
+ changeFilters,
+
+ getVal = function( elem ) {
+ var type = elem.type, val = elem.value;
+
+ if ( type === "radio" || type === "checkbox" ) {
+ val = elem.checked;
+
+ } else if ( type === "select-multiple" ) {
+ val = elem.selectedIndex > -1 ?
+ jQuery.map( elem.options, function( elem ) {
+ return elem.selected;
+ }).join("-") :
+ "";
+
+ } else if ( elem.nodeName.toLowerCase() === "select" ) {
+ val = elem.selectedIndex;
+ }
+
+ return val;
+ },
+
+ testChange = function testChange( e ) {
+ var elem = e.target, data, val;
+
+ if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
+ return;
+ }
+
+ data = jQuery.data( elem, "_change_data" );
+ val = getVal(elem);
+
+ // the current data will be also retrieved by beforeactivate
+ if ( e.type !== "focusout" || elem.type !== "radio" ) {
+ jQuery.data( elem, "_change_data", val );
+ }
+
+ if ( data === undefined || val === data ) {
+ return;
+ }
+
+ if ( data != null || val ) {
+ e.type = "change";
+ return jQuery.event.trigger( e, arguments[1], elem );
+ }
+ };
+
+ jQuery.event.special.change = {
+ filters: {
+ focusout: testChange,
+
+ click: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+ return testChange.call( this, e );
+ }
+ },
+
+ // Change has to be called before submit
+ // Keydown will be called before keypress, which is used in submit-event delegation
+ keydown: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+ (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+ type === "select-multiple" ) {
+ return testChange.call( this, e );
+ }
+ },
+
+ // Beforeactivate happens also before the previous element is blurred
+ // with this event you can't trigger a change event, but you can store
+ // information/focus[in] is not needed anymore
+ beforeactivate: function( e ) {
+ var elem = e.target;
+ jQuery.data( elem, "_change_data", getVal(elem) );
+ }
+ },
+
+ setup: function( data, namespaces ) {
+ if ( this.type === "file" ) {
+ return false;
+ }
+
+ for ( var type in changeFilters ) {
+ jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+ }
+
+ return formElems.test( this.nodeName );
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialChange" );
+
+ return formElems.test( this.nodeName );
+ }
+ };
+
+ changeFilters = jQuery.event.special.change.filters;
+}
+
+function trigger( type, elem, args ) {
+ args[0].type = type;
+ return jQuery.event.handle.apply( elem, args );
+}
+
+// Create "bubbling" focus and blur events
+if ( document.addEventListener ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ this.addEventListener( orig, handler, true );
+ },
+ teardown: function() {
+ this.removeEventListener( orig, handler, true );
+ }
+ };
+
+ function handler( e ) {
+ e = jQuery.event.fix( e );
+ e.type = fix;
+ return jQuery.event.handle.call( this, e );
+ }
+ });
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+ jQuery.fn[ name ] = function( type, data, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" ) {
+ for ( var key in type ) {
+ this[ name ](key, data, type[key], fn);
+ }
+ return this;
+ }
+
+ if ( jQuery.isFunction( data ) ) {
+ fn = data;
+ data = undefined;
+ }
+
+ var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
+ jQuery( this ).unbind( event, handler );
+ return fn.apply( this, arguments );
+ }) : fn;
+
+ if ( type === "unload" && name !== "one" ) {
+ this.one( type, data, fn );
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.add( this[i], type, handler, data );
+ }
+ }
+
+ return this;
+ };
+});
+
+jQuery.fn.extend({
+ unbind: function( type, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" && !type.preventDefault ) {
+ for ( var key in type ) {
+ this.unbind(key, type[key]);
+ }
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.remove( this[i], type, fn );
+ }
+ }
+
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.live( types, data, fn, selector );
+ },
+
+ undelegate: function( selector, types, fn ) {
+ if ( arguments.length === 0 ) {
+ return this.unbind( "live" );
+
+ } else {
+ return this.die( types, null, fn, selector );
+ }
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ var event = jQuery.Event( type );
+ event.preventDefault();
+ event.stopPropagation();
+ jQuery.event.trigger( event, data, this[0] );
+ return event.result;
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while ( i < args.length ) {
+ jQuery.proxy( fn, args[ i++ ] );
+ }
+
+ return this.click( jQuery.proxy( fn, function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ }));
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+var liveMap = {
+ focus: "focusin",
+ blur: "focusout",
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+ jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+ var type, i = 0, match, namespaces, preType,
+ selector = origSelector || this.selector,
+ context = origSelector ? this : jQuery( this.context );
+
+ if ( jQuery.isFunction( data ) ) {
+ fn = data;
+ data = undefined;
+ }
+
+ types = (types || "").split(" ");
+
+ while ( (type = types[ i++ ]) != null ) {
+ match = rnamespaces.exec( type );
+ namespaces = "";
+
+ if ( match ) {
+ namespaces = match[0];
+ type = type.replace( rnamespaces, "" );
+ }
+
+ if ( type === "hover" ) {
+ types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+ continue;
+ }
+
+ preType = type;
+
+ if ( type === "focus" || type === "blur" ) {
+ types.push( liveMap[ type ] + namespaces );
+ type = type + namespaces;
+
+ } else {
+ type = (liveMap[ type ] || type) + namespaces;
+ }
+
+ if ( name === "live" ) {
+ // bind live handler
+ context.each(function(){
+ jQuery.event.add( this, liveConvert( type, selector ),
+ { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+ });
+
+ } else {
+ // unbind live handler
+ context.unbind( liveConvert( type, selector ), fn );
+ }
+ }
+
+ return this;
+ }
+});
+
+function liveHandler( event ) {
+ var stop, elems = [], selectors = [], args = arguments,
+ related, match, handleObj, elem, j, i, l, data,
+ events = jQuery.data( this, "events" );
+
+ // Make sure we avoid non-left-click bubbling in Firefox (#3861)
+ if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) {
+ return;
+ }
+
+ event.liveFired = this;
+
+ var live = events.live.slice(0);
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+ selectors.push( handleObj.selector );
+
+ } else {
+ live.splice( j--, 1 );
+ }
+ }
+
+ match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+ for ( i = 0, l = match.length; i < l; i++ ) {
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( match[i].selector === handleObj.selector ) {
+ elem = match[i].elem;
+ related = null;
+
+ // Those two events require additional checking
+ if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+ related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+ }
+
+ if ( !related || related !== elem ) {
+ elems.push({ elem: elem, handleObj: handleObj });
+ }
+ }
+ }
+ }
+
+ for ( i = 0, l = elems.length; i < l; i++ ) {
+ match = elems[i];
+ event.currentTarget = match.elem;
+ event.data = match.handleObj.data;
+ event.handleObj = match.handleObj;
+
+ if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) {
+ stop = false;
+ break;
+ }
+ }
+
+ return stop;
+}
+
+function liveConvert( type, selector ) {
+ return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( fn ) {
+ return fn ? this.bind( name, fn ) : this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+});
+
+// Prevent memory leaks in IE
+// Window isn't included so as not to unbind existing unload events
+// More info:
+// - http://isaacschlueter.com/2006/10/msie-memory-leaks/
+if ( window.attachEvent && !window.addEventListener ) {
+ window.attachEvent("onunload", function() {
+ for ( var id in jQuery.cache ) {
+ if ( jQuery.cache[ id ].handle ) {
+ // Try/Catch is to handle iframes being unloaded, see #4280
+ try {
+ jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+ } catch(e) {}
+ }
+ }
+ });
+}
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
* More information: http://sizzlejs.com/
*/
-(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML=' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})();
\ No newline at end of file
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ var filter = Expr.filter[ type ], found, item, left = match[1];
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = part.toLowerCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = part.toLowerCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = part.toLowerCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ return match[1].toLowerCase();
+ },
+ CHILD: function(match){
+ if ( match[1] === "nth" ) {
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 === i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ Sizzle.error( "Syntax error, unrecognized expression: " + name );
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ if ( type === "first" ) {
+ return true;
+ }
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first === 0 ) {
+ return diff === 0;
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){
+ return "\\" + (num - 0 + 1);
+ }));
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.sourceIndex ? -1 : 1;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.ownerDocument ? -1 : 1;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+function getText( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+ })();
+}
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+var contains = document.compareDocumentPosition ? function(a, b){
+ return !!(a.compareDocumentPosition(b) & 16);
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = getText;
+jQuery.isXMLDoc = isXML;
+jQuery.contains = contains;
+
+return;
+
+window.Sizzle = Sizzle;
+
+})();
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ slice = Array.prototype.slice;
+
+// Implement the identical functionality for filter and not
+var winnow = function( elements, qualifier, keep ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return (elem === qualifier) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+ });
+};
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var ret = this.pushStack( "", "find", selector ), length = 0;
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( var n = length; n < ret.length; n++ ) {
+ for ( var r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.filter( selector, this ).length > 0;
+ },
+
+ closest: function( selectors, context ) {
+ if ( jQuery.isArray( selectors ) ) {
+ var ret = [], cur = this[0], match, matches = {}, selector;
+
+ if ( cur && selectors.length ) {
+ for ( var i = 0, l = selectors.length; i < l; i++ ) {
+ selector = selectors[i];
+
+ if ( !matches[selector] ) {
+ matches[selector] = jQuery.expr.match.POS.test( selector ) ?
+ jQuery( selector, context || this.context ) :
+ selector;
+ }
+ }
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( selector in matches ) {
+ match = matches[selector];
+
+ if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
+ ret.push({ selector: selector, elem: cur });
+ delete matches[selector];
+ }
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ return ret;
+ }
+
+ var pos = jQuery.expr.match.POS.test( selectors ) ?
+ jQuery( selectors, context || this.context ) : null;
+
+ return this.map(function( i, cur ) {
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) {
+ return cur;
+ }
+ cur = cur.parentNode;
+ }
+ return null;
+ });
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ if ( !elem || typeof elem === "string" ) {
+ return jQuery.inArray( this[0],
+ // If it receives a string, the selector is used
+ // If it receives nothing, the siblings are used
+ elem ? jQuery( elem ) : this.parent().children() );
+ }
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context || this.context ) :
+ jQuery.makeArray( selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call(arguments).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [], cur = elem[dir];
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g,
+ rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnocache = /<script|<object|<embed|<option|<style/i,
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5)
+ fcloseTag = function( all, front, tag ) {
+ return rselfClosing.test( tag ) ?
+ all :
+ front + "></" + tag + ">";
+ },
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ };
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( text ) {
+ if ( jQuery.isFunction(text) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.text( text.call(this, i, self.text()) );
+ });
+ }
+
+ if ( typeof text !== "object" && text !== undefined ) {
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+ }
+
+ return jQuery.text( this );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append(this);
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ), contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function() {
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery(arguments[0]);
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery(arguments[0]).toArray() );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function() {
+ if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var html = this.outerHTML, ownerDocument = this.ownerDocument;
+ if ( !html ) {
+ var div = ownerDocument.createElement("div");
+ div.appendChild( this.cloneNode(true) );
+ html = div.innerHTML;
+ }
+
+ return jQuery.clean([html.replace(rinlinejQuery, "")
+ // Handle the case in IE 8 where action=/test/> self-closes a tag
+ .replace(/=([^="'>\s]+\/)>/g, '="$1">')
+ .replace(rleadingWhitespace, "")], ownerDocument)[0];
+ } else {
+ return this.cloneNode(true);
+ }
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true ) {
+ cloneCopyEvent( this, ret );
+ cloneCopyEvent( this.find("*"), ret.find("*") );
+ }
+
+ // Return the cloned set
+ return ret;
+ },
+
+ html: function( value ) {
+ if ( value === undefined ) {
+ return this[0] && this[0].nodeType === 1 ?
+ this[0].innerHTML.replace(rinlinejQuery, "") :
+ null;
+
+ // See if we can take a shortcut and just use innerHTML
+ } else if ( typeof value === "string" && !rnocache.test( value ) &&
+ (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+ value = value.replace(rxhtmlTag, fcloseTag);
+
+ try {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( this[i].nodeType === 1 ) {
+ jQuery.cleanData( this[i].getElementsByTagName("*") );
+ this[i].innerHTML = value;
+ }
+ }
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {
+ this.empty().append( value );
+ }
+
+ } else if ( jQuery.isFunction( value ) ) {
+ this.each(function(i){
+ var self = jQuery(this), old = self.html();
+ self.empty().append(function(){
+ return value.call( this, i, old );
+ });
+ });
+
+ } else {
+ this.empty().append( value );
+ }
+
+ return this;
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery(value).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling, parent = this.parentNode;
+
+ jQuery(this).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, value = args[0], scripts = [], fragment, parent;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ i > 0 || results.cacheable || this.length > 1 ?
+ fragment.cloneNode(true) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, evalScript );
+ }
+ }
+
+ return this;
+
+ function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+ }
+ }
+});
+
+function cloneCopyEvent(orig, ret) {
+ var i = 0;
+
+ ret.each(function() {
+ if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) {
+ return;
+ }
+
+ var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( var type in events ) {
+ for ( var handler in events[ type ] ) {
+ jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+ }
+ }
+ }
+ });
+}
+
+function buildFragment( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults,
+ doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
+
+ // Only cache "small" (1/2 KB) strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+ !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+ cacheable = true;
+ cacheresults = jQuery.fragments[ args[0] ];
+ if ( cacheresults ) {
+ if ( cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+}
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [], insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+jQuery.extend({
+ clean: function( elems, context, fragment, scripts ) {
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ var ret = [];
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" && !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+
+ } else if ( typeof elem === "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, fcloseTag);
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ for ( var i = 0; ret[i]; i++ ) {
+ if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+ } else {
+ if ( ret[i].nodeType === 1 ) {
+ ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+ }
+ fragment.appendChild( ret[i] );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id, cache = jQuery.cache,
+ special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ];
+
+ if ( data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ } else {
+ removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+// exclude the following css properties to add px
+var rexclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ ralpha = /alpha\([^)]*\)/,
+ ropacity = /opacity=([^)]*)/,
+ rfloat = /float/i,
+ rdashAlpha = /-([a-z])/ig,
+ rupper = /([A-Z])/g,
+ rnumpx = /^-?\d+(?:px)?$/i,
+ rnum = /^-?\d/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display:"block" },
+ cssWidth = [ "Left", "Right" ],
+ cssHeight = [ "Top", "Bottom" ],
+
+ // cache check for defaultView.getComputedStyle
+ getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
+ // normalize float css property
+ styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat",
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+jQuery.fn.css = function( name, value ) {
+ return access( this, name, value, true, function( elem, name, value ) {
+ if ( value === undefined ) {
+ return jQuery.curCSS( elem, name );
+ }
+
+ if ( typeof value === "number" && !rexclude.test(name) ) {
+ value += "px";
+ }
+
+ jQuery.style( elem, name, value );
+ });
+};
+
+jQuery.extend({
+ style: function( elem, name, value ) {
+ // don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ // ignore negative width and height values #1599
+ if ( (name === "width" || name === "height") && parseFloat(value) < 0 ) {
+ value = undefined;
+ }
+
+ var style = elem.style || elem, set = value !== undefined;
+
+ // IE uses filters for opacity
+ if ( !jQuery.support.opacity && name === "opacity" ) {
+ if ( set ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ var opacity = parseInt( value, 10 ) + "" === "NaN" ? "" : "alpha(opacity=" + value * 100 + ")";
+ var filter = style.filter || jQuery.curCSS( elem, "filter" ) || "";
+ style.filter = ralpha.test(filter) ? filter.replace(ralpha, opacity) : opacity;
+ }
+
+ return style.filter && style.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( ropacity.exec(style.filter)[1] ) / 100) + "":
+ "";
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( rfloat.test( name ) ) {
+ name = styleFloat;
+ }
+
+ name = name.replace(rdashAlpha, fcamelCase);
+
+ if ( set ) {
+ style[ name ] = value;
+ }
+
+ return style[ name ];
+ },
+
+ css: function( elem, name, force, extra ) {
+ if ( name === "width" || name === "height" ) {
+ var val, props = cssShow, which = name === "width" ? cssWidth : cssHeight;
+
+ function getWH() {
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
+
+ if ( extra === "border" ) {
+ return;
+ }
+
+ jQuery.each( which, function() {
+ if ( !extra ) {
+ val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ }
+
+ if ( extra === "margin" ) {
+ val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
+ } else {
+ val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ }
+ });
+ }
+
+ if ( elem.offsetWidth !== 0 ) {
+ getWH();
+ } else {
+ jQuery.swap( elem, props, getWH );
+ }
+
+ return Math.max(0, Math.round(val));
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style, filter;
+
+ // IE uses filters for opacity
+ if ( !jQuery.support.opacity && name === "opacity" && elem.currentStyle ) {
+ ret = ropacity.test(elem.currentStyle.filter || "") ?
+ (parseFloat(RegExp.$1) / 100) + "" :
+ "";
+
+ return ret === "" ?
+ "1" :
+ ret;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( rfloat.test( name ) ) {
+ name = styleFloat;
+ }
+
+ if ( !force && style && style[ name ] ) {
+ ret = style[ name ];
+
+ } else if ( getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( rfloat.test( name ) ) {
+ name = "float";
+ }
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ var defaultView = elem.ownerDocument.defaultView;
+
+ if ( !defaultView ) {
+ return null;
+ }
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle ) {
+ ret = computedStyle.getPropertyValue( name );
+ }
+
+ // We should always get a number back from opacity
+ if ( name === "opacity" && ret === "" ) {
+ ret = "1";
+ }
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(rdashAlpha, fcamelCase);
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = camelCase === "fontSize" ? "1em" : (ret || 0);
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth, height = elem.offsetHeight,
+ skip = elem.nodeName.toLowerCase() === "tr";
+
+ return width === 0 && height === 0 && !skip ?
+ true :
+ width > 0 && height > 0 && !skip ?
+ false :
+ jQuery.curCSS(elem, "display") === "none";
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+var jsc = now(),
+ rscript = /<script(.|\s)*?\/script>/gi,
+ rselectTextarea = /select|textarea/i,
+ rinput = /color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,
+ jsre = /=\?(&|$)/,
+ rquery = /\?/,
+ rts = /(\?|&)_=.*?(&|$)/,
+ rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+ r20 = /%20/g,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load;
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" ) {
+ return _load.call( this, url );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = null;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function( res, status ) {
+ // If successful, inject the HTML into all the matched elements
+ if ( status === "success" || status === "notmodified" ) {
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div />")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(res.responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ res.responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [res.responseText, status, res] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function() {
+ return this.elements ? jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function() {
+ return this.name && !this.disabled &&
+ (this.checked || rselectTextarea.test(this.nodeName) ||
+ rinput.test(this.type));
+ })
+ .map(function( i, elem ) {
+ var val = jQuery(this).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray(val) ?
+ jQuery.map( val, function( val, i ) {
+ return { name: elem.name, value: val };
+ }) :
+ { name: elem.name, value: val };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) {
+ jQuery.fn[o] = function( f ) {
+ return this.bind(o, f);
+ };
+});
+
+jQuery.extend({
+
+ get: function( url, data, callback, type ) {
+ // shift arguments if data argument was omited
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = null;
+ }
+
+ return jQuery.ajax({
+ type: "GET",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get(url, null, callback, "script");
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get(url, data, callback, "json");
+ },
+
+ post: function( url, data, callback, type ) {
+ // shift arguments if data argument was omited
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = {};
+ }
+
+ return jQuery.ajax({
+ type: "POST",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ ajaxSetup: function( settings ) {
+ jQuery.extend( jQuery.ajaxSettings, settings );
+ },
+
+ ajaxSettings: {
+ url: location.href,
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ username: null,
+ password: null,
+ traditional: false,
+ */
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7 (can't request local files),
+ // so we use the ActiveXObject when it is available
+ // This function can be overriden by calling jQuery.ajaxSetup
+ xhr: window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject) ?
+ function() {
+ return new window.XMLHttpRequest();
+ } :
+ function() {
+ try {
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {}
+ },
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ script: "text/javascript, application/javascript",
+ json: "application/json, text/javascript",
+ text: "text/plain",
+ _default: "*/*"
+ }
+ },
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajax: function( origSettings ) {
+ var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings);
+
+ var jsonp, status, data,
+ callbackContext = origSettings && origSettings.context || s,
+ type = s.type.toUpperCase();
+
+ // convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Handle JSONP Parameter Callbacks
+ if ( s.dataType === "jsonp" ) {
+ if ( type === "GET" ) {
+ if ( !jsre.test( s.url ) ) {
+ s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ }
+ } else if ( !s.data || !jsre.test(s.data) ) {
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ }
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
+ jsonp = s.jsonpCallback || ("jsonp" + jsc++);
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data ) {
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ }
+
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+
+ try {
+ delete window[ jsonp ];
+ } catch(e) {}
+
+ if ( head ) {
+ head.removeChild( script );
+ }
+ };
+ }
+
+ if ( s.dataType === "script" && s.cache === null ) {
+ s.cache = false;
+ }
+
+ if ( s.cache === false && type === "GET" ) {
+ var ts = now();
+
+ // try replacing _= if it is there
+ var ret = s.url.replace(rts, "$1_=" + ts + "$2");
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
+ }
+
+ // If data is available, append data to url for get requests
+ if ( s.data && type === "GET" ) {
+ s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
+ }
+
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // Matches an absolute URL, and saves the domain
+ var parts = rurl.exec( s.url ),
+ remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType === "script" && type === "GET" && remote ) {
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
+ var script = document.createElement("script");
+ script.src = s.url;
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ // Handle Script loading
+ if ( !jsonp ) {
+ var done = false;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function() {
+ if ( !done && (!this.readyState ||
+ this.readyState === "loaded" || this.readyState === "complete") ) {
+ done = true;
+ success();
+ complete();
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+ }
+ };
+ }
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object
+ var xhr = s.xhr();
+
+ if ( !xhr ) {
+ return;
+ }
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ } else {
+ xhr.open(type, s.url, s.async);
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ // Set the correct header, if data is being sent
+ if ( s.data || origSettings && origSettings.contentType ) {
+ xhr.setRequestHeader("Content-Type", s.contentType);
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[s.url] ) {
+ xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]);
+ }
+
+ if ( jQuery.etag[s.url] ) {
+ xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]);
+ }
+ }
+
+ // Set header so the called script knows that it's an XMLHttpRequest
+ // Only send the header if it's not a remote XHR
+ if ( !remote ) {
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+ s.accepts[ s.dataType ] + ", */*" :
+ s.accepts._default );
+ } catch(e) {}
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && s.beforeSend.call(callbackContext, xhr, s) === false ) {
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global ) {
+ trigger("ajaxSend", [xhr, s]);
+ }
+
+ // Wait for a response to come back
+ var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
+ // The request was aborted
+ if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
+ // Opera doesn't call onreadystatechange before this point
+ // so we simulate the call
+ if ( !requestDone ) {
+ complete();
+ }
+
+ requestDone = true;
+ if ( xhr ) {
+ xhr.onreadystatechange = jQuery.noop;
+ }
+
+ // The transfer is complete and the data is available, or the request timed out
+ } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
+ requestDone = true;
+ xhr.onreadystatechange = jQuery.noop;
+
+ status = isTimeout === "timeout" ?
+ "timeout" :
+ !jQuery.httpSuccess( xhr ) ?
+ "error" :
+ s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
+ "notmodified" :
+ "success";
+
+ var errMsg;
+
+ if ( status === "success" ) {
+ // Watch for, and catch, XML document parse errors
+ try {
+ // process the data (runs the xml through httpData regardless of callback)
+ data = jQuery.httpData( xhr, s.dataType, s );
+ } catch(err) {
+ status = "parsererror";
+ errMsg = err;
+ }
+ }
+
+ // Make sure that the request was successful or notmodified
+ if ( status === "success" || status === "notmodified" ) {
+ // JSONP handles its own success callback
+ if ( !jsonp ) {
+ success();
+ }
+ } else {
+ jQuery.handleError(s, xhr, status, errMsg);
+ }
+
+ // Fire the complete handlers
+ complete();
+
+ if ( isTimeout === "timeout" ) {
+ xhr.abort();
+ }
+
+ // Stop memory leaks
+ if ( s.async ) {
+ xhr = null;
+ }
+ }
+ };
+
+ // Override the abort handler, if we can (IE doesn't allow it, but that's OK)
+ // Opera doesn't fire onreadystatechange at all on abort
+ try {
+ var oldAbort = xhr.abort;
+ xhr.abort = function() {
+ if ( xhr ) {
+ oldAbort.call( xhr );
+ }
+
+ onreadystatechange( "abort" );
+ };
+ } catch(e) { }
+
+ // Timeout checker
+ if ( s.async && s.timeout > 0 ) {
+ setTimeout(function() {
+ // Check to see if the request is still happening
+ if ( xhr && !requestDone ) {
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null );
+ } catch(e) {
+ jQuery.handleError(s, xhr, null, e);
+ // Fire the complete handlers
+ complete();
+ }
+
+ // firefox 1.5 doesn't fire statechange for sync requests
+ if ( !s.async ) {
+ onreadystatechange();
+ }
+
+ function success() {
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success ) {
+ s.success.call( callbackContext, data, status, xhr );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ trigger( "ajaxSuccess", [xhr, s] );
+ }
+ }
+
+ function complete() {
+ // Process result
+ if ( s.complete ) {
+ s.complete.call( callbackContext, xhr, status);
+ }
+
+ // The request was completed
+ if ( s.global ) {
+ trigger( "ajaxComplete", [xhr, s] );
+ }
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+
+ function trigger(type, args) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) {
+ s.error.call( s.context || s, xhr, status, e );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] );
+ }
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol === "file:" ||
+ // Opera returns 0 when status is 304
+ ( xhr.status >= 200 && xhr.status < 300 ) ||
+ xhr.status === 304 || xhr.status === 1223 || xhr.status === 0;
+ } catch(e) {}
+
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ var lastModified = xhr.getResponseHeader("Last-Modified"),
+ etag = xhr.getResponseHeader("Etag");
+
+ if ( lastModified ) {
+ jQuery.lastModified[url] = lastModified;
+ }
+
+ if ( etag ) {
+ jQuery.etag[url] = etag;
+ }
+
+ // Opera returns 0 when status is 304
+ return xhr.status === 304 || xhr.status === 0;
+ },
+
+ httpData: function( xhr, type, s ) {
+ var ct = xhr.getResponseHeader("content-type") || "",
+ xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.nodeName === "parsererror" ) {
+ jQuery.error( "parsererror" );
+ }
+
+ // Allow a pre-filtering function to sanitize the response
+ // s is checked to keep backwards compatibility
+ if ( s && s.dataFilter ) {
+ data = s.dataFilter( data, type );
+ }
+
+ // The filter can actually parse the response
+ if ( typeof data === "string" ) {
+ // Get the JavaScript object, if JSON is used.
+ if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
+ data = jQuery.parseJSON( data );
+
+ // If the type is "script", eval it in global context
+ } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
+ jQuery.globalEval( data );
+ }
+ }
+
+ return data;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [];
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray(a) || a.jquery ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[prefix] );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join("&").replace(r20, "+");
+
+ function buildParams( prefix, obj ) {
+ if ( jQuery.isArray(obj) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || /\[\]$/.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v );
+ }
+ });
+
+ } else if ( !traditional && obj != null && typeof obj === "object" ) {
+ // Serialize object item.
+ jQuery.each( obj, function( k, v ) {
+ buildParams( prefix + "[" + k + "]", v );
+ });
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+ }
+
+ function add( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction(value) ? value() : value;
+ s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
+ }
+ }
+});
+var elemdisplay = {},
+ rfxtypes = /toggle|show|hide/,
+ rfxnum = /^([+-]=)?([\d+-.]+)(.*)$/,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ];
+
+jQuery.fn.extend({
+ show: function( speed, callback ) {
+ if ( speed || speed === 0) {
+ return this.animate( genFx("show", 3), speed, callback);
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var old = jQuery.data(this[i], "olddisplay");
+
+ this[i].style.display = old || "";
+
+ if ( jQuery.css(this[i], "display") === "none" ) {
+ var nodeName = this[i].nodeName, display;
+
+ if ( elemdisplay[ nodeName ] ) {
+ display = elemdisplay[ nodeName ];
+
+ } else {
+ var elem = jQuery("<" + nodeName + " />").appendTo("body");
+
+ display = elem.css("display");
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+
+ elem.remove();
+
+ elemdisplay[ nodeName ] = display;
+ }
+
+ jQuery.data(this[i], "olddisplay", display);
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var j = 0, k = this.length; j < k; j++ ) {
+ this[j].style.display = jQuery.data(this[j], "olddisplay") || "";
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, callback);
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var old = jQuery.data(this[i], "olddisplay");
+ if ( !old && old !== "none" ) {
+ jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var j = 0, k = this.length; j < k; j++ ) {
+ this[j].style.display = "none";
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete );
+ }
+
+ return this[ optall.queue === false ? "each" : "queue" ](function() {
+ var opt = jQuery.extend({}, optall), p,
+ hidden = this.nodeType === 1 && jQuery(this).is(":hidden"),
+ self = this;
+
+ for ( p in prop ) {
+ var name = p.replace(rdashAlpha, fcamelCase);
+
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ p = name;
+ }
+
+ if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
+ return opt.complete.call(this);
+ }
+
+ if ( ( p === "height" || p === "width" ) && this.style ) {
+ // Store display property
+ opt.display = jQuery.css(this, "display");
+
+ // Make sure that nothing sneaks out
+ opt.overflow = this.style.overflow;
+ }
+
+ if ( jQuery.isArray( prop[p] ) ) {
+ // Create (if needed) and add to specialEasing
+ (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
+ prop[p] = prop[p][0];
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function( name, val ) {
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( rfxtypes.test(val) ) {
+ e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+
+ } else {
+ var parts = rfxnum.exec(val),
+ start = e.cur(true) || 0;
+
+ if ( parts ) {
+ var end = parseFloat( parts[2] ),
+ unit = parts[3] || "px";
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ self.style[ name ] = (end || 1) + unit;
+ start = ((end || 1) / e.cur(true)) * start;
+ self.style[ name ] = start + unit;
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ stop: function( clearQueue, gotoEnd ) {
+ var timers = jQuery.timers;
+
+ if ( clearQueue ) {
+ this.queue([]);
+ }
+
+ this.each(function() {
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- ) {
+ if ( timers[i].elem === this ) {
+ if (gotoEnd) {
+ // force the next step to be the last
+ timers[i](true);
+ }
+
+ timers.splice(i, 1);
+ }
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if ( !gotoEnd ) {
+ this.dequeue();
+ }
+
+ return this;
+ }
+
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show", 1),
+ slideUp: genFx("hide", 1),
+ slideToggle: genFx("toggle", 1),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, callback ) {
+ return this.animate( props, speed, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function() {
+ if ( opt.queue !== false ) {
+ jQuery(this).dequeue();
+ }
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig ) {
+ options.orig = {};
+ }
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+ // Set display property to block for height/width animations
+ if ( ( this.prop === "height" || this.prop === "width" ) && this.elem.style ) {
+ this.elem.style.display = "block";
+ }
+ },
+
+ // Get the current size
+ cur: function( force ) {
+ if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+ return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ this.startTime = now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ var self = this;
+ function t( gotoEnd ) {
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval(jQuery.fx.tick, 13);
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var t = now(), done = true;
+
+ if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ for ( var i in this.options.curAnim ) {
+ if ( this.options.curAnim[i] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ if ( this.options.display != null ) {
+ // Reset the overflow
+ this.elem.style.overflow = this.options.overflow;
+
+ // Reset the display
+ var old = jQuery.data(this.elem, "olddisplay");
+ this.elem.style.display = old ? old : this.options.display;
+
+ if ( jQuery.css(this.elem, "display") === "none" ) {
+ this.elem.style.display = "block";
+ }
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide ) {
+ jQuery(this.elem).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show ) {
+ for ( var p in this.options.curAnim ) {
+ jQuery.style(this.elem, p, this.options.orig[p]);
+ }
+ }
+
+ // Execute the complete function
+ this.options.complete.call( this.elem );
+ }
+
+ return false;
+
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
+ var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
+ this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ ) {
+ if ( !timers[i]() ) {
+ timers.splice(i--, 1);
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style(fx.elem, "opacity", fx.now);
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+if ( "getBoundingClientRect" in document.documentElement ) {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement,
+ clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ top = box.top + (self.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop,
+ left = box.left + (self.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ jQuery.offset.initialize();
+
+ var offsetParent = elem.offsetParent, prevOffsetParent = elem,
+ doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+ body = doc.body, defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop, left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.offset = {
+ initialize: function() {
+ var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0,
+ html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+ jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+ container.innerHTML = html;
+ body.insertBefore( container, body.firstChild );
+ innerDiv = container.firstChild;
+ checkDiv = innerDiv.firstChild;
+ td = innerDiv.nextSibling.firstChild.firstChild;
+
+ this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+ this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+ checkDiv.style.position = "fixed", checkDiv.style.top = "20px";
+ // safari subtracts parent border width here which is 5px
+ this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+ checkDiv.style.position = checkDiv.style.top = "";
+
+ innerDiv.style.overflow = "hidden", innerDiv.style.position = "relative";
+ this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+ this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+ body.removeChild( container );
+ body = container = innerDiv = checkDiv = table = td = null;
+ jQuery.offset.initialize = jQuery.noop;
+ },
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop, left = body.offsetLeft;
+
+ jQuery.offset.initialize();
+
+ if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0;
+ left += parseFloat( jQuery.curCSS(body, "marginLeft", true) ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ // set position first, in-case top/left are set even on static elem
+ if ( /static/.test( jQuery.curCSS( elem, "position" ) ) ) {
+ elem.style.position = "relative";
+ }
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curTop = parseInt( jQuery.curCSS( elem, "top", true ), 10 ) || 0,
+ curLeft = parseInt( jQuery.curCSS( elem, "left", true ), 10 ) || 0;
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ var props = {
+ top: (options.top - curOffset.top) + curTop,
+ left: (options.left - curOffset.left) + curLeft
+ };
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.curCSS(elem, "marginTop", true) ) || 0;
+ offset.left -= parseFloat( jQuery.curCSS(elem, "marginLeft", true) ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.curCSS(offsetParent[0], "borderTopWidth", true) ) || 0;
+ parentOffset.left += parseFloat( jQuery.curCSS(offsetParent[0], "borderLeftWidth", true) ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+ var method = "scroll" + name;
+
+ jQuery.fn[ method ] = function(val) {
+ var elem = this[0], win;
+
+ if ( !elem ) {
+ return null;
+ }
+
+ if ( val !== undefined ) {
+ // Set the scroll offset
+ return this.each(function() {
+ win = getWindow( this );
+
+ if ( win ) {
+ win.scrollTo(
+ !i ? val : jQuery(win).scrollLeft(),
+ i ? val : jQuery(win).scrollTop()
+ );
+
+ } else {
+ this[ method ] = val;
+ }
+ });
+ } else {
+ win = getWindow( elem );
+
+ // Return the scroll offset
+ return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+ };
+});
+
+function getWindow( elem ) {
+ return ("scrollTo" in elem && elem.document) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+ var type = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function() {
+ return this[0] ?
+ jQuery.css( this[0], type, false, "padding" ) :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function( margin ) {
+ return this[0] ?
+ jQuery.css( this[0], type, false, margin ? "margin" : "border" ) :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ var elem = this[0];
+ if ( !elem ) {
+ return size == null ? null : this;
+ }
+
+ if ( jQuery.isFunction( size ) ) {
+ return this.each(function( i ) {
+ var self = jQuery( this );
+ self[ type ]( size.call( this, i, self[ type ]() ) );
+ });
+ }
+
+ return ("scrollTo" in elem && elem.document) ? // does it walk and quack like a window?
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] ||
+ elem.document.body[ "client" + name ] :
+
+ // Get document width or height
+ (elem.nodeType === 9) ? // is it a document
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ elem.documentElement["client" + name],
+ elem.body["scroll" + name], elem.documentElement["scroll" + name],
+ elem.body["offset" + name], elem.documentElement["offset" + name]
+ ) :
+
+ // Get or set width or height on the element
+ size === undefined ?
+ // Get width or height on the element
+ jQuery.css( elem, type ) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, typeof size === "string" ? size : size + "px" );
+ };
+
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+})(window);
--- a/web/data/jquery.ui.css Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/jquery.ui.css Fri Mar 11 09:46:45 2011 +0100
@@ -1,8 +1,12 @@
/*
-* jQuery UI CSS Framework
-* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
-* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
-*/
+ * jQuery UI CSS Framework @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
/* Layout helpers
----------------------------------*/
@@ -37,18 +41,23 @@
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
-
/*
-* jQuery UI CSS Framework
-* Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
-* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
-* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
-*/
+ * jQuery UI CSS Framework @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+ */
/* Component containers
----------------------------------*/
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
.ui-widget-content a { color: #333333; }
@@ -57,23 +66,24 @@
/* Interaction states
----------------------------------*/
-.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; outline: none; }
-.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; outline: none; }
-.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; outline: none; }
-.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; outline: none; }
-.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; outline: none; }
-.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; outline: none; text-decoration: none; }
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
+.ui-widget :active { outline: none; }
/* Interaction Cues
----------------------------------*/
-.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
-.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
-.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
-.ui-state-error a, .ui-widget-content .ui-state-error a { color: #ffffff; }
-.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #ffffff; }
-.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
-.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
-.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
/* Icons
----------------------------------*/
@@ -224,6 +234,8 @@
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
@@ -268,28 +280,227 @@
----------------------------------*/
/* Corner radius */
-.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
-.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
-.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
-.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
-.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
-.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
-.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
-.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
-.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
/* Overlays */
.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
-.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; }/* Accordion
-----------------------------------*/
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
+ * jQuery UI Resizable @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
+ * jQuery UI Selectable @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*
+ * jQuery UI Accordion @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
.ui-accordion .ui-accordion-li-fix { display: inline; }
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
-.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
-.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
-.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker
-----------------------------------*/
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }/*
+ * jQuery UI Autocomplete @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+ list-style:none;
+ padding: 2px;
+ margin: 0;
+ display:block;
+ float: left;
+}
+.ui-menu .ui-menu {
+ margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+ margin:0;
+ padding: 0;
+ zoom: 1;
+ float: left;
+ clear: left;
+ width: 100%;
+}
+.ui-menu .ui-menu-item a {
+ text-decoration:none;
+ display:block;
+ padding:.2em .4em;
+ line-height:1.5;
+ zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+ font-weight: normal;
+ margin: -1px;
+}
+/*
+ * jQuery UI Button @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*
+ * jQuery UI Dialog @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*
+ * jQuery UI Slider @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
+ * jQuery UI Tabs @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
+ * jQuery UI Datepicker @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
@@ -300,11 +511,10 @@
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
-.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
-.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; }
@@ -349,58 +559,14 @@
left: -4px; /*must have*/
width: 200px; /*must have*/
height: 200px; /*must have*/
-}/* Dialog
-----------------------------------*/
-.ui-dialog { position: relative; padding: .2em; width: 300px; }
-.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; }
-.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; }
-.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
-.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
-.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
-.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
-.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
-.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
-.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
-.ui-draggable .ui-dialog-titlebar { cursor: move; }
-/* Progressbar
-----------------------------------*/
+}/*
+ * jQuery UI Progressbar @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
.ui-progressbar { height:2em; text-align: left; }
-.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
-----------------------------------*/
-.ui-resizable { position: relative;}
-.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
-.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
-.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
-.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
-.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
-.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
-.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
-.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
-.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
-.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
-----------------------------------*/
-.ui-slider { position: relative; text-align: left; }
-.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
-.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
-
-.ui-slider-horizontal { height: .8em; }
-.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
-.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
-.ui-slider-horizontal .ui-slider-range-min { left: 0; }
-.ui-slider-horizontal .ui-slider-range-max { right: 0; }
-
-.ui-slider-vertical { width: .8em; height: 100px; }
-.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
-.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
-.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
-.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
-----------------------------------*/
-.ui-tabs { padding: .2em; zoom: 1; }
-.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
-.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
-.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
-.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
-.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
-.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
-.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
-.ui-tabs .ui-tabs-hide { display: none !important; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
\ No newline at end of file
--- a/web/data/jquery.ui.js Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/jquery.ui.js Fri Mar 11 09:46:45 2011 +0100
@@ -1,298 +1,778 @@
-/*
- * jQuery UI 1.7.2
+/*!
+ * jQuery UI 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
-jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.2",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m<n.length;m++){if(j.options[n[m][0]]){n[m][1].apply(j.element,k)}}}},contains:function(k,j){return document.compareDocumentPosition?k.compareDocumentPosition(j)&16:k!==j&&k.contains(j)},hasScroll:function(m,k){if(c(m).css("overflow")=="hidden"){return false}var j=(k&&k=="left")?"scrollLeft":"scrollTop",l=false;if(m[j]>0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
- * jQuery UI Draggable 1.7.2
+(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.5",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,
+NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,
+"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");
+if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"));if(!isNaN(b)&&b!=0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind("mousedown.ui-disableSelection selectstart.ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f,
+"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c.style(this,h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c.style(this,
+h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}});
+c(function(){var a=document.createElement("div"),b=document.body;c.extend(a.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.appendChild(a).offsetHeight===100;b.removeChild(a).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,
+d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
+;/*!
+ * jQuery UI Widget 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */
+(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
+a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.substring(0,1)===
+"_")return h;e?this.each(function(){var g=b.data(this,a);if(!g)throw"cannot call methods on "+a+" prior to initialization; attempted to call method '"+d+"'";if(!b.isFunction(g[d]))throw"no such method '"+d+"' for "+a+" widget instance";var i=g[d].apply(g,f);if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",
+widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,b.metadata&&b.metadata.get(c)[this.widgetName],a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._init()},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+
+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a,e=this;if(arguments.length===0)return b.extend({},e.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}b.each(d,function(f,h){e._setOption(f,h)});return e},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},enable:function(){return this._setOption("disabled",
+false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
+;/*!
+ * jQuery UI Mouse 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * http://docs.jquery.com/UI/Mouse
+ *
+ * Depends:
+ * jquery.ui.widget.js
+ */
+(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&&
+this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();
+return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&
+this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-
+a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
+;/*
+ * jQuery UI Position 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function(c){c.ui=c.ui||{};var n=/left|center|right/,o=/top|center|bottom/,t=c.fn.position,u=c.fn.offset;c.fn.position=function(b){if(!b||!b.of)return t.apply(this,arguments);b=c.extend({},b);var a=c(b.of),d=a[0],g=(b.collision||"flip").split(" "),e=b.offset?b.offset.split(" "):[0,0],h,k,j;if(d.nodeType===9){h=a.width();k=a.height();j={top:0,left:0}}else if(d.scrollTo&&d.document){h=a.width();k=a.height();j={top:a.scrollTop(),left:a.scrollLeft()}}else if(d.preventDefault){b.at="left top";h=k=0;j=
+{top:b.of.pageY,left:b.of.pageX}}else{h=a.outerWidth();k=a.outerHeight();j=a.offset()}c.each(["my","at"],function(){var f=(b[this]||"").split(" ");if(f.length===1)f=n.test(f[0])?f.concat(["center"]):o.test(f[0])?["center"].concat(f):["center","center"];f[0]=n.test(f[0])?f[0]:"center";f[1]=o.test(f[1])?f[1]:"center";b[this]=f});if(g.length===1)g[1]=g[0];e[0]=parseInt(e[0],10)||0;if(e.length===1)e[1]=e[0];e[1]=parseInt(e[1],10)||0;if(b.at[0]==="right")j.left+=h;else if(b.at[0]==="center")j.left+=h/
+2;if(b.at[1]==="bottom")j.top+=k;else if(b.at[1]==="center")j.top+=k/2;j.left+=e[0];j.top+=e[1];return this.each(function(){var f=c(this),l=f.outerWidth(),m=f.outerHeight(),p=parseInt(c.curCSS(this,"marginLeft",true))||0,q=parseInt(c.curCSS(this,"marginTop",true))||0,v=l+p+parseInt(c.curCSS(this,"marginRight",true))||0,w=m+q+parseInt(c.curCSS(this,"marginBottom",true))||0,i=c.extend({},j),r;if(b.my[0]==="right")i.left-=l;else if(b.my[0]==="center")i.left-=l/2;if(b.my[1]==="bottom")i.top-=m;else if(b.my[1]===
+"center")i.top-=m/2;i.left=parseInt(i.left);i.top=parseInt(i.top);r={left:i.left-p,top:i.top-q};c.each(["left","top"],function(s,x){c.ui.position[g[s]]&&c.ui.position[g[s]][x](i,{targetWidth:h,targetHeight:k,elemWidth:l,elemHeight:m,collisionPosition:r,collisionWidth:v,collisionHeight:w,offset:e,my:b.my,at:b.at})});c.fn.bgiframe&&f.bgiframe();f.offset(c.extend(i,{using:b.using}))})};c.ui.position={fit:{left:function(b,a){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();
+b.left=d>0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];
+b.left+=a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=
+c(b),g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery);
+;/*
+ * jQuery UI Draggable 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Draggables
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");if(this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval){this.helper.remove()}this.helper=null;this.cancelHelperRemoval=false},_trigger:function(b,c,d){d=d||this._uiHash();a.ui.plugin.call(this,b,[c,d]);if(b=="drag"){this.positionAbs=this._convertPositionTo("absolute")}return a.widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(b){return{helper:this.helper,position:this.position,absolutePosition:this.positionAbs,offset:this.positionAbs}}}));a.extend(a.ui.draggable,{version:"1.7.2",eventPrefix:"drag",defaults:{addClasses:true,appendTo:"parent",axis:false,cancel:":input,option",connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false}});a.ui.plugin.add("draggable","connectToSortable",{start:function(c,e){var d=a(this).data("draggable"),f=d.options,b=a.extend({},e,{item:d.element});d.sortables=[];a(f.connectToSortable).each(function(){var g=a.data(this,"sortable");if(g&&!g.options.disabled){d.sortables.push({instance:g,shouldRevert:g.options.revert});g._refreshItems();g._trigger("activate",c,b)}})},stop:function(c,e){var d=a(this).data("draggable"),b=a.extend({},e,{item:d.element});a.each(d.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;d.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert){this.instance.options.revert=true}this.instance._mouseStop(c);this.instance.options.helper=this.instance.options._helper;if(d.options.helper=="original"){this.instance.currentItem.css({top:"auto",left:"auto"})}}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",c,b)}})},drag:function(c,f){var e=a(this).data("draggable"),b=this;var d=function(i){var n=this.offset.click.top,m=this.offset.click.left;var g=this.positionAbs.top,k=this.positionAbs.left;var j=i.height,l=i.width;var p=i.top,h=i.left;return a.ui.isOver(g+n,k+m,p,h,j,l)};a.each(e.sortables,function(g){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(b).clone().appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return f.helper[0]};c.target=this.instance.currentItem[0];this.instance._mouseCapture(c,true);this.instance._mouseStart(c,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;e._trigger("toSortable",c);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}if(this.instance.currentItem){this.instance._mouseDrag(c)}}else{if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",c,this.instance._uiHash(this.instance));this.instance._mouseStop(c,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();if(this.instance.placeholder){this.instance.placeholder.remove()}e._trigger("fromSortable",c);e.dropped=false}}})}});a.ui.plugin.add("draggable","cursor",{start:function(c,d){var b=a("body"),e=a(this).data("draggable").options;if(b.css("cursor")){e._cursor=b.css("cursor")}b.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._cursor){a("body").css("cursor",d._cursor)}}});a.ui.plugin.add("draggable","iframeFix",{start:function(b,c){var d=a(this).data("draggable").options;a(d.iframeFix===true?"iframe":d.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop+f.scrollSpeed}else{if(d.pageY-c.overflowOffset.top<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop-f.scrollSpeed}}}if(!f.axis||f.axis!="y"){if((c.overflowOffset.left+c.scrollParent[0].offsetWidth)-d.pageX<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft+f.scrollSpeed}else{if(d.pageX-c.overflowOffset.left<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft-f.scrollSpeed}}}}else{if(!f.axis||f.axis!="x"){if(d.pageY-a(document).scrollTop()<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-f.scrollSpeed)}else{if(a(window).height()-(d.pageY-a(document).scrollTop())<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+f.scrollSpeed)}}}if(!f.axis||f.axis!="y"){if(d.pageX-a(document).scrollLeft()<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-f.scrollSpeed)}else{if(a(window).width()-(d.pageX-a(document).scrollLeft())<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+f.scrollSpeed)}}}}if(b!==false&&a.ui.ddmanager&&!f.dropBehaviour){a.ui.ddmanager.prepareOffsets(c,d)}}});a.ui.plugin.add("draggable","snap",{start:function(c,d){var b=a(this).data("draggable"),e=b.options;b.snapElements=[];a(e.snap.constructor!=String?(e.snap.items||":data(draggable)"):e.snap).each(function(){var g=a(this);var f=g.offset();if(this!=b.element[0]){b.snapElements.push({item:this,width:g.outerWidth(),height:g.outerHeight(),top:f.top,left:f.left})}})},drag:function(u,p){var g=a(this).data("draggable"),q=g.options;var y=q.snapTolerance;var x=p.offset.left,w=x+g.helperProportions.width,f=p.offset.top,e=f+g.helperProportions.height;for(var v=g.snapElements.length-1;v>=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y<x&&x<n+y&&m-y<f&&f<A+y)||(s-y<x&&x<n+y&&m-y<e&&e<A+y)||(s-y<w&&w<n+y&&m-y<f&&f<A+y)||(s-y<w&&w<n+y&&m-y<e&&e<A+y))){if(g.snapElements[v].snapping){(g.options.snap.release&&g.options.snap.release.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=false;continue}if(q.snapMode!="inner"){var c=Math.abs(m-e)<=y;var z=Math.abs(A-f)<=y;var j=Math.abs(s-w)<=y;var k=Math.abs(n-x)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m-g.helperProportions.height,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s-g.helperProportions.width}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n}).left-g.margins.left}}var h=(c||z||j||k);if(q.snapMode!="outer"){var c=Math.abs(m-f)<=y;var z=Math.abs(A-e)<=y;var j=Math.abs(s-x)<=y;var k=Math.abs(n-w)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A-g.helperProportions.height,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n-g.helperProportions.width}).left-g.margins.left}}if(!g.snapElements[v].snapping&&(c||z||j||k||h)){(g.options.snap.snap&&g.options.snap.snap.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=(c||z||j||k||h)}}});a.ui.plugin.add("draggable","stack",{start:function(b,c){var e=a(this).data("draggable").options;var d=a.makeArray(a(e.stack.group)).sort(function(g,f){return(parseInt(a(g).css("zIndex"),10)||e.stack.min)-(parseInt(a(f).css("zIndex"),10)||e.stack.min)});a(d).each(function(f){this.style.zIndex=e.stack.min+f});this[0].style.zIndex=e.stack.min+d.length}});a.ui.plugin.add("draggable","zIndex",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("zIndex")){e._zIndex=b.css("zIndex")}b.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._zIndex){a(c.helper).css("zIndex",d._zIndex)}}})})(jQuery);;/*
- * jQuery UI Droppable 1.7.2
+(function(d){d.widget("ui.draggable",d.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:true,appendTo:"parent",axis:false,connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false},_create:function(){if(this.options.helper==
+"original"&&!/^(?:r|a|f)/.test(this.element.css("position")))this.element[0].style.position="relative";this.options.addClasses&&this.element.addClass("ui-draggable");this.options.disabled&&this.element.addClass("ui-draggable-disabled");this._mouseInit()},destroy:function(){if(this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy();return this}},_mouseCapture:function(a){var b=
+this.options;if(this.helper||b.disabled||d(a.target).is(".ui-resizable-handle"))return false;this.handle=this._getHandle(a);if(!this.handle)return false;return true},_mouseStart:function(a){var b=this.options;this.helper=this._createHelper(a);this._cacheHelperProportions();if(d.ui.ddmanager)d.ui.ddmanager.current=this;this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.positionAbs=this.element.offset();this.offset={top:this.offset.top-
+this.margins.top,left:this.offset.left-this.margins.left};d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this.position=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);b.containment&&this._setContainment();if(this._trigger("start",a)===false){this._clear();return false}this._cacheHelperProportions();
+d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.helper.addClass("ui-draggable-dragging");this._mouseDrag(a,true);return true},_mouseDrag:function(a,b){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!b){b=this._uiHash();if(this._trigger("drag",a,b)===false){this._mouseUp({});return false}this.position=b.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||
+this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);return false},_mouseStop:function(a){var b=false;if(d.ui.ddmanager&&!this.options.dropBehaviour)b=d.ui.ddmanager.drop(this,a);if(this.dropped){b=this.dropped;this.dropped=false}if(!this.element[0]||!this.element[0].parentNode)return false;if(this.options.revert=="invalid"&&!b||this.options.revert=="valid"&&b||this.options.revert===true||d.isFunction(this.options.revert)&&this.options.revert.call(this.element,
+b)){var c=this;d(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){c._trigger("stop",a)!==false&&c._clear()})}else this._trigger("stop",a)!==false&&this._clear();return false},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(a){var b=!this.options.handle||!d(this.options.handle,this.element).length?true:false;d(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==
+a.target)b=true});return b},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a])):b.helper=="clone"?this.element.clone():this.element;a.parents("body").length||a.appendTo(b.appendTo=="parent"?this.element[0].parentNode:b.appendTo);a[0]!=this.element[0]&&!/(fixed|absolute)/.test(a.css("position"))&&a.css("position","absolute");return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||
+0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],
+this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-
+(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment==
+"parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)&&
+a.containment.constructor!=Array){var b=d(a.containment)[0];if(b){a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),
+10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}}else if(a.containment.constructor==Array)this.containment=a.containment},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],
+this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():
+f?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,f=/(html|body)/i.test(c[0].tagName),e=a.pageX,g=a.pageY;if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.left<this.containment[0])e=this.containment[0]+this.offset.click.left;if(a.pageY-this.offset.click.top<this.containment[1])g=this.containment[1]+
+this.offset.click.top;if(a.pageX-this.offset.click.left>this.containment[2])e=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:!(g-this.offset.click.top<this.containment[1])?g-b.grid[1]:g+b.grid[1]:g;e=this.originalPageX+
+Math.round((e-this.originalPageX)/b.grid[0])*b.grid[0];e=this.containment?!(e-this.offset.click.left<this.containment[0]||e-this.offset.click.left>this.containment[2])?e:!(e-this.offset.click.left<this.containment[0])?e-b.grid[0]:e+b.grid[0]:e}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():f?0:c.scrollTop()),left:e-this.offset.click.left-
+this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&d.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():f?0:c.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging");this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove();this.helper=null;this.cancelHelperRemoval=false},_trigger:function(a,b,c){c=c||this._uiHash();d.ui.plugin.call(this,a,[b,c]);if(a=="drag")this.positionAbs=
+this._convertPositionTo("absolute");return d.Widget.prototype._trigger.call(this,a,b,c)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}});d.extend(d.ui.draggable,{version:"1.8.5"});d.ui.plugin.add("draggable","connectToSortable",{start:function(a,b){var c=d(this).data("draggable"),f=c.options,e=d.extend({},b,{item:c.element});c.sortables=[];d(f.connectToSortable).each(function(){var g=d.data(this,"sortable");
+if(g&&!g.options.disabled){c.sortables.push({instance:g,shouldRevert:g.options.revert});g._refreshItems();g._trigger("activate",a,e)}})},stop:function(a,b){var c=d(this).data("draggable"),f=d.extend({},b,{item:c.element});d.each(c.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;c.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert)this.instance.options.revert=true;this.instance._mouseStop(a);this.instance.options.helper=this.instance.options._helper;
+c.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",a,f)}})},drag:function(a,b){var c=d(this).data("draggable"),f=this;d.each(c.sortables,function(){this.instance.positionAbs=c.positionAbs;this.instance.helperProportions=c.helperProportions;this.instance.offset.click=c.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=
+1;this.instance.currentItem=d(f).clone().appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return b.helper[0]};a.target=this.instance.currentItem[0];this.instance._mouseCapture(a,true);this.instance._mouseStart(a,true,true);this.instance.offset.click.top=c.offset.click.top;this.instance.offset.click.left=c.offset.click.left;this.instance.offset.parent.left-=c.offset.parent.left-this.instance.offset.parent.left;
+this.instance.offset.parent.top-=c.offset.parent.top-this.instance.offset.parent.top;c._trigger("toSortable",a);c.dropped=this.instance.element;c.currentItem=c.element;this.instance.fromOutside=c}this.instance.currentItem&&this.instance._mouseDrag(a)}else if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",a,this.instance._uiHash(this.instance));this.instance._mouseStop(a,true);this.instance.options.helper=
+this.instance.options._helper;this.instance.currentItem.remove();this.instance.placeholder&&this.instance.placeholder.remove();c._trigger("fromSortable",a);c.dropped=false}})}});d.ui.plugin.add("draggable","cursor",{start:function(){var a=d("body"),b=d(this).data("draggable").options;if(a.css("cursor"))b._cursor=a.css("cursor");a.css("cursor",b.cursor)},stop:function(){var a=d(this).data("draggable").options;a._cursor&&d("body").css("cursor",a._cursor)}});d.ui.plugin.add("draggable","iframeFix",{start:function(){var a=
+d(this).data("draggable").options;d(a.iframeFix===true?"iframe":a.iframeFix).each(function(){d('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1E3}).css(d(this).offset()).appendTo("body")})},stop:function(){d("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});d.ui.plugin.add("draggable","opacity",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;
+if(a.css("opacity"))b._opacity=a.css("opacity");a.css("opacity",b.opacity)},stop:function(a,b){a=d(this).data("draggable").options;a._opacity&&d(b.helper).css("opacity",a._opacity)}});d.ui.plugin.add("draggable","scroll",{start:function(){var a=d(this).data("draggable");if(a.scrollParent[0]!=document&&a.scrollParent[0].tagName!="HTML")a.overflowOffset=a.scrollParent.offset()},drag:function(a){var b=d(this).data("draggable"),c=b.options,f=false;if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!=
+"HTML"){if(!c.axis||c.axis!="x")if(b.overflowOffset.top+b.scrollParent[0].offsetHeight-a.pageY<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop+c.scrollSpeed;else if(a.pageY-b.overflowOffset.top<c.scrollSensitivity)b.scrollParent[0].scrollTop=f=b.scrollParent[0].scrollTop-c.scrollSpeed;if(!c.axis||c.axis!="y")if(b.overflowOffset.left+b.scrollParent[0].offsetWidth-a.pageX<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft+c.scrollSpeed;else if(a.pageX-
+b.overflowOffset.left<c.scrollSensitivity)b.scrollParent[0].scrollLeft=f=b.scrollParent[0].scrollLeft-c.scrollSpeed}else{if(!c.axis||c.axis!="x")if(a.pageY-d(document).scrollTop()<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()-c.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<c.scrollSensitivity)f=d(document).scrollTop(d(document).scrollTop()+c.scrollSpeed);if(!c.axis||c.axis!="y")if(a.pageX-d(document).scrollLeft()<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()-
+c.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<c.scrollSensitivity)f=d(document).scrollLeft(d(document).scrollLeft()+c.scrollSpeed)}f!==false&&d.ui.ddmanager&&!c.dropBehaviour&&d.ui.ddmanager.prepareOffsets(b,a)}});d.ui.plugin.add("draggable","snap",{start:function(){var a=d(this).data("draggable"),b=a.options;a.snapElements=[];d(b.snap.constructor!=String?b.snap.items||":data(draggable)":b.snap).each(function(){var c=d(this),f=c.offset();this!=a.element[0]&&a.snapElements.push({item:this,
+width:c.outerWidth(),height:c.outerHeight(),top:f.top,left:f.left})})},drag:function(a,b){for(var c=d(this).data("draggable"),f=c.options,e=f.snapTolerance,g=b.offset.left,n=g+c.helperProportions.width,m=b.offset.top,o=m+c.helperProportions.height,h=c.snapElements.length-1;h>=0;h--){var i=c.snapElements[h].left,k=i+c.snapElements[h].width,j=c.snapElements[h].top,l=j+c.snapElements[h].height;if(i-e<g&&g<k+e&&j-e<m&&m<l+e||i-e<g&&g<k+e&&j-e<o&&o<l+e||i-e<n&&n<k+e&&j-e<m&&m<l+e||i-e<n&&n<k+e&&j-e<o&&
+o<l+e){if(f.snapMode!="inner"){var p=Math.abs(j-o)<=e,q=Math.abs(l-m)<=e,r=Math.abs(i-n)<=e,s=Math.abs(k-g)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:j-c.helperProportions.height,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:l,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:i-c.helperProportions.width}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:k}).left-c.margins.left}var t=
+p||q||r||s;if(f.snapMode!="outer"){p=Math.abs(j-m)<=e;q=Math.abs(l-o)<=e;r=Math.abs(i-g)<=e;s=Math.abs(k-n)<=e;if(p)b.position.top=c._convertPositionTo("relative",{top:j,left:0}).top-c.margins.top;if(q)b.position.top=c._convertPositionTo("relative",{top:l-c.helperProportions.height,left:0}).top-c.margins.top;if(r)b.position.left=c._convertPositionTo("relative",{top:0,left:i}).left-c.margins.left;if(s)b.position.left=c._convertPositionTo("relative",{top:0,left:k-c.helperProportions.width}).left-c.margins.left}if(!c.snapElements[h].snapping&&
+(p||q||r||s||t))c.options.snap.snap&&c.options.snap.snap.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[h].item}));c.snapElements[h].snapping=p||q||r||s||t}else{c.snapElements[h].snapping&&c.options.snap.release&&c.options.snap.release.call(c.element,a,d.extend(c._uiHash(),{snapItem:c.snapElements[h].item}));c.snapElements[h].snapping=false}}}});d.ui.plugin.add("draggable","stack",{start:function(){var a=d(this).data("draggable").options;a=d.makeArray(d(a.stack)).sort(function(c,f){return(parseInt(d(c).css("zIndex"),
+10)||0)-(parseInt(d(f).css("zIndex"),10)||0)});if(a.length){var b=parseInt(a[0].style.zIndex)||0;d(a).each(function(c){this.style.zIndex=b+c});this[0].style.zIndex=b+a.length}}});d.ui.plugin.add("draggable","zIndex",{start:function(a,b){a=d(b.helper);b=d(this).data("draggable").options;if(a.css("zIndex"))b._zIndex=a.css("zIndex");a.css("zIndex",b.zIndex)},stop:function(a,b){a=d(this).data("draggable").options;a._zIndex&&d(b.helper).css("zIndex",a._zIndex)}})})(jQuery);
+;/*
+ * jQuery UI Droppable 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Droppables
*
* Depends:
- * ui.core.js
- * ui.draggable.js
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.mouse.js
+ * jquery.ui.draggable.js
*/
-(function(a){a.widget("ui.droppable",{_init:function(){var c=this.options,b=c.accept;this.isover=0;this.isout=1;this.options.accept=this.options.accept&&a.isFunction(this.options.accept)?this.options.accept:function(e){return e.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};a.ui.ddmanager.droppables[this.options.scope]=a.ui.ddmanager.droppables[this.options.scope]||[];a.ui.ddmanager.droppables[this.options.scope].push(this);(this.options.addClasses&&this.element.addClass("ui-droppable"))},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++){if(b[c]==this){b.splice(c,1)}}this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable")},_setData:function(b,c){if(b=="accept"){this.options.accept=c&&a.isFunction(c)?c:function(e){return e.is(c)}}else{a.widget.prototype._setData.apply(this,arguments)}},_activate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.addClass(this.options.activeClass)}(b&&this._trigger("activate",c,this.ui(b)))},_deactivate:function(c){var b=a.ui.ddmanager.current;if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}(b&&this._trigger("deactivate",c,this.ui(b)))},_over:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.addClass(this.options.hoverClass)}this._trigger("over",c,this.ui(b))}},_out:function(c){var b=a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("out",c,this.ui(b))}},_drop:function(c,d){var b=d||a.ui.ddmanager.current;if(!b||(b.currentItem||b.element)[0]==this.element[0]){return false}var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var f=a.data(this,"droppable");if(f.options.greedy&&a.ui.intersect(b,a.extend(f,{offset:f.element.offset()}),f.options.tolerance)){e=true;return false}});if(e){return false}if(this.options.accept.call(this.element[0],(b.currentItem||b.element))){if(this.options.activeClass){this.element.removeClass(this.options.activeClass)}if(this.options.hoverClass){this.element.removeClass(this.options.hoverClass)}this._trigger("drop",c,this.ui(b));return this.element}return false},ui:function(b){return{draggable:(b.currentItem||b.element),helper:b.helper,position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs}}});a.extend(a.ui.droppable,{version:"1.7.2",eventPrefix:"drop",defaults:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"}});a.ui.intersect=function(q,j,o){if(!j.offset){return false}var e=(q.positionAbs||q.position.absolute).left,d=e+q.helperProportions.width,n=(q.positionAbs||q.position.absolute).top,m=n+q.helperProportions.height;var g=j.offset.left,c=g+j.proportions.width,p=j.offset.top,k=p+j.proportions.height;switch(o){case"fit":return(g<e&&d<c&&p<n&&m<k);break;case"intersect":return(g<e+(q.helperProportions.width/2)&&d-(q.helperProportions.width/2)<c&&p<n+(q.helperProportions.height/2)&&m-(q.helperProportions.height/2)<k);break;case"pointer":var h=((q.positionAbs||q.position.absolute).left+(q.clickOffset||q.offset.click).left),i=((q.positionAbs||q.position.absolute).top+(q.clickOffset||q.offset.click).top),f=a.ui.isOver(i,h,p,g,j.proportions.height,j.proportions.width);return f;break;case"touch":return((n>=p&&n<=k)||(m>=p&&m<=k)||(n<p&&m>k))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(e<g&&d>c));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d<b.length;d++){if(b[d].options.disabled||(e&&!b[d].options.accept.call(b[d].element[0],(e.currentItem||e.element)))){continue}for(var c=0;c<h.length;c++){if(h[c]==b[d].element[0]){b[d].proportions.height=0;continue droppablesLoop}}b[d].visible=b[d].element.css("display")!="none";if(!b[d].visible){continue}b[d].offset=b[d].element.offset();b[d].proportions={width:b[d].element[0].offsetWidth,height:b[d].element[0].offsetHeight};if(f=="mousedown"){b[d]._activate.call(b[d],g)}}},drop:function(b,c){var d=false;a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(!this.options){return}if(!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)){d=this._drop.call(this,c)}if(!this.options.disabled&&this.visible&&this.options.accept.call(this.element[0],(b.currentItem||b.element))){this.isout=1;this.isover=0;this._deactivate.call(this,c)}});return d},drag:function(b,c){if(b.options.refreshPositions){a.ui.ddmanager.prepareOffsets(b,c)}a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(this.options.disabled||this.greedyChild||!this.visible){return}var e=a.ui.intersect(b,this,this.options.tolerance);var g=!e&&this.isover==1?"isout":(e&&this.isover==0?"isover":null);if(!g){return}var f;if(this.options.greedy){var d=this.element.parents(":data(droppable):eq(0)");if(d.length){f=a.data(d[0],"droppable");f.greedyChild=(g=="isover"?1:0)}}if(f&&g=="isover"){f.isover=0;f.isout=1;f._out.call(f,c)}this[g]=1;this[g=="isout"?"isover":"isout"]=0;this[g=="isover"?"_over":"_out"].call(this,c);if(f&&g=="isout"){f.isout=0;f.isover=1;f._over.call(f,c)}})}}})(jQuery);;/*
- * jQuery UI Resizable 1.7.2
+(function(d){d.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"},_create:function(){var a=this.options,b=a.accept;this.isover=0;this.isout=1;this.accept=d.isFunction(b)?b:function(c){return c.is(b)};this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight};d.ui.ddmanager.droppables[a.scope]=d.ui.ddmanager.droppables[a.scope]||[];d.ui.ddmanager.droppables[a.scope].push(this);
+a.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){for(var a=d.ui.ddmanager.droppables[this.options.scope],b=0;b<a.length;b++)a[b]==this&&a.splice(b,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(a,b){if(a=="accept")this.accept=d.isFunction(b)?b:function(c){return c.is(b)};d.Widget.prototype._setOption.apply(this,arguments)},_activate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&&
+this.element.addClass(this.options.activeClass);b&&this._trigger("activate",a,this.ui(b))},_deactivate:function(a){var b=d.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass);b&&this._trigger("deactivate",a,this.ui(b))},_over:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.addClass(this.options.hoverClass);
+this._trigger("over",a,this.ui(b))}},_out:function(a){var b=d.ui.ddmanager.current;if(!(!b||(b.currentItem||b.element)[0]==this.element[0]))if(this.accept.call(this.element[0],b.currentItem||b.element)){this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("out",a,this.ui(b))}},_drop:function(a,b){var c=b||d.ui.ddmanager.current;if(!c||(c.currentItem||c.element)[0]==this.element[0])return false;var e=false;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var g=
+d.data(this,"droppable");if(g.options.greedy&&!g.options.disabled&&g.options.scope==c.options.scope&&g.accept.call(g.element[0],c.currentItem||c.element)&&d.ui.intersect(c,d.extend(g,{offset:g.element.offset()}),g.options.tolerance)){e=true;return false}});if(e)return false;if(this.accept.call(this.element[0],c.currentItem||c.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass);this.options.hoverClass&&this.element.removeClass(this.options.hoverClass);this._trigger("drop",
+a,this.ui(c));return this.element}return false},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}});d.extend(d.ui.droppable,{version:"1.8.5"});d.ui.intersect=function(a,b,c){if(!b.offset)return false;var e=(a.positionAbs||a.position.absolute).left,g=e+a.helperProportions.width,f=(a.positionAbs||a.position.absolute).top,h=f+a.helperProportions.height,i=b.offset.left,k=i+b.proportions.width,j=b.offset.top,l=j+b.proportions.height;
+switch(c){case "fit":return i<=e&&g<=k&&j<=f&&h<=l;case "intersect":return i<e+a.helperProportions.width/2&&g-a.helperProportions.width/2<k&&j<f+a.helperProportions.height/2&&h-a.helperProportions.height/2<l;case "pointer":return d.ui.isOver((a.positionAbs||a.position.absolute).top+(a.clickOffset||a.offset.click).top,(a.positionAbs||a.position.absolute).left+(a.clickOffset||a.offset.click).left,j,i,b.proportions.height,b.proportions.width);case "touch":return(f>=j&&f<=l||h>=j&&h<=l||f<j&&h>l)&&(e>=
+i&&e<=k||g>=i&&g<=k||e<i&&g>k);default:return false}};d.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(a,b){var c=d.ui.ddmanager.droppables[a.options.scope]||[],e=b?b.type:null,g=(a.currentItem||a.element).find(":data(droppable)").andSelf(),f=0;a:for(;f<c.length;f++)if(!(c[f].options.disabled||a&&!c[f].accept.call(c[f].element[0],a.currentItem||a.element))){for(var h=0;h<g.length;h++)if(g[h]==c[f].element[0]){c[f].proportions.height=0;continue a}c[f].visible=c[f].element.css("display")!=
+"none";if(c[f].visible){c[f].offset=c[f].element.offset();c[f].proportions={width:c[f].element[0].offsetWidth,height:c[f].element[0].offsetHeight};e=="mousedown"&&c[f]._activate.call(c[f],b)}}},drop:function(a,b){var c=false;d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(this.options){if(!this.options.disabled&&this.visible&&d.ui.intersect(a,this,this.options.tolerance))c=c||this._drop.call(this,b);if(!this.options.disabled&&this.visible&&this.accept.call(this.element[0],a.currentItem||
+a.element)){this.isout=1;this.isover=0;this._deactivate.call(this,b)}}});return c},drag:function(a,b){a.options.refreshPositions&&d.ui.ddmanager.prepareOffsets(a,b);d.each(d.ui.ddmanager.droppables[a.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var c=d.ui.intersect(a,this,this.options.tolerance);if(c=!c&&this.isover==1?"isout":c&&this.isover==0?"isover":null){var e;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");if(g.length){e=
+d.data(g[0],"droppable");e.greedyChild=c=="isover"?1:0}}if(e&&c=="isover"){e.isover=0;e.isout=1;e._out.call(e,b)}this[c]=1;this[c=="isout"?"isover":"isout"]=0;this[c=="isover"?"_over":"_out"].call(this,b);if(e&&c=="isout"){e.isout=0;e.isover=1;e._over.call(e,b)}}}})}}})(jQuery);
+;/*
+ * jQuery UI Resizable 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizables
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
*/
-(function(c){c.widget("ui.resizable",c.extend({},c.ui.mouse,{_init:function(){var e=this,j=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(j.aspectRatio),aspectRatio:j.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:j.helper||j.ghost||j.animate?j.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){if(/relative/.test(this.element.css("position"))&&c.browser.opera){this.element.css({position:"relative",top:"auto",left:"auto"})}this.element.wrap(c('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=j.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var k=this.handles.split(",");this.handles={};for(var f=0;f<k.length;f++){var h=c.trim(k[f]),d="ui-resizable-"+h;var g=c('<div class="ui-resizable-handle '+d+'"></div>');if(/sw|se|ne|nw/.test(h)){g.css({zIndex:++j.zIndex})}if("se"==h){g.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[h]=".ui-resizable-"+h;this.element.append(g)}}this._renderAxis=function(p){p=p||this.element;for(var m in this.handles){if(this.handles[m].constructor==String){this.handles[m]=c(this.handles[m],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var n=c(this.handles[m],this.element),o=0;o=/sw|ne|nw|se|n|s/.test(m)?n.outerHeight():n.outerWidth();var l=["padding",/ne|nw|n/.test(m)?"Top":/se|sw|s/.test(m)?"Bottom":/^e$/.test(m)?"Right":"Left"].join("");p.css(l,o);this._proportionallyResize()}if(!c(this.handles[m]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!e.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}e.axis=i&&i[1]?i[1]:"se"}});if(j.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){c(this).removeClass("ui-resizable-autohide");e._handles.show()},function(){if(!e.resizing){c(this).addClass("ui-resizable-autohide");e._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var d=function(f){c(f).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){d(this.element);var e=this.element;e.parent().append(this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")})).end().remove()}this.originalElement.css("resize",this.originalResizeStyle);d(this.originalElement)},_mouseCapture:function(e){var f=false;for(var d in this.handles){if(c(this.handles[d])[0]==e.target){f=true}}return this.options.disabled||!!f},_mouseStart:function(f){var i=this.options,e=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(d.is(".ui-draggable")||(/absolute/).test(d.css("position"))){d.css({position:"absolute",top:e.top,left:e.left})}if(c.browser.opera&&(/relative/).test(d.css("position"))){d.css({position:"relative",top:"auto",left:"auto"})}this._renderProxy();var j=b(this.helper.css("left")),g=b(this.helper.css("top"));if(i.containment){j+=c(i.containment).scrollLeft()||0;g+=c(i.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:j,top:g};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:j,top:g};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:f.pageX,top:f.pageY};this.aspectRatio=(typeof i.aspectRatio=="number")?i.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var h=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",h=="auto"?this.axis+"-resize":h);d.addClass("ui-resizable-resizing");this._propagate("start",f);return true},_mouseDrag:function(d){var g=this.helper,f=this.options,l={},p=this,i=this.originalMousePosition,m=this.axis;var q=(d.pageX-i.left)||0,n=(d.pageY-i.top)||0;var h=this._change[m];if(!h){return false}var k=h.apply(this,[d,q,n]),j=c.browser.msie&&c.browser.version<7,e=this.sizeDiff;if(this._aspectRatio||d.shiftKey){k=this._updateRatio(k,d)}k=this._respectSize(k,d);this._propagate("resize",d);g.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(k);this._trigger("resize",d,this.ui());return false},_mouseStop:function(g){this.resizing=false;var h=this.options,l=this;if(this._helper){var f=this._proportionallyResizeElements,d=f.length&&(/textarea/i).test(f[0].nodeName),e=d&&c.ui.hasScroll(f[0],"left")?0:l.sizeDiff.height,j=d?0:l.sizeDiff.width;var m={width:(l.size.width-j),height:(l.size.height-e)},i=(parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left))||null,k=(parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top))||null;if(!h.animate){this.element.css(c.extend(m,{top:k,left:i}))}l.helper.height(l.size.height);l.helper.width(l.size.width);if(this._helper&&!h.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",g);if(this._helper){this.helper.remove()}return false},_updateCache:function(d){var e=this.options;this.offset=this.helper.offset();if(a(d.left)){this.position.left=d.left}if(a(d.top)){this.position.top=d.top}if(a(d.height)){this.size.height=d.height}if(a(d.width)){this.size.width=d.width}},_updateRatio:function(g,f){var h=this.options,i=this.position,e=this.size,d=this.axis;if(g.height){g.width=(e.height*this.aspectRatio)}else{if(g.width){g.height=(e.width/this.aspectRatio)}}if(d=="sw"){g.left=i.left+(e.width-g.width);g.top=null}if(d=="nw"){g.top=i.top+(e.height-g.height);g.left=i.left+(e.width-g.width)}return g},_respectSize:function(k,f){var i=this.helper,h=this.options,q=this._aspectRatio||f.shiftKey,p=this.axis,s=a(k.width)&&h.maxWidth&&(h.maxWidth<k.width),l=a(k.height)&&h.maxHeight&&(h.maxHeight<k.height),g=a(k.width)&&h.minWidth&&(h.minWidth>k.width),r=a(k.height)&&h.minHeight&&(h.minHeight>k.height);if(g){k.width=h.minWidth}if(r){k.height=h.minHeight}if(s){k.width=h.maxWidth}if(l){k.height=h.maxHeight}var e=this.originalPosition.left+this.originalSize.width,n=this.position.top+this.size.height;var j=/sw|nw|w/.test(p),d=/nw|ne|n/.test(p);if(g&&j){k.left=e-h.minWidth}if(s&&j){k.left=e-h.maxWidth}if(r&&d){k.top=n-h.minHeight}if(l&&d){k.top=n-h.maxHeight}var m=!k.width&&!k.height;if(m&&!k.left&&k.top){k.top=null}else{if(m&&!k.top&&k.left){k.left=null}}return k},_proportionallyResize:function(){var j=this.options;if(!this._proportionallyResizeElements.length){return}var f=this.helper||this.element;for(var e=0;e<this._proportionallyResizeElements.length;e++){var g=this._proportionallyResizeElements[e];if(!this.borderDif){var d=[g.css("borderTopWidth"),g.css("borderRightWidth"),g.css("borderBottomWidth"),g.css("borderLeftWidth")],h=[g.css("paddingTop"),g.css("paddingRight"),g.css("paddingBottom"),g.css("paddingLeft")];this.borderDif=c.map(d,function(k,m){var l=parseInt(k,10)||0,n=parseInt(h[m],10)||0;return l+n})}if(c.browser.msie&&!(!(c(f).is(":hidden")||c(f).parents(":hidden").length))){continue}g.css({height:(f.height()-this.borderDif[0]-this.borderDif[2])||0,width:(f.width()-this.borderDif[1]-this.borderDif[3])||0})}},_renderProxy:function(){var e=this.element,h=this.options;this.elementOffset=e.offset();if(this._helper){this.helper=this.helper||c('<div style="overflow:hidden;"></div>');var d=c.browser.msie&&c.browser.version<7,f=(d?1:0),g=(d?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+g,height:this.element.outerHeight()+g,position:"absolute",left:this.elementOffset.left-f+"px",top:this.elementOffset.top-f+"px",zIndex:++h.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(f,e,d){return{width:this.originalSize.width+e}},w:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{left:h.left+e,width:f.width-e}},n:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{top:h.top+d,height:f.height-d}},s:function(f,e,d){return{height:this.originalSize.height+d}},se:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},sw:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[f,e,d]))},ne:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},nw:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[f,e,d]))}},_propagate:function(e,d){c.ui.plugin.call(this,e,[d,this.ui()]);(e!="resize"&&this._trigger(e,d,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}));c.extend(c.ui.resizable,{version:"1.7.2",eventPrefix:"resize",defaults:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,cancel:":input,option",containment:false,delay:0,distance:1,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000}});c.ui.plugin.add("resizable","alsoResize",{start:function(e,f){var d=c(this).data("resizable"),g=d.options;_store=function(h){c(h).each(function(){c(this).data("resizable-alsoresize",{width:parseInt(c(this).width(),10),height:parseInt(c(this).height(),10),left:parseInt(c(this).css("left"),10),top:parseInt(c(this).css("top"),10)})})};if(typeof(g.alsoResize)=="object"&&!g.alsoResize.parentNode){if(g.alsoResize.length){g.alsoResize=g.alsoResize[0];_store(g.alsoResize)}else{c.each(g.alsoResize,function(h,i){_store(h)})}}else{_store(g.alsoResize)}},resize:function(f,h){var e=c(this).data("resizable"),i=e.options,g=e.originalSize,k=e.originalPosition;var j={height:(e.size.height-g.height)||0,width:(e.size.width-g.width)||0,top:(e.position.top-k.top)||0,left:(e.position.left-k.left)||0},d=function(l,m){c(l).each(function(){var p=c(this),q=c(this).data("resizable-alsoresize"),o={},n=m&&m.length?m:["width","height","top","left"];c.each(n||["width","height","top","left"],function(r,t){var s=(q[t]||0)+(j[t]||0);if(s&&s>=0){o[t]=s||null}});if(/relative/.test(p.css("position"))&&c.browser.opera){e._revertToRelativePosition=true;p.css({position:"absolute",top:"auto",left:"auto"})}p.css(o)})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.nodeType){c.each(i.alsoResize,function(l,m){d(l,m)})}else{d(i.alsoResize)}},stop:function(e,f){var d=c(this).data("resizable");if(d._revertToRelativePosition&&c.browser.opera){d._revertToRelativePosition=false;el.css({position:"relative"})}c(this).removeData("resizable-alsoresize-start")}});c.ui.plugin.add("resizable","animate",{stop:function(h,m){var n=c(this).data("resizable"),i=n.options;var g=n._proportionallyResizeElements,d=g.length&&(/textarea/i).test(g[0].nodeName),e=d&&c.ui.hasScroll(g[0],"left")?0:n.sizeDiff.height,k=d?0:n.sizeDiff.width;var f={width:(n.size.width-k),height:(n.size.height-e)},j=(parseInt(n.element.css("left"),10)+(n.position.left-n.originalPosition.left))||null,l=(parseInt(n.element.css("top"),10)+(n.position.top-n.originalPosition.top))||null;n.element.animate(c.extend(f,l&&j?{top:l,left:j}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var o={width:parseInt(n.element.css("width"),10),height:parseInt(n.element.css("height"),10),top:parseInt(n.element.css("top"),10),left:parseInt(n.element.css("left"),10)};if(g&&g.length){c(g[0]).css({width:o.width,height:o.height})}n._updateCache(o);n._propagate("resize",h)}})}});c.ui.plugin.add("resizable","containment",{start:function(e,q){var s=c(this).data("resizable"),i=s.options,k=s.element;var f=i.containment,j=(f instanceof c)?f.get(0):(/parent/.test(f))?k.parent().get(0):f;if(!j){return}s.containerElement=c(j);if(/document/.test(f)||f==document){s.containerOffset={left:0,top:0};s.containerPosition={left:0,top:0};s.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var m=c(j),h=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){h[p]=b(m.css("padding"+o))});s.containerOffset=m.offset();s.containerPosition=m.position();s.containerSize={height:(m.innerHeight()-h[3]),width:(m.innerWidth()-h[1])};var n=s.containerOffset,d=s.containerSize.height,l=s.containerSize.width,g=(c.ui.hasScroll(j,"left")?j.scrollWidth:l),r=(c.ui.hasScroll(j)?j.scrollHeight:d);s.parentData={element:j,left:n.left,top:n.top,width:g,height:r}}},resize:function(f,p){var s=c(this).data("resizable"),h=s.options,e=s.containerSize,n=s.containerOffset,l=s.size,m=s.position,q=s._aspectRatio||f.shiftKey,d={top:0,left:0},g=s.containerElement;if(g[0]!=document&&(/static/).test(g.css("position"))){d=n}if(m.left<(s._helper?n.left:0)){s.size.width=s.size.width+(s._helper?(s.position.left-n.left):(s.position.left-d.left));if(q){s.size.height=s.size.width/h.aspectRatio}s.position.left=h.helper?n.left:0}if(m.top<(s._helper?n.top:0)){s.size.height=s.size.height+(s._helper?(s.position.top-n.top):s.position.top);if(q){s.size.width=s.size.height*h.aspectRatio}s.position.top=s._helper?n.top:0}s.offset.left=s.parentData.left+s.position.left;s.offset.top=s.parentData.top+s.position.top;var k=Math.abs((s._helper?s.offset.left-d.left:(s.offset.left-d.left))+s.sizeDiff.width),r=Math.abs((s._helper?s.offset.top-d.top:(s.offset.top-n.top))+s.sizeDiff.height);var j=s.containerElement.get(0)==s.element.parent().get(0),i=/relative|absolute/.test(s.containerElement.css("position"));if(j&&i){k-=s.parentData.left}if(k+s.size.width>=s.parentData.width){s.size.width=s.parentData.width-k;if(q){s.size.height=s.size.width/s.aspectRatio}}if(r+s.size.height>=s.parentData.height){s.size.height=s.parentData.height-r;if(q){s.size.width=s.size.height*s.aspectRatio}}},stop:function(e,m){var p=c(this).data("resizable"),f=p.options,k=p.position,l=p.containerOffset,d=p.containerPosition,g=p.containerElement;var i=c(p.helper),q=i.offset(),n=i.outerWidth()-p.sizeDiff.width,j=i.outerHeight()-p.sizeDiff.height;if(p._helper&&!f.animate&&(/relative/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}if(p._helper&&!f.animate&&(/static/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}}});c.ui.plugin.add("resizable","ghost",{start:function(f,g){var d=c(this).data("resizable"),h=d.options,e=d.size;d.ghost=d.originalElement.clone();d.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof h.ghost=="string"?h.ghost:"");d.ghost.appendTo(d.helper)},resize:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost){d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})}},stop:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost&&d.helper){d.helper.get(0).removeChild(d.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(d,l){var n=c(this).data("resizable"),g=n.options,j=n.size,h=n.originalSize,i=n.originalPosition,m=n.axis,k=g._aspectRatio||d.shiftKey;g.grid=typeof g.grid=="number"?[g.grid,g.grid]:g.grid;var f=Math.round((j.width-h.width)/(g.grid[0]||1))*(g.grid[0]||1),e=Math.round((j.height-h.height)/(g.grid[1]||1))*(g.grid[1]||1);if(/^(se|s|e)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e}else{if(/^(ne)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e}else{if(/^(sw)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.left=i.left-f}else{n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e;n.position.left=i.left-f}}}}});var b=function(d){return parseInt(d,10)||0};var a=function(d){return !isNaN(parseInt(d,10))}})(jQuery);;/*
- * jQuery UI Selectable 1.7.2
+(function(e){e.widget("ui.resizable",e.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var b=this,a=this.options;this.element.addClass("ui-resizable");e.extend(this,{_aspectRatio:!!a.aspectRatio,aspectRatio:a.aspectRatio,originalElement:this.element,
+_proportionallyResizeElements:[],_helper:a.helper||a.ghost||a.animate?a.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&e.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(e('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),
+top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=
+this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",
+nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d<c.length;d++){var f=e.trim(c[d]),g=e('<div class="ui-resizable-handle '+("ui-resizable-"+f)+'"></div>');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor==
+String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),k=0;k=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,k);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection();
+this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){e(this).removeClass("ui-resizable-autohide");b._handles.show()},function(){if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};
+if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(),
+d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset=
+this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio:
+this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize",
+b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height;
+f={width:c.size.width-(f?0:c.sizeDiff.width),height:c.size.height-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",
+b);this._helper&&this.helper.remove();return false},_updateCache:function(b){this.offset=this.helper.offset();if(l(b.left))this.position.left=b.left;if(l(b.top))this.position.top=b.top;if(l(b.height))this.size.height=b.height;if(l(b.width))this.size.width=b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(b.height)b.width=c.height*this.aspectRatio;else if(b.width)b.height=c.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top=
+a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this.options,c=this.axis,d=l(b.width)&&a.maxWidth&&a.maxWidth<b.width,f=l(b.height)&&a.maxHeight&&a.maxHeight<b.height,g=l(b.width)&&a.minWidth&&a.minWidth>b.width,h=l(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height,
+k=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&k)b.left=i-a.minWidth;if(d&&k)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a<this._proportionallyResizeElements.length;a++){var c=this._proportionallyResizeElements[a];if(!this.borderDif){var d=[c.css("borderTopWidth"),
+c.css("borderRightWidth"),c.css("borderBottomWidth"),c.css("borderLeftWidth")],f=[c.css("paddingTop"),c.css("paddingRight"),c.css("paddingBottom"),c.css("paddingLeft")];this.borderDif=e.map(d,function(g,h){g=parseInt(g,10)||0;h=parseInt(f[h],10)||0;return g+h})}e.browser.msie&&(e(b).is(":hidden")||e(b).parents(":hidden").length)||c.css({height:b.height()-this.borderDif[0]-this.borderDif[2]||0,width:b.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var b=this.options;this.elementOffset=
+this.element.offset();if(this._helper){this.helper=this.helper||e('<div style="overflow:hidden;"></div>');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+
+a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this,
+arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable,
+{version:"1.8.5"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize,
+function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var k=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:k.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n=
+(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(k.css("position"))){c._revertToRelativePosition=true;k.css({position:"absolute",top:"auto",left:"auto"})}k.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition=
+false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left-
+a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize",
+b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top",
+"Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset,
+f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left=
+a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+
+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&&
+e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative",
+height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width=
+d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},l=function(b){return!isNaN(parseInt(b,10))}})(jQuery);
+;/*
+ * jQuery UI Selectable 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Selectables
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.selectable",a.extend({},a.ui.mouse,{_init:function(){var b=this;this.element.addClass("ui-selectable");this.dragged=false;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]);c.each(function(){var d=a(this);var e=d.offset();a.data(this,"selectable-item",{element:this,$element:d,left:e.left,top:e.top,right:e.left+d.outerWidth(),bottom:e.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"),selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=a(document.createElement("div")).css({border:"1px dotted black"}).addClass("ui-selectable-helper")},destroy:function(){this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy()},_mouseStart:function(d){var b=this;this.opos=[d.pageX,d.pageY];if(this.options.disabled){return}var c=this.options;this.selectees=a(c.filter,this.element[0]);this._trigger("start",d);a(c.appendTo).append(this.helper);this.helper.css({"z-index":100,position:"absolute",left:d.clientX,top:d.clientY,width:0,height:0});if(c.autoRefresh){this.refresh()}this.selectees.filter(".ui-selected").each(function(){var e=a.data(this,"selectable-item");e.startselected=true;if(!d.metaKey){e.$element.removeClass("ui-selected");e.selected=false;e.$element.addClass("ui-unselecting");e.unselecting=true;b._trigger("unselecting",d,{unselecting:e.element})}});a(d.target).parents().andSelf().each(function(){var e=a.data(this,"selectable-item");if(e){e.$element.removeClass("ui-unselecting").addClass("ui-selecting");e.unselecting=false;e.selecting=true;e.selected=true;b._trigger("selecting",d,{selecting:e.element});return false}})},_mouseDrag:function(i){var c=this;this.dragged=true;if(this.options.disabled){return}var e=this.options;var d=this.opos[0],h=this.opos[1],b=i.pageX,g=i.pageY;if(d>b){var f=b;b=d;d=f}if(h>g){var f=g;g=h;h=f}this.helper.css({left:d,top:h,width:b-d,height:g-h});this.selectees.each(function(){var j=a.data(this,"selectable-item");if(!j||j.element==c.element[0]){return}var k=false;if(e.tolerance=="touch"){k=(!(j.left>b||j.right<d||j.top>g||j.bottom<h))}else{if(e.tolerance=="fit"){k=(j.left>d&&j.right<b&&j.top>h&&j.bottom<g)}}if(k){if(j.selected){j.$element.removeClass("ui-selected");j.selected=false}if(j.unselecting){j.$element.removeClass("ui-unselecting");j.unselecting=false}if(!j.selecting){j.$element.addClass("ui-selecting");j.selecting=true;c._trigger("selecting",i,{selecting:j.element})}}else{if(j.selecting){if(i.metaKey&&j.startselected){j.$element.removeClass("ui-selecting");j.selecting=false;j.$element.addClass("ui-selected");j.selected=true}else{j.$element.removeClass("ui-selecting");j.selecting=false;if(j.startselected){j.$element.addClass("ui-unselecting");j.unselecting=true}c._trigger("unselecting",i,{unselecting:j.element})}}if(j.selected){if(!i.metaKey&&!j.startselected){j.$element.removeClass("ui-selected");j.selected=false;j.$element.addClass("ui-unselecting");j.unselecting=true;c._trigger("unselecting",i,{unselecting:j.element})}}}});return false},_mouseStop:function(d){var b=this;this.dragged=false;var c=this.options;a(".ui-unselecting",this.element[0]).each(function(){var e=a.data(this,"selectable-item");e.$element.removeClass("ui-unselecting");e.unselecting=false;e.startselected=false;b._trigger("unselected",d,{unselected:e.element})});a(".ui-selecting",this.element[0]).each(function(){var e=a.data(this,"selectable-item");e.$element.removeClass("ui-selecting").addClass("ui-selected");e.selecting=false;e.selected=true;e.startselected=true;b._trigger("selected",d,{selected:e.element})});this._trigger("stop",d);this.helper.remove();return false}}));a.extend(a.ui.selectable,{version:"1.7.2",defaults:{appendTo:"body",autoRefresh:true,cancel:":input,option",delay:0,distance:0,filter:"*",tolerance:"touch"}})})(jQuery);;/*
- * jQuery UI Sortable 1.7.2
+(function(e){e.widget("ui.selectable",e.ui.mouse,{options:{appendTo:"body",autoRefresh:true,distance:0,filter:"*",tolerance:"touch"},_create:function(){var c=this;this.element.addClass("ui-selectable");this.dragged=false;var f;this.refresh=function(){f=e(c.options.filter,c.element[0]);f.each(function(){var d=e(this),b=d.offset();e.data(this,"selectable-item",{element:this,$element:d,left:b.left,top:b.top,right:b.left+d.outerWidth(),bottom:b.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"),
+selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=f.addClass("ui-selectee");this._mouseInit();this.helper=e("<div class='ui-selectable-helper'></div>")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item");this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy();return this},_mouseStart:function(c){var f=this;this.opos=[c.pageX,
+c.pageY];if(!this.options.disabled){var d=this.options;this.selectees=e(d.filter,this.element[0]);this._trigger("start",c);e(d.appendTo).append(this.helper);this.helper.css({left:c.clientX,top:c.clientY,width:0,height:0});d.autoRefresh&&this.refresh();this.selectees.filter(".ui-selected").each(function(){var b=e.data(this,"selectable-item");b.startselected=true;if(!c.metaKey){b.$element.removeClass("ui-selected");b.selected=false;b.$element.addClass("ui-unselecting");b.unselecting=true;f._trigger("unselecting",
+c,{unselecting:b.element})}});e(c.target).parents().andSelf().each(function(){var b=e.data(this,"selectable-item");if(b){var g=!c.metaKey||!b.$element.hasClass("ui-selected");b.$element.removeClass(g?"ui-unselecting":"ui-selected").addClass(g?"ui-selecting":"ui-unselecting");b.unselecting=!g;b.selecting=g;(b.selected=g)?f._trigger("selecting",c,{selecting:b.element}):f._trigger("unselecting",c,{unselecting:b.element});return false}})}},_mouseDrag:function(c){var f=this;this.dragged=true;if(!this.options.disabled){var d=
+this.options,b=this.opos[0],g=this.opos[1],h=c.pageX,i=c.pageY;if(b>h){var j=h;h=b;b=j}if(g>i){j=i;i=g;g=j}this.helper.css({left:b,top:g,width:h-b,height:i-g});this.selectees.each(function(){var a=e.data(this,"selectable-item");if(!(!a||a.element==f.element[0])){var k=false;if(d.tolerance=="touch")k=!(a.left>h||a.right<b||a.top>i||a.bottom<g);else if(d.tolerance=="fit")k=a.left>b&&a.right<h&&a.top>g&&a.bottom<i;if(k){if(a.selected){a.$element.removeClass("ui-selected");a.selected=false}if(a.unselecting){a.$element.removeClass("ui-unselecting");
+a.unselecting=false}if(!a.selecting){a.$element.addClass("ui-selecting");a.selecting=true;f._trigger("selecting",c,{selecting:a.element})}}else{if(a.selecting)if(c.metaKey&&a.startselected){a.$element.removeClass("ui-selecting");a.selecting=false;a.$element.addClass("ui-selected");a.selected=true}else{a.$element.removeClass("ui-selecting");a.selecting=false;if(a.startselected){a.$element.addClass("ui-unselecting");a.unselecting=true}f._trigger("unselecting",c,{unselecting:a.element})}if(a.selected)if(!c.metaKey&&
+!a.startselected){a.$element.removeClass("ui-selected");a.selected=false;a.$element.addClass("ui-unselecting");a.unselecting=true;f._trigger("unselecting",c,{unselecting:a.element})}}}});return false}},_mouseStop:function(c){var f=this;this.dragged=false;e(".ui-unselecting",this.element[0]).each(function(){var d=e.data(this,"selectable-item");d.$element.removeClass("ui-unselecting");d.unselecting=false;d.startselected=false;f._trigger("unselected",c,{unselected:d.element})});e(".ui-selecting",this.element[0]).each(function(){var d=
+e.data(this,"selectable-item");d.$element.removeClass("ui-selecting").addClass("ui-selected");d.selecting=false;d.selected=true;d.startselected=true;f._trigger("selected",c,{selected:d.element})});this._trigger("stop",c);this.helper.remove();return false}});e.extend(e.ui.selectable,{version:"1.8.5"})})(jQuery);
+;/*
+ * jQuery UI Sortable 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Sortables
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.sortable",a.extend({},a.ui.mouse,{_init:function(){var b=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?(/left|right/).test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--){this.items[b].item.removeData("sortable-item")}},_mouseCapture:function(e,f){if(this.reverting){return false}if(this.options.disabled||this.options.type=="static"){return false}this._refreshItems(e);var d=null,c=this,b=a(e.target).parents().each(function(){if(a.data(this,"sortable-item")==c){d=a(this);return false}});if(a.data(e.target,"sortable-item")==c){d=a(e.target)}if(!d){return false}if(this.options.handle&&!f){var g=false;a(this.options.handle,d).find("*").andSelf().each(function(){if(this==e.target){g=true}});if(!g){return false}}this.currentItem=d;this._removeCurrentsFromItems();return true},_mouseStart:function(e,f,b){var g=this.options,c=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(e);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(e);this.originalPageX=e.pageX;this.originalPageY=e.pageY;if(g.cursorAt){this._adjustOffsetFromHelper(g.cursorAt)}this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide()}this._createPlaceholder();if(g.containment){this._setContainment()}if(g.cursor){if(a("body").css("cursor")){this._storedCursor=a("body").css("cursor")}a("body").css("cursor",g.cursor)}if(g.opacity){if(this.helper.css("opacity")){this._storedOpacity=this.helper.css("opacity")}this.helper.css("opacity",g.opacity)}if(g.zIndex){if(this.helper.css("zIndex")){this._storedZIndex=this.helper.css("zIndex")}this.helper.css("zIndex",g.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){this.overflowOffset=this.scrollParent.offset()}this._trigger("start",e,this._uiHash());if(!this._preserveHelperProportions){this._cacheHelperProportions()}if(!b){for(var d=this.containers.length-1;d>=0;d--){this.containers[d]._trigger("activate",e,c._uiHash(this))}}if(a.ui.ddmanager){a.ui.ddmanager.current=this}if(a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,e)}this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(e);return true},_mouseDrag:function(f){this.position=this._generatePosition(f);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}if(this.options.scroll){var g=this.options,b=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-f.pageY<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop+g.scrollSpeed}else{if(f.pageY-this.overflowOffset.top<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop-g.scrollSpeed}}if((this.overflowOffset.left+this.scrollParent[0].offsetWidth)-f.pageX<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft+g.scrollSpeed}else{if(f.pageX-this.overflowOffset.left<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft-g.scrollSpeed}}}else{if(f.pageY-a(document).scrollTop()<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-g.scrollSpeed)}else{if(a(window).height()-(f.pageY-a(document).scrollTop())<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+g.scrollSpeed)}}if(f.pageX-a(document).scrollLeft()<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-g.scrollSpeed)}else{if(a(window).width()-(f.pageX-a(document).scrollLeft())<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+g.scrollSpeed)}}}if(b!==false&&a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,f)}}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d],c=e.item[0],h=this._intersectsWithPointer(e);if(!h){continue}if(c!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=c&&!a.ui.contains(this.placeholder[0],c)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],c):true)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(e)){this._rearrange(f,e)}else{break}this._trigger("change",f,this._uiHash());break}}this._contactContainers(f);if(a.ui.ddmanager){a.ui.ddmanager.drag(this,f)}this._trigger("sort",f,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(c,d){if(!c){return}if(a.ui.ddmanager&&!this.options.dropBehaviour){a.ui.ddmanager.drop(this,c)}if(this.options.revert){var b=this;var e=b.placeholder.offset();b.reverting=true;a(this.helper).animate({left:e.left-this.offset.parent.left-b.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-b.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){b._clear(c)})}else{this._clear(c,d)}return false},cancel:function(){var b=this;if(this.dragging){this._mouseUp();if(this.options.helper=="original"){this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,b._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,b._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder[0].parentNode){this.placeholder[0].parentNode.removeChild(this.placeholder[0])}if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode){this.helper.remove()}a.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){a(this.domPosition.prev).after(this.currentItem)}else{a(this.domPosition.parent).prepend(this.currentItem)}return true},serialize:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};a(b).each(function(){var e=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||(/(.+)[-=_](.+)/));if(e){c.push((d.key||e[1]+"[]")+"="+(d.key&&d.expression?e[1]:e[2]))}});return c.join("&")},toArray:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};b.each(function(){c.push(a(d.item||this).attr(d.attribute||"id")||"")});return c},_intersectsWith:function(m){var e=this.positionAbs.left,d=e+this.helperProportions.width,k=this.positionAbs.top,j=k+this.helperProportions.height;var f=m.left,c=f+m.width,n=m.top,i=n+m.height;var o=this.offset.click.top,h=this.offset.click.left;var g=(k+o)>n&&(k+o)<i&&(e+h)>f&&(e+h)<c;if(this.options.tolerance=="pointer"||this.options.forcePointerForContainers||(this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>m[this.floating?"width":"height"])){return g}else{return(f<e+(this.helperProportions.width/2)&&d-(this.helperProportions.width/2)<c&&n<k+(this.helperProportions.height/2)&&j-(this.helperProportions.height/2)<i)}},_intersectsWithPointer:function(d){var e=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top,d.height),c=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left,d.width),g=e&&c,b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(!g){return false}return this.floating?(((f&&f=="right")||b=="down")?2:1):(b&&(b=="down"?2:1))},_intersectsWithSides:function(e){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+(e.height/2),e.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+(e.width/2),e.width),b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(this.floating&&f){return((f=="right"&&d)||(f=="left"&&!d))}else{return b&&((b=="down"&&c)||(b=="up"&&!c))}},_getDragVerticalDirection:function(){var b=this.positionAbs.top-this.lastPositionAbs.top;return b!=0&&(b>0?"down":"up")},_getDragHorizontalDirection:function(){var b=this.positionAbs.left-this.lastPositionAbs.left;return b!=0&&(b>0?"right":"left")},refresh:function(b){this._refreshItems(b);this.refreshPositions()},_connectWith:function(){var b=this.options;return b.connectWith.constructor==String?[b.connectWith]:b.connectWith},_getItemsAsjQuery:function(b){var l=this;var g=[];var e=[];var h=this._connectWith();if(h&&b){for(var d=h.length-1;d>=0;d--){var k=a(h[d]);for(var c=k.length-1;c>=0;c--){var f=a.data(k[c],"sortable");if(f&&f!=this&&!f.options.disabled){e.push([a.isFunction(f.options.items)?f.options.items.call(f.element):a(f.options.items,f.element).not(".ui-sortable-helper"),f])}}}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper"),this]);for(var d=e.length-1;d>=0;d--){e[d][0].each(function(){g.push(this)})}return a(g)},_removeCurrentsFromItems:function(){var d=this.currentItem.find(":data(sortable-item)");for(var c=0;c<this.items.length;c++){for(var b=0;b<d.length;b++){if(d[b]==this.items[c].item[0]){this.items.splice(c,1)}}}},_refreshItems:function(b){this.items=[];this.containers=[this];var h=this.items;var p=this;var f=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]];var l=this._connectWith();if(l){for(var e=l.length-1;e>=0;e--){var m=a(l[e]);for(var d=m.length-1;d>=0;d--){var g=a.data(m[d],"sortable");if(g&&g!=this&&!g.options.disabled){f.push([a.isFunction(g.options.items)?g.options.items.call(g.element[0],b,{item:this.currentItem}):a(g.options.items,g.element),g]);this.containers.push(g)}}}}for(var e=f.length-1;e>=0;e--){var k=f[e][1];var c=f[e][0];for(var d=0,n=c.length;d<n;d++){var o=a(c[d]);o.data("sortable-item",k);h.push({item:o,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){if(this.offsetParent&&this.helper){this.offset.parent=this._getParentOffset()}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d];if(e.instance!=this.currentContainer&&this.currentContainer&&e.item[0]!=this.currentItem[0]){continue}var c=this.options.toleranceElement?a(this.options.toleranceElement,e.item):e.item;if(!b){e.width=c.outerWidth();e.height=c.outerHeight()}var f=c.offset();e.left=f.left;e.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this)}else{for(var d=this.containers.length-1;d>=0;d--){var f=this.containers[d].element.offset();this.containers[d].containerCache.left=f.left;this.containers[d].containerCache.top=f.top;this.containers[d].containerCache.width=this.containers[d].element.outerWidth();this.containers[d].containerCache.height=this.containers[d].element.outerHeight()}}},_createPlaceholder:function(d){var b=d||this,e=b.options;if(!e.placeholder||e.placeholder.constructor==String){var c=e.placeholder;e.placeholder={element:function(){var f=a(document.createElement(b.currentItem[0].nodeName)).addClass(c||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!c){f.style.visibility="hidden"}return f},update:function(f,g){if(c&&!e.forcePlaceholderSize){return}if(!g.height()){g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10))}if(!g.width()){g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=a(e.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);e.placeholder.update(b,b.placeholder)},_contactContainers:function(d){for(var c=this.containers.length-1;c>=0;c--){if(this._intersectsWith(this.containers[c].containerCache)){if(!this.containers[c].containerCache.over){if(this.currentContainer!=this.containers[c]){var h=10000;var g=null;var e=this.positionAbs[this.containers[c].floating?"left":"top"];for(var b=this.items.length-1;b>=0;b--){if(!a.ui.contains(this.containers[c].element[0],this.items[b].item[0])){continue}var f=this.items[b][this.containers[c].floating?"left":"top"];if(Math.abs(f-e)<h){h=Math.abs(f-e);g=this.items[b]}}if(!g&&!this.options.dropOnEmpty){continue}this.currentContainer=this.containers[c];g?this._rearrange(d,g,null,true):this._rearrange(d,null,this.containers[c].element,true);this._trigger("change",d,this._uiHash());this.containers[c]._trigger("change",d,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder)}this.containers[c]._trigger("over",d,this._uiHash(this));this.containers[c].containerCache.over=1}}else{if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",d,this._uiHash(this));this.containers[c].containerCache.over=0}}}},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c,this.currentItem])):(d.helper=="clone"?this.currentItem.clone():this.currentItem);if(!b.parents("body").length){a(d.appendTo!="parent"?d.appendTo:this.currentItem[0].parentNode)[0].appendChild(b[0])}if(b[0]==this.currentItem[0]){this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}}if(b[0].style.width==""||d.forceHelperSize){b.width(this.currentItem.width())}if(b[0].style.height==""||d.forceHelperSize){b.height(this.currentItem.height())}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.currentItem.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.currentItem.css("marginLeft"),10)||0),top:(parseInt(this.currentItem.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)){var c=a(e.containment)[0];var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_rearrange:function(g,f,c,e){c?c[0].appendChild(this.placeholder[0]):f.item[0].parentNode.insertBefore(this.placeholder[0],(this.direction=="down"?f.item[0]:f.item[0].nextSibling));this.counter=this.counter?++this.counter:1;var d=this,b=this.counter;window.setTimeout(function(){if(b==d.counter){d.refreshPositions(!e)}},0)},_clear:function(d,e){this.reverting=false;var f=[],b=this;if(!this._noFinalSort&&this.currentItem[0].parentNode){this.placeholder.before(this.currentItem)}this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var c in this._storedCSS){if(this._storedCSS[c]=="auto"||this._storedCSS[c]=="static"){this._storedCSS[c]=""}}this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}if(this.fromOutside&&!e){f.push(function(g){this._trigger("receive",g,this._uiHash(this.fromOutside))})}if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!e){f.push(function(g){this._trigger("update",g,this._uiHash())})}if(!a.ui.contains(this.element[0],this.currentItem[0])){if(!e){f.push(function(g){this._trigger("remove",g,this._uiHash())})}for(var c=this.containers.length-1;c>=0;c--){if(a.ui.contains(this.containers[c].element[0],this.currentItem[0])&&!e){f.push((function(g){return function(h){g._trigger("receive",h,this._uiHash(this))}}).call(this,this.containers[c]));f.push((function(g){return function(h){g._trigger("update",h,this._uiHash(this))}}).call(this,this.containers[c]))}}}for(var c=this.containers.length-1;c>=0;c--){if(!e){f.push((function(g){return function(h){g._trigger("deactivate",h,this._uiHash(this))}}).call(this,this.containers[c]))}if(this.containers[c].containerCache.over){f.push((function(g){return function(h){g._trigger("out",h,this._uiHash(this))}}).call(this,this.containers[c]));this.containers[c].containerCache.over=0}}if(this._storedCursor){a("body").css("cursor",this._storedCursor)}if(this._storedOpacity){this.helper.css("opacity",this._storedOpacity)}if(this._storedZIndex){this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex)}this.dragging=false;if(this.cancelHelperRemoval){if(!e){this._trigger("beforeStop",d,this._uiHash());for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}return false}if(!e){this._trigger("beforeStop",d,this._uiHash())}this.placeholder[0].parentNode.removeChild(this.placeholder[0]);if(this.helper[0]!=this.currentItem[0]){this.helper.remove()}this.helper=null;if(!e){for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){if(a.widget.prototype._trigger.apply(this,arguments)===false){this.cancel()}},_uiHash:function(c){var b=c||this;return{helper:b.helper,placeholder:b.placeholder||a([]),position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs,item:b.currentItem,sender:c?c.element:null}}}));a.extend(a.ui.sortable,{getter:"serialize toArray",version:"1.7.2",eventPrefix:"sort",defaults:{appendTo:"parent",axis:false,cancel:":input,option",connectWith:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000}})})(jQuery);;/*
- * jQuery UI Accordion 1.7.2
+(function(d){d.widget("ui.sortable",d.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){this.containerCache={};this.element.addClass("ui-sortable");
+this.refresh();this.floating=this.items.length?/left|right/.test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a==="disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this,
+arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem=
+c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset,
+{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment();
+if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",
+a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");
+if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY<b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop+b.scrollSpeed;else if(a.pageY-this.overflowOffset.top<b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop-b.scrollSpeed;if(this.overflowOffset.left+
+this.scrollParent[0].offsetWidth-a.pageX<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft+b.scrollSpeed;else if(a.pageX-this.overflowOffset.left<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft-b.scrollSpeed}else{if(a.pageY-d(document).scrollTop()<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()-b.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()+
+b.scrollSpeed);if(a.pageX-d(document).scrollLeft()<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()-b.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()+b.scrollSpeed)}c!==false&&d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+
+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(b=this.items.length-1;b>=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a,
+c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]==
+document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp();this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate",
+null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem):
+d(this.domPosition.parent).prepend(this.currentItem);return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")},toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||
+"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+j<k&&b+l>g&&b+l<h;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?j:g<b+
+this.helperProportions.width/2&&c-this.helperProportions.width/2<h&&i<e+this.helperProportions.height/2&&f-this.helperProportions.height/2<k},_intersectsWithPointer:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left,a.width);b=b&&a;a=this._getDragVerticalDirection();var c=this._getDragHorizontalDirection();if(!b)return false;return this.floating?c&&c=="right"||a=="down"?2:1:a&&(a=="down"?
+2:1)},_intersectsWithSides:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top+a.height/2,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left+a.width/2,a.width);var c=this._getDragVerticalDirection(),e=this._getDragHorizontalDirection();return this.floating&&e?e=="right"&&a||e=="left"&&!a:c&&(c=="down"&&b||c=="up"&&!b)},_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},
+_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!=
+this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a=
+this.currentItem.find(":data(sortable-item)"),b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(a){this.items=[];this.containers=[this];var b=this.items,c=[[d.isFunction(this.options.items)?this.options.items.call(this.element[0],a,{item:this.currentItem}):d(this.options.items,this.element),this]],e=this._connectWith();if(e)for(var f=e.length-1;f>=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");
+if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h<g;h++){i=d(e[h]);i.data("sortable-item",a);b.push({item:i,instance:a,width:0,height:0,left:0,top:0})}}},refreshPositions:function(a){if(this.offsetParent&&this.helper)this.offset.parent=this._getParentOffset();for(var b=this.items.length-1;b>=
+0;b--){var c=this.items[b],e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width=
+this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f},
+update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=
+null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this));
+this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-f)<b){b=Math.abs(h-f);e=this.items[g]}}if(e||this.options.dropOnEmpty){this.currentContainer=this.containers[c];e?this._rearrange(a,e,null,true):this._rearrange(a,
+null,this.containers[c].element,true);this._trigger("change",a,this._uiHash());this.containers[c]._trigger("change",a,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder);this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}}},_createHelper:function(a){var b=this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a,this.currentItem])):b.helper=="clone"?this.currentItem.clone():this.currentItem;a.parents("body").length||
+d(b.appendTo!="parent"?b.appendTo:this.currentItem[0].parentNode)[0].appendChild(a[0]);if(a[0]==this.currentItem[0])this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")};if(a[0].style.width==""||b.forceHelperSize)a.width(this.currentItem.width());if(a[0].style.height==""||b.forceHelperSize)a.height(this.currentItem.height());return a},_adjustOffsetFromHelper:function(a){if(typeof a==
+"string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition==
+"absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a={top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition==
+"relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},
+_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,d(a.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-
+this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)){var b=d(a.containment)[0];a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"),10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),
+10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(a,b){if(!b)b=this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?
+this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=
+this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0]))this.offset.relative=this._getRelativeOffset();var f=a.pageX,g=a.pageY;if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.left<this.containment[0])f=this.containment[0]+
+this.offset.click.left;if(a.pageY-this.offset.click.top<this.containment[1])g=this.containment[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?
+g:!(g-this.offset.click.top<this.containment[1])?g-b.grid[1]:g+b.grid[1]:g;f=this.originalPageX+Math.round((f-this.originalPageX)/b.grid[0])*b.grid[0];f=this.containment?!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:!(f-this.offset.click.left<this.containment[0])?f-b.grid[0]:f+b.grid[0]:f}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():
+e?0:c.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())}},_rearrange:function(a,b,c,e){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?b.item[0]:b.item[0].nextSibling);this.counter=this.counter?++this.counter:1;var f=this,g=this.counter;window.setTimeout(function(){g==
+f.counter&&f.refreshPositions(!e)},0)},_clear:function(a,b){this.reverting=false;var c=[];!this._noFinalSort&&this.currentItem[0].parentNode&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var e in this._storedCSS)if(this._storedCSS[e]=="auto"||this._storedCSS[e]=="static")this._storedCSS[e]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!b&&c.push(function(f){this._trigger("receive",
+f,this._uiHash(this.fromOutside))});if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!b)c.push(function(f){this._trigger("update",f,this._uiHash())});if(!d.ui.contains(this.element[0],this.currentItem[0])){b||c.push(function(f){this._trigger("remove",f,this._uiHash())});for(e=this.containers.length-1;e>=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",
+g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=
+0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",a,this._uiHash());for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}return false}b||this._trigger("beforeStop",a,this._uiHash());this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+this.helper[0]!=this.currentItem[0]&&this.helper.remove();this.helper=null;if(!b){for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){d.Widget.prototype._trigger.apply(this,arguments)===false&&this.cancel()},_uiHash:function(a){var b=a||this;return{helper:b.helper,placeholder:b.placeholder||d([]),position:b.position,originalPosition:b.originalPosition,offset:b.positionAbs,item:b.currentItem,sender:a?a.element:null}}});
+d.extend(d.ui.sortable,{version:"1.8.5"})})(jQuery);
+;/*
+ * jQuery UI Accordion 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Accordion
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function(c){c.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");
+a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
+if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var f=d.closest(".ui-accordion-header");a.active=f.length?f:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion",function(g){return a._keydown(g)}).next().attr("role",
+"tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(g){a._clickHandler.call(a,g,this);g.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("<span></span>").addClass("ui-icon "+a.icons.header).prependTo(this.headers);
+this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex");
+this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons();
+b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,f=this.headers.index(a.target),g=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:g=this.headers[(f+1)%d];break;case b.LEFT:case b.UP:g=this.headers[(f-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target);
+a.preventDefault()}if(g){c(a.target).attr("tabIndex",-1);c(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+
+c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options;
+if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);
+a.next().addClass("ui-accordion-content-active")}h=a.next();f=this.active.next();g={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):h,oldContent:f};d=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(h,f,g,b,d)}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);
+this.active.next().addClass("ui-accordion-content-active");var f=this.active.next(),g={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:f},h=this.active=c([]);this._toggle(h,f,g)}},_toggle:function(a,b,d,f,g){var h=this,e=h.options;h.toShow=a;h.toHide=b;h.data=d;var j=function(){if(h)return h._completed.apply(h,arguments)};h._trigger("changestart",null,h.data);h.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&f?{toShow:c([]),toHide:b,complete:j,
+down:g,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:g,autoHeight:e.autoHeight||e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;f=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!f[k]&&!c.easing[k])k="slide";f[k]||(f[k]=function(l){this.slide(l,{easing:k,duration:i||700})});
+f[k](d)}else{if(e.collapsible&&f)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.5",animations:{slide:function(a,
+b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),f=0,g={},h={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){h[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);g[i]={value:j[1],
+unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(h,{step:function(j,i){if(i.prop=="height")f=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=f*g[i.prop].value+g[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide",
+paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery);
+;/*
+ * jQuery UI Autocomplete 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.position.js
*/
-(function(a){a.widget("ui.accordion",{_init:function(){var d=this.options,b=this;this.running=0;if(d.collapsible==a.ui.accordion.defaults.collapsible&&d.alwaysOpen!=a.ui.accordion.defaults.alwaysOpen){d.collapsible=!d.alwaysOpen}if(d.navigation){var c=this.element.find("a").filter(d.navigationFilter);if(c.length){if(c.filter(d.header).length){this.active=c}else{this.active=c.parent().parent().prev();c.addClass("ui-accordion-content-active")}}}this.element.addClass("ui-accordion ui-widget ui-helper-reset");if(this.element[0].nodeName=="UL"){this.element.children("li").addClass("ui-accordion-li-fix")}this.headers=this.element.find(d.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){a(this).removeClass("ui-state-focus")});this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");this.active=this._findActive(this.active||d.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");this.active.next().addClass("ui-accordion-content-active");a("<span/>").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.find(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);if(a.browser.msie){this.element.find("a").css("zoom","1")}this.resize();this.element.attr("role","tablist");this.headers.attr("role","tab").bind("keydown",function(e){return b._keydown(e)}).next().attr("role","tabpanel");this.headers.not(this.active||"").attr("aria-expanded","false").attr("tabIndex","-1").next().hide();if(!this.active.length){this.headers.eq(0).attr("tabIndex","0")}else{this.active.attr("aria-expanded","true").attr("tabIndex","0")}if(!a.browser.safari){this.headers.find("a").attr("tabIndex","-1")}if(d.event){this.headers.bind((d.event)+".accordion",function(e){return b._clickHandler.call(b,e,this)})}},destroy:function(){var c=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role").unbind(".accordion").removeData("accordion");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex");this.headers.find("a").removeAttr("tabindex");this.headers.children(".ui-icon").remove();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active");if(c.autoHeight||c.fillHeight){b.css("height","")}},_setData:function(b,c){if(b=="alwaysOpen"){b="collapsible";c=!c}a.widget.prototype._setData.apply(this,arguments)},_keydown:function(e){var g=this.options,f=a.ui.keyCode;if(g.disabled||e.altKey||e.ctrlKey){return}var d=this.headers.length;var b=this.headers.index(e.target);var c=false;switch(e.keyCode){case f.RIGHT:case f.DOWN:c=this.headers[(b+1)%d];break;case f.LEFT:case f.UP:c=this.headers[(b-1+d)%d];break;case f.SPACE:case f.ENTER:return this._clickHandler({target:e.target},e.target)}if(c){a(e.target).attr("tabIndex","-1");a(c).attr("tabIndex","0");c.focus();return false}return true},resize:function(){var e=this.options,d;if(e.fillSpace){if(a.browser.msie){var b=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}d=this.element.parent().height();if(a.browser.msie){this.element.parent().css("overflow",b)}this.headers.each(function(){d-=a(this).outerHeight()});var c=0;this.headers.next().each(function(){c=Math.max(c,a(this).innerHeight()-a(this).height())}).height(Math.max(0,d-c)).css("overflow","auto")}else{if(e.autoHeight){d=0;this.headers.next().each(function(){d=Math.max(d,a(this).outerHeight())}).height(d)}}},activate:function(b){var c=this._findActive(b)[0];this._clickHandler({target:c},c)},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,f){var d=this.options;if(d.disabled){return false}if(!b.target&&d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var h=this.active.next(),e={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:h},c=(this.active=a([]));this._toggle(c,h,e);return false}var g=a(b.currentTarget||f);var i=g[0]==this.active[0];if(this.running||(!d.collapsible&&i)){return false}this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");if(!i){g.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").find(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);g.next().addClass("ui-accordion-content-active")}var c=g.next(),h=this.active.next(),e={options:d,newHeader:i&&d.collapsible?a([]):g,oldHeader:this.active,newContent:i&&d.collapsible?a([]):c.find("> *"),oldContent:h.find("> *")},j=this.headers.index(this.active[0])>this.headers.index(g[0]);this.active=i?a([]):g;this._toggle(c,h,e,i,j);return false},_toggle:function(b,i,g,j,k){var d=this.options,m=this;this.toShow=b;this.toHide=i;this.data=g;var c=function(){if(!m){return}return m._completed.apply(m,arguments)};this._trigger("changestart",null,this.data);this.running=i.size()===0?b.size():i.size();if(d.animated){var f={};if(d.collapsible&&j){f={toShow:a([]),toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}else{f={toShow:b,toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}if(!d.proxied){d.proxied=d.animated}if(!d.proxiedDuration){d.proxiedDuration=d.duration}d.animated=a.isFunction(d.proxied)?d.proxied(f):d.proxied;d.duration=a.isFunction(d.proxiedDuration)?d.proxiedDuration(f):d.proxiedDuration;var l=a.ui.accordion.animations,e=d.duration,h=d.animated;if(!l[h]){l[h]=function(n){this.slide(n,{easing:h,duration:e||700})}}l[h](f)}else{if(d.collapsible&&j){b.toggle()}else{i.hide();b.show()}c(true)}i.prev().attr("aria-expanded","false").attr("tabIndex","-1").blur();b.prev().attr("aria-expanded","true").attr("tabIndex","0").focus()},_completed:function(b){var c=this.options;this.running=b?0:--this.running;if(this.running){return}if(c.clearStyle){this.toShow.add(this.toHide).css({height:"",overflow:""})}this._trigger("change",null,this.data)}});a.extend(a.ui.accordion,{version:"1.7.2",defaults:{active:null,alwaysOpen:true,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()==location.href.toLowerCase()}},animations:{slide:function(j,h){j=a.extend({easing:"swing",duration:300},j,h);if(!j.toHide.size()){j.toShow.animate({height:"show"},j);return}if(!j.toShow.size()){j.toHide.animate({height:"hide"},j);return}var c=j.toShow.css("overflow"),g,d={},f={},e=["height","paddingTop","paddingBottom"],b;var i=j.toShow;b=i[0].style.width;i.width(parseInt(i.parent().width(),10)-parseInt(i.css("paddingLeft"),10)-parseInt(i.css("paddingRight"),10)-(parseInt(i.css("borderLeftWidth"),10)||0)-(parseInt(i.css("borderRightWidth"),10)||0));a.each(e,function(k,m){f[m]="hide";var l=(""+a.css(j.toShow[0],m)).match(/^([\d+-.]+)(.*)$/);d[m]={value:l[1],unit:l[2]||"px"}});j.toShow.css({height:0,overflow:"hidden"}).show();j.toHide.filter(":hidden").each(j.complete).end().filter(":visible").animate(f,{step:function(k,l){if(l.prop=="height"){g=(l.now-l.start)/(l.end-l.start)}j.toShow[0].style[l.prop]=(g*d[l.prop].value)+d[l.prop].unit},duration:j.duration,easing:j.easing,complete:function(){if(!j.autoHeight){j.toShow.css("height","")}j.toShow.css("width",b);j.toShow.css({overflow:c});j.complete()}})},bounceslide:function(b){this.slide(b,{easing:b.down?"easeOutBounce":"swing",duration:b.down?1000:200})},easeslide:function(b){this.slide(b,{easing:"easeinout",duration:700})}}})})(jQuery);;/*
- * jQuery UI Dialog 1.7.2
+(function(e){e.widget("ui.autocomplete",{options:{appendTo:"body",delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},_create:function(){var a=this,b=this.element[0].ownerDocument;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!a.options.disabled){var d=e.ui.keyCode;switch(c.keyCode){case d.PAGE_UP:a._move("previousPage",
+c);break;case d.PAGE_DOWN:a._move("nextPage",c);break;case d.UP:a._move("previous",c);c.preventDefault();break;case d.DOWN:a._move("next",c);c.preventDefault();break;case d.ENTER:case d.NUMPAD_ENTER:a.menu.element.is(":visible")&&c.preventDefault();case d.TAB:if(!a.menu.active)return;a.menu.select(c);break;case d.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!=a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);
+break}}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)};this.menu=e("<ul></ul>").addClass("ui-autocomplete").appendTo(e(this.options.appendTo||"body",b)[0]).mousedown(function(c){var d=a.menu.element[0];
+c.target===d&&setTimeout(function(){e(document).one("mousedown",function(f){f.target!==a.element[0]&&f.target!==d&&!e.ui.contains(d,f.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,d){d=d.item.data("item.autocomplete");false!==a._trigger("focus",null,{item:d})&&/^key/.test(c.originalEvent.type)&&a.element.val(d.value)},selected:function(c,d){d=d.item.data("item.autocomplete");var f=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();
+a.previous=f}if(false!==a._trigger("select",c,{item:d})){a.term=d.value;a.element.val(d.value)}a.close(c);a.selectedItem=d},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu");e.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");
+this.menu.element.remove();e.Widget.prototype.destroy.call(this)},_setOption:function(a,b){e.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(e(b||"body",this.element[0].ownerDocument)[0])},_initSource:function(){var a=this,b,c;if(e.isArray(this.options.source)){b=this.options.source;this.source=function(d,f){f(e.ui.autocomplete.filter(b,d.term))}}else if(typeof this.options.source==="string"){c=this.options.source;this.source=
+function(d,f){a.xhr&&a.xhr.abort();a.xhr=e.getJSON(c,d,function(g,i,h){h===a.xhr&&f(g);a.xhr=null})}}else this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search")!==false)return this._search(a)},_search:function(a){this.element.addClass("ui-autocomplete-loading");this.source({term:a},this.response)},_response:function(a){if(a.length){a=
+this._normalize(a);this._suggest(a);this._trigger("open")}else this.close();this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing);if(this.menu.element.is(":visible")){this._trigger("close",a);this.menu.element.hide();this.menu.deactivate()}},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(a){if(a.length&&a[0].label&&a[0].value)return a;return e.map(a,function(b){if(typeof b===
+"string")return{label:b,value:b};return e.extend({label:b.label||b.value,value:b.value||b.label},b)})},_suggest:function(a){var b=this.menu.element.empty().zIndex(this.element.zIndex()+1),c;this._renderMenu(b,a);this.menu.deactivate();this.menu.refresh();this.menu.element.show().position(e.extend({of:this.element},this.options.position));a=b.width("").outerWidth();c=this.element.outerWidth();b.outerWidth(Math.max(a,c))},_renderMenu:function(a,b){var c=this;e.each(b,function(d,f){c._renderItem(a,f)})},
+_renderItem:function(a,b){return e("<li></li>").data("item.autocomplete",b).append(e("<a></a>").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});e.extend(e.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},
+filter:function(a,b){var c=new RegExp(e.ui.autocomplete.escapeRegex(b),"i");return e.grep(a,function(d){return c.test(d.label||d.value||d)})}})})(jQuery);
+(function(e){e.widget("ui.menu",{_create:function(){var a=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(b){if(e(b.target).closest(".ui-menu-item a").length){b.preventDefault();a.select(b)}});this.refresh()},refresh:function(){var a=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex",
+-1).mouseenter(function(b){a.activate(b,e(this).parent())}).mouseleave(function(){a.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.attr("scrollTop"),f=this.element.height();if(c<0)this.element.attr("scrollTop",d+c);else c>=f&&this.element.attr("scrollTop",d+c-f+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",a,{item:b})},
+deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(this.active){a=this.active[a+"All"](".ui-menu-item").eq(0);
+a.length?this.activate(c,a):this.activate(c,this.element.children(b))}else this.activate(c,this.element.children(b))},nextPage:function(a){if(this.hasScroll())if(!this.active||this.last())this.activate(a,this.element.children(":first"));else{var b=this.active.offset().top,c=this.element.height(),d=this.element.children("li").filter(function(){var f=e(this).offset().top-b-c+e(this).height();return f<10&&f>-10});d.length||(d=this.element.children(":last"));this.activate(a,d)}else this.activate(a,this.element.children(!this.active||
+this.last()?":first":":last"))},previousPage:function(a){if(this.hasScroll())if(!this.active||this.first())this.activate(a,this.element.children(":last"));else{var b=this.active.offset().top,c=this.element.height();result=this.element.children("li").filter(function(){var d=e(this).offset().top-b+c-e(this).height();return d<10&&d>-10});result.length||(result=this.element.children(":first"));this.activate(a,result)}else this.activate(a,this.element.children(!this.active||this.first()?":last":":first"))},
+hasScroll:function(){return this.element.height()<this.element.attr("scrollHeight")},select:function(a){this._trigger("selected",a,{item:this.active})}})})(jQuery);
+;/*
+ * jQuery UI Button 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * http://docs.jquery.com/UI/Button
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function(a){var g,i=function(b){a(":ui-button",b.target.form).each(function(){var c=a(this).data("button");setTimeout(function(){c.refresh()},1)})},h=function(b){var c=b.name,d=b.form,e=a([]);if(c)e=d?a(d).find("[name='"+c+"']"):a("[name='"+c+"']",b.ownerDocument).filter(function(){return!this.form});return e};a.widget("ui.button",{options:{disabled:null,text:true,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",
+i);if(typeof this.options.disabled!=="boolean")this.options.disabled=this.element.attr("disabled");this._determineButtonType();this.hasTitle=!!this.buttonElement.attr("title");var b=this,c=this.options,d=this.type==="checkbox"||this.type==="radio",e="ui-state-hover"+(!d?" ui-state-active":"");if(c.label===null)c.label=this.buttonElement.html();if(this.element.is(":disabled"))c.disabled=true;this.buttonElement.addClass("ui-button ui-widget ui-state-default ui-corner-all").attr("role","button").bind("mouseenter.button",
+function(){if(!c.disabled){a(this).addClass("ui-state-hover");this===g&&a(this).addClass("ui-state-active")}}).bind("mouseleave.button",function(){c.disabled||a(this).removeClass(e)}).bind("focus.button",function(){a(this).addClass("ui-state-focus")}).bind("blur.button",function(){a(this).removeClass("ui-state-focus")});d&&this.element.bind("change.button",function(){b.refresh()});if(this.type==="checkbox")this.buttonElement.bind("click.button",function(){if(c.disabled)return false;a(this).toggleClass("ui-state-active");
+b.buttonElement.attr("aria-pressed",b.element[0].checked)});else if(this.type==="radio")this.buttonElement.bind("click.button",function(){if(c.disabled)return false;a(this).addClass("ui-state-active");b.buttonElement.attr("aria-pressed",true);var f=b.element[0];h(f).not(f).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed",false)});else{this.buttonElement.bind("mousedown.button",function(){if(c.disabled)return false;a(this).addClass("ui-state-active");
+g=this;a(document).one("mouseup",function(){g=null})}).bind("mouseup.button",function(){if(c.disabled)return false;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(f){if(c.disabled)return false;if(f.keyCode==a.ui.keyCode.SPACE||f.keyCode==a.ui.keyCode.ENTER)a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")});this.buttonElement.is("a")&&this.buttonElement.keyup(function(f){f.keyCode===a.ui.keyCode.SPACE&&a(this).click()})}this._setOption("disabled",
+c.disabled)},_determineButtonType:function(){this.type=this.element.is(":checkbox")?"checkbox":this.element.is(":radio")?"radio":this.element.is("input")?"input":"button";if(this.type==="checkbox"||this.type==="radio"){this.buttonElement=this.element.parents().last().find("label[for="+this.element.attr("id")+"]");this.element.addClass("ui-helper-hidden-accessible");var b=this.element.is(":checked");b&&this.buttonElement.addClass("ui-state-active");this.buttonElement.attr("aria-pressed",b)}else this.buttonElement=
+this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible");this.buttonElement.removeClass("ui-button ui-widget ui-state-default ui-corner-all ui-state-hover ui-state-active ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only").removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html());this.hasTitle||
+this.buttonElement.removeAttr("title");a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments);if(b==="disabled")c?this.element.attr("disabled",true):this.element.removeAttr("disabled");this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b);if(this.type==="radio")h(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed",
+true):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed",false)});else if(this.type==="checkbox")this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed",true):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed",false)},_resetButton:function(){if(this.type==="input")this.options.label&&this.element.val(this.options.label);else{var b=this.buttonElement.removeClass("ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only"),
+c=a("<span></span>").addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary;if(d.primary||d.secondary){b.addClass("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary"));d.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+d.primary+"'></span>");d.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+d.secondary+"'></span>");if(!this.options.text){b.addClass(e?"ui-button-icons-only":"ui-button-icon-only").removeClass("ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary");
+this.hasTitle||b.attr("title",c)}}else b.addClass("ui-button-text-only")}}});a.widget("ui.buttonset",{_create:function(){this.element.addClass("ui-buttonset");this._init()},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c);a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){this.buttons=this.element.find(":button, :submit, :reset, :checkbox, :radio, a, :data(button)").filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":visible").filter(":first").addClass("ui-corner-left").end().filter(":last").addClass("ui-corner-right").end().end().end()},
+destroy:function(){this.element.removeClass("ui-buttonset");this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy");a.Widget.prototype.destroy.call(this)}})})(jQuery);
+;/*
+ * jQuery UI Dialog 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Dialog
*
* Depends:
- * ui.core.js
- * ui.draggable.js
- * ui.resizable.js
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.button.js
+ * jquery.ui.draggable.js
+ * jquery.ui.mouse.js
+ * jquery.ui.position.js
+ * jquery.ui.resizable.js
*/
-(function(c){var b={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},a="ui-dialog ui-widget ui-widget-content ui-corner-all ";c.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr("title");var l=this,m=this.options,j=m.title||this.originalTitle||" ",e=c.ui.dialog.getTitleId(this.element),k=(this.uiDialog=c("<div/>")).appendTo(document.body).hide().addClass(a+m.dialogClass).css({position:"absolute",overflow:"hidden",zIndex:m.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){(m.closeOnEscape&&n.keyCode&&n.keyCode==c.ui.keyCode.ESCAPE&&l.close(n))}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(n){l.moveToTop(false,n)}),g=this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(k),f=(this.uiDialogTitlebar=c("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(k),i=c('<a href="#"/>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){i.addClass("ui-state-hover")},function(){i.removeClass("ui-state-hover")}).focus(function(){i.addClass("ui-state-focus")}).blur(function(){i.removeClass("ui-state-focus")}).mousedown(function(n){n.stopPropagation()}).click(function(n){l.close(n);return false}).appendTo(f),h=(this.uiDialogTitlebarCloseText=c("<span/>")).addClass("ui-icon ui-icon-closethick").text(m.closeText).appendTo(i),d=c("<span/>").addClass("ui-dialog-title").attr("id",e).html(j).prependTo(f);f.find("*").add(f).disableSelection();(m.draggable&&c.fn.draggable&&this._makeDraggable());(m.resizable&&c.fn.resizable&&this._makeResizable());this._createButtons(m.buttons);this._isOpen=false;(m.bgiframe&&c.fn.bgiframe&&k.bgiframe());(m.autoOpen&&this.open())},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");this.uiDialog.remove();(this.originalTitle&&this.element.attr("title",this.originalTitle))},close:function(f){var d=this;if(false===d._trigger("beforeclose",f)){return}(d.overlay&&d.overlay.destroy());d.uiDialog.unbind("keypress.ui-dialog");(d.options.hide?d.uiDialog.hide(d.options.hide,function(){d._trigger("close",f)}):d.uiDialog.hide()&&d._trigger("close",f));c.ui.dialog.overlay.resize();d._isOpen=false;if(d.options.modal){var e=0;c(".ui-dialog").each(function(){if(this!=d.uiDialog[0]){e=Math.max(e,c(this).css("z-index"))}});c.ui.dialog.maxZ=e}},isOpen:function(){return this._isOpen},moveToTop:function(f,e){if((this.options.modal&&!f)||(!this.options.stack&&!this.options.modal)){return this._trigger("focus",e)}if(this.options.zIndex>c.ui.dialog.maxZ){c.ui.dialog.maxZ=this.options.zIndex}(this.overlay&&this.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=++c.ui.dialog.maxZ));var d={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};this.uiDialog.css("z-index",++c.ui.dialog.maxZ);this.element.attr(d);this._trigger("focus",e)},open:function(){if(this._isOpen){return}var e=this.options,d=this.uiDialog;this.overlay=e.modal?new c.ui.dialog.overlay(this):null;(d.next().length&&d.appendTo("body"));this._size();this._position(e.position);d.show(e.show);this.moveToTop(true);(e.modal&&d.bind("keypress.ui-dialog",function(h){if(h.keyCode!=c.ui.keyCode.TAB){return}var g=c(":tabbable",this),i=g.filter(":first")[0],f=g.filter(":last")[0];if(h.target==f&&!h.shiftKey){setTimeout(function(){i.focus()},1)}else{if(h.target==i&&h.shiftKey){setTimeout(function(){f.focus()},1)}}}));c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();this._trigger("open");this._isOpen=true},_createButtons:function(g){var f=this,d=false,e=c("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiDialog.find(".ui-dialog-buttonpane").remove();(typeof g=="object"&&g!==null&&c.each(g,function(){return !(d=true)}));if(d){c.each(g,function(h,i){c('<button type="button"></button>').addClass("ui-state-default ui-corner-all").text(h).click(function(){i.apply(f.element[0],arguments)}).hover(function(){c(this).addClass("ui-state-hover")},function(){c(this).removeClass("ui-state-hover")}).focus(function(){c(this).addClass("ui-state-focus")}).blur(function(){c(this).removeClass("ui-state-focus")}).appendTo(e)});e.appendTo(this.uiDialog)}},_makeDraggable:function(){var d=this,f=this.options,e;this.uiDialog.draggable({cancel:".ui-dialog-content",handle:".ui-dialog-titlebar",containment:"document",start:function(){e=f.height;c(this).height(c(this).height()).addClass("ui-dialog-dragging");(f.dragStart&&f.dragStart.apply(d.element[0],arguments))},drag:function(){(f.drag&&f.drag.apply(d.element[0],arguments))},stop:function(){c(this).removeClass("ui-dialog-dragging").height(e);(f.dragStop&&f.dragStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}})},_makeResizable:function(g){g=(g===undefined?this.options.resizable:g);var d=this,f=this.options,e=typeof g=="string"?g:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",alsoResize:this.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:f.minHeight,start:function(){c(this).addClass("ui-dialog-resizing");(f.resizeStart&&f.resizeStart.apply(d.element[0],arguments))},resize:function(){(f.resize&&f.resize.apply(d.element[0],arguments))},handles:e,stop:function(){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();(f.resizeStop&&f.resizeStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}}).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_position:function(i){var e=c(window),f=c(document),g=f.scrollTop(),d=f.scrollLeft(),h=g;if(c.inArray(i,["center","top","right","bottom","left"])>=0){i=[i=="right"||i=="left"?i:"center",i=="top"||i=="bottom"?i:"middle"]}if(i.constructor!=Array){i=["center","middle"]}if(i[0].constructor==Number){d+=i[0]}else{switch(i[0]){case"left":d+=0;break;case"right":d+=e.width()-this.uiDialog.outerWidth();break;default:case"center":d+=(e.width()-this.uiDialog.outerWidth())/2}}if(i[1].constructor==Number){g+=i[1]}else{switch(i[1]){case"top":g+=0;break;case"bottom":g+=e.height()-this.uiDialog.outerHeight();break;default:case"middle":g+=(e.height()-this.uiDialog.outerHeight())/2}}g=Math.max(g,h);this.uiDialog.css({top:g,left:d})},_setData:function(e,f){(b[e]&&this.uiDialog.data(b[e],f));switch(e){case"buttons":this._createButtons(f);break;case"closeText":this.uiDialogTitlebarCloseText.text(f);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(a+f);break;case"draggable":(f?this._makeDraggable():this.uiDialog.draggable("destroy"));break;case"height":this.uiDialog.height(f);break;case"position":this._position(f);break;case"resizable":var d=this.uiDialog,g=this.uiDialog.is(":data(resizable)");(g&&!f&&d.resizable("destroy"));(g&&typeof f=="string"&&d.resizable("option","handles",f));(g||this._makeResizable(f));break;case"title":c(".ui-dialog-title",this.uiDialogTitlebar).html(f||" ");break;case"width":this.uiDialog.width(f);break}c.widget.prototype._setData.apply(this,arguments)},_size:function(){var e=this.options;this.element.css({height:0,minHeight:0,width:"auto"});var d=this.uiDialog.css({height:"auto",width:e.width}).height();this.element.css({minHeight:Math.max(e.minHeight-d,0),height:e.height=="auto"?"auto":Math.max(e.height-d,0)})}});c.extend(c.ui.dialog,{version:"1.7.2",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1000},getter:"isOpen",uuid:0,maxZ:0,getTitleId:function(d){return"ui-dialog-title-"+(d.attr("id")||++this.uuid)},overlay:function(d){this.$el=c.ui.dialog.overlay.create(d)}});c.extend(c.ui.dialog.overlay,{instances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(d){return d+".dialog-overlay"}).join(" "),create:function(e){if(this.instances.length===0){setTimeout(function(){if(c.ui.dialog.overlay.instances.length){c(document).bind(c.ui.dialog.overlay.events,function(f){var g=c(f.target).parents(".ui-dialog").css("zIndex")||0;return(g>c.ui.dialog.overlay.maxZ)})}},1);c(document).bind("keydown.dialog-overlay",function(f){(e.options.closeOnEscape&&f.keyCode&&f.keyCode==c.ui.keyCode.ESCAPE&&e.close(f))});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var d=c("<div></div>").appendTo(document.body).addClass("ui-widget-overlay").css({width:this.width(),height:this.height()});(e.options.bgiframe&&c.fn.bgiframe&&d.bgiframe());this.instances.push(d);return d},destroy:function(d){this.instances.splice(c.inArray(this.instances,d),1);if(this.instances.length===0){c([document,window]).unbind(".dialog-overlay")}d.remove();var e=0;c.each(this.instances,function(){e=Math.max(e,this.css("z-index"))});this.maxZ=e},height:function(){if(c.browser.msie&&c.browser.version<7){var e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var d=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(e<d){return c(window).height()+"px"}else{return e+"px"}}else{return c(document).height()+"px"}},width:function(){if(c.browser.msie&&c.browser.version<7){var d=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);var e=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);if(d<e){return c(window).width()+"px"}else{return d+"px"}}else{return c(document).width()+"px"}},resize:function(){var d=c([]);c.each(c.ui.dialog.overlay.instances,function(){d=d.add(this)});d.css({width:0,height:0}).css({width:c.ui.dialog.overlay.width(),height:c.ui.dialog.overlay.height()})}});c.extend(c.ui.dialog.overlay.prototype,{destroy:function(){c.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*
- * jQuery UI Slider 1.7.2
+(function(c,j){c.widget("ui.dialog",{options:{autoOpen:true,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:{my:"center",at:"center",of:window,collision:"fit",using:function(a){var b=c(this).css(a).offset().top;b<0&&c(this).css("top",a.top-b)}},resizable:true,show:null,stack:true,title:"",width:300,zIndex:1E3},_create:function(){this.originalTitle=this.element.attr("title");
+if(typeof this.originalTitle!=="string")this.originalTitle="";this.options.title=this.options.title||this.originalTitle;var a=this,b=a.options,d=b.title||" ",f=c.ui.dialog.getTitleId(a.element),g=(a.uiDialog=c("<div></div>")).appendTo(document.body).hide().addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b.dialogClass).css({zIndex:b.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(i){if(b.closeOnEscape&&i.keyCode&&i.keyCode===c.ui.keyCode.ESCAPE){a.close(i);i.preventDefault()}}).attr({role:"dialog",
+"aria-labelledby":f}).mousedown(function(i){a.moveToTop(false,i)});a.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g);var e=(a.uiDialogTitlebar=c("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),h=c('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){h.addClass("ui-state-hover")},function(){h.removeClass("ui-state-hover")}).focus(function(){h.addClass("ui-state-focus")}).blur(function(){h.removeClass("ui-state-focus")}).click(function(i){a.close(i);
+return false}).appendTo(e);(a.uiDialogTitlebarCloseText=c("<span></span>")).addClass("ui-icon ui-icon-closethick").text(b.closeText).appendTo(h);c("<span></span>").addClass("ui-dialog-title").attr("id",f).html(d).prependTo(e);if(c.isFunction(b.beforeclose)&&!c.isFunction(b.beforeClose))b.beforeClose=b.beforeclose;e.find("*").add(e).disableSelection();b.draggable&&c.fn.draggable&&a._makeDraggable();b.resizable&&c.fn.resizable&&a._makeResizable();a._createButtons(b.buttons);a._isOpen=false;c.fn.bgiframe&&
+g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy();a.uiDialog.hide();a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");a.uiDialog.remove();a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(a){var b=this,d;if(false!==b._trigger("beforeClose",a)){b.overlay&&b.overlay.destroy();b.uiDialog.unbind("keypress.ui-dialog");
+b._isOpen=false;if(b.options.hide)b.uiDialog.hide(b.options.hide,function(){b._trigger("close",a)});else{b.uiDialog.hide();b._trigger("close",a)}c.ui.dialog.overlay.resize();if(b.options.modal){d=0;c(".ui-dialog").each(function(){if(this!==b.uiDialog[0])d=Math.max(d,c(this).css("z-index"))});c.ui.dialog.maxZ=d}return b}},isOpen:function(){return this._isOpen},moveToTop:function(a,b){var d=this,f=d.options;if(f.modal&&!a||!f.stack&&!f.modal)return d._trigger("focus",b);if(f.zIndex>c.ui.dialog.maxZ)c.ui.dialog.maxZ=
+f.zIndex;if(d.overlay){c.ui.dialog.maxZ+=1;d.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=c.ui.dialog.maxZ)}a={scrollTop:d.element.attr("scrollTop"),scrollLeft:d.element.attr("scrollLeft")};c.ui.dialog.maxZ+=1;d.uiDialog.css("z-index",c.ui.dialog.maxZ);d.element.attr(a);d._trigger("focus",b);return d},open:function(){if(!this._isOpen){var a=this,b=a.options,d=a.uiDialog;a.overlay=b.modal?new c.ui.dialog.overlay(a):null;d.next().length&&d.appendTo("body");a._size();a._position(b.position);d.show(b.show);
+a.moveToTop(true);b.modal&&d.bind("keypress.ui-dialog",function(f){if(f.keyCode===c.ui.keyCode.TAB){var g=c(":tabbable",this),e=g.filter(":first");g=g.filter(":last");if(f.target===g[0]&&!f.shiftKey){e.focus(1);return false}else if(f.target===e[0]&&f.shiftKey){g.focus(1);return false}}});c(a.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus();a._isOpen=true;a._trigger("open");return a}},_createButtons:function(a){var b=this,d=false,
+f=c("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=c("<div></div>").addClass("ui-dialog-buttonset").appendTo(f);b.uiDialog.find(".ui-dialog-buttonpane").remove();typeof a==="object"&&a!==null&&c.each(a,function(){return!(d=true)});if(d){c.each(a,function(e,h){h=c.isFunction(h)?{click:h,text:e}:h;e=c("<button></button>",h).unbind("click").click(function(){h.click.apply(b.element[0],arguments)}).appendTo(g);c.fn.button&&e.button()});f.appendTo(b.uiDialog)}},_makeDraggable:function(){function a(e){return{position:e.position,
+offset:e.offset}}var b=this,d=b.options,f=c(document),g;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(e,h){g=d.height==="auto"?"auto":c(this).height();c(this).height(c(this).height()).addClass("ui-dialog-dragging");b._trigger("dragStart",e,a(h))},drag:function(e,h){b._trigger("drag",e,a(h))},stop:function(e,h){d.position=[h.position.left-f.scrollLeft(),h.position.top-f.scrollTop()];c(this).removeClass("ui-dialog-dragging").height(g);
+b._trigger("dragStop",e,a(h));c.ui.dialog.overlay.resize()}})},_makeResizable:function(a){function b(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}a=a===j?this.options.resizable:a;var d=this,f=d.options,g=d.uiDialog.css("position");a=typeof a==="string"?a:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:d._minHeight(),
+handles:a,start:function(e,h){c(this).addClass("ui-dialog-resizing");d._trigger("resizeStart",e,b(h))},resize:function(e,h){d._trigger("resize",e,b(h))},stop:function(e,h){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();d._trigger("resizeStop",e,b(h));c.ui.dialog.overlay.resize()}}).css("position",g).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,
+a.height)},_position:function(a){var b=[],d=[0,0],f;if(a){if(typeof a==="string"||typeof a==="object"&&"0"in a){b=a.split?a.split(" "):[a[0],a[1]];if(b.length===1)b[1]=b[0];c.each(["left","top"],function(g,e){if(+b[g]===b[g]){d[g]=b[g];b[g]=e}});a={my:b.join(" "),at:b.join(" "),offset:d.join(" ")}}a=c.extend({},c.ui.dialog.prototype.options.position,a)}else a=c.ui.dialog.prototype.options.position;(f=this.uiDialog.is(":visible"))||this.uiDialog.show();this.uiDialog.css({top:0,left:0}).position(a);
+f||this.uiDialog.hide()},_setOption:function(a,b){var d=this,f=d.uiDialog,g=f.is(":data(resizable)"),e=false;switch(a){case "beforeclose":a="beforeClose";break;case "buttons":d._createButtons(b);e=true;break;case "closeText":d.uiDialogTitlebarCloseText.text(""+b);break;case "dialogClass":f.removeClass(d.options.dialogClass).addClass("ui-dialog ui-widget ui-widget-content ui-corner-all "+b);break;case "disabled":b?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case "draggable":b?
+d._makeDraggable():f.draggable("destroy");break;case "height":e=true;break;case "maxHeight":g&&f.resizable("option","maxHeight",b);e=true;break;case "maxWidth":g&&f.resizable("option","maxWidth",b);e=true;break;case "minHeight":g&&f.resizable("option","minHeight",b);e=true;break;case "minWidth":g&&f.resizable("option","minWidth",b);e=true;break;case "position":d._position(b);break;case "resizable":g&&!b&&f.resizable("destroy");g&&typeof b==="string"&&f.resizable("option","handles",b);!g&&b!==false&&
+d._makeResizable(b);break;case "title":c(".ui-dialog-title",d.uiDialogTitlebar).html(""+(b||" "));break;case "width":e=true;break}c.Widget.prototype._setOption.apply(d,arguments);e&&d._size()},_size:function(){var a=this.options,b;this.element.css({width:"auto",minHeight:0,height:0});if(a.minWidth>a.width)a.width=a.minWidth;b=this.uiDialog.css({height:"auto",width:a.width}).height();this.element.css(a.height==="auto"?{minHeight:Math.max(a.minHeight-b,0),height:c.support.minHeight?"auto":Math.max(a.minHeight-
+b,0)}:{minHeight:0,height:Math.max(a.height-b,0)}).show();this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}});c.extend(c.ui.dialog,{version:"1.8.5",uuid:0,maxZ:0,getTitleId:function(a){a=a.attr("id");if(!a){this.uuid+=1;a=this.uuid}return"ui-dialog-title-"+a},overlay:function(a){this.$el=c.ui.dialog.overlay.create(a)}});c.extend(c.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),
+function(a){return a+".dialog-overlay"}).join(" "),create:function(a){if(this.instances.length===0){setTimeout(function(){c.ui.dialog.overlay.instances.length&&c(document).bind(c.ui.dialog.overlay.events,function(d){if(c(d.target).zIndex()<c.ui.dialog.overlay.maxZ)return false})},1);c(document).bind("keydown.dialog-overlay",function(d){if(a.options.closeOnEscape&&d.keyCode&&d.keyCode===c.ui.keyCode.ESCAPE){a.close(d);d.preventDefault()}});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var b=
+(this.oldInstances.pop()||c("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});c.fn.bgiframe&&b.bgiframe();this.instances.push(b);return b},destroy:function(a){this.oldInstances.push(this.instances.splice(c.inArray(a,this.instances),1)[0]);this.instances.length===0&&c([document,window]).unbind(".dialog-overlay");a.remove();var b=0;c.each(this.instances,function(){b=Math.max(b,this.css("z-index"))});this.maxZ=b},height:function(){var a,
+b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);b=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return a<b?c(window).height()+"px":a+"px"}else return c(document).height()+"px"},width:function(){var a,b;if(c.browser.msie&&c.browser.version<7){a=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);b=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);return a<
+b?c(window).width()+"px":a+"px"}else return c(document).width()+"px"},resize:function(){var a=c([]);c.each(c.ui.dialog.overlay.instances,function(){a=a.add(this)});a.css({width:0,height:0}).css({width:c.ui.dialog.overlay.width(),height:c.ui.dialog.overlay.height()})}});c.extend(c.ui.dialog.overlay.prototype,{destroy:function(){c.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);
+;/*
+ * jQuery UI Slider 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.mouse.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.slider",a.extend({},a.ui.mouse,{_init:function(){var b=this,c=this.options;this._keySliding=false;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");this.range=a([]);if(c.range){if(c.range===true){this.range=a("<div></div>");if(!c.values){c.values=[this._valueMin(),this._valueMin()]}if(c.values.length&&c.values.length!=2){c.values=[c.values[0],c.values[0]]}}else{this.range=a("<div></div>")}this.range.appendTo(this.element).addClass("ui-slider-range");if(c.range=="min"||c.range=="max"){this.range.addClass("ui-slider-range-"+c.range)}this.range.addClass("ui-widget-header")}if(a(".ui-slider-handle",this.element).length==0){a('<a href="#"></a>').appendTo(this.element).addClass("ui-slider-handle")}if(c.values&&c.values.length){while(a(".ui-slider-handle",this.element).length<c.values.length){a('<a href="#"></a>').appendTo(this.element).addClass("ui-slider-handle")}}this.handles=a(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(d){d.preventDefault()}).hover(function(){if(!c.disabled){a(this).addClass("ui-state-hover")}},function(){a(this).removeClass("ui-state-hover")}).focus(function(){if(!c.disabled){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}else{a(this).blur()}}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((this.options.values.length==2&&this.options.range===true)&&((e==0&&d>b)||(e==1&&d<b))){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"disabled":if(d){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled")}else{this.handles.removeAttr("disabled")}case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_values:function(b){if(arguments.length){var c=this.options.values[b];if(c<this._valueMin()){c=this._valueMin()}if(c>this._valueMax()){c=this._valueMax()}return c}else{return this.options.values}},_valueMin:function(){var b=this.options.min;return b},_valueMax:function(){var b=this.options.max;return b},_refreshValue:function(c){var f=this.options.range,d=this.options,l=this;if(this.options.values&&this.options.values.length){var i,h;this.handles.each(function(p,n){var o=(l.values(p)-l._valueMin())/(l._valueMax()-l._valueMin())*100;var m={};m[l.orientation=="horizontal"?"left":"bottom"]=o+"%";a(this).stop(1,1)[c?"animate":"css"](m,d.animate);if(l.options.range===true){if(l.orientation=="horizontal"){(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({left:o+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({width:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}else{(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({bottom:(o)+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({height:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}}lastValPercent=o})}else{var j=this.value(),g=this._valueMin(),k=this._valueMax(),e=k!=g?(j-g)/(k-g)*100:0;var b={};b[l.orientation=="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[c?"animate":"css"](b,d.animate);(f=="min")&&(this.orientation=="horizontal")&&this.range.stop(1,1)[c?"animate":"css"]({width:e+"%"},d.animate);(f=="max")&&(this.orientation=="horizontal")&&this.range[c?"animate":"css"]({width:(100-e)+"%"},{queue:false,duration:d.animate});(f=="min")&&(this.orientation=="vertical")&&this.range.stop(1,1)[c?"animate":"css"]({height:e+"%"},d.animate);(f=="max")&&(this.orientation=="vertical")&&this.range[c?"animate":"css"]({height:(100-e)+"%"},{queue:false,duration:d.animate})}}}));a.extend(a.ui.slider,{getter:"value values",version:"1.7.2",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);;/*
- * jQuery UI Tabs 1.7.2
+(function(d){d.widget("ui.slider",d.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var a=this,b=this.options;this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");b.disabled&&this.element.addClass("ui-slider-disabled ui-disabled");
+this.range=d([]);if(b.range){if(b.range===true){this.range=d("<div></div>");if(!b.values)b.values=[this._valueMin(),this._valueMin()];if(b.values.length&&b.values.length!==2)b.values=[b.values[0],b.values[0]]}else this.range=d("<div></div>");this.range.appendTo(this.element).addClass("ui-slider-range");if(b.range==="min"||b.range==="max")this.range.addClass("ui-slider-range-"+b.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");
+if(b.values&&b.values.length)for(;d(".ui-slider-handle",this.element).length<b.values.length;)d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){b.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(b.disabled)d(this).blur();
+else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),h,g,i;if(!a.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e=
+false;if(!a._keySliding){a._keySliding=true;d(this).addClass("ui-state-active");h=a._start(c,f);if(h===false)return}break}i=a.options.step;h=a.options.values&&a.options.values.length?(g=a.values(f)):(g=a.value());switch(c.keyCode){case d.ui.keyCode.HOME:g=a._valueMin();break;case d.ui.keyCode.END:g=a._valueMax();break;case d.ui.keyCode.PAGE_UP:g=a._trimAlignValue(h+(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:g=a._trimAlignValue(h-(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(h===
+a._valueMax())return;g=a._trimAlignValue(h+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(h===a._valueMin())return;g=a._trimAlignValue(h-i);break}a._slide(c,f,g);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(a._keySliding){a._keySliding=false;a._stop(c,e);a._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");
+this._mouseDestroy();return this},_mouseCapture:function(a){var b=this.options,c,e,f,h,g;if(b.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c=this._normValueFromMouse({x:a.pageX,y:a.pageY});e=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(i){var j=Math.abs(c-h.values(i));if(e>j){e=j;f=d(this);g=i}});if(b.range===true&&this.values(1)===b.min){g+=1;f=d(this.handles[g])}if(this._start(a,
+g)===false)return false;this._mouseSliding=true;h._handleIndex=g;f.addClass("ui-state-active").focus();b=f.offset();this._clickOffset=!d(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-b.left-f.width()/2,top:a.pageY-b.top-f.height()/2-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};this._slide(a,g,c);return this._animateOff=true},_mouseStart:function(){return true},_mouseDrag:function(a){var b=
+this._normValueFromMouse({x:a.pageX,y:a.pageY});this._slide(a,this._handleIndex,b);return false},_mouseStop:function(a){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(a,this._handleIndex);this._change(a,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b;if(this.orientation==="horizontal"){b=
+this.elementSize.width;a=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{b=this.elementSize.height;a=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}b=a/b;if(b>1)b=1;if(b<0)b=0;if(this.orientation==="vertical")b=1-b;a=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+b*a)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);
+c.values=this.values()}return this._trigger("start",a,c)},_slide:function(a,b,c){var e;if(this.options.values&&this.options.values.length){e=this.values(b?0:1);if(this.options.values.length===2&&this.options.range===true&&(b===0&&c>e||b===1&&c<e))c=e;if(c!==this.values(b)){e=this.values();e[b]=c;a=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e});this.values(b?0:1);a!==false&&this.values(b,c,true)}}else if(c!==this.value()){a=this._trigger("slide",a,{handle:this.handles[b],value:c});
+a!==false&&this.value(c)}},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value=
+this._trimAlignValue(a);this._refreshValue();this._change(null,0)}return this._value()},values:function(a,b){var c,e,f;if(arguments.length>1){this.options.values[a]=this._trimAlignValue(b);this._refreshValue();this._change(null,a)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f<c.length;f+=1){c[f]=this._trimAlignValue(e[f]);this._change(null,f)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(a):this.value();
+else return this._values()},_setOption:function(a,b){var c,e=0;if(d.isArray(this.options.values))e=this.options.values.length;d.Widget.prototype._setOption.apply(this,arguments);switch(a){case "disabled":if(b){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled");this.element.addClass("ui-disabled")}else{this.handles.removeAttr("disabled");this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation();
+this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(c=0;c<e;c+=1)this._change(null,c);this._animateOff=false;break}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a)},_values:function(a){var b,c;if(arguments.length){b=this.options.values[a];
+return b=this._trimAlignValue(b)}else{b=this.options.values.slice();for(c=0;c<b.length;c+=1)b[c]=this._trimAlignValue(b[c]);return b}},_trimAlignValue:function(a){if(a<this._valueMin())return this._valueMin();if(a>this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=a%b;a=a-c;if(Math.abs(c)*2>=b)a+=c>0?b:-b;return parseFloat(a.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var a=
+this.options.range,b=this.options,c=this,e=!this._animateOff?b.animate:false,f,h={},g,i,j,l;if(this.options.values&&this.options.values.length)this.handles.each(function(k){f=(c.values(k)-c._valueMin())/(c._valueMax()-c._valueMin())*100;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](h,b.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(k===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({width:f-
+g+"%"},{queue:false,duration:b.animate})}else{if(k===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},b.animate);if(k===1)c.range[e?"animate":"css"]({height:f-g+"%"},{queue:false,duration:b.animate})}g=f});else{i=this.value();j=this._valueMin();l=this._valueMax();f=l!==j?(i-j)/(l-j)*100:0;h[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](h,b.animate);if(a==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},
+b.animate);if(a==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:b.animate});if(a==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},b.animate);if(a==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:b.animate})}}});d.extend(d.ui.slider,{version:"1.8.5"})})(jQuery);
+;/*
+ * jQuery UI Tabs 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Tabs
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.tabs",{_init:function(){if(this.options.deselectable!==undefined){this.options.collapsible=this.options.deselectable}this._tabify(true)},_setData:function(b,c){if(b=="selected"){if(this.options.collapsible&&c==this.options.selected){return}this.select(c)}else{this.options[b]=c;if(b=="deselectable"){this.options.collapsible=c}this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+a.data(b)},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+a.data(this.list[0]));return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(c,b){return{tab:c,panel:b,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(n){this.list=this.element.children("ul:first");this.lis=a("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);var p=this,d=this.options;var c=/^#.+/;this.anchors.each(function(r,o){var q=a(o).attr("href");var s=q.split("#")[0],u;if(s&&(s===location.toString().split("#")[0]||(u=a("base")[0])&&s===u.href)){q=o.hash;o.href=q}if(c.test(q)){p.panels=p.panels.add(p._sanitizeSelector(q))}else{if(q!="#"){a.data(o,"href.tabs",q);a.data(o,"load.tabs",q.replace(/#.*$/,""));var w=p._tabId(o);o.href="#"+w;var v=a("#"+w);if(!v.length){v=a(d.panelTemplate).attr("id",w).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(p.panels[r-1]||p.list);v.data("destroy.tabs",true)}p.panels=p.panels.add(v)}else{d.disabled.push(r)}}});if(n){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(d.selected===undefined){if(location.hash){this.anchors.each(function(q,o){if(o.hash==location.hash){d.selected=q;return false}})}if(typeof d.selected!="number"&&d.cookie){d.selected=parseInt(p._cookie(),10)}if(typeof d.selected!="number"&&this.lis.filter(".ui-tabs-selected").length){d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}d.selected=d.selected||0}else{if(d.selected===null){d.selected=-1}}d.selected=((d.selected>=0&&this.anchors[d.selected])||d.selected<0)?d.selected:0;d.disabled=a.unique(d.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(q,o){return p.lis.index(q)}))).sort();if(a.inArray(d.selected,d.disabled)!=-1){d.disabled.splice(a.inArray(d.selected,d.disabled),1)}this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");if(d.selected>=0&&this.anchors.length){this.panels.eq(d.selected).removeClass("ui-tabs-hide");this.lis.eq(d.selected).addClass("ui-tabs-selected ui-state-active");p.element.queue("tabs",function(){p._trigger("show",null,p._ui(p.anchors[d.selected],p.panels[d.selected]))});this.load(d.selected)}a(window).bind("unload",function(){p.lis.add(p.anchors).unbind(".tabs");p.lis=p.anchors=p.panels=null})}else{d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}this.element[d.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");if(d.cookie){this._cookie(d.selected,d.cookie)}for(var g=0,m;(m=this.lis[g]);g++){a(m)[a.inArray(g,d.disabled)!=-1&&!a(m).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled")}if(d.cache===false){this.anchors.removeData("cache.tabs")}this.lis.add(this.anchors).unbind(".tabs");if(d.event!="mouseover"){var f=function(o,i){if(i.is(":not(.ui-state-disabled)")){i.addClass("ui-state-"+o)}};var j=function(o,i){i.removeClass("ui-state-"+o)};this.lis.bind("mouseover.tabs",function(){f("hover",a(this))});this.lis.bind("mouseout.tabs",function(){j("hover",a(this))});this.anchors.bind("focus.tabs",function(){f("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var b,h;if(d.fx){if(a.isArray(d.fx)){b=d.fx[0];h=d.fx[1]}else{b=h=d.fx}}function e(i,o){i.css({display:""});if(a.browser.msie&&o.opacity){i[0].style.removeAttribute("filter")}}var k=h?function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.hide().removeClass("ui-tabs-hide").animate(h,h.duration||"normal",function(){e(o,h);p._trigger("show",null,p._ui(i,o[0]))})}:function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");p._trigger("show",null,p._ui(i,o[0]))};var l=b?function(o,i){i.animate(b,b.duration||"normal",function(){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");e(i,b);p.element.dequeue("tabs")})}:function(o,i,q){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");p.element.dequeue("tabs")};this.anchors.bind(d.event+".tabs",function(){var o=this,r=a(this).closest("li"),i=p.panels.filter(":not(.ui-tabs-hide)"),q=a(p._sanitizeSelector(this.hash));if((r.hasClass("ui-tabs-selected")&&!d.collapsible)||r.hasClass("ui-state-disabled")||r.hasClass("ui-state-processing")||p._trigger("select",null,p._ui(this,q[0]))===false){this.blur();return false}d.selected=p.anchors.index(this);p.abort();if(d.collapsible){if(r.hasClass("ui-tabs-selected")){d.selected=-1;if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){l(o,i)}).dequeue("tabs");this.blur();return false}else{if(!i.length){if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this));this.blur();return false}}}if(d.cookie){p._cookie(d.selected,d.cookie)}if(q.length){if(i.length){p.element.queue("tabs",function(){l(o,i)})}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this))}else{throw"jQuery UI Tabs: Mismatching fragment identifier."}if(a.browser.msie){this.blur()}});this.anchors.bind("click.tabs",function(){return false})},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var c=a.data(this,"href.tabs");if(c){this.href=c}var d=a(this).unbind(".tabs");a.each(["href","load","cache"],function(e,f){d.removeData(f+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){if(a.data(this,"destroy.tabs")){a(this).remove()}else{a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}});if(b.cookie){this._cookie(null,b.cookie)}},add:function(e,d,c){if(c===undefined){c=this.anchors.length}var b=this,g=this.options,i=a(g.tabTemplate.replace(/#\{href\}/g,e).replace(/#\{label\}/g,d)),h=!e.indexOf("#")?e.replace("#",""):this._tabId(a("a",i)[0]);i.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var f=a("#"+h);if(!f.length){f=a(g.panelTemplate).attr("id",h).data("destroy.tabs",true)}f.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(c>=this.lis.length){i.appendTo(this.list);f.appendTo(this.list[0].parentNode)}else{i.insertBefore(this.lis[c]);f.insertBefore(this.panels[c])}g.disabled=a.map(g.disabled,function(k,j){return k>=c?++k:k});this._tabify();if(this.anchors.length==1){i.addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[0],b.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[c],this.panels[c]))},remove:function(b){var d=this.options,e=this.lis.eq(b).remove(),c=this.panels.eq(b).remove();if(e.hasClass("ui-tabs-selected")&&this.anchors.length>1){this.select(b+(b+1<this.anchors.length?1:-1))}d.disabled=a.map(a.grep(d.disabled,function(g,f){return g!=b}),function(g,f){return g>=b?--g:g});this._tabify();this._trigger("remove",null,this._ui(e.find("a")[0],c[0]))},enable:function(b){var c=this.options;if(a.inArray(b,c.disabled)==-1){return}this.lis.eq(b).removeClass("ui-state-disabled");c.disabled=a.grep(c.disabled,function(e,d){return e!=b});this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]))},disable:function(c){var b=this,d=this.options;if(c!=d.selected){this.lis.eq(c).addClass("ui-state-disabled");d.disabled.push(c);d.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}},select:function(b){if(typeof b=="string"){b=this.anchors.index(this.anchors.filter("[href$="+b+"]"))}else{if(b===null){b=-1}}if(b==-1&&this.options.collapsible){b=this.options.selected}this.anchors.eq(b).trigger(this.options.event+".tabs")},load:function(e){var c=this,g=this.options,b=this.anchors.eq(e)[0],d=a.data(b,"load.tabs");this.abort();if(!d||this.element.queue("tabs").length!==0&&a.data(b,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(e).addClass("ui-state-processing");if(g.spinner){var f=a("span",b);f.data("label.tabs",f.html()).html(g.spinner)}this.xhr=a.ajax(a.extend({},g.ajaxOptions,{url:d,success:function(i,h){a(c._sanitizeSelector(b.hash)).html(i);c._cleanup();if(g.cache){a.data(b,"cache.tabs",true)}c._trigger("load",null,c._ui(c.anchors[e],c.panels[e]));try{g.ajaxOptions.success(i,h)}catch(j){}c.element.dequeue("tabs")}}))},abort:function(){this.element.queue([]);this.panels.stop(false,true);if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup()},url:function(c,b){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",b)},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.7.2",getter:"length",defaults:{ajaxOptions:null,cache:false,cookie:null,collapsible:false,disabled:[],event:"click",fx:null,idPrefix:"ui-tabs-",panelTemplate:"<div></div>",spinner:"<em>Loading…</em>",tabTemplate:'<li><a href="#{href}"><span>#{label}</span></a></li>'}});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(d,f){var b=this,g=this.options;var c=b._rotate||(b._rotate=function(h){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var i=g.selected;b.select(++i<b.anchors.length?i:0)},d);if(h){h.stopPropagation()}});var e=b._unrotate||(b._unrotate=!f?function(h){if(h.clientX){b.rotate(null)}}:function(h){t=g.selected;c()});if(d){this.element.bind("tabsshow",c);this.anchors.bind(g.event+".tabs",e);c()}else{clearTimeout(b.rotation);this.element.unbind("tabsshow",c);this.anchors.unbind(g.event+".tabs",e);delete this._rotate;delete this._unrotate}}})})(jQuery);;/*
- * jQuery UI Datepicker 1.7.2
+(function(d,p){function u(){return++v}function w(){return++x}var v=0,x=0;d.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:false,cookie:null,collapsible:false,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading…</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(true)},_setOption:function(a,e){if(a=="selected")this.options.collapsible&&
+e==this.options.selected||this.select(e);else{this.options[a]=e;this._tabify()}},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+u()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var a=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+w());return d.cookie.apply(null,[a].concat(d.makeArray(arguments)))},_ui:function(a,e){return{tab:a,panel:e,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var a=
+d(this);a.html(a.data("label.tabs")).removeData("label.tabs")})},_tabify:function(a){function e(g,f){g.css("display","");!d.support.opacity&&f.opacity&&g[0].style.removeAttribute("filter")}var b=this,c=this.options,h=/^#.+/;this.list=this.element.find("ol,ul").eq(0);this.lis=d(" > li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return d("a",this)[0]});this.panels=d([]);this.anchors.each(function(g,f){var i=d(f).attr("href"),l=i.split("#")[0],q;if(l&&(l===location.toString().split("#")[0]||
+(q=d("base")[0])&&l===q.href)){i=f.hash;f.href=i}if(h.test(i))b.panels=b.panels.add(b._sanitizeSelector(i));else if(i&&i!=="#"){d.data(f,"href.tabs",i);d.data(f,"load.tabs",i.replace(/#.*$/,""));i=b._tabId(f);f.href="#"+i;f=d("#"+i);if(!f.length){f=d(c.panelTemplate).attr("id",i).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(b.panels[g-1]||b.list);f.data("destroy.tabs",true)}b.panels=b.panels.add(f)}else c.disabled.push(g)});if(a){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");
+this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(c.selected===p){location.hash&&this.anchors.each(function(g,f){if(f.hash==location.hash){c.selected=g;return false}});if(typeof c.selected!=="number"&&c.cookie)c.selected=parseInt(b._cookie(),10);if(typeof c.selected!=="number"&&this.lis.filter(".ui-tabs-selected").length)c.selected=
+this.lis.index(this.lis.filter(".ui-tabs-selected"));c.selected=c.selected||(this.lis.length?0:-1)}else if(c.selected===null)c.selected=-1;c.selected=c.selected>=0&&this.anchors[c.selected]||c.selected<0?c.selected:0;c.disabled=d.unique(c.disabled.concat(d.map(this.lis.filter(".ui-state-disabled"),function(g){return b.lis.index(g)}))).sort();d.inArray(c.selected,c.disabled)!=-1&&c.disabled.splice(d.inArray(c.selected,c.disabled),1);this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");
+if(c.selected>=0&&this.anchors.length){this.panels.eq(c.selected).removeClass("ui-tabs-hide");this.lis.eq(c.selected).addClass("ui-tabs-selected ui-state-active");b.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[c.selected],b.panels[c.selected]))});this.load(c.selected)}d(window).bind("unload",function(){b.lis.add(b.anchors).unbind(".tabs");b.lis=b.anchors=b.panels=null})}else c.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"));this.element[c.collapsible?"addClass":
+"removeClass"]("ui-tabs-collapsible");c.cookie&&this._cookie(c.selected,c.cookie);a=0;for(var j;j=this.lis[a];a++)d(j)[d.inArray(a,c.disabled)!=-1&&!d(j).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");c.cache===false&&this.anchors.removeData("cache.tabs");this.lis.add(this.anchors).unbind(".tabs");if(c.event!=="mouseover"){var k=function(g,f){f.is(":not(.ui-state-disabled)")&&f.addClass("ui-state-"+g)},n=function(g,f){f.removeClass("ui-state-"+g)};this.lis.bind("mouseover.tabs",
+function(){k("hover",d(this))});this.lis.bind("mouseout.tabs",function(){n("hover",d(this))});this.anchors.bind("focus.tabs",function(){k("focus",d(this).closest("li"))});this.anchors.bind("blur.tabs",function(){n("focus",d(this).closest("li"))})}var m,o;if(c.fx)if(d.isArray(c.fx)){m=c.fx[0];o=c.fx[1]}else m=o=c.fx;var r=o?function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.hide().removeClass("ui-tabs-hide").animate(o,o.duration||"normal",function(){e(f,o);b._trigger("show",
+null,b._ui(g,f[0]))})}:function(g,f){d(g).closest("li").addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");b._trigger("show",null,b._ui(g,f[0]))},s=m?function(g,f){f.animate(m,m.duration||"normal",function(){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");e(f,m);b.element.dequeue("tabs")})}:function(g,f){b.lis.removeClass("ui-tabs-selected ui-state-active");f.addClass("ui-tabs-hide");b.element.dequeue("tabs")};this.anchors.bind(c.event+".tabs",
+function(){var g=this,f=d(g).closest("li"),i=b.panels.filter(":not(.ui-tabs-hide)"),l=d(b._sanitizeSelector(g.hash));if(f.hasClass("ui-tabs-selected")&&!c.collapsible||f.hasClass("ui-state-disabled")||f.hasClass("ui-state-processing")||b.panels.filter(":animated").length||b._trigger("select",null,b._ui(this,l[0]))===false){this.blur();return false}c.selected=b.anchors.index(this);b.abort();if(c.collapsible)if(f.hasClass("ui-tabs-selected")){c.selected=-1;c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs",
+function(){s(g,i)}).dequeue("tabs");this.blur();return false}else if(!i.length){c.cookie&&b._cookie(c.selected,c.cookie);b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this));this.blur();return false}c.cookie&&b._cookie(c.selected,c.cookie);if(l.length){i.length&&b.element.queue("tabs",function(){s(g,i)});b.element.queue("tabs",function(){r(g,l)});b.load(b.anchors.index(this))}else throw"jQuery UI Tabs: Mismatching fragment identifier.";d.browser.msie&&this.blur()});this.anchors.bind("click.tabs",
+function(){return false})},_getIndex:function(a){if(typeof a=="string")a=this.anchors.index(this.anchors.filter("[href$="+a+"]"));return a},destroy:function(){var a=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var e=d.data(this,"href.tabs");if(e)this.href=
+e;var b=d(this).unbind(".tabs");d.each(["href","load","cache"],function(c,h){b.removeData(h+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){d.data(this,"destroy.tabs")?d(this).remove():d(this).removeClass("ui-state-default ui-corner-top ui-tabs-selected ui-state-active ui-state-hover ui-state-focus ui-state-disabled ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide")});a.cookie&&this._cookie(null,a.cookie);return this},add:function(a,e,b){if(b===p)b=this.anchors.length;
+var c=this,h=this.options;e=d(h.tabTemplate.replace(/#\{href\}/g,a).replace(/#\{label\}/g,e));a=!a.indexOf("#")?a.replace("#",""):this._tabId(d("a",e)[0]);e.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var j=d("#"+a);j.length||(j=d(h.panelTemplate).attr("id",a).data("destroy.tabs",true));j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(b>=this.lis.length){e.appendTo(this.list);j.appendTo(this.list[0].parentNode)}else{e.insertBefore(this.lis[b]);
+j.insertBefore(this.panels[b])}h.disabled=d.map(h.disabled,function(k){return k>=b?++k:k});this._tabify();if(this.anchors.length==1){h.selected=0;e.addClass("ui-tabs-selected ui-state-active");j.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){c._trigger("show",null,c._ui(c.anchors[0],c.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[b],this.panels[b]));return this},remove:function(a){a=this._getIndex(a);var e=this.options,b=this.lis.eq(a).remove(),c=this.panels.eq(a).remove();
+if(b.hasClass("ui-tabs-selected")&&this.anchors.length>1)this.select(a+(a+1<this.anchors.length?1:-1));e.disabled=d.map(d.grep(e.disabled,function(h){return h!=a}),function(h){return h>=a?--h:h});this._tabify();this._trigger("remove",null,this._ui(b.find("a")[0],c[0]));return this},enable:function(a){a=this._getIndex(a);var e=this.options;if(d.inArray(a,e.disabled)!=-1){this.lis.eq(a).removeClass("ui-state-disabled");e.disabled=d.grep(e.disabled,function(b){return b!=a});this._trigger("enable",null,
+this._ui(this.anchors[a],this.panels[a]));return this}},disable:function(a){a=this._getIndex(a);var e=this.options;if(a!=e.selected){this.lis.eq(a).addClass("ui-state-disabled");e.disabled.push(a);e.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a]))}return this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;this.anchors.eq(a).trigger(this.options.event+".tabs");return this},
+load:function(a){a=this._getIndex(a);var e=this,b=this.options,c=this.anchors.eq(a)[0],h=d.data(c,"load.tabs");this.abort();if(!h||this.element.queue("tabs").length!==0&&d.data(c,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(a).addClass("ui-state-processing");if(b.spinner){var j=d("span",c);j.data("label.tabs",j.html()).html(b.spinner)}this.xhr=d.ajax(d.extend({},b.ajaxOptions,{url:h,success:function(k,n){d(e._sanitizeSelector(c.hash)).html(k);e._cleanup();b.cache&&d.data(c,"cache.tabs",
+true);e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.success(k,n)}catch(m){}},error:function(k,n){e._cleanup();e._trigger("load",null,e._ui(e.anchors[a],e.panels[a]));try{b.ajaxOptions.error(k,n,a,c)}catch(m){}}}));e.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]);this.panels.stop(false,true);this.element.queue("tabs",this.element.queue("tabs").splice(-2,2));if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup();return this},url:function(a,
+e){this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",e);return this},length:function(){return this.anchors.length}});d.extend(d.ui.tabs,{version:"1.8.5"});d.extend(d.ui.tabs.prototype,{rotation:null,rotate:function(a,e){var b=this,c=this.options,h=b._rotate||(b._rotate=function(j){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var k=c.selected;b.select(++k<b.anchors.length?k:0)},a);j&&j.stopPropagation()});e=b._unrotate||(b._unrotate=!e?function(j){j.clientX&&b.rotate(null)}:
+function(){t=c.selected;h()});if(a){this.element.bind("tabsshow",h);this.anchors.bind(c.event+".tabs",e);h()}else{clearTimeout(b.rotation);this.element.unbind("tabsshow",h);this.anchors.unbind(c.event+".tabs",e);delete this._rotate;delete this._unrotate}return this}})})(jQuery);
+;/*
+ * jQuery UI Datepicker 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Datepicker
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
*/
-(function($){$.extend($.ui,{datepicker:{version:"1.7.2"}});var PROP_NAME="datepicker";function Datepicker(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._datepickerShowing=false;this._inDialog=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass="ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],dateFormat:"mm/dd/yy",firstDay:0,isRTL:false};this._defaults={showOn:"focus",showAnim:"show",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,showMonthAfterYear:false,yearRange:"-10:+10",showOtherMonths:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"normal",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false};$.extend(this._defaults,this.regional[""]);this.dpDiv=$('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",log:function(){if(this.debug){console.log.apply("",arguments)}},setDefaults:function(settings){extendRemove(this._defaults,settings||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase();var inline=(nodeName=="div"||nodeName=="span");if(!target.id){target.id="dp"+(++this.uuid)}var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{});if(nodeName=="input"){this._connectDatepicker(target,inst)}else{if(inline){this._inlineDatepicker(target,inst)}}},_newInst:function(target,inline){var id=target[0].id.replace(/([:\[\]\.])/g,"\\\\$1");return{id:id,input:target,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:inline,dpDiv:(!inline?this.dpDiv:$('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(target,inst){var input=$(target);inst.append=$([]);inst.trigger=$([]);if(input.hasClass(this.markerClassName)){return}var appendText=this._get(inst,"appendText");var isRTL=this._get(inst,"isRTL");if(appendText){inst.append=$('<span class="'+this._appendClass+'">'+appendText+"</span>");input[isRTL?"before":"after"](inst.append)}var showOn=this._get(inst,"showOn");if(showOn=="focus"||showOn=="both"){input.focus(this._showDatepicker)}if(showOn=="button"||showOn=="both"){var buttonText=this._get(inst,"buttonText");var buttonImage=this._get(inst,"buttonImage");inst.trigger=$(this._get(inst,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:buttonImage,alt:buttonText,title:buttonText}):$('<button type="button"></button>').addClass(this._triggerClass).html(buttonImage==""?buttonText:$("<img/>").attr({src:buttonImage,alt:buttonText,title:buttonText})));input[isRTL?"before":"after"](inst.trigger);inst.trigger.click(function(){if($.datepicker._datepickerShowing&&$.datepicker._lastInput==target){$.datepicker._hideDatepicker()}else{$.datepicker._showDatepicker(target)}return false})}input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst)},_inlineDatepicker:function(target,inst){var divSpan=$(target);if(divSpan.hasClass(this.markerClassName)){return}divSpan.addClass(this.markerClassName).append(inst.dpDiv).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst);this._setDate(inst,this._getDefaultDate(inst));this._updateDatepicker(inst);this._updateAlternate(inst)},_dialogDatepicker:function(input,dateText,onSelect,settings,pos){var inst=this._dialogInst;if(!inst){var id="dp"+(++this.uuid);this._dialogInput=$('<input type="text" id="'+id+'" size="1" style="position: absolute; top: -100px;"/>');this._dialogInput.keydown(this._doKeyDown);$("body").append(this._dialogInput);inst=this._dialogInst=this._newInst(this._dialogInput,false);inst.settings={};$.data(this._dialogInput[0],PROP_NAME,inst)}extendRemove(inst.settings,settings||{});this._dialogInput.val(dateText);this._pos=(pos?(pos.length?pos:[pos.pageX,pos.pageY]):null);if(!this._pos){var browserWidth=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;var browserHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight;var scrollX=document.documentElement.scrollLeft||document.body.scrollLeft;var scrollY=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(browserWidth/2)-100+scrollX,(browserHeight/2)-150+scrollY]}this._dialogInput.css("left",this._pos[0]+"px").css("top",this._pos[1]+"px");inst.settings.onSelect=onSelect;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if($.blockUI){$.blockUI(this.dpDiv)}$.data(this._dialogInput[0],PROP_NAME,inst);return this},_destroyDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();$.removeData(target,PROP_NAME);if(nodeName=="input"){inst.append.remove();inst.trigger.remove();$target.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress)}else{if(nodeName=="div"||nodeName=="span"){$target.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=false;inst.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().removeClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)})},_disableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=true;inst.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().addClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)});this._disabledInputs[this._disabledInputs.length]=target},_isDisabledDatepicker:function(target){if(!target){return false}for(var i=0;i<this._disabledInputs.length;i++){if(this._disabledInputs[i]==target){return true}}return false},_getInst:function(target){try{return $.data(target,PROP_NAME)}catch(err){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(target,name,value){var inst=this._getInst(target);if(arguments.length==2&&typeof name=="string"){return(name=="defaults"?$.extend({},$.datepicker._defaults):(inst?(name=="all"?$.extend({},inst.settings):this._get(inst,name)):null))}var settings=name||{};if(typeof name=="string"){settings={};settings[name]=value}if(inst){if(this._curInst==inst){this._hideDatepicker(null)}var date=this._getDateDatepicker(target);extendRemove(inst.settings,settings);this._setDateDatepicker(target,date);this._updateDatepicker(inst)}},_changeDatepicker:function(target,name,value){this._optionDatepicker(target,name,value)},_refreshDatepicker:function(target){var inst=this._getInst(target);if(inst){this._updateDatepicker(inst)}},_setDateDatepicker:function(target,date,endDate){var inst=this._getInst(target);if(inst){this._setDate(inst,date,endDate);this._updateDatepicker(inst);this._updateAlternate(inst)}},_getDateDatepicker:function(target){var inst=this._getInst(target);if(inst&&!inst.inline){this._setDateFromField(inst)}return(inst?this._getDate(inst):null)},_doKeyDown:function(event){var inst=$.datepicker._getInst(event.target);var handled=true;var isRTL=inst.dpDiv.is(".ui-datepicker-rtl");inst._keyEvent=true;if($.datepicker._datepickerShowing){switch(event.keyCode){case 9:$.datepicker._hideDatepicker(null,"");break;case 13:var sel=$("td."+$.datepicker._dayOverClass+", td."+$.datepicker._currentClass,inst.dpDiv);if(sel[0]){$.datepicker._selectDay(event.target,inst.selectedMonth,inst.selectedYear,sel[0])}else{$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"))}return false;break;case 27:$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"));break;case 33:$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M");break;case 34:$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M");break;case 35:if(event.ctrlKey||event.metaKey){$.datepicker._clearDate(event.target)}handled=event.ctrlKey||event.metaKey;break;case 36:if(event.ctrlKey||event.metaKey){$.datepicker._gotoToday(event.target)}handled=event.ctrlKey||event.metaKey;break;case 37:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?+1:-1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M")}break;case 38:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,-7,"D")}handled=event.ctrlKey||event.metaKey;break;case 39:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?-1:+1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M")}break;case 40:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,+7,"D")}handled=event.ctrlKey||event.metaKey;break;default:handled=false}}else{if(event.keyCode==36&&event.ctrlKey){$.datepicker._showDatepicker(this)}else{handled=false}}if(handled){event.preventDefault();event.stopPropagation()}},_doKeyPress:function(event){var inst=$.datepicker._getInst(event.target);if($.datepicker._get(inst,"constrainInput")){var chars=$.datepicker._possibleChars($.datepicker._get(inst,"dateFormat"));var chr=String.fromCharCode(event.charCode==undefined?event.keyCode:event.charCode);return event.ctrlKey||(chr<" "||!chars||chars.indexOf(chr)>-1)}},_showDatepicker:function(input){input=input.target||input;if(input.nodeName.toLowerCase()!="input"){input=$("input",input.parentNode)[0]}if($.datepicker._isDisabledDatepicker(input)||$.datepicker._lastInput==input){return}var inst=$.datepicker._getInst(input);var beforeShow=$.datepicker._get(inst,"beforeShow");extendRemove(inst.settings,(beforeShow?beforeShow.apply(input,[input,inst]):{}));$.datepicker._hideDatepicker(null,"");$.datepicker._lastInput=input;$.datepicker._setDateFromField(inst);if($.datepicker._inDialog){input.value=""}if(!$.datepicker._pos){$.datepicker._pos=$.datepicker._findPos(input);$.datepicker._pos[1]+=input.offsetHeight}var isFixed=false;$(input).parents().each(function(){isFixed|=$(this).css("position")=="fixed";return !isFixed});if(isFixed&&$.browser.opera){$.datepicker._pos[0]-=document.documentElement.scrollLeft;$.datepicker._pos[1]-=document.documentElement.scrollTop}var offset={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null;inst.rangeStart=null;inst.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});$.datepicker._updateDatepicker(inst);offset=$.datepicker._checkOffset(inst,offset,isFixed);inst.dpDiv.css({position:($.datepicker._inDialog&&$.blockUI?"static":(isFixed?"fixed":"absolute")),display:"none",left:offset.left+"px",top:offset.top+"px"});if(!inst.inline){var showAnim=$.datepicker._get(inst,"showAnim")||"show";var duration=$.datepicker._get(inst,"duration");var postProcess=function(){$.datepicker._datepickerShowing=true;if($.browser.msie&&parseInt($.browser.version,10)<7){$("iframe.ui-datepicker-cover").css({width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4})}};if($.effects&&$.effects[showAnim]){inst.dpDiv.show(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[showAnim](duration,postProcess)}if(duration==""){postProcess()}if(inst.input[0].type!="hidden"){inst.input[0].focus()}$.datepicker._curInst=inst}},_updateDatepicker:function(inst){var dims={width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4};var self=this;inst.dpDiv.empty().append(this._generateHTML(inst)).find("iframe.ui-datepicker-cover").css({width:dims.width,height:dims.height}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){$(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).removeClass("ui-datepicker-next-hover")}}).bind("mouseover",function(){if(!self._isDisabledDatepicker(inst.inline?inst.dpDiv.parent()[0]:inst.input[0])){$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");$(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).addClass("ui-datepicker-next-hover")}}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();var numMonths=this._getNumberOfMonths(inst);var cols=numMonths[1];var width=17;if(cols>1){inst.dpDiv.addClass("ui-datepicker-multi-"+cols).css("width",(width*cols)+"em")}else{inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("")}inst.dpDiv[(numMonths[0]!=1||numMonths[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");inst.dpDiv[(this._get(inst,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(inst.input&&inst.input[0].type!="hidden"&&inst==$.datepicker._curInst){$(inst.input[0]).focus()}},_checkOffset:function(inst,offset,isFixed){var dpWidth=inst.dpDiv.outerWidth();var dpHeight=inst.dpDiv.outerHeight();var inputWidth=inst.input?inst.input.outerWidth():0;var inputHeight=inst.input?inst.input.outerHeight():0;var viewWidth=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+$(document).scrollLeft();var viewHeight=(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)+$(document).scrollTop();offset.left-=(this._get(inst,"isRTL")?(dpWidth-inputWidth):0);offset.left-=(isFixed&&offset.left==inst.input.offset().left)?$(document).scrollLeft():0;offset.top-=(isFixed&&offset.top==(inst.input.offset().top+inputHeight))?$(document).scrollTop():0;offset.left-=(offset.left+dpWidth>viewWidth&&viewWidth>dpWidth)?Math.abs(offset.left+dpWidth-viewWidth):0;offset.top-=(offset.top+dpHeight>viewHeight&&viewHeight>dpHeight)?Math.abs(offset.top+dpHeight+inputHeight*2-viewHeight):0;return offset},_findPos:function(obj){while(obj&&(obj.type=="hidden"||obj.nodeType!=1)){obj=obj.nextSibling}var position=$(obj).offset();return[position.left,position.top]},_hideDatepicker:function(input,duration){var inst=this._curInst;if(!inst||(input&&inst!=$.data(input,PROP_NAME))){return}if(inst.stayOpen){this._selectDate("#"+inst.id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear))}inst.stayOpen=false;if(this._datepickerShowing){duration=(duration!=null?duration:this._get(inst,"duration"));var showAnim=this._get(inst,"showAnim");var postProcess=function(){$.datepicker._tidyDialog(inst)};if(duration!=""&&$.effects&&$.effects[showAnim]){inst.dpDiv.hide(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[(duration==""?"hide":(showAnim=="slideDown"?"slideUp":(showAnim=="fadeIn"?"fadeOut":"hide")))](duration,postProcess)}if(duration==""){this._tidyDialog(inst)}var onClose=this._get(inst,"onClose");if(onClose){onClose.apply((inst.input?inst.input[0]:null),[(inst.input?inst.input.val():""),inst])}this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if($.blockUI){$.unblockUI();$("body").append(this.dpDiv)}}this._inDialog=false}this._curInst=null},_tidyDialog:function(inst){inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(event){if(!$.datepicker._curInst){return}var $target=$(event.target);if(($target.parents("#"+$.datepicker._mainDivId).length==0)&&!$target.hasClass($.datepicker.markerClassName)&&!$target.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&!($.datepicker._inDialog&&$.blockUI)){$.datepicker._hideDatepicker(null,"")}},_adjustDate:function(id,offset,period){var target=$(id);var inst=this._getInst(target[0]);if(this._isDisabledDatepicker(target[0])){return}this._adjustInstDate(inst,offset+(period=="M"?this._get(inst,"showCurrentAtPos"):0),period);this._updateDatepicker(inst)},_gotoToday:function(id){var target=$(id);var inst=this._getInst(target[0]);if(this._get(inst,"gotoCurrent")&&inst.currentDay){inst.selectedDay=inst.currentDay;inst.drawMonth=inst.selectedMonth=inst.currentMonth;inst.drawYear=inst.selectedYear=inst.currentYear}else{var date=new Date();inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear()}this._notifyChange(inst);this._adjustDate(target)},_selectMonthYear:function(id,select,period){var target=$(id);var inst=this._getInst(target[0]);inst._selectingMonthYear=false;inst["selected"+(period=="M"?"Month":"Year")]=inst["draw"+(period=="M"?"Month":"Year")]=parseInt(select.options[select.selectedIndex].value,10);this._notifyChange(inst);this._adjustDate(target)},_clickMonthYear:function(id){var target=$(id);var inst=this._getInst(target[0]);if(inst.input&&inst._selectingMonthYear&&!$.browser.msie){inst.input[0].focus()}inst._selectingMonthYear=!inst._selectingMonthYear},_selectDay:function(id,month,year,td){var target=$(id);if($(td).hasClass(this._unselectableClass)||this._isDisabledDatepicker(target[0])){return}var inst=this._getInst(target[0]);inst.selectedDay=inst.currentDay=$("a",td).html();inst.selectedMonth=inst.currentMonth=month;inst.selectedYear=inst.currentYear=year;if(inst.stayOpen){inst.endDay=inst.endMonth=inst.endYear=null}this._selectDate(id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear));if(inst.stayOpen){inst.rangeStart=this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay));this._updateDatepicker(inst)}},_clearDate:function(id){var target=$(id);var inst=this._getInst(target[0]);inst.stayOpen=false;inst.endDay=inst.endMonth=inst.endYear=inst.rangeStart=null;this._selectDate(target,"")},_selectDate:function(id,dateStr){var target=$(id);var inst=this._getInst(target[0]);dateStr=(dateStr!=null?dateStr:this._formatDate(inst));if(inst.input){inst.input.val(dateStr)}this._updateAlternate(inst);var onSelect=this._get(inst,"onSelect");if(onSelect){onSelect.apply((inst.input?inst.input[0]:null),[dateStr,inst])}else{if(inst.input){inst.input.trigger("change")}}if(inst.inline){this._updateDatepicker(inst)}else{if(!inst.stayOpen){this._hideDatepicker(null,this._get(inst,"duration"));this._lastInput=inst.input[0];if(typeof(inst.input[0])!="object"){inst.input[0].focus()}this._lastInput=null}}},_updateAlternate:function(inst){var altField=this._get(inst,"altField");if(altField){var altFormat=this._get(inst,"altFormat")||this._get(inst,"dateFormat");var date=this._getDate(inst);dateStr=this.formatDate(altFormat,date,this._getFormatConfig(inst));$(altField).each(function(){$(this).val(dateStr)})}},noWeekends:function(date){var day=date.getDay();return[(day>0&&day<6),""]},iso8601Week:function(date){var checkDate=new Date(date.getFullYear(),date.getMonth(),date.getDate());var firstMon=new Date(checkDate.getFullYear(),1-1,4);var firstDay=firstMon.getDay()||7;firstMon.setDate(firstMon.getDate()+1-firstDay);if(firstDay<4&&checkDate<firstMon){checkDate.setDate(checkDate.getDate()-3);return $.datepicker.iso8601Week(checkDate)}else{if(checkDate>new Date(checkDate.getFullYear(),12-1,28)){firstDay=new Date(checkDate.getFullYear()+1,1-1,4).getDay()||7;if(firstDay>4&&(checkDate.getDay()||7)<firstDay-3){return 1}}}return Math.floor(((checkDate-firstMon)/86400000)/7)+1},parseDate:function(format,value,settings){if(format==null||value==null){throw"Invalid arguments"}value=(typeof value=="object"?value.toString():value+"");if(value==""){return null}var shortYearCutoff=(settings?settings.shortYearCutoff:null)||this._defaults.shortYearCutoff;var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var year=-1;var month=-1;var day=-1;var doy=-1;var literal=false;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var getNumber=function(match){lookAhead(match);var origSize=(match=="@"?14:(match=="y"?4:(match=="o"?3:2)));var size=origSize;var num=0;while(size>0&&iValue<value.length&&value.charAt(iValue)>="0"&&value.charAt(iValue)<="9"){num=num*10+parseInt(value.charAt(iValue++),10);size--}if(size==origSize){throw"Missing number at position "+iValue}return num};var getName=function(match,shortNames,longNames){var names=(lookAhead(match)?longNames:shortNames);var size=0;for(var j=0;j<names.length;j++){size=Math.max(size,names[j].length)}var name="";var iInit=iValue;while(size>0&&iValue<value.length){name+=value.charAt(iValue++);for(var i=0;i<names.length;i++){if(name==names[i]){return i+1}}size--}throw"Unknown name at position "+iInit};var checkLiteral=function(){if(value.charAt(iValue)!=format.charAt(iFormat)){throw"Unexpected literal at position "+iValue}iValue++};var iValue=0;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{checkLiteral()}}else{switch(format.charAt(iFormat)){case"d":day=getNumber("d");break;case"D":getName("D",dayNamesShort,dayNames);break;case"o":doy=getNumber("o");break;case"m":month=getNumber("m");break;case"M":month=getName("M",monthNamesShort,monthNames);break;case"y":year=getNumber("y");break;case"@":var date=new Date(getNumber("@"));year=date.getFullYear();month=date.getMonth()+1;day=date.getDate();break;case"'":if(lookAhead("'")){checkLiteral()}else{literal=true}break;default:checkLiteral()}}}if(year==-1){year=new Date().getFullYear()}else{if(year<100){year+=new Date().getFullYear()-new Date().getFullYear()%100+(year<=shortYearCutoff?0:-100)}}if(doy>-1){month=1;day=doy;do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}var date=this._daylightSavingAdjust(new Date(year,month-1,day));if(date.getFullYear()!=year||date.getMonth()+1!=month||date.getDate()!=day){throw"Invalid date"}return date},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TIMESTAMP:"@",W3C:"yy-mm-dd",formatDate:function(format,date,settings){if(!date){return""}var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var formatNumber=function(match,value,len){var num=""+value;if(lookAhead(match)){while(num.length<len){num="0"+num}}return num};var formatName=function(match,value,shortNames,longNames){return(lookAhead(match)?longNames[value]:shortNames[value])};var output="";var literal=false;if(date){for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{output+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":output+=formatNumber("d",date.getDate(),2);break;case"D":output+=formatName("D",date.getDay(),dayNamesShort,dayNames);break;case"o":var doy=date.getDate();for(var m=date.getMonth()-1;m>=0;m--){doy+=this._getDaysInMonth(date.getFullYear(),m)}output+=formatNumber("o",doy,3);break;case"m":output+=formatNumber("m",date.getMonth()+1,2);break;case"M":output+=formatName("M",date.getMonth(),monthNamesShort,monthNames);break;case"y":output+=(lookAhead("y")?date.getFullYear():(date.getYear()%100<10?"0":"")+date.getYear()%100);break;case"@":output+=date.getTime();break;case"'":if(lookAhead("'")){output+="'"}else{literal=true}break;default:output+=format.charAt(iFormat)}}}}return output},_possibleChars:function(format){var chars="";var literal=false;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{chars+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":case"m":case"y":case"@":chars+="0123456789";break;case"D":case"M":return null;case"'":if(lookAhead("'")){chars+="'"}else{literal=true}break;default:chars+=format.charAt(iFormat)}}}return chars},_get:function(inst,name){return inst.settings[name]!==undefined?inst.settings[name]:this._defaults[name]},_setDateFromField:function(inst){var dateFormat=this._get(inst,"dateFormat");var dates=inst.input?inst.input.val():null;inst.endDay=inst.endMonth=inst.endYear=null;var date=defaultDate=this._getDefaultDate(inst);var settings=this._getFormatConfig(inst);try{date=this.parseDate(dateFormat,dates,settings)||defaultDate}catch(event){this.log(event);date=defaultDate}inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();inst.currentDay=(dates?date.getDate():0);inst.currentMonth=(dates?date.getMonth():0);inst.currentYear=(dates?date.getFullYear():0);this._adjustInstDate(inst)},_getDefaultDate:function(inst){var date=this._determineDate(this._get(inst,"defaultDate"),new Date());var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);return date},_determineDate:function(date,defaultDate){var offsetNumeric=function(offset){var date=new Date();date.setDate(date.getDate()+offset);return date};var offsetString=function(offset,getDaysInMonth){var date=new Date();var year=date.getFullYear();var month=date.getMonth();var day=date.getDate();var pattern=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;var matches=pattern.exec(offset);while(matches){switch(matches[2]||"d"){case"d":case"D":day+=parseInt(matches[1],10);break;case"w":case"W":day+=parseInt(matches[1],10)*7;break;case"m":case"M":month+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break;case"y":case"Y":year+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break}matches=pattern.exec(offset)}return new Date(year,month,day)};date=(date==null?defaultDate:(typeof date=="string"?offsetString(date,this._getDaysInMonth):(typeof date=="number"?(isNaN(date)?defaultDate:offsetNumeric(date)):date)));date=(date&&date.toString()=="Invalid Date"?defaultDate:date);if(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0)}return this._daylightSavingAdjust(date)},_daylightSavingAdjust:function(date){if(!date){return null}date.setHours(date.getHours()>12?date.getHours()+2:0);return date},_setDate:function(inst,date,endDate){var clear=!(date);var origMonth=inst.selectedMonth;var origYear=inst.selectedYear;date=this._determineDate(date,new Date());inst.selectedDay=inst.currentDay=date.getDate();inst.drawMonth=inst.selectedMonth=inst.currentMonth=date.getMonth();inst.drawYear=inst.selectedYear=inst.currentYear=date.getFullYear();if(origMonth!=inst.selectedMonth||origYear!=inst.selectedYear){this._notifyChange(inst)}this._adjustInstDate(inst);if(inst.input){inst.input.val(clear?"":this._formatDate(inst))}},_getDate:function(inst){var startDate=(!inst.currentYear||(inst.input&&inst.input.val()=="")?null:this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return startDate},_generateHTML:function(inst){var today=new Date();today=this._daylightSavingAdjust(new Date(today.getFullYear(),today.getMonth(),today.getDate()));var isRTL=this._get(inst,"isRTL");var showButtonPanel=this._get(inst,"showButtonPanel");var hideIfNoPrevNext=this._get(inst,"hideIfNoPrevNext");var navigationAsDateFormat=this._get(inst,"navigationAsDateFormat");var numMonths=this._getNumberOfMonths(inst);var showCurrentAtPos=this._get(inst,"showCurrentAtPos");var stepMonths=this._get(inst,"stepMonths");var stepBigMonths=this._get(inst,"stepBigMonths");var isMultiMonth=(numMonths[0]!=1||numMonths[1]!=1);var currentDate=this._daylightSavingAdjust((!inst.currentDay?new Date(9999,9,9):new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");var drawMonth=inst.drawMonth-showCurrentAtPos;var drawYear=inst.drawYear;if(drawMonth<0){drawMonth+=12;drawYear--}if(maxDate){var maxDraw=this._daylightSavingAdjust(new Date(maxDate.getFullYear(),maxDate.getMonth()-numMonths[1]+1,maxDate.getDate()));maxDraw=(minDate&&maxDraw<minDate?minDate:maxDraw);while(this._daylightSavingAdjust(new Date(drawYear,drawMonth,1))>maxDraw){drawMonth--;if(drawMonth<0){drawMonth=11;drawYear--}}}inst.drawMonth=drawMonth;inst.drawYear=drawYear;var prevText=this._get(inst,"prevText");prevText=(!navigationAsDateFormat?prevText:this.formatDate(prevText,this._daylightSavingAdjust(new Date(drawYear,drawMonth-stepMonths,1)),this._getFormatConfig(inst)));var prev=(this._canAdjustMonth(inst,-1,drawYear,drawMonth)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', -"+stepMonths+", 'M');\" title=\""+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>"));var nextText=this._get(inst,"nextText");nextText=(!navigationAsDateFormat?nextText:this.formatDate(nextText,this._daylightSavingAdjust(new Date(drawYear,drawMonth+stepMonths,1)),this._getFormatConfig(inst)));var next=(this._canAdjustMonth(inst,+1,drawYear,drawMonth)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', +"+stepMonths+", 'M');\" title=\""+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>"));var currentText=this._get(inst,"currentText");var gotoDate=(this._get(inst,"gotoCurrent")&&inst.currentDay?currentDate:today);currentText=(!navigationAsDateFormat?currentText:this.formatDate(currentText,gotoDate,this._getFormatConfig(inst)));var controls=(!inst.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery.datepicker._hideDatepicker();">'+this._get(inst,"closeText")+"</button>":"");var buttonPanel=(showButtonPanel)?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(isRTL?controls:"")+(this._isInRange(inst,gotoDate)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery.datepicker._gotoToday(\'#'+inst.id+"');\">"+currentText+"</button>":"")+(isRTL?"":controls)+"</div>":"";var firstDay=parseInt(this._get(inst,"firstDay"),10);firstDay=(isNaN(firstDay)?0:firstDay);var dayNames=this._get(inst,"dayNames");var dayNamesShort=this._get(inst,"dayNamesShort");var dayNamesMin=this._get(inst,"dayNamesMin");var monthNames=this._get(inst,"monthNames");var monthNamesShort=this._get(inst,"monthNamesShort");var beforeShowDay=this._get(inst,"beforeShowDay");var showOtherMonths=this._get(inst,"showOtherMonths");var calculateWeek=this._get(inst,"calculateWeek")||this.iso8601Week;var endDate=inst.endDay?this._daylightSavingAdjust(new Date(inst.endYear,inst.endMonth,inst.endDay)):currentDate;var defaultDate=this._getDefaultDate(inst);var html="";for(var row=0;row<numMonths[0];row++){var group="";for(var col=0;col<numMonths[1];col++){var selectedDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,inst.selectedDay));var cornerClass=" ui-corner-all";var calender="";if(isMultiMonth){calender+='<div class="ui-datepicker-group ui-datepicker-group-';switch(col){case 0:calender+="first";cornerClass=" ui-corner-"+(isRTL?"right":"left");break;case numMonths[1]-1:calender+="last";cornerClass=" ui-corner-"+(isRTL?"left":"right");break;default:calender+="middle";cornerClass="";break}calender+='">'}calender+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+cornerClass+'">'+(/all|left/.test(cornerClass)&&row==0?(isRTL?next:prev):"")+(/all|right/.test(cornerClass)&&row==0?(isRTL?prev:next):"")+this._generateMonthYearHeader(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,row>0||col>0,monthNames,monthNamesShort)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var thead="";for(var dow=0;dow<7;dow++){var day=(dow+firstDay)%7;thead+="<th"+((dow+firstDay+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+dayNames[day]+'">'+dayNamesMin[day]+"</span></th>"}calender+=thead+"</tr></thead><tbody>";var daysInMonth=this._getDaysInMonth(drawYear,drawMonth);if(drawYear==inst.selectedYear&&drawMonth==inst.selectedMonth){inst.selectedDay=Math.min(inst.selectedDay,daysInMonth)}var leadDays=(this._getFirstDayOfMonth(drawYear,drawMonth)-firstDay+7)%7;var numRows=(isMultiMonth?6:Math.ceil((leadDays+daysInMonth)/7));var printDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,1-leadDays));for(var dRow=0;dRow<numRows;dRow++){calender+="<tr>";var tbody="";for(var dow=0;dow<7;dow++){var daySettings=(beforeShowDay?beforeShowDay.apply((inst.input?inst.input[0]:null),[printDate]):[true,""]);var otherMonth=(printDate.getMonth()!=drawMonth);var unselectable=otherMonth||!daySettings[0]||(minDate&&printDate<minDate)||(maxDate&&printDate>maxDate);tbody+='<td class="'+((dow+firstDay+6)%7>=5?" ui-datepicker-week-end":"")+(otherMonth?" ui-datepicker-other-month":"")+((printDate.getTime()==selectedDate.getTime()&&drawMonth==inst.selectedMonth&&inst._keyEvent)||(defaultDate.getTime()==printDate.getTime()&&defaultDate.getTime()==selectedDate.getTime())?" "+this._dayOverClass:"")+(unselectable?" "+this._unselectableClass+" ui-state-disabled":"")+(otherMonth&&!showOtherMonths?"":" "+daySettings[1]+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" "+this._currentClass:"")+(printDate.getTime()==today.getTime()?" ui-datepicker-today":""))+'"'+((!otherMonth||showOtherMonths)&&daySettings[2]?' title="'+daySettings[2]+'"':"")+(unselectable?"":" onclick=\"DP_jQuery.datepicker._selectDay('#"+inst.id+"',"+drawMonth+","+drawYear+', this);return false;"')+">"+(otherMonth?(showOtherMonths?printDate.getDate():" "):(unselectable?'<span class="ui-state-default">'+printDate.getDate()+"</span>":'<a class="ui-state-default'+(printDate.getTime()==today.getTime()?" ui-state-highlight":"")+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" ui-state-active":"")+'" href="#">'+printDate.getDate()+"</a>"))+"</td>";printDate.setDate(printDate.getDate()+1);printDate=this._daylightSavingAdjust(printDate)}calender+=tbody+"</tr>"}drawMonth++;if(drawMonth>11){drawMonth=0;drawYear++}calender+="</tbody></table>"+(isMultiMonth?"</div>"+((numMonths[0]>0&&col==numMonths[1]-1)?'<div class="ui-datepicker-row-break"></div>':""):"");group+=calender}html+=group}html+=buttonPanel+($.browser.msie&&parseInt($.browser.version,10)<7&&!inst.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,secondary,monthNames,monthNamesShort){minDate=(inst.rangeStart&&minDate&&selectedDate<minDate?selectedDate:minDate);var changeMonth=this._get(inst,"changeMonth");var changeYear=this._get(inst,"changeYear");var showMonthAfterYear=this._get(inst,"showMonthAfterYear");var html='<div class="ui-datepicker-title">';var monthHtml="";if(secondary||!changeMonth){monthHtml+='<span class="ui-datepicker-month">'+monthNames[drawMonth]+"</span> "}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='<select class="ui-datepicker-month" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'M');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(var month=0;month<12;month++){if((!inMinYear||month>=minDate.getMonth())&&(!inMaxYear||month<=maxDate.getMonth())){monthHtml+='<option value="'+month+'"'+(month==drawMonth?' selected="selected"':"")+">"+monthNamesShort[month]+"</option>"}}monthHtml+="</select>"}if(!showMonthAfterYear){html+=monthHtml+((secondary||changeMonth||changeYear)&&(!(changeMonth&&changeYear))?" ":"")}if(secondary||!changeYear){html+='<span class="ui-datepicker-year">'+drawYear+"</span>"}else{var years=this._get(inst,"yearRange").split(":");var year=0;var endYear=0;if(years.length!=2){year=drawYear-10;endYear=drawYear+10}else{if(years[0].charAt(0)=="+"||years[0].charAt(0)=="-"){year=drawYear+parseInt(years[0],10);endYear=drawYear+parseInt(years[1],10)}else{year=parseInt(years[0],10);endYear=parseInt(years[1],10)}}year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='<select class="ui-datepicker-year" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'Y');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(;year<=endYear;year++){html+='<option value="'+year+'"'+(year==drawYear?' selected="selected"':"")+">"+year+"</option>"}html+="</select>"}if(showMonthAfterYear){html+=(secondary||changeMonth||changeYear?" ":"")+monthHtml}html+="</div>";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._daylightSavingAdjust(new Date(year,month,day));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax,checkRange){var date=this._determineDate(this._get(inst,minMax+"Date"),null);return(!checkRange||!inst.rangeStart?date:(!date||inst.rangeStart>date?inst.rangeStart:date))},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var newMinDate=(!inst.rangeStart?null:this._daylightSavingAdjust(new Date(inst.selectedYear,inst.selectedMonth,inst.selectedDay)));newMinDate=(newMinDate&&inst.rangeStart<newMinDate?inst.rangeStart:newMinDate);var minDate=newMinDate||this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");return((!minDate||date>=minDate)&&(!maxDate||date<=maxDate))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}if(options=="option"&&arguments.length==2&&typeof arguments[1]=="string"){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.7.2";window.DP_jQuery=$})(jQuery);;/*
- * jQuery UI Progressbar 1.7.2
+(function(d,G){function L(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._inDialog=this._datepickerShowing=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass=
+"ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su",
+"Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",
+minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};d.extend(this._defaults,this.regional[""]);this.dpDiv=d('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}function E(a,b){d.extend(a,
+b);for(var c in b)if(b[c]==null||b[c]==G)a[c]=b[c];return a}d.extend(d.ui,{datepicker:{version:"1.8.5"}});var y=(new Date).getTime();d.extend(L.prototype,{markerClassName:"hasDatepicker",log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){E(this._defaults,a||{});return this},_attachDatepicker:function(a,b){var c=null;for(var e in this._defaults){var f=a.getAttribute("date:"+e);if(f){c=c||{};try{c[e]=eval(f)}catch(h){c[e]=
+f}}}e=a.nodeName.toLowerCase();f=e=="div"||e=="span";if(!a.id){this.uuid+=1;a.id="dp"+this.uuid}var i=this._newInst(d(a),f);i.settings=d.extend({},b||{},c||{});if(e=="input")this._connectDatepicker(a,i);else f&&this._inlineDatepicker(a,i)},_newInst:function(a,b){return{id:a[0].id.replace(/([^A-Za-z0-9_])/g,"\\\\$1"),input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:!b?this.dpDiv:d('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')}},
+_connectDatepicker:function(a,b){var c=d(a);b.append=d([]);b.trigger=d([]);if(!c.hasClass(this.markerClassName)){this._attachments(c,b);c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});this._autoSize(b);d.data(a,"datepicker",b)}},_attachments:function(a,b){var c=this._get(b,"appendText"),e=this._get(b,"isRTL");b.append&&
+b.append.remove();if(c){b.append=d('<span class="'+this._appendClass+'">'+c+"</span>");a[e?"before":"after"](b.append)}a.unbind("focus",this._showDatepicker);b.trigger&&b.trigger.remove();c=this._get(b,"showOn");if(c=="focus"||c=="both")a.focus(this._showDatepicker);if(c=="button"||c=="both"){c=this._get(b,"buttonText");var f=this._get(b,"buttonImage");b.trigger=d(this._get(b,"buttonImageOnly")?d("<img/>").addClass(this._triggerClass).attr({src:f,alt:c,title:c}):d('<button type="button"></button>').addClass(this._triggerClass).html(f==
+""?c:d("<img/>").attr({src:f,alt:c,title:c})));a[e?"before":"after"](b.trigger);b.trigger.click(function(){d.datepicker._datepickerShowing&&d.datepicker._lastInput==a[0]?d.datepicker._hideDatepicker():d.datepicker._showDatepicker(a[0]);return false})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var e=function(f){for(var h=0,i=0,g=0;g<f.length;g++)if(f[g].length>h){h=f[g].length;i=g}return i};b.setMonth(e(this._get(a,
+c.match(/MM/)?"monthNames":"monthNamesShort")));b.setDate(e(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=d(a);if(!c.hasClass(this.markerClassName)){c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(e,f,h){b.settings[f]=h}).bind("getData.datepicker",function(e,f){return this._get(b,f)});d.data(a,"datepicker",b);this._setDate(b,this._getDefaultDate(b),
+true);this._updateDatepicker(b);this._updateAlternate(b)}},_dialogDatepicker:function(a,b,c,e,f){a=this._dialogInst;if(!a){this.uuid+=1;this._dialogInput=d('<input type="text" id="'+("dp"+this.uuid)+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');this._dialogInput.keydown(this._doKeyDown);d("body").append(this._dialogInput);a=this._dialogInst=this._newInst(this._dialogInput,false);a.settings={};d.data(this._dialogInput[0],"datepicker",a)}E(a.settings,e||{});b=b&&b.constructor==
+Date?this._formatDate(a,b):b;this._dialogInput.val(b);this._pos=f?f.length?f:[f.pageX,f.pageY]:null;if(!this._pos)this._pos=[document.documentElement.clientWidth/2-100+(document.documentElement.scrollLeft||document.body.scrollLeft),document.documentElement.clientHeight/2-150+(document.documentElement.scrollTop||document.body.scrollTop)];this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px");a.settings.onSelect=c;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);
+d.blockUI&&d.blockUI(this.dpDiv);d.data(this._dialogInput[0],"datepicker",a);return this},_destroyDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();d.removeData(a,"datepicker");if(e=="input"){c.append.remove();c.trigger.remove();b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else if(e=="div"||e=="span")b.removeClass(this.markerClassName).empty()}},
+_enableDatepicker:function(a){var b=d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=false;c.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().removeClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f})}},_disableDatepicker:function(a){var b=
+d(a),c=d.data(a,"datepicker");if(b.hasClass(this.markerClassName)){var e=a.nodeName.toLowerCase();if(e=="input"){a.disabled=true;c.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else if(e=="div"||e=="span")b.children("."+this._inlineClass).children().addClass("ui-state-disabled");this._disabledInputs=d.map(this._disabledInputs,function(f){return f==a?null:f});this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return false;
+for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return true;return false},_getInst:function(a){try{return d.data(a,"datepicker")}catch(b){throw"Missing instance data for this datepicker";}},_optionDatepicker:function(a,b,c){var e=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?d.extend({},d.datepicker._defaults):e?b=="all"?d.extend({},e.settings):this._get(e,b):null;var f=b||{};if(typeof b=="string"){f={};f[b]=c}if(e){this._curInst==e&&
+this._hideDatepicker();var h=this._getDateDatepicker(a,true);E(e.settings,f);this._attachments(d(a),e);this._autoSize(e);this._setDateDatepicker(a,h);this._updateDatepicker(e)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){(a=this._getInst(a))&&this._updateDatepicker(a)},_setDateDatepicker:function(a,b){if(a=this._getInst(a)){this._setDate(a,b);this._updateDatepicker(a);this._updateAlternate(a)}},_getDateDatepicker:function(a,b){(a=this._getInst(a))&&
+!a.inline&&this._setDateFromField(a,b);return a?this._getDate(a):null},_doKeyDown:function(a){var b=d.datepicker._getInst(a.target),c=true,e=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=true;if(d.datepicker._datepickerShowing)switch(a.keyCode){case 9:d.datepicker._hideDatepicker();c=false;break;case 13:c=d("td."+d.datepicker._dayOverClass,b.dpDiv).add(d("td."+d.datepicker._currentClass,b.dpDiv));c[0]?d.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,c[0]):d.datepicker._hideDatepicker();
+return false;case 27:d.datepicker._hideDatepicker();break;case 33:d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 34:d.datepicker._adjustDate(a.target,a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 35:if(a.ctrlKey||a.metaKey)d.datepicker._clearDate(a.target);c=a.ctrlKey||a.metaKey;break;case 36:if(a.ctrlKey||a.metaKey)d.datepicker._gotoToday(a.target);c=a.ctrlKey||
+a.metaKey;break;case 37:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,e?+1:-1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,a.ctrlKey?-d.datepicker._get(b,"stepBigMonths"):-d.datepicker._get(b,"stepMonths"),"M");break;case 38:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,-7,"D");c=a.ctrlKey||a.metaKey;break;case 39:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,e?-1:+1,"D");c=a.ctrlKey||a.metaKey;if(a.originalEvent.altKey)d.datepicker._adjustDate(a.target,
+a.ctrlKey?+d.datepicker._get(b,"stepBigMonths"):+d.datepicker._get(b,"stepMonths"),"M");break;case 40:if(a.ctrlKey||a.metaKey)d.datepicker._adjustDate(a.target,+7,"D");c=a.ctrlKey||a.metaKey;break;default:c=false}else if(a.keyCode==36&&a.ctrlKey)d.datepicker._showDatepicker(this);else c=false;if(c){a.preventDefault();a.stopPropagation()}},_doKeyPress:function(a){var b=d.datepicker._getInst(a.target);if(d.datepicker._get(b,"constrainInput")){b=d.datepicker._possibleChars(d.datepicker._get(b,"dateFormat"));
+var c=String.fromCharCode(a.charCode==G?a.keyCode:a.charCode);return a.ctrlKey||c<" "||!b||b.indexOf(c)>-1}},_doKeyUp:function(a){a=d.datepicker._getInst(a.target);if(a.input.val()!=a.lastVal)try{if(d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),a.input?a.input.val():null,d.datepicker._getFormatConfig(a))){d.datepicker._setDateFromField(a);d.datepicker._updateAlternate(a);d.datepicker._updateDatepicker(a)}}catch(b){d.datepicker.log(b)}return true},_showDatepicker:function(a){a=a.target||
+a;if(a.nodeName.toLowerCase()!="input")a=d("input",a.parentNode)[0];if(!(d.datepicker._isDisabledDatepicker(a)||d.datepicker._lastInput==a)){var b=d.datepicker._getInst(a);d.datepicker._curInst&&d.datepicker._curInst!=b&&d.datepicker._curInst.dpDiv.stop(true,true);var c=d.datepicker._get(b,"beforeShow");E(b.settings,c?c.apply(a,[a,b]):{});b.lastVal=null;d.datepicker._lastInput=a;d.datepicker._setDateFromField(b);if(d.datepicker._inDialog)a.value="";if(!d.datepicker._pos){d.datepicker._pos=d.datepicker._findPos(a);
+d.datepicker._pos[1]+=a.offsetHeight}var e=false;d(a).parents().each(function(){e|=d(this).css("position")=="fixed";return!e});if(e&&d.browser.opera){d.datepicker._pos[0]-=document.documentElement.scrollLeft;d.datepicker._pos[1]-=document.documentElement.scrollTop}c={left:d.datepicker._pos[0],top:d.datepicker._pos[1]};d.datepicker._pos=null;b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});d.datepicker._updateDatepicker(b);c=d.datepicker._checkOffset(b,c,e);b.dpDiv.css({position:d.datepicker._inDialog&&
+d.blockUI?"static":e?"fixed":"absolute",display:"none",left:c.left+"px",top:c.top+"px"});if(!b.inline){c=d.datepicker._get(b,"showAnim");var f=d.datepicker._get(b,"duration"),h=function(){d.datepicker._datepickerShowing=true;var i=d.datepicker._getBorders(b.dpDiv);b.dpDiv.find("iframe.ui-datepicker-cover").css({left:-i[0],top:-i[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})};b.dpDiv.zIndex(d(a).zIndex()+1);d.effects&&d.effects[c]?b.dpDiv.show(c,d.datepicker._get(b,"showOptions"),f,
+h):b.dpDiv[c||"show"](c?f:null,h);if(!c||!f)h();b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus();d.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this,c=d.datepicker._getBorders(a.dpDiv);a.dpDiv.empty().append(this._generateHTML(a)).find("iframe.ui-datepicker-cover").css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){d(this).removeClass("ui-state-hover");
+this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).removeClass("ui-datepicker-prev-hover");this.className.indexOf("ui-datepicker-next")!=-1&&d(this).removeClass("ui-datepicker-next-hover")}).bind("mouseover",function(){if(!b._isDisabledDatepicker(a.inline?a.dpDiv.parent()[0]:a.input[0])){d(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");d(this).addClass("ui-state-hover");this.className.indexOf("ui-datepicker-prev")!=-1&&d(this).addClass("ui-datepicker-prev-hover");
+this.className.indexOf("ui-datepicker-next")!=-1&&d(this).addClass("ui-datepicker-next-hover")}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();c=this._getNumberOfMonths(a);var e=c[1];e>1?a.dpDiv.addClass("ui-datepicker-multi-"+e).css("width",17*e+"em"):a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");a.dpDiv[(c[0]!=1||c[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");
+a==d.datepicker._curInst&&d.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input.focus()},_getBorders:function(a){var b=function(c){return{thin:1,medium:2,thick:3}[c]||c};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var e=a.dpDiv.outerWidth(),f=a.dpDiv.outerHeight(),h=a.input?a.input.outerWidth():0,i=a.input?a.input.outerHeight():0,g=document.documentElement.clientWidth+d(document).scrollLeft(),
+k=document.documentElement.clientHeight+d(document).scrollTop();b.left-=this._get(a,"isRTL")?e-h:0;b.left-=c&&b.left==a.input.offset().left?d(document).scrollLeft():0;b.top-=c&&b.top==a.input.offset().top+i?d(document).scrollTop():0;b.left-=Math.min(b.left,b.left+e>g&&g>e?Math.abs(b.left+e-g):0);b.top-=Math.min(b.top,b.top+f>k&&k>f?Math.abs(f+i):0);return b},_findPos:function(a){for(var b=this._get(this._getInst(a),"isRTL");a&&(a.type=="hidden"||a.nodeType!=1);)a=a[b?"previousSibling":"nextSibling"];
+a=d(a).offset();return[a.left,a.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=d.data(a,"datepicker")))if(this._datepickerShowing){a=this._get(b,"showAnim");var c=this._get(b,"duration"),e=function(){d.datepicker._tidyDialog(b);this._curInst=null};d.effects&&d.effects[a]?b.dpDiv.hide(a,d.datepicker._get(b,"showOptions"),c,e):b.dpDiv[a=="slideDown"?"slideUp":a=="fadeIn"?"fadeOut":"hide"](a?c:null,e);a||e();if(a=this._get(b,"onClose"))a.apply(b.input?b.input[0]:null,[b.input?b.input.val():
+"",b]);this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if(d.blockUI){d.unblockUI();d("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(d.datepicker._curInst){a=d(a.target);a[0].id!=d.datepicker._mainDivId&&a.parents("#"+d.datepicker._mainDivId).length==0&&!a.hasClass(d.datepicker.markerClassName)&&
+!a.hasClass(d.datepicker._triggerClass)&&d.datepicker._datepickerShowing&&!(d.datepicker._inDialog&&d.blockUI)&&d.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){a=d(a);var e=this._getInst(a[0]);if(!this._isDisabledDatepicker(a[0])){this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c);this._updateDatepicker(e)}},_gotoToday:function(a){a=d(a);var b=this._getInst(a[0]);if(this._get(b,"gotoCurrent")&&b.currentDay){b.selectedDay=b.currentDay;b.drawMonth=b.selectedMonth=b.currentMonth;
+b.drawYear=b.selectedYear=b.currentYear}else{var c=new Date;b.selectedDay=c.getDate();b.drawMonth=b.selectedMonth=c.getMonth();b.drawYear=b.selectedYear=c.getFullYear()}this._notifyChange(b);this._adjustDate(a)},_selectMonthYear:function(a,b,c){a=d(a);var e=this._getInst(a[0]);e._selectingMonthYear=false;e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10);this._notifyChange(e);this._adjustDate(a)},_clickMonthYear:function(a){var b=
+this._getInst(d(a)[0]);b.input&&b._selectingMonthYear&&setTimeout(function(){b.input.focus()},0);b._selectingMonthYear=!b._selectingMonthYear},_selectDay:function(a,b,c,e){var f=d(a);if(!(d(e).hasClass(this._unselectableClass)||this._isDisabledDatepicker(f[0]))){f=this._getInst(f[0]);f.selectedDay=f.currentDay=d("a",e).html();f.selectedMonth=f.currentMonth=b;f.selectedYear=f.currentYear=c;this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){a=
+d(a);this._getInst(a[0]);this._selectDate(a,"")},_selectDate:function(a,b){a=this._getInst(d(a)[0]);b=b!=null?b:this._formatDate(a);a.input&&a.input.val(b);this._updateAlternate(a);var c=this._get(a,"onSelect");if(c)c.apply(a.input?a.input[0]:null,[b,a]);else a.input&&a.input.trigger("change");if(a.inline)this._updateDatepicker(a);else{this._hideDatepicker();this._lastInput=a.input[0];typeof a.input[0]!="object"&&a.input.focus();this._lastInput=null}},_updateAlternate:function(a){var b=this._get(a,
+"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),e=this._getDate(a),f=this.formatDate(c,e,this._getFormatConfig(a));d(b).each(function(){d(this).val(f)})}},noWeekends:function(a){a=a.getDay();return[a>0&&a<6,""]},iso8601Week:function(a){a=new Date(a.getTime());a.setDate(a.getDate()+4-(a.getDay()||7));var b=a.getTime();a.setMonth(0);a.setDate(1);return Math.floor(Math.round((b-a)/864E5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b==
+"object"?b.toString():b+"";if(b=="")return null;for(var e=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff,f=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,h=(c?c.dayNames:null)||this._defaults.dayNames,i=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,k=c=-1,l=-1,u=-1,j=false,o=function(p){(p=z+1<a.length&&a.charAt(z+1)==p)&&z++;return p},m=function(p){o(p);p=new RegExp("^\\d{1,"+(p=="@"?14:p=="!"?20:p=="y"?4:p=="o"?
+3:2)+"}");p=b.substring(s).match(p);if(!p)throw"Missing number at position "+s;s+=p[0].length;return parseInt(p[0],10)},n=function(p,w,H){p=o(p)?H:w;for(w=0;w<p.length;w++)if(b.substr(s,p[w].length).toLowerCase()==p[w].toLowerCase()){s+=p[w].length;return w+1}throw"Unknown name at position "+s;},r=function(){if(b.charAt(s)!=a.charAt(z))throw"Unexpected literal at position "+s;s++},s=0,z=0;z<a.length;z++)if(j)if(a.charAt(z)=="'"&&!o("'"))j=false;else r();else switch(a.charAt(z)){case "d":l=m("d");
+break;case "D":n("D",f,h);break;case "o":u=m("o");break;case "m":k=m("m");break;case "M":k=n("M",i,g);break;case "y":c=m("y");break;case "@":var v=new Date(m("@"));c=v.getFullYear();k=v.getMonth()+1;l=v.getDate();break;case "!":v=new Date((m("!")-this._ticksTo1970)/1E4);c=v.getFullYear();k=v.getMonth()+1;l=v.getDate();break;case "'":if(o("'"))r();else j=true;break;default:r()}if(c==-1)c=(new Date).getFullYear();else if(c<100)c+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c<=e?0:-100);if(u>
+-1){k=1;l=u;do{e=this._getDaysInMonth(c,k-1);if(l<=e)break;k++;l-=e}while(1)}v=this._daylightSavingAdjust(new Date(c,k-1,l));if(v.getFullYear()!=c||v.getMonth()+1!=k||v.getDate()!=l)throw"Invalid date";return v},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*
+60*60*1E7,formatDate:function(a,b,c){if(!b)return"";var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,h=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort;c=(c?c.monthNames:null)||this._defaults.monthNames;var i=function(o){(o=j+1<a.length&&a.charAt(j+1)==o)&&j++;return o},g=function(o,m,n){m=""+m;if(i(o))for(;m.length<n;)m="0"+m;return m},k=function(o,m,n,r){return i(o)?r[m]:n[m]},l="",u=false;if(b)for(var j=0;j<a.length;j++)if(u)if(a.charAt(j)==
+"'"&&!i("'"))u=false;else l+=a.charAt(j);else switch(a.charAt(j)){case "d":l+=g("d",b.getDate(),2);break;case "D":l+=k("D",b.getDay(),e,f);break;case "o":l+=g("o",(b.getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864E5,3);break;case "m":l+=g("m",b.getMonth()+1,2);break;case "M":l+=k("M",b.getMonth(),h,c);break;case "y":l+=i("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case "@":l+=b.getTime();break;case "!":l+=b.getTime()*1E4+this._ticksTo1970;break;case "'":if(i("'"))l+=
+"'";else u=true;break;default:l+=a.charAt(j)}return l},_possibleChars:function(a){for(var b="",c=false,e=function(h){(h=f+1<a.length&&a.charAt(f+1)==h)&&f++;return h},f=0;f<a.length;f++)if(c)if(a.charAt(f)=="'"&&!e("'"))c=false;else b+=a.charAt(f);else switch(a.charAt(f)){case "d":case "m":case "y":case "@":b+="0123456789";break;case "D":case "M":return null;case "'":if(e("'"))b+="'";else c=true;break;default:b+=a.charAt(f)}return b},_get:function(a,b){return a.settings[b]!==G?a.settings[b]:this._defaults[b]},
+_setDateFromField:function(a,b){if(a.input.val()!=a.lastVal){var c=this._get(a,"dateFormat"),e=a.lastVal=a.input?a.input.val():null,f,h;f=h=this._getDefaultDate(a);var i=this._getFormatConfig(a);try{f=this.parseDate(c,e,i)||h}catch(g){this.log(g);e=b?"":e}a.selectedDay=f.getDate();a.drawMonth=a.selectedMonth=f.getMonth();a.drawYear=a.selectedYear=f.getFullYear();a.currentDay=e?f.getDate():0;a.currentMonth=e?f.getMonth():0;a.currentYear=e?f.getFullYear():0;this._adjustInstDate(a)}},_getDefaultDate:function(a){return this._restrictMinMax(a,
+this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var e=function(h){var i=new Date;i.setDate(i.getDate()+h);return i},f=function(h){try{return d.datepicker.parseDate(d.datepicker._get(a,"dateFormat"),h,d.datepicker._getFormatConfig(a))}catch(i){}var g=(h.toLowerCase().match(/^c/)?d.datepicker._getDate(a):null)||new Date,k=g.getFullYear(),l=g.getMonth();g=g.getDate();for(var u=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,j=u.exec(h);j;){switch(j[2]||"d"){case "d":case "D":g+=
+parseInt(j[1],10);break;case "w":case "W":g+=parseInt(j[1],10)*7;break;case "m":case "M":l+=parseInt(j[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(k,l));break;case "y":case "Y":k+=parseInt(j[1],10);g=Math.min(g,d.datepicker._getDaysInMonth(k,l));break}j=u.exec(h)}return new Date(k,l,g)};if(b=(b=b==null?c:typeof b=="string"?f(b):typeof b=="number"?isNaN(b)?c:e(b):b)&&b.toString()=="Invalid Date"?c:b){b.setHours(0);b.setMinutes(0);b.setSeconds(0);b.setMilliseconds(0)}return this._daylightSavingAdjust(b)},
+_daylightSavingAdjust:function(a){if(!a)return null;a.setHours(a.getHours()>12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var e=!b,f=a.selectedMonth,h=a.selectedYear;b=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=b.getDate();a.drawMonth=a.selectedMonth=a.currentMonth=b.getMonth();a.drawYear=a.selectedYear=a.currentYear=b.getFullYear();if((f!=a.selectedMonth||h!=a.selectedYear)&&!c)this._notifyChange(a);this._adjustInstDate(a);if(a.input)a.input.val(e?
+"":this._formatDate(a))},_getDate:function(a){return!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay))},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),e=this._get(a,"showButtonPanel"),f=this._get(a,"hideIfNoPrevNext"),h=this._get(a,"navigationAsDateFormat"),i=this._getNumberOfMonths(a),g=this._get(a,"showCurrentAtPos"),k=
+this._get(a,"stepMonths"),l=i[0]!=1||i[1]!=1,u=this._daylightSavingAdjust(!a.currentDay?new Date(9999,9,9):new Date(a.currentYear,a.currentMonth,a.currentDay)),j=this._getMinMaxDate(a,"min"),o=this._getMinMaxDate(a,"max");g=a.drawMonth-g;var m=a.drawYear;if(g<0){g+=12;m--}if(o){var n=this._daylightSavingAdjust(new Date(o.getFullYear(),o.getMonth()-i[0]*i[1]+1,o.getDate()));for(n=j&&n<j?j:n;this._daylightSavingAdjust(new Date(m,g,1))>n;){g--;if(g<0){g=11;m--}}}a.drawMonth=g;a.drawYear=m;n=this._get(a,
+"prevText");n=!h?n:this.formatDate(n,this._daylightSavingAdjust(new Date(m,g-k,1)),this._getFormatConfig(a));n=this._canAdjustMonth(a,-1,m,g)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', -"+k+", 'M');\" title=\""+n+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+n+"</span></a>":f?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+n+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+
+n+"</span></a>";var r=this._get(a,"nextText");r=!h?r:this.formatDate(r,this._daylightSavingAdjust(new Date(m,g+k,1)),this._getFormatConfig(a));f=this._canAdjustMonth(a,+1,m,g)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+y+".datepicker._adjustDate('#"+a.id+"', +"+k+", 'M');\" title=\""+r+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+r+"</span></a>":f?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+r+'"><span class="ui-icon ui-icon-circle-triangle-'+
+(c?"w":"e")+'">'+r+"</span></a>";k=this._get(a,"currentText");r=this._get(a,"gotoCurrent")&&a.currentDay?u:b;k=!h?k:this.formatDate(k,r,this._getFormatConfig(a));h=!a.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+y+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>":"";e=e?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?h:"")+(this._isInRange(a,r)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+
+y+".datepicker._gotoToday('#"+a.id+"');\">"+k+"</button>":"")+(c?"":h)+"</div>":"";h=parseInt(this._get(a,"firstDay"),10);h=isNaN(h)?0:h;k=this._get(a,"showWeek");r=this._get(a,"dayNames");this._get(a,"dayNamesShort");var s=this._get(a,"dayNamesMin"),z=this._get(a,"monthNames"),v=this._get(a,"monthNamesShort"),p=this._get(a,"beforeShowDay"),w=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths");this._get(a,"calculateWeek");for(var M=this._getDefaultDate(a),I="",C=0;C<i[0];C++){for(var N=
+"",D=0;D<i[1];D++){var J=this._daylightSavingAdjust(new Date(m,g,a.selectedDay)),t=" ui-corner-all",x="";if(l){x+='<div class="ui-datepicker-group';if(i[1]>1)switch(D){case 0:x+=" ui-datepicker-group-first";t=" ui-corner-"+(c?"right":"left");break;case i[1]-1:x+=" ui-datepicker-group-last";t=" ui-corner-"+(c?"left":"right");break;default:x+=" ui-datepicker-group-middle";t="";break}x+='">'}x+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+t+'">'+(/all|left/.test(t)&&C==0?c?
+f:n:"")+(/all|right/.test(t)&&C==0?c?n:f:"")+this._generateMonthYearHeader(a,g,m,j,o,C>0||D>0,z,v)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var A=k?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(t=0;t<7;t++){var q=(t+h)%7;A+="<th"+((t+h+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+r[q]+'">'+s[q]+"</span></th>"}x+=A+"</tr></thead><tbody>";A=this._getDaysInMonth(m,g);if(m==a.selectedYear&&g==a.selectedMonth)a.selectedDay=Math.min(a.selectedDay,
+A);t=(this._getFirstDayOfMonth(m,g)-h+7)%7;A=l?6:Math.ceil((t+A)/7);q=this._daylightSavingAdjust(new Date(m,g,1-t));for(var O=0;O<A;O++){x+="<tr>";var P=!k?"":'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(q)+"</td>";for(t=0;t<7;t++){var F=p?p.apply(a.input?a.input[0]:null,[q]):[true,""],B=q.getMonth()!=g,K=B&&!H||!F[0]||j&&q<j||o&&q>o;P+='<td class="'+((t+h+6)%7>=5?" ui-datepicker-week-end":"")+(B?" ui-datepicker-other-month":"")+(q.getTime()==J.getTime()&&g==a.selectedMonth&&
+a._keyEvent||M.getTime()==q.getTime()&&M.getTime()==J.getTime()?" "+this._dayOverClass:"")+(K?" "+this._unselectableClass+" ui-state-disabled":"")+(B&&!w?"":" "+F[1]+(q.getTime()==u.getTime()?" "+this._currentClass:"")+(q.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!B||w)&&F[2]?' title="'+F[2]+'"':"")+(K?"":' onclick="DP_jQuery_'+y+".datepicker._selectDay('#"+a.id+"',"+q.getMonth()+","+q.getFullYear()+', this);return false;"')+">"+(B&&!w?" ":K?'<span class="ui-state-default">'+q.getDate()+
+"</span>":'<a class="ui-state-default'+(q.getTime()==b.getTime()?" ui-state-highlight":"")+(q.getTime()==J.getTime()?" ui-state-active":"")+(B?" ui-priority-secondary":"")+'" href="#">'+q.getDate()+"</a>")+"</td>";q.setDate(q.getDate()+1);q=this._daylightSavingAdjust(q)}x+=P+"</tr>"}g++;if(g>11){g=0;m++}x+="</tbody></table>"+(l?"</div>"+(i[0]>0&&D==i[1]-1?'<div class="ui-datepicker-row-break"></div>':""):"");N+=x}I+=N}I+=e+(d.browser.msie&&parseInt(d.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':
+"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var k=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),j='<div class="ui-datepicker-title">',o="";if(h||!k)o+='<span class="ui-datepicker-month">'+i[b]+"</span>";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+
+a.id+"');\">";for(var n=0;n<12;n++)if((!i||n>=e.getMonth())&&(!m||n<=f.getMonth()))o+='<option value="'+n+'"'+(n==b?' selected="selected"':"")+">"+g[n]+"</option>";o+="</select>"}u||(j+=o+(h||!(k&&l)?" ":""));if(h||!l)j+='<span class="ui-datepicker-year">'+c+"</span>";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b,
+i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(j+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+y+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" onclick=\"DP_jQuery_"+y+".datepicker._clickMonthYear('#"+a.id+"');\">";b<=g;b++)j+='<option value="'+b+'"'+(b==c?' selected="selected"':"")+">"+b+"</option>";j+="</select>"}j+=this._get(a,"yearSuffix");if(u)j+=(h||!(k&&l)?" ":"")+o;j+="</div>";return j},_adjustInstDate:function(a,b,c){var e=
+a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&b<c?c:b;return b=a&&b>a?a:b},_notifyChange:function(a){var b=this._get(a,
+"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a);
+c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,
+"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=
+function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));
+return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.5";window["DP_jQuery_"+y]=d})(jQuery);
+;/*
+ * jQuery UI Progressbar 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Progressbar
*
* Depends:
- * ui.core.js
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
*/
-(function(a){a.widget("ui.progressbar",{_init:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this._valueMin(),"aria-valuemax":this._valueMax(),"aria-valuenow":this._value()});this.valueDiv=a('<div class="ui-progressbar-value ui-widget-header ui-corner-left"></div>').appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();a.widget.prototype.destroy.apply(this,arguments)},value:function(b){if(b===undefined){return this._value()}this._setData("value",b);return this},_setData:function(b,c){switch(b){case"value":this.options.value=c;this._refreshValue();this._trigger("change",null,{});break}a.widget.prototype._setData.apply(this,arguments)},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_valueMin:function(){var b=0;return b},_valueMax:function(){var b=100;return b},_refreshValue:function(){var b=this.value();this.valueDiv[b==this._valueMax()?"addClass":"removeClass"]("ui-corner-right");this.valueDiv.width(b+"%");this.element.attr("aria-valuenow",b)}});a.extend(a.ui.progressbar,{version:"1.7.2",defaults:{value:0}})})(jQuery);;/*
- * jQuery UI Effects 1.7.2
+(function(b,c){b.widget("ui.progressbar",{options:{value:0},min:0,max:100,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.max,"aria-valuenow":this._value()});this.valueDiv=b("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow");
+this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===c)return this._value();this._setOption("value",a);return this},_setOption:function(a,d){if(a==="value"){this.options.value=d;this._refreshValue();this._trigger("change")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.max,Math.max(this.min,a))},_refreshValue:function(){var a=this.value();this.valueDiv.toggleClass("ui-corner-right",
+a===this.max).width(a+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.5"})})(jQuery);
+;/*
+ * jQuery UI Effects 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/
*/
-jQuery.effects||(function(d){d.effects={version:"1.7.2",save:function(g,h){for(var f=0;f<h.length;f++){if(h[f]!==null){g.data("ec.storage."+h[f],g[0].style[h[f]])}}},restore:function(g,h){for(var f=0;f<h.length;f++){if(h[f]!==null){g.css(h[f],g.data("ec.storage."+h[f]))}}},setMode:function(f,g){if(g=="toggle"){g=f.is(":hidden")?"show":"hide"}return g},getBaseline:function(g,h){var i,f;switch(g[0]){case"top":i=0;break;case"middle":i=0.5;break;case"bottom":i=1;break;default:i=g[0]/h.height}switch(g[1]){case"left":f=0;break;case"center":f=0.5;break;case"right":f=1;break;default:f=g[1]/h.width}return{x:f,y:i}},createWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent()}var g={width:f.outerWidth(true),height:f.outerHeight(true),"float":f.css("float")};f.wrap('<div class="ui-effects-wrapper" style="font-size:100%;background:transparent;border:none;margin:0;padding:0"></div>');var j=f.parent();if(f.css("position")=="static"){j.css({position:"relative"});f.css({position:"relative"})}else{var i=f.css("top");if(isNaN(parseInt(i,10))){i="auto"}var h=f.css("left");if(isNaN(parseInt(h,10))){h="auto"}j.css({position:f.css("position"),top:i,left:h,zIndex:f.css("z-index")}).show();f.css({position:"relative",top:0,left:0})}j.css(g);return j},removeWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent().replaceWith(f)}return f},setTransition:function(g,i,f,h){h=h||{};d.each(i,function(k,j){unit=g.cssUnit(j);if(unit[0]>0){h[j]=unit[0]*f+unit[1]}});return h},animateClass:function(h,i,k,j){var f=(typeof k=="function"?k:(j?j:null));var g=(typeof k=="string"?k:null);return this.each(function(){var q={};var o=d(this);var p=o.attr("style")||"";if(typeof p=="object"){p=p.cssText}if(h.toggle){o.hasClass(h.toggle)?h.remove=h.toggle:h.add=h.toggle}var l=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.addClass(h.add)}if(h.remove){o.removeClass(h.remove)}var m=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.removeClass(h.add)}if(h.remove){o.addClass(h.remove)}for(var r in m){if(typeof m[r]!="function"&&m[r]&&r.indexOf("Moz")==-1&&r.indexOf("length")==-1&&m[r]!=l[r]&&(r.match(/color/i)||(!r.match(/color/i)&&!isNaN(parseInt(m[r],10))))&&(l.position!="static"||(l.position=="static"&&!r.match(/left|top|bottom|right/)))){q[r]=m[r]}}o.animate(q,i,g,function(){if(typeof d(this).attr("style")=="object"){d(this).attr("style")["cssText"]="";d(this).attr("style")["cssText"]=p}else{d(this).attr("style",p)}if(h.add){d(this).addClass(h.add)}if(h.remove){d(this).removeClass(h.remove)}if(f){f.apply(this,arguments)}})})}};function c(g,f){var i=g[1]&&g[1].constructor==Object?g[1]:{};if(f){i.mode=f}var h=g[1]&&g[1].constructor!=Object?g[1]:(i.duration?i.duration:g[2]);h=d.fx.off?0:typeof h==="number"?h:d.fx.speeds[h]||d.fx.speeds._default;var j=i.callback||(d.isFunction(g[1])&&g[1])||(d.isFunction(g[2])&&g[2])||(d.isFunction(g[3])&&g[3]);return[g[0],i,h,j]}d.fn.extend({_show:d.fn.show,_hide:d.fn.hide,__toggle:d.fn.toggle,_addClass:d.fn.addClass,_removeClass:d.fn.removeClass,_toggleClass:d.fn.toggleClass,effect:function(g,f,h,i){return d.effects[g]?d.effects[g].call(this,{method:g,options:f||{},duration:h,callback:i}):null},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._show.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"show"))}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._hide.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"hide"))}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||(d.isFunction(arguments[0])||typeof arguments[0]=="boolean")){return this.__toggle.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"toggle"))}},addClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{add:g},f,i,h]):this._addClass(g)},removeClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{remove:g},f,i,h]):this._removeClass(g)},toggleClass:function(g,f,i,h){return((typeof f!=="boolean")&&f)?d.effects.animateClass.apply(this,[{toggle:g},f,i,h]):this._toggleClass(g,f)},morph:function(f,h,g,j,i){return d.effects.animateClass.apply(this,[{add:h,remove:f},g,j,i])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(f){var g=this.css(f),h=[];d.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}});d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(g,f){d.fx.step[f]=function(h){if(h.state==0){h.start=e(h.elem,f);h.end=b(h.end)}h.elem.style[f]="rgb("+[Math.max(Math.min(parseInt((h.pos*(h.end[0]-h.start[0]))+h.start[0],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[1]-h.start[1]))+h.start[1],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[2]-h.start[2]))+h.start[2],10),255),0)].join(",")+")"}});function b(g){var f;if(g&&g.constructor==Array&&g.length==3){return g}if(f=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(g)){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}if(f=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(g)){return[parseFloat(f[1])*2.55,parseFloat(f[2])*2.55,parseFloat(f[3])*2.55]}if(f=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(g)){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}if(f=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(g)){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}if(f=/rgba\(0, 0, 0, 0\)/.exec(g)){return a.transparent}return a[d.trim(g).toLowerCase()]}function e(h,f){var g;do{g=d.curCSS(h,f);if(g!=""&&g!="transparent"||d.nodeName(h,"body")){break}f="backgroundColor"}while(h=h.parentNode);return b(g)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};d.easing.jswing=d.easing.swing;d.extend(d.easing,{def:"easeOutQuad",swing:function(g,h,f,j,i){return d.easing[d.easing.def](g,h,f,j,i)},easeInQuad:function(g,h,f,j,i){return j*(h/=i)*h+f},easeOutQuad:function(g,h,f,j,i){return -j*(h/=i)*(h-2)+f},easeInOutQuad:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h+f}return -j/2*((--h)*(h-2)-1)+f},easeInCubic:function(g,h,f,j,i){return j*(h/=i)*h*h+f},easeOutCubic:function(g,h,f,j,i){return j*((h=h/i-1)*h*h+1)+f},easeInOutCubic:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h+f}return j/2*((h-=2)*h*h+2)+f},easeInQuart:function(g,h,f,j,i){return j*(h/=i)*h*h*h+f},easeOutQuart:function(g,h,f,j,i){return -j*((h=h/i-1)*h*h*h-1)+f},easeInOutQuart:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h+f}return -j/2*((h-=2)*h*h*h-2)+f},easeInQuint:function(g,h,f,j,i){return j*(h/=i)*h*h*h*h+f},easeOutQuint:function(g,h,f,j,i){return j*((h=h/i-1)*h*h*h*h+1)+f},easeInOutQuint:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h*h+f}return j/2*((h-=2)*h*h*h*h+2)+f},easeInSine:function(g,h,f,j,i){return -j*Math.cos(h/i*(Math.PI/2))+j+f},easeOutSine:function(g,h,f,j,i){return j*Math.sin(h/i*(Math.PI/2))+f},easeInOutSine:function(g,h,f,j,i){return -j/2*(Math.cos(Math.PI*h/i)-1)+f},easeInExpo:function(g,h,f,j,i){return(h==0)?f:j*Math.pow(2,10*(h/i-1))+f},easeOutExpo:function(g,h,f,j,i){return(h==i)?f+j:j*(-Math.pow(2,-10*h/i)+1)+f},easeInOutExpo:function(g,h,f,j,i){if(h==0){return f}if(h==i){return f+j}if((h/=i/2)<1){return j/2*Math.pow(2,10*(h-1))+f}return j/2*(-Math.pow(2,-10*--h)+2)+f},easeInCirc:function(g,h,f,j,i){return -j*(Math.sqrt(1-(h/=i)*h)-1)+f},easeOutCirc:function(g,h,f,j,i){return j*Math.sqrt(1-(h=h/i-1)*h)+f},easeInOutCirc:function(g,h,f,j,i){if((h/=i/2)<1){return -j/2*(Math.sqrt(1-h*h)-1)+f}return j/2*(Math.sqrt(1-(h-=2)*h)+1)+f},easeInElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}return -(h*Math.pow(2,10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k))+f},easeOutElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}return h*Math.pow(2,-10*i)*Math.sin((i*l-j)*(2*Math.PI)/k)+m+f},easeInOutElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l/2)==2){return f+m}if(!k){k=l*(0.3*1.5)}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}if(i<1){return -0.5*(h*Math.pow(2,10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k))+f}return h*Math.pow(2,-10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k)*0.5+m+f},easeInBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}return k*(h/=j)*h*((i+1)*h-i)+f},easeOutBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}return k*((h=h/j-1)*h*((i+1)*h+i)+1)+f},easeInOutBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}if((h/=j/2)<1){return k/2*(h*h*(((i*=(1.525))+1)*h-i))+f}return k/2*((h-=2)*h*(((i*=(1.525))+1)*h+i)+2)+f},easeInBounce:function(g,h,f,j,i){return j-d.easing.easeOutBounce(g,i-h,0,j,i)+f},easeOutBounce:function(g,h,f,j,i){if((h/=i)<(1/2.75)){return j*(7.5625*h*h)+f}else{if(h<(2/2.75)){return j*(7.5625*(h-=(1.5/2.75))*h+0.75)+f}else{if(h<(2.5/2.75)){return j*(7.5625*(h-=(2.25/2.75))*h+0.9375)+f}else{return j*(7.5625*(h-=(2.625/2.75))*h+0.984375)+f}}}},easeInOutBounce:function(g,h,f,j,i){if(h<i/2){return d.easing.easeInBounce(g,h*2,0,j,i)*0.5+f}return d.easing.easeOutBounce(g,h*2-i,0,j,i)*0.5+j*0.5+f}})})(jQuery);;/*
- * jQuery UI Effects Blind 1.7.2
+jQuery.effects||function(f,j){function l(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1],
+16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return m.transparent;return m[f.trim(c).toLowerCase()]}function r(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return l(b)}function n(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,
+a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function o(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in s||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function t(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d=
+a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:f.fx.speeds[b]||f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=r(b.elem,a);b.end=l(b.end);b.colorInit=
+true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var m={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,
+183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,
+165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},p=["add","remove","toggle"],s={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b,d){if(f.isFunction(b)){d=b;b=null}return this.each(function(){var e=f(this),g=e.attr("style")||" ",h=o(n.call(this)),q,u=e.attr("className");f.each(p,function(v,
+i){c[i]&&e[i+"Class"](c[i])});q=o(n.call(this));e.attr("className",u);e.animate(t(h,q),a,b,function(){f.each(p,function(v,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)})})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?
+f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.5",save:function(c,a){for(var b=0;b<a.length;b++)a[b]!==
+null&&c.data("ec.storage."+a[b],c[0].style[a[b]])},restore:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.css(a[b],c.data("ec.storage."+a[b]))},setMode:function(c,a){if(a=="toggle")a=c.is(":hidden")?"show":"hide";return a},getBaseline:function(c,a){var b;switch(c[0]){case "top":b=0;break;case "middle":b=0.5;break;case "bottom":b=1;break;default:b=c[0]/a.height}switch(c[1]){case "left":c=0;break;case "center":c=0.5;break;case "right":c=1;break;default:c=c[1]/a.width}return{x:c,y:b}},createWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent();
+var a={width:c.outerWidth(true),height:c.outerHeight(true),"float":c.css("float")},b=f("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});
+c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments);a={options:a[1],duration:a[2],callback:a[3]};var b=f.effects[c];return b&&!f.fx.off?b.call(this,a):this},_show:f.fn.show,show:function(c){if(!c||
+typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c]||typeof c==
+"boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,
+a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=
+e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+
+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/
+2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return-(h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g))+b},easeOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*
+a)*Math.sin((a*e-c)*2*Math.PI/g)+d+b},easeInOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e/2)==2)return b+d;g||(g=e*0.3*1.5);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);if(a<1)return-0.5*h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)+b;return h*Math.pow(2,-10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)*0.5+d+b},easeInBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*(a/=e)*a*((g+1)*a-g)+b},easeOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;
+return d*((a=a/e-1)*a*((g+1)*a+g)+1)+b},easeInOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;if((a/=e/2)<1)return d/2*a*a*(((g*=1.525)+1)*a-g)+b;return d/2*((a-=2)*a*(((g*=1.525)+1)*a+g)+2)+b},easeInBounce:function(c,a,b,d,e){return d-f.easing.easeOutBounce(c,e-a,0,d,e)+b},easeOutBounce:function(c,a,b,d,e){return(a/=e)<1/2.75?d*7.5625*a*a+b:a<2/2.75?d*(7.5625*(a-=1.5/2.75)*a+0.75)+b:a<2.5/2.75?d*(7.5625*(a-=2.25/2.75)*a+0.9375)+b:d*(7.5625*(a-=2.625/2.75)*a+0.984375)+b},easeInOutBounce:function(c,
+a,b,d,e){if(a<e/2)return f.easing.easeInBounce(c,a*2,0,d,e)*0.5+b;return f.easing.easeOutBounce(c,a*2-e,0,d,e)*0.5+d*0.5+b}})}(jQuery);
+;/*
+ * jQuery UI Effects Blind 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Blind
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.blind=function(b){return this.queue(function(){var d=a(this),c=["position","top","left"];var h=a.effects.setMode(d,b.options.mode||"hide");var g=b.options.direction||"vertical";a.effects.save(d,c);d.show();var j=a.effects.createWrapper(d).css({overflow:"hidden"});var e=(g=="vertical")?"height":"width";var i=(g=="vertical")?j.height():j.width();if(h=="show"){j.css(e,0)}var f={};f[e]=h=="show"?i:0;j.animate(f,b.duration,b.options.easing,function(){if(h=="hide"){d.hide()}a.effects.restore(d,c);a.effects.removeWrapper(d);if(b.callback){b.callback.apply(d[0],arguments)}d.dequeue()})})}})(jQuery);;/*
- * jQuery UI Effects Bounce 1.7.2
+(function(b){b.effects.blind=function(c){return this.queue(function(){var a=b(this),g=["position","top","left"],f=b.effects.setMode(a,c.options.mode||"hide"),d=c.options.direction||"vertical";b.effects.save(a,g);a.show();var e=b.effects.createWrapper(a).css({overflow:"hidden"}),h=d=="vertical"?"height":"width";d=d=="vertical"?e.height():e.width();f=="show"&&e.css(h,0);var i={};i[h]=f=="show"?d:0;e.animate(i,c.duration,c.options.easing,function(){f=="hide"&&a.hide();b.effects.restore(a,g);b.effects.removeWrapper(a);
+c.callback&&c.callback.apply(a[0],arguments);a.dequeue()})})}})(jQuery);
+;/*
+ * jQuery UI Effects Bounce 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Bounce
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.bounce=function(b){return this.queue(function(){var e=a(this),l=["position","top","left"];var k=a.effects.setMode(e,b.options.mode||"effect");var n=b.options.direction||"up";var c=b.options.distance||20;var d=b.options.times||5;var g=b.duration||250;if(/show|hide/.test(k)){l.push("opacity")}a.effects.save(e,l);e.show();a.effects.createWrapper(e);var f=(n=="up"||n=="down")?"top":"left";var p=(n=="up"||n=="left")?"pos":"neg";var c=b.options.distance||(f=="top"?e.outerHeight({margin:true})/3:e.outerWidth({margin:true})/3);if(k=="show"){e.css("opacity",0).css(f,p=="pos"?-c:c)}if(k=="hide"){c=c/(d*2)}if(k!="hide"){d--}if(k=="show"){var h={opacity:1};h[f]=(p=="pos"?"+=":"-=")+c;e.animate(h,g/2,b.options.easing);c=c/2;d--}for(var j=0;j<d;j++){var o={},m={};o[f]=(p=="pos"?"-=":"+=")+c;m[f]=(p=="pos"?"+=":"-=")+c;e.animate(o,g/2,b.options.easing).animate(m,g/2,b.options.easing);c=(k=="hide")?c*2:c/2}if(k=="hide"){var h={opacity:0};h[f]=(p=="pos"?"-=":"+=")+c;e.animate(h,g/2,b.options.easing,function(){e.hide();a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}})}else{var o={},m={};o[f]=(p=="pos"?"-=":"+=")+c;m[f]=(p=="pos"?"+=":"-=")+c;e.animate(o,g/2,b.options.easing).animate(m,g/2,b.options.easing,function(){a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}})}e.queue("fx",function(){e.dequeue()});e.dequeue()})}})(jQuery);;/*
- * jQuery UI Effects Clip 1.7.2
+(function(e){e.effects.bounce=function(b){return this.queue(function(){var a=e(this),l=["position","top","left"],h=e.effects.setMode(a,b.options.mode||"effect"),d=b.options.direction||"up",c=b.options.distance||20,m=b.options.times||5,i=b.duration||250;/show|hide/.test(h)&&l.push("opacity");e.effects.save(a,l);a.show();e.effects.createWrapper(a);var f=d=="up"||d=="down"?"top":"left";d=d=="up"||d=="left"?"pos":"neg";c=b.options.distance||(f=="top"?a.outerHeight({margin:true})/3:a.outerWidth({margin:true})/
+3);if(h=="show")a.css("opacity",0).css(f,d=="pos"?-c:c);if(h=="hide")c/=m*2;h!="hide"&&m--;if(h=="show"){var g={opacity:1};g[f]=(d=="pos"?"+=":"-=")+c;a.animate(g,i/2,b.options.easing);c/=2;m--}for(g=0;g<m;g++){var j={},k={};j[f]=(d=="pos"?"-=":"+=")+c;k[f]=(d=="pos"?"+=":"-=")+c;a.animate(j,i/2,b.options.easing).animate(k,i/2,b.options.easing);c=h=="hide"?c*2:c/2}if(h=="hide"){g={opacity:0};g[f]=(d=="pos"?"-=":"+=")+c;a.animate(g,i/2,b.options.easing,function(){a.hide();e.effects.restore(a,l);e.effects.removeWrapper(a);
+b.callback&&b.callback.apply(this,arguments)})}else{j={};k={};j[f]=(d=="pos"?"-=":"+=")+c;k[f]=(d=="pos"?"+=":"-=")+c;a.animate(j,i/2,b.options.easing).animate(k,i/2,b.options.easing,function(){e.effects.restore(a,l);e.effects.removeWrapper(a);b.callback&&b.callback.apply(this,arguments)})}a.queue("fx",function(){a.dequeue()});a.dequeue()})}})(jQuery);
+;/*
+ * jQuery UI Effects Clip 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Clip
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.clip=function(b){return this.queue(function(){var f=a(this),j=["position","top","left","height","width"];var i=a.effects.setMode(f,b.options.mode||"hide");var k=b.options.direction||"vertical";a.effects.save(f,j);f.show();var c=a.effects.createWrapper(f).css({overflow:"hidden"});var e=f[0].tagName=="IMG"?c:f;var g={size:(k=="vertical")?"height":"width",position:(k=="vertical")?"top":"left"};var d=(k=="vertical")?e.height():e.width();if(i=="show"){e.css(g.size,0);e.css(g.position,d/2)}var h={};h[g.size]=i=="show"?d:0;h[g.position]=i=="show"?0:d/2;e.animate(h,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){f.hide()}a.effects.restore(f,j);a.effects.removeWrapper(f);if(b.callback){b.callback.apply(f[0],arguments)}f.dequeue()}})})}})(jQuery);;/*
- * jQuery UI Effects Drop 1.7.2
+(function(b){b.effects.clip=function(e){return this.queue(function(){var a=b(this),i=["position","top","left","height","width"],f=b.effects.setMode(a,e.options.mode||"hide"),c=e.options.direction||"vertical";b.effects.save(a,i);a.show();var d=b.effects.createWrapper(a).css({overflow:"hidden"});d=a[0].tagName=="IMG"?d:a;var g={size:c=="vertical"?"height":"width",position:c=="vertical"?"top":"left"};c=c=="vertical"?d.height():d.width();if(f=="show"){d.css(g.size,0);d.css(g.position,c/2)}var h={};h[g.size]=
+f=="show"?c:0;h[g.position]=f=="show"?0:c/2;d.animate(h,{queue:false,duration:e.duration,easing:e.options.easing,complete:function(){f=="hide"&&a.hide();b.effects.restore(a,i);b.effects.removeWrapper(a);e.callback&&e.callback.apply(a[0],arguments);a.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Drop 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Drop
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.drop=function(b){return this.queue(function(){var e=a(this),d=["position","top","left","opacity"];var i=a.effects.setMode(e,b.options.mode||"hide");var h=b.options.direction||"left";a.effects.save(e,d);e.show();a.effects.createWrapper(e);var f=(h=="up"||h=="down")?"top":"left";var c=(h=="up"||h=="left")?"pos":"neg";var j=b.options.distance||(f=="top"?e.outerHeight({margin:true})/2:e.outerWidth({margin:true})/2);if(i=="show"){e.css("opacity",0).css(f,c=="pos"?-j:j)}var g={opacity:i=="show"?1:0};g[f]=(i=="show"?(c=="pos"?"+=":"-="):(c=="pos"?"-=":"+="))+j;e.animate(g,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){e.hide()}a.effects.restore(e,d);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
- * jQuery UI Effects Explode 1.7.2
+(function(c){c.effects.drop=function(d){return this.queue(function(){var a=c(this),h=["position","top","left","opacity"],e=c.effects.setMode(a,d.options.mode||"hide"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a);var f=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var g=d.options.distance||(f=="top"?a.outerHeight({margin:true})/2:a.outerWidth({margin:true})/2);if(e=="show")a.css("opacity",0).css(f,b=="pos"?-g:g);var i={opacity:e=="show"?1:
+0};i[f]=(e=="show"?b=="pos"?"+=":"-=":b=="pos"?"-=":"+=")+g;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){e=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Explode 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Explode
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.explode=function(b){return this.queue(function(){var k=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;var e=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?(a(this).is(":visible")?"hide":"show"):b.options.mode;var h=a(this).show().css("visibility","hidden");var l=h.offset();l.top-=parseInt(h.css("marginTop"),10)||0;l.left-=parseInt(h.css("marginLeft"),10)||0;var g=h.outerWidth(true);var c=h.outerHeight(true);for(var f=0;f<k;f++){for(var d=0;d<e;d++){h.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-d*(g/e),top:-f*(c/k)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/e,height:c/k,left:l.left+d*(g/e)+(b.options.mode=="show"?(d-Math.floor(e/2))*(g/e):0),top:l.top+f*(c/k)+(b.options.mode=="show"?(f-Math.floor(k/2))*(c/k):0),opacity:b.options.mode=="show"?0:1}).animate({left:l.left+d*(g/e)+(b.options.mode=="show"?0:(d-Math.floor(e/2))*(g/e)),top:l.top+f*(c/k)+(b.options.mode=="show"?0:(f-Math.floor(k/2))*(c/k)),opacity:b.options.mode=="show"?1:0},b.duration||500)}}setTimeout(function(){b.options.mode=="show"?h.css({visibility:"visible"}):h.css({visibility:"visible"}).hide();if(b.callback){b.callback.apply(h[0])}h.dequeue();a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*
- * jQuery UI Effects Fold 1.7.2
+(function(j){j.effects.explode=function(a){return this.queue(function(){var c=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3,d=a.options.pieces?Math.round(Math.sqrt(a.options.pieces)):3;a.options.mode=a.options.mode=="toggle"?j(this).is(":visible")?"hide":"show":a.options.mode;var b=j(this).show().css("visibility","hidden"),g=b.offset();g.top-=parseInt(b.css("marginTop"),10)||0;g.left-=parseInt(b.css("marginLeft"),10)||0;for(var h=b.outerWidth(true),i=b.outerHeight(true),e=0;e<c;e++)for(var f=
+0;f<d;f++)b.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+
+e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery);
+;/*
+ * jQuery UI Effects Fade 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * http://docs.jquery.com/UI/Effects/Fade
+ *
+ * Depends:
+ * jquery.effects.core.js
+ */
+(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Fold 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Fold
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.fold=function(b){return this.queue(function(){var e=a(this),k=["position","top","left"];var h=a.effects.setMode(e,b.options.mode||"hide");var o=b.options.size||15;var n=!(!b.options.horizFirst);var g=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(e,k);e.show();var d=a.effects.createWrapper(e).css({overflow:"hidden"});var i=((h=="show")!=n);var f=i?["width","height"]:["height","width"];var c=i?[d.width(),d.height()]:[d.height(),d.width()];var j=/([0-9]+)%/.exec(o);if(j){o=parseInt(j[1],10)/100*c[h=="hide"?0:1]}if(h=="show"){d.css(n?{height:0,width:o}:{height:o,width:0})}var m={},l={};m[f[0]]=h=="show"?c[0]:o;l[f[1]]=h=="show"?c[1]:0;d.animate(m,g,b.options.easing).animate(l,g,b.options.easing,function(){if(h=="hide"){e.hide()}a.effects.restore(e,k);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(e[0],arguments)}e.dequeue()})})}})(jQuery);;/*
- * jQuery UI Effects Highlight 1.7.2
+(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","left"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1],10)/100*
+f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery);
+;/*
+ * jQuery UI Effects Highlight 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Highlight
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.highlight=function(b){return this.queue(function(){var e=a(this),d=["backgroundImage","backgroundColor","opacity"];var h=a.effects.setMode(e,b.options.mode||"show");var c=b.options.color||"#ffff99";var g=e.css("backgroundColor");a.effects.save(e,d);e.show();e.css({backgroundImage:"none",backgroundColor:c});var f={backgroundColor:g};if(h=="hide"){f.opacity=0}e.animate(f,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(h=="hide"){e.hide()}a.effects.restore(e,d);if(h=="show"&&a.browser.msie){this.style.removeAttribute("filter")}if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
- * jQuery UI Effects Pulsate 1.7.2
+(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&&
+this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Pulsate 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Pulsate
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.pulsate=function(b){return this.queue(function(){var d=a(this);var g=a.effects.setMode(d,b.options.mode||"show");var f=b.options.times||5;var e=b.duration?b.duration/2:a.fx.speeds._default/2;if(g=="hide"){f--}if(d.is(":hidden")){d.css("opacity",0);d.show();d.animate({opacity:1},e,b.options.easing);f=f-2}for(var c=0;c<f;c++){d.animate({opacity:0},e,b.options.easing).animate({opacity:1},e,b.options.easing)}if(g=="hide"){d.animate({opacity:0},e,b.options.easing,function(){d.hide();if(b.callback){b.callback.apply(this,arguments)}})}else{d.animate({opacity:0},e,b.options.easing).animate({opacity:1},e,b.options.easing,function(){if(b.callback){b.callback.apply(this,arguments)}})}d.queue("fx",function(){d.dequeue()});d.dequeue()})}})(jQuery);;/*
- * jQuery UI Effects Scale 1.7.2
+(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c<times;c++){b.animate({opacity:animateTo},duration,a.options.easing);animateTo=(animateTo+1)%2}b.animate({opacity:animateTo},duration,
+a.options.easing,function(){animateTo==0&&b.hide();a.callback&&a.callback.apply(this,arguments)});b.queue("fx",function(){b.dequeue()}).dequeue()})}})(jQuery);
+;/*
+ * jQuery UI Effects Scale 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Scale
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.puff=function(b){return this.queue(function(){var f=a(this);var c=a.extend(true,{},b.options);var h=a.effects.setMode(f,b.options.mode||"hide");var g=parseInt(b.options.percent,10)||150;c.fade=true;var e={height:f.height(),width:f.width()};var d=g/100;f.from=(h=="hide")?e:{height:e.height*d,width:e.width*d};c.from=f.from;c.percent=(h=="hide")?g:100;c.mode=h;f.effect("scale",c,b.duration,b.callback);f.dequeue()})};a.effects.scale=function(b){return this.queue(function(){var g=a(this);var d=a.extend(true,{},b.options);var j=a.effects.setMode(g,b.options.mode||"effect");var h=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:(j=="hide"?0:100));var i=b.options.direction||"both";var c=b.options.origin;if(j!="effect"){d.origin=c||["middle","center"];d.restore=true}var f={height:g.height(),width:g.width()};g.from=b.options.from||(j=="show"?{height:0,width:0}:f);var e={y:i!="horizontal"?(h/100):1,x:i!="vertical"?(h/100):1};g.to={height:f.height*e.y,width:f.width*e.x};if(b.options.fade){if(j=="show"){g.from.opacity=0;g.to.opacity=1}if(j=="hide"){g.from.opacity=1;g.to.opacity=0}}d.from=g.from;d.to=g.to;d.mode=j;g.effect("size",d,b.duration,b.callback);g.dequeue()})};a.effects.size=function(b){return this.queue(function(){var c=a(this),n=["position","top","left","width","height","overflow","opacity"];var m=["position","top","left","overflow","opacity"];var j=["width","height","overflow"];var p=["fontSize"];var k=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"];var f=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"];var g=a.effects.setMode(c,b.options.mode||"effect");var i=b.options.restore||false;var e=b.options.scale||"both";var o=b.options.origin;var d={height:c.height(),width:c.width()};c.from=b.options.from||d;c.to=b.options.to||d;if(o){var h=a.effects.getBaseline(o,d);c.from.top=(d.height-c.from.height)*h.y;c.from.left=(d.width-c.from.width)*h.x;c.to.top=(d.height-c.to.height)*h.y;c.to.left=(d.width-c.to.width)*h.x}var l={from:{y:c.from.height/d.height,x:c.from.width/d.width},to:{y:c.to.height/d.height,x:c.to.width/d.width}};if(e=="box"||e=="both"){if(l.from.y!=l.to.y){n=n.concat(k);c.from=a.effects.setTransition(c,k,l.from.y,c.from);c.to=a.effects.setTransition(c,k,l.to.y,c.to)}if(l.from.x!=l.to.x){n=n.concat(f);c.from=a.effects.setTransition(c,f,l.from.x,c.from);c.to=a.effects.setTransition(c,f,l.to.x,c.to)}}if(e=="content"||e=="both"){if(l.from.y!=l.to.y){n=n.concat(p);c.from=a.effects.setTransition(c,p,l.from.y,c.from);c.to=a.effects.setTransition(c,p,l.to.y,c.to)}}a.effects.save(c,i?n:m);c.show();a.effects.createWrapper(c);c.css("overflow","hidden").css(c.from);if(e=="content"||e=="both"){k=k.concat(["marginTop","marginBottom"]).concat(p);f=f.concat(["marginLeft","marginRight"]);j=n.concat(k).concat(f);c.find("*[width]").each(function(){child=a(this);if(i){a.effects.save(child,j)}var q={height:child.height(),width:child.width()};child.from={height:q.height*l.from.y,width:q.width*l.from.x};child.to={height:q.height*l.to.y,width:q.width*l.to.x};if(l.from.y!=l.to.y){child.from=a.effects.setTransition(child,k,l.from.y,child.from);child.to=a.effects.setTransition(child,k,l.to.y,child.to)}if(l.from.x!=l.to.x){child.from=a.effects.setTransition(child,f,l.from.x,child.from);child.to=a.effects.setTransition(child,f,l.to.x,child.to)}child.css(child.from);child.animate(child.to,b.duration,b.options.easing,function(){if(i){a.effects.restore(child,j)}})})}c.animate(c.to,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(g=="hide"){c.hide()}a.effects.restore(c,i?n:m);a.effects.removeWrapper(c);if(b.callback){b.callback.apply(this,arguments)}c.dequeue()}})})}})(jQuery);;/*
- * jQuery UI Effects Shake 1.7.2
+(function(c){c.effects.puff=function(b){return this.queue(function(){var a=c(this),e=c.effects.setMode(a,b.options.mode||"hide"),g=parseInt(b.options.percent,10)||150,h=g/100,i={height:a.height(),width:a.width()};c.extend(b.options,{fade:true,mode:e,percent:e=="hide"?g:100,from:e=="hide"?i:{height:i.height*h,width:i.width*h}});a.effect("scale",b.options,b.duration,b.callback);a.dequeue()})};c.effects.scale=function(b){return this.queue(function(){var a=c(this),e=c.extend(true,{},b.options),g=c.effects.setMode(a,
+b.options.mode||"effect"),h=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:g=="hide"?0:100),i=b.options.direction||"both",f=b.options.origin;if(g!="effect"){e.origin=f||["middle","center"];e.restore=true}f={height:a.height(),width:a.width()};a.from=b.options.from||(g=="show"?{height:0,width:0}:f);h={y:i!="horizontal"?h/100:1,x:i!="vertical"?h/100:1};a.to={height:f.height*h.y,width:f.width*h.x};if(b.options.fade){if(g=="show"){a.from.opacity=0;a.to.opacity=1}if(g=="hide"){a.from.opacity=
+1;a.to.opacity=0}}e.from=a.from;e.to=a.to;e.mode=g;a.effect("size",e,b.duration,b.callback);a.dequeue()})};c.effects.size=function(b){return this.queue(function(){var a=c(this),e=["position","top","left","width","height","overflow","opacity"],g=["position","top","left","overflow","opacity"],h=["width","height","overflow"],i=["fontSize"],f=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],k=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=c.effects.setMode(a,
+b.options.mode||"effect"),n=b.options.restore||false,m=b.options.scale||"both",l=b.options.origin,j={height:a.height(),width:a.width()};a.from=b.options.from||j;a.to=b.options.to||j;if(l){l=c.effects.getBaseline(l,j);a.from.top=(j.height-a.from.height)*l.y;a.from.left=(j.width-a.from.width)*l.x;a.to.top=(j.height-a.to.height)*l.y;a.to.left=(j.width-a.to.width)*l.x}var d={from:{y:a.from.height/j.height,x:a.from.width/j.width},to:{y:a.to.height/j.height,x:a.to.width/j.width}};if(m=="box"||m=="both"){if(d.from.y!=
+d.to.y){e=e.concat(f);a.from=c.effects.setTransition(a,f,d.from.y,a.from);a.to=c.effects.setTransition(a,f,d.to.y,a.to)}if(d.from.x!=d.to.x){e=e.concat(k);a.from=c.effects.setTransition(a,k,d.from.x,a.from);a.to=c.effects.setTransition(a,k,d.to.x,a.to)}}if(m=="content"||m=="both")if(d.from.y!=d.to.y){e=e.concat(i);a.from=c.effects.setTransition(a,i,d.from.y,a.from);a.to=c.effects.setTransition(a,i,d.to.y,a.to)}c.effects.save(a,n?e:g);a.show();c.effects.createWrapper(a);a.css("overflow","hidden").css(a.from);
+if(m=="content"||m=="both"){f=f.concat(["marginTop","marginBottom"]).concat(i);k=k.concat(["marginLeft","marginRight"]);h=e.concat(f).concat(k);a.find("*[width]").each(function(){child=c(this);n&&c.effects.save(child,h);var o={height:child.height(),width:child.width()};child.from={height:o.height*d.from.y,width:o.width*d.from.x};child.to={height:o.height*d.to.y,width:o.width*d.to.x};if(d.from.y!=d.to.y){child.from=c.effects.setTransition(child,f,d.from.y,child.from);child.to=c.effects.setTransition(child,
+f,d.to.y,child.to)}if(d.from.x!=d.to.x){child.from=c.effects.setTransition(child,k,d.from.x,child.from);child.to=c.effects.setTransition(child,k,d.to.x,child.to)}child.css(child.from);child.animate(child.to,b.duration,b.options.easing,function(){n&&c.effects.restore(child,h)})})}a.animate(a.to,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){a.to.opacity===0&&a.css("opacity",a.from.opacity);p=="hide"&&a.hide();c.effects.restore(a,n?e:g);c.effects.removeWrapper(a);b.callback&&
+b.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Shake 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Shake
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.shake=function(b){return this.queue(function(){var e=a(this),l=["position","top","left"];var k=a.effects.setMode(e,b.options.mode||"effect");var n=b.options.direction||"left";var c=b.options.distance||20;var d=b.options.times||3;var g=b.duration||b.options.duration||140;a.effects.save(e,l);e.show();a.effects.createWrapper(e);var f=(n=="up"||n=="down")?"top":"left";var p=(n=="up"||n=="left")?"pos":"neg";var h={},o={},m={};h[f]=(p=="pos"?"-=":"+=")+c;o[f]=(p=="pos"?"+=":"-=")+c*2;m[f]=(p=="pos"?"-=":"+=")+c*2;e.animate(h,g,b.options.easing);for(var j=1;j<d;j++){e.animate(o,g,b.options.easing).animate(m,g,b.options.easing)}e.animate(o,g,b.options.easing).animate(h,g/2,b.options.easing,function(){a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}});e.queue("fx",function(){e.dequeue()});e.dequeue()})}})(jQuery);;/*
- * jQuery UI Effects Slide 1.7.2
+(function(d){d.effects.shake=function(a){return this.queue(function(){var b=d(this),j=["position","top","left"];d.effects.setMode(b,a.options.mode||"effect");var c=a.options.direction||"left",e=a.options.distance||20,l=a.options.times||3,f=a.duration||a.options.duration||140;d.effects.save(b,j);b.show();d.effects.createWrapper(b);var g=c=="up"||c=="down"?"top":"left",h=c=="up"||c=="left"?"pos":"neg";c={};var i={},k={};c[g]=(h=="pos"?"-=":"+=")+e;i[g]=(h=="pos"?"+=":"-=")+e*2;k[g]=(h=="pos"?"-=":"+=")+
+e*2;b.animate(c,f,a.options.easing);for(e=1;e<l;e++)b.animate(i,f,a.options.easing).animate(k,f,a.options.easing);b.animate(i,f,a.options.easing).animate(c,f/2,a.options.easing,function(){d.effects.restore(b,j);d.effects.removeWrapper(b);a.callback&&a.callback.apply(this,arguments)});b.queue("fx",function(){b.dequeue()});b.dequeue()})}})(jQuery);
+;/*
+ * jQuery UI Effects Slide 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Slide
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.slide=function(b){return this.queue(function(){var e=a(this),d=["position","top","left"];var i=a.effects.setMode(e,b.options.mode||"show");var h=b.options.direction||"left";a.effects.save(e,d);e.show();a.effects.createWrapper(e).css({overflow:"hidden"});var f=(h=="up"||h=="down")?"top":"left";var c=(h=="up"||h=="left")?"pos":"neg";var j=b.options.distance||(f=="top"?e.outerHeight({margin:true}):e.outerWidth({margin:true}));if(i=="show"){e.css(f,c=="pos"?-j:j)}var g={};g[f]=(i=="show"?(c=="pos"?"+=":"-="):(c=="pos"?"-=":"+="))+j;e.animate(g,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){e.hide()}a.effects.restore(e,d);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
- * jQuery UI Effects Transfer 1.7.2
+(function(c){c.effects.slide=function(d){return this.queue(function(){var a=c(this),h=["position","top","left"],e=c.effects.setMode(a,d.options.mode||"show"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a).css({overflow:"hidden"});var f=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var g=d.options.distance||(f=="top"?a.outerHeight({margin:true}):a.outerWidth({margin:true}));if(e=="show")a.css(f,b=="pos"?-g:g);var i={};i[f]=(e=="show"?b=="pos"?
+"+=":"-=":b=="pos"?"-=":"+=")+g;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){e=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
+;/*
+ * jQuery UI Effects Transfer 1.8.5
*
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Transfer
*
* Depends:
- * effects.core.js
+ * jquery.effects.core.js
*/
-(function(a){a.effects.transfer=function(b){return this.queue(function(){var f=a(this),h=a(b.options.to),e=h.offset(),g={top:e.top,left:e.left,height:h.innerHeight(),width:h.innerWidth()},d=f.offset(),c=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:d.top,left:d.left,height:f.innerHeight(),width:f.innerWidth(),position:"absolute"}).animate(g,b.duration,b.options.easing,function(){c.remove();(b.callback&&b.callback.apply(f[0],arguments));f.dequeue()})})}})(jQuery);;
\ No newline at end of file
+(function(e){e.effects.transfer=function(a){return this.queue(function(){var b=e(this),c=e(a.options.to),d=c.offset();c={top:d.top,left:d.left,height:c.innerHeight(),width:c.innerWidth()};d=b.offset();var f=e('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments);
+b.dequeue()})})}})(jQuery);
+;
\ No newline at end of file
--- a/web/data/uiprops.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/data/uiprops.py Fri Mar 11 09:46:45 2011 +0100
@@ -103,18 +103,36 @@
pageContentPadding = '1em'
pageMinHeight = '800px'
-# boxes
-boxTitleBg = lazystr('%(headerBgColor)s url("boxHeader.png") repeat-x 50%% 50%%')
-boxBodyBgColor = '#efefde'
+# boxes ########################################################################
+
+# title of contextFree / contextual boxes
+contextFreeBoxTitleBgColor = '#CFCEB7'
+contextFreeBoxTitleBg = lazystr('%(contextFreeBoxTitleBgColor)s url("contextFreeBoxHeader.png") repeat-x 50%% 50%%')
+contextFreeBoxTitleColor = '#000'
+
+contextualBoxTitleBgColor = '#FF9900'
+contextualBoxTitleBg = lazystr('%(contextualBoxTitleBgColor)s url("contextualBoxHeader.png") repeat-x 50%% 50%%')
+contextualBoxTitleColor = '#FFF'
-# action, search, sideBoxes
-actionBoxTitleBgColor = '#cfceb7'
-actionBoxTitleBg = lazystr('%(actionBoxTitleBgColor)s url("actionBoxHeader.png") repeat-x 50%% 50%%')
-sideBoxBodyBgColor = '#f8f8ee'
-sideBoxBodyBg = lazystr('%(sideBoxBodyBgColor)s')
-sideBoxBodyColor = '#555544'
+# title of 'incontext' boxes (eg displayed inside the primary view)
+incontextBoxTitleBgColor = lazystr('%(contextFreeBoxTitleBgColor)s')
+incontextBoxTitleBg = lazystr('%(incontextBoxTitleBgColor)s url("incontextBoxHeader.png") repeat-x 50%% 50%%')
+incontextBoxTitleColor = '#000'
-# table listing & co
+# body of boxes in the left or right column (whatever contextFree / contextual)
+leftrightBoxBodyBgColor = '#FFF'
+leftrightBoxBodyBg = lazystr('%(leftrightBoxBodyBgColor)s')
+leftrightBoxBodyColor = 'black'
+leftrightBoxBodyHoverBgColor = '#EEEDD9'
+
+# body of 'incontext' boxes (eg displayed insinde the primary view)
+incontextBoxBodyBgColor = '#f8f8ee'
+incontextBoxBodyBg = lazystr('%(incontextBoxBodyBgColor)s')
+incontextBoxBodyColor = '#555544'
+incontextBoxBodyHoverBgColor = lazystr('%(incontextBoxBodyBgColor)s')
+
+
+# table listing & co ###########################################################
listingBorderColor = '#ccc'
listingHeaderBgColor = '#efefef'
listingHihligthedBgColor = '#fbfbfb'
--- a/web/facet.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/facet.py Fri Mar 11 09:46:45 2011 +0100
@@ -45,6 +45,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from copy import deepcopy
from datetime import date, datetime, timedelta
@@ -55,7 +56,7 @@
from logilab.common.date import datetime2ticks
from logilab.common.compat import all
-from rql import parse, nodes
+from rql import parse, nodes, utils
from cubicweb import Unauthorized, typed_eid
from cubicweb.schema import display_name
@@ -165,18 +166,27 @@
return ovar
return None
+def _make_relation(rqlst, mainvar, rtype, role):
+ newvar = rqlst.make_variable()
+ if role == 'object':
+ rel = nodes.make_relation(newvar, rtype, (mainvar,), nodes.VariableRef)
+ else:
+ rel = nodes.make_relation(mainvar, rtype, (newvar,), nodes.VariableRef)
+ return newvar, rel
+
def _add_rtype_relation(rqlst, mainvar, rtype, role):
"""add a relation relying `mainvar` to entities linked by the `rtype`
relation (where `mainvar` has `role`)
return the inserted variable for linked entities.
"""
- newvar = rqlst.make_variable()
- if role == 'object':
- rqlst.add_relation(newvar, rtype, mainvar)
- else:
- rqlst.add_relation(mainvar, rtype, newvar)
- return newvar
+ newvar, newrel = _make_relation(rqlst, mainvar, rtype, role)
+ rqlst.add_restriction(newrel)
+ return newvar, newrel
+
+def _add_eid_restr(rel, restrvar, value):
+ rrel = nodes.make_constant_restriction(restrvar, 'eid', value, 'Int')
+ rel.parent.replace(rel, nodes.And(rel, rrel))
def _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
select_target_entity=True):
@@ -187,7 +197,7 @@
* add the new variable to GROUPBY clause if necessary
* add the new variable to the selection
"""
- newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)
+ newvar = _add_rtype_relation(rqlst, mainvar, rtype, role)[0]
if select_target_entity:
if rqlst.groupby:
rqlst.add_group_var(newvar)
@@ -240,7 +250,6 @@
_cleanup_rqlst(rqlst, mainvar)
var = _prepare_vocabulary_rqlst(rqlst, mainvar, rtype, role,
select_target_entity)
- # not found, create one
attrvar = rqlst.make_variable()
rqlst.add_relation(var, attrname, attrvar)
# if query is grouped, we have to add the attribute variable
@@ -449,6 +458,10 @@
The relation is defined by the `rtype` and `role` attributes.
+ The `no_relation` boolean flag tells if a special 'no relation' value should be
+ added (allowing to filter on entities which *do not* have the relation set).
+ Default is computed according the relation's cardinality.
+
The values displayed for related entities will be:
* result of calling their `label_vid` view if specified
@@ -456,7 +469,8 @@
* else their eid (you usually want something nicer...)
When no `label_vid` is set, you will get translated value if `i18nable` is
- set.
+ set. By default, `i18nable` will be set according to the schema, but you can
+ force its value by setting it has a class attribute.
You can filter out target entity types by specifying `target_type`
@@ -506,8 +520,6 @@
role = 'subject'
target_attr = 'eid'
target_type = None
- # should value be internationalized (XXX could be guessed from the schema)
- i18nable = True
# set this to a stored procedure name if you want to sort on the result of
# this function's result instead of direct value
sortfunc = None
@@ -520,6 +532,23 @@
_select_target_entity = True
title = property(rtype_facet_title)
+ no_relation_label = '<no relation>'
+
+ @property
+ def i18nable(self):
+ """should label be internationalized"""
+ if self.target_type:
+ eschema = self._cw.vreg.schema.eschema(self.target_type)
+ elif self.role == 'subject':
+ eschema = self._cw.vreg.schema.rschema(self.rtype).objects()[0]
+ else:
+ eschema = self._cw.vreg.schema.rschema(self.rtype).subjects()[0]
+ return getattr(eschema.rdef(self.target_attr), 'internationalizable', False)
+
+ @property
+ def no_relation(self):
+ return (not self._cw.vreg.schema.rschema(self.rtype).final
+ and self._search_card('?*'))
@property
def rql_sort(self):
@@ -529,6 +558,7 @@
"""
return self.sortfunc is not None or (self.label_vid is None
and not self.i18nable)
+
def vocabulary(self):
"""return vocabulary for this facet, eg a list of 2-uple (label, value)
"""
@@ -555,7 +585,10 @@
rqlst.recover()
# don't call rset_vocabulary on empty result set, it may be an empty
# *list* (see rqlexec implementation)
- return rset and self.rset_vocabulary(rset)
+ values = rset and self.rset_vocabulary(rset) or []
+ if self._include_no_relation():
+ values.insert(0, (self._cw._(self.no_relation_label), ''))
+ return values
def possible_values(self):
"""return a list of possible values (as string since it's used to
@@ -572,12 +605,15 @@
insert_attr_select_relation(
rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr,
select_target_entity=False)
- return [str(x) for x, in self.rqlexec(rqlst.as_string())]
+ values = [str(x) for x, in self.rqlexec(rqlst.as_string())]
except:
- import traceback
- traceback.print_exc()
+ self.exception('while computing values for %s', self)
+ return []
finally:
rqlst.recover()
+ if self._include_no_relation():
+ values.append('')
+ return values
def rset_vocabulary(self, rset):
if self.i18nable:
@@ -585,56 +621,118 @@
else:
_ = unicode
if self.rql_sort:
- return [(_(label), eid) for eid, label in rset]
- if self.label_vid is None:
- assert self.i18nable
values = [(_(label), eid) for eid, label in rset]
else:
- values = [(entity.view(self.label_vid), entity.eid)
- for entity in rset.entities()]
- values = sorted(values)
- if self.sortasc:
- return values
- return reversed(values)
+ if self.label_vid is None:
+ values = [(_(label), eid) for eid, label in rset]
+ else:
+ values = [(entity.view(self.label_vid), entity.eid)
+ for entity in rset.entities()]
+ values = sorted(values)
+ if not self.sortasc:
+ values = list(reversed(values))
+ return values
+
+ def support_and(self):
+ return self._search_card('+*')
+
+ def add_rql_restrictions(self):
+ """add restriction for this facet into the rql syntax tree"""
+ value = self._cw.form.get(self.__regid__)
+ if value is None:
+ return
+ mainvar = self.filtered_variable
+ restrvar, rel = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
+ self.role)
+ if isinstance(value, basestring):
+ # only one value selected
+ if value:
+ self.rqlst.add_eid_restriction(restrvar, value)
+ else:
+ rel.parent.replace(rel, nodes.Not(rel))
+ elif self.operator == 'OR':
+ # set_distinct only if rtype cardinality is > 1
+ if self.support_and():
+ self.rqlst.set_distinct(True)
+ # multiple ORed values: using IN is fine
+ if '' in value:
+ value.remove('')
+ self._add_not_rel_restr(rel)
+ _add_eid_restr(rel, restrvar, value)
+ else:
+ # multiple values with AND operator
+ if '' in value:
+ value.remove('')
+ self._add_not_rel_restr(rel)
+ _add_eid_restr(rel, restrvar, value.pop())
+ while value:
+ restrvar, rtrel = _make_relation(self.rqlst, mainvar,
+ self.rtype, self.role)
+ _add_eid_restr(rel, restrvar, value.pop())
@cached
- def support_and(self):
+ def _search_card(self, cards):
+ for rdef in self._iter_rdefs():
+ if rdef.role_cardinality(self.role) in cards:
+ return True
+ return False
+
+ def _iter_rdefs(self):
rschema = self._cw.vreg.schema.rschema(self.rtype)
# XXX when called via ajax, no rset to compute possible types
possibletypes = self.cw_rset and self.cw_rset.column_types(0)
for rdef in rschema.rdefs.itervalues():
if possibletypes is not None:
if self.role == 'subject':
- if not rdef.subject in possibletypes:
+ if rdef.subject not in possibletypes:
continue
- elif not rdef.object in possibletypes:
+ elif rdef.object not in possibletypes:
continue
- if rdef.role_cardinality(self.role) in '+*':
- return True
- return False
+ if self.target_type is not None:
+ if self.role == 'subject':
+ if rdef.object != self.target_type:
+ continue
+ elif rdef.subject != self.target_type:
+ continue
+ yield rdef
- def add_rql_restrictions(self):
- """add restriction for this facet into the rql syntax tree"""
- value = self._cw.form.get(self.__regid__)
- if not value:
- return
- mainvar = self.filtered_variable
- restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
- if isinstance(value, basestring):
- # only one value selected
- self.rqlst.add_eid_restriction(restrvar, value)
- elif self.operator == 'OR':
- # set_distinct only if rtype cardinality is > 1
- if self.support_and():
- self.rqlst.set_distinct(True)
- # multiple ORed values: using IN is fine
- self.rqlst.add_eid_restriction(restrvar, value)
+ def _include_no_relation(self):
+ if not self.no_relation:
+ return False
+ if self._cw.vreg.schema.rschema(self.rtype).final:
+ return False
+ if self.role == 'object':
+ subj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
+ aliases=self.rqlst.aliases).next()
+ obj = self.filtered_variable.name
else:
- # multiple values with AND operator
- self.rqlst.add_eid_restriction(restrvar, value.pop())
- while value:
- restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
- self.rqlst.add_eid_restriction(restrvar, value.pop())
+ subj = self.filtered_variable.name
+ obj = utils.rqlvar_maker(defined=self.rqlst.defined_vars,
+ aliases=self.rqlst.aliases).next()
+ restrictions = []
+ if self.rqlst.where:
+ restrictions.append(self.rqlst.where.as_string())
+ if self.rqlst.with_:
+ restrictions.append('WITH ' + ','.join(
+ term.as_string() for term in self.rqlst.with_))
+ if restrictions:
+ restrictions = ',' + ','.join(restrictions)
+ else:
+ restrictions = ''
+ rql = 'Any %s LIMIT 1 WHERE NOT %s %s %s%s' % (
+ self.filtered_variable.name, subj, self.rtype, obj, restrictions)
+ try:
+ return bool(self.rqlexec(rql, self.cw_rset and self.cw_rset.args))
+ except:
+ # catch exception on executing rql, work-around #1356884 until a
+ # proper fix
+ self.exception('cant handle rql generated by %s', self)
+ return False
+
+ def _add_not_rel_restr(self, rel):
+ nrrel = nodes.Not(_make_relation(self.rqlst, self.filtered_variable,
+ self.rtype, self.role)[1])
+ rel.parent.replace(rel, nodes.Or(nrrel, rel))
class RelationAttributeFacet(RelationFacet):
@@ -699,20 +797,25 @@
if not value:
return
mainvar = self.filtered_variable
- restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
+ restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
+ self.role)[0]
self.rqlst.set_distinct(True)
if isinstance(value, basestring) or self.operator == 'OR':
# only one value selected or multiple ORed values: using IN is fine
- self.rqlst.add_constant_restriction(restrvar, self.target_attr, value,
- self.attrtype, self.comparator)
+ self.rqlst.add_constant_restriction(
+ restrvar, self.target_attr, value,
+ self.attrtype, self.comparator)
else:
# multiple values with AND operator
- self.rqlst.add_constant_restriction(restrvar, self.target_attr, value.pop(),
- self.attrtype, self.comparator)
+ self.rqlst.add_constant_restriction(
+ restrvar, self.target_attr, value.pop(),
+ self.attrtype, self.comparator)
while value:
- restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype, self.role)
- self.rqlst.add_constant_restriction(restrvar, self.target_attr, value.pop(),
- self.attrtype, self.comparator)
+ restrvar = _add_rtype_relation(self.rqlst, mainvar, self.rtype,
+ self.role)[0]
+ self.rqlst.add_constant_restriction(
+ restrvar, self.target_attr, value.pop(),
+ self.attrtype, self.comparator)
class AttributeFacet(RelationAttributeFacet):
@@ -749,6 +852,16 @@
_select_target_entity = True
+ @property
+ def i18nable(self):
+ """should label be internationalized"""
+ for rdef in self._iter_rdefs():
+ # no 'internationalizable' property for rdef whose object is not a
+ # String
+ if not getattr(rdef, 'internationalizable', False):
+ return False
+ return True
+
def vocabulary(self):
"""return vocabulary for this facet, eg a list of 2-uple (label, value)
"""
--- a/web/formfields.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/formfields.py Fri Mar 11 09:46:45 2011 +0100
@@ -73,6 +73,7 @@
FormatConstraint)
from cubicweb import Binary, tags, uilib
+from cubicweb.utils import support_args
from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
formwidgets as fw, uicfg
@@ -332,7 +333,7 @@
if self.eidparam and self.role is not None:
entity = form.edited_entity
if form._cw.vreg.schema.rschema(self.name).final:
- if entity.has_eid() or self.name in entity:
+ if entity.has_eid() or self.name in entity.cw_attr_cache:
value = getattr(entity, self.name)
if value is not None or not self.fallback_on_none_attribute:
return value
@@ -345,7 +346,12 @@
def initial_typed_value(self, form, load_bytes):
if self.value is not _MARKER:
if callable(self.value):
- return self.value(form)
+ if support_args(self.value, 'form', 'field'):
+ return self.value(form, self)
+ else:
+ warn("[3.10] field's value callback must now take form and "
+ "field as argument (%s)" % self, DeprecationWarning)
+ return self.value(form)
return self.value
formattr = '%s_%s_default' % (self.role, self.name)
if hasattr(form, formattr):
@@ -422,12 +428,14 @@
vocab[i] = option
return vocab
- def format(self, form):
+ # support field as argument to avoid warning when used as format field value
+ # callback
+ def format(self, form, field=None):
"""return MIME type used for the given (text or bytes) field"""
if self.eidparam and self.role == 'subject':
entity = form.edited_entity
if entity.e_schema.has_metadata(self.name, 'format') and (
- entity.has_eid() or '%s_format' % self.name in entity):
+ entity.has_eid() or '%s_format' % self.name in entity.cw_attr_cache):
return form.edited_entity.cw_attr_metadata(self.name, 'format')
return form._cw.property_value('ui.default-text-format')
--- a/web/formwidgets.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/formwidgets.py Fri Mar 11 09:46:45 2011 +0100
@@ -28,6 +28,7 @@
.. autoclass:: cubicweb.web.formwidgets.FieldWidget
+
HTML <input> based widgets
''''''''''''''''''''''''''
@@ -37,6 +38,7 @@
.. autoclass:: cubicweb.web.formwidgets.FileInput
.. autoclass:: cubicweb.web.formwidgets.ButtonInput
+
Other standard HTML widgets
'''''''''''''''''''''''''''
@@ -45,6 +47,7 @@
.. autoclass:: cubicweb.web.formwidgets.CheckBox
.. autoclass:: cubicweb.web.formwidgets.Radio
+
Date and time widgets
'''''''''''''''''''''
@@ -53,30 +56,35 @@
.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
+
Ajax / javascript widgets
'''''''''''''''''''''''''
.. autoclass:: cubicweb.web.formwidgets.FCKEditor
.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
+.. autoclass:: cubicweb.web.formwidgets.InOutWidget
.. kill or document StaticFileAutoCompletionWidget
.. kill or document LazyRestrictedAutoCompletionWidget
.. kill or document RestrictedAutoCompletionWidget
+
Other widgets
'''''''''''''
+
.. autoclass:: cubicweb.web.formwidgets.PasswordInput
.. autoclass:: cubicweb.web.formwidgets.IntervalWidget
.. autoclass:: cubicweb.web.formwidgets.HorizontalLayoutWidget
.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+
Form controls
'''''''''''''
-Those classes are not proper widget (they are not associated to
-field) but are used as form controls. Their API is similar
-to widgets except that `field` argument given to :meth:`render`
-will be `None`.
+
+Those classes are not proper widget (they are not associated to field) but are
+used as form controls. Their API is similar to widgets except that `field`
+argument given to :meth:`render` will be `None`.
.. autoclass:: cubicweb.web.formwidgets.Button
.. autoclass:: cubicweb.web.formwidgets.SubmitButton
@@ -93,6 +101,7 @@
from logilab.common.date import todatetime
from cubicweb import tags, uilib
+from cubicweb.utils import json_dumps
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
@@ -106,15 +115,20 @@
:attr:`needs_js`
list of javascript files needed by the widget.
+
:attr:`needs_css`
list of css files needed by the widget.
+
:attr:`setdomid`
flag telling if HTML DOM identifier should be set on input.
+
:attr:`settabindex`
flag telling if HTML tabindex attribute of inputs should be set.
+
:attr:`suffix`
string to use a suffix when generating input, to ease usage as a
sub-widgets (eg widget used by another widget)
+
:attr:`vocabulary_widget`
flag telling if this widget expect a vocabulary
@@ -211,7 +225,7 @@
generating the form)
4. field's typed value (returned by its
- :meth:`~cubicweb.web.formfields.Field.typed_value` method)
+ :meth:`~cubicweb.web.formfields.Field.typed_value` method)
Values found in 1. and 2. are expected te be already some 'display
value' (eg a string) while those found in 3. and 4. are expected to be
@@ -698,12 +712,13 @@
controller. This method is expected to return allowed values for the input,
that the widget will use to propose matching values as you type.
"""
- needs_js = ('cubicweb.widgets.js', 'jquery.autocomplete.js')
- needs_css = ('jquery.autocomplete.css',)
- wdgtype = 'SuggestField'
- loadtype = 'auto'
+ needs_js = ('cubicweb.widgets.js', 'jquery.ui.js')
+ needs_css = ('jquery.ui.css',)
+ default_settings = {}
def __init__(self, *args, **kwargs):
+ self.autocomplete_settings = kwargs.pop('autocomplete_settings',
+ self.default_settings)
try:
self.autocomplete_initfunc = kwargs.pop('autocomplete_initfunc')
except KeyError:
@@ -720,12 +735,17 @@
values = ('',)
return values
- def attributes(self, form, field):
- attrs = super(AutoCompletionWidget, self).attributes(form, field)
- init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
- # XXX entity form specific
- attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
- return attrs
+ def _render(self, form, field, renderer):
+ entity = form.edited_entity
+ domid = field.dom_id(form).replace(':', r'\\:')
+ if callable(self.autocomplete_initfunc):
+ data = self.autocomplete_initfunc(form, field)
+ else:
+ data = xml_escape(self._get_url(entity, field))
+ form._cw.add_onload(u'$("#%s").cwautocomplete(%s, %s);'
+ % (domid, json_dumps(data),
+ json_dumps(self.autocomplete_settings)))
+ return super(AutoCompletionWidget, self)._render(form, field, renderer)
def _get_url(self, entity, field):
if self.autocomplete_initfunc is None:
@@ -737,6 +757,7 @@
pageid=entity._cw.pageid)
+
class StaticFileAutoCompletionWidget(AutoCompletionWidget):
"""XXX describe me"""
wdgtype = 'StaticFileSuggestField'
@@ -752,12 +773,11 @@
class RestrictedAutoCompletionWidget(AutoCompletionWidget):
"""XXX describe me"""
- wdgtype = 'RestrictedSuggestField'
+ default_settings = {'mustMatch': True}
class LazyRestrictedAutoCompletionWidget(RestrictedAutoCompletionWidget):
"""remote autocomplete """
- wdgtype = 'LazySuggestField'
def values_and_attributes(self, form, field):
"""override values_and_attributes to handle initial displayed values"""
@@ -881,7 +901,9 @@
values = {}
path = req.form.get(field.input_name(form, 'path'))
if isinstance(path, basestring):
- path = path.strip() or None
+ path = path.strip()
+ if path is None:
+ path = u''
fqs = req.form.get(field.input_name(form, 'fqs'))
if isinstance(fqs, basestring):
fqs = fqs.strip() or None
@@ -978,3 +1000,55 @@
'label': label, 'imgsrc': imgsrc,
'domid': self.domid, 'href': self.href}
+class InOutWidget(Select):
+ needs_js = ('cubicweb.widgets.js', )
+ template = """
+<table id="%(widgetid)s">
+<tr><td>%(inoutinput)s</td>
+ <td><div style="margin-bottom:3px">%(addinput)s</div> <div>%(removeinput)s</div></td>
+ <td>%(resinput)s</td></tr>
+</table>
+"""
+ add_button = """<input type="button" id="cwinoutadd" class="wdgButton cwinoutadd" value=">>" size="10" />"""
+ remove_button ="""<input type="button" class="wdgButton cwinoutremove" value="<<" size="10" />"""
+
+ def __init__(self, attrs=None):
+ super(InOutWidget, self).__init__(attrs, multiple=True)
+
+ def render_select(self, form, field, name, selected=False):
+ values, attrs = self.values_and_attributes(form, field)
+ options = []
+ inputs = []
+ for _option in field.vocabulary(form):
+ try:
+ label, value, oattrs = _option
+ except ValueError:
+ label, value = _option
+ if selected:
+ # add values
+ if value in values:
+ options.append(tags.option(label, value=value))
+ # add hidden inputs
+ inputs.append(tags.input(value=value, name=field.dom_id(form), type="hidden"))
+ else:
+ options.append(tags.option(label, value=value))
+ if 'size' not in attrs:
+ attrs['size'] = 5
+ if 'id' in attrs :
+ attrs.pop('id')
+ return tags.select(name=name, multiple=self._multiple, id=name,
+ options=options, **attrs) + '\n'.join(inputs)
+
+
+ def _render(self, form, field, renderer):
+ domid = field.dom_id(form)
+ jsnodes = {'widgetid': domid, 'from': 'from_' + domid, 'to': 'to_' + domid}
+ form._cw.add_onload(u'$(cw.jqNode("%s")).cwinoutwidget("%s", "%s");'
+ % (jsnodes['widgetid'], jsnodes['from'], jsnodes['to']))
+ field.required=True
+ return self.template % {'widgetid': jsnodes['widgetid'],
+ 'inoutinput' : self.render_select(form, field, jsnodes['from']), # helpinfo select tag
+ 'resinput' : self.render_select(form, field, jsnodes['to'], selected=True), # select tag with resultats
+ 'addinput' : self.add_button % jsnodes,
+ 'removeinput': self.remove_button % jsnodes
+ }
--- a/web/htmlwidgets.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/htmlwidgets.py Fri Mar 11 09:46:45 2011 +0100
@@ -19,17 +19,18 @@
those are in cubicweb since we need to know available widgets at schema
serialization time
-
"""
+import random
from math import floor
-import random
from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_deprecated
from cubicweb.utils import UStringIO
from cubicweb.uilib import toggle_action, htmlescape
from cubicweb.web import jsonize
+from cubicweb.web.component import _bwcompatible_render_item
# XXX HTMLWidgets should have access to req (for datadir / static urls,
# i18n strings, etc.)
@@ -55,7 +56,8 @@
return False
-class BoxWidget(HTMLWidget):
+class BoxWidget(HTMLWidget): # XXX Deprecated
+
def __init__(self, title, id, items=None, _class="boxFrame",
islist=True, shadow=True, escape=True):
self.title = title
@@ -108,13 +110,16 @@
if self.items:
self.box_begin_content()
for item in self.items:
- item.render(self.w)
+ _bwcompatible_render_item(self.w, item)
self.box_end_content()
self.w(u'</div>')
class SideBoxWidget(BoxWidget):
"""default CubicWeb's sidebox widget"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
+
title_class = u'sideBoxTitle'
main_div_class = u'sideBoxBody'
listing_class = ''
@@ -125,6 +130,7 @@
class MenuWidget(BoxWidget):
+
main_div_class = 'menuContent'
listing_class = 'menuListing'
@@ -134,8 +140,9 @@
self.w(u'</div>\n')
-class RawBoxItem(HTMLWidget):
+class RawBoxItem(HTMLWidget): # XXX deprecated
"""a simpe box item displaying raw data"""
+
def __init__(self, label, liclass=None):
self.label = label
self.liclass = liclass
@@ -154,6 +161,7 @@
class BoxMenu(RawBoxItem):
"""a menu in a box"""
+
link_class = 'boxMenu'
def __init__(self, label, items=None, isitem=True, liclass=None, ident=None,
@@ -182,7 +190,7 @@
toggle_action(ident), self.link_class, self.label))
self._begin_menu(ident)
for item in self.items:
- item.render(self.w)
+ _bwcompatible_render_item(self.w, item)
self._end_menu()
if self.isitem:
self.w(u'</li>')
@@ -203,6 +211,8 @@
class BoxField(HTMLWidget):
"""couples label / value meant to be displayed in a box"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, label, value):
self.label = label
self.value = value
@@ -214,6 +224,8 @@
class BoxSeparator(HTMLWidget):
"""a menu separator"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def _render(self):
self.w(u'</ul><hr class="boxSeparator"/><ul>')
@@ -221,6 +233,8 @@
class BoxLink(HTMLWidget):
"""a link in a box"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, href, label, _class='', title='', ident='', escape=False):
self.href = href
if escape:
@@ -242,6 +256,8 @@
class BoxHtml(HTMLWidget):
"""a form in a box"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, rawhtml):
self.rawhtml = rawhtml
@@ -267,6 +283,7 @@
def add_attr(self, attr, value):
self.cell_attrs[attr] = value
+
class SimpleTableModel(object):
"""
uses a list of lists as a storage backend
@@ -278,7 +295,6 @@
def __init__(self, rows):
self._rows = rows
-
def get_rows(self):
return self._rows
--- a/web/request.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/request.py Fri Mar 11 09:46:45 2011 +0100
@@ -46,6 +46,10 @@
_MARKER = object()
+def build_cb_uid(seed):
+ sha = hashlib.sha1('%s%s%s' % (time.time(), seed, random.random()))
+ return 'cb_%s' % (sha.hexdigest())
+
def list_form_param(form, param, pop=False):
"""get param from form parameters and return its value as a list,
@@ -80,7 +84,6 @@
def __init__(self, vreg, https, form=None):
super(CubicWebRequestBase, self).__init__(vreg)
- self.authmode = vreg.config['auth-mode']
self.https = https
if https:
self.uiprops = vreg.config.https_uiprops
@@ -117,6 +120,10 @@
self.html_headers.define_var('pageid', pid, override=False)
@property
+ def authmode(self):
+ return self.vreg.config['auth-mode']
+
+ @property
def varmaker(self):
"""the rql varmaker is exposed both as a property and as the
set_varmaker function since we've two use cases:
@@ -382,10 +389,7 @@
return "javascript: %s" % getattr(js, jsfunc)(cbname, *args)
def register_onetime_callback(self, func, *args):
- cbname = 'cb_%s' % (
- hashlib.sha1('%s%s%s%s' % (time.time(), func.__name__,
- random.random(),
- self.user.login)).hexdigest())
+ cbname = build_cb_uid(func.__name__)
def _cb(req):
try:
ret = func(req, *args)
@@ -607,10 +611,17 @@
Arbitrary extra named arguments may be given, they will be included as
parameters of the generated url.
"""
+ # define a function in headers and use it in the link to avoid url
+ # unescaping pb: browsers give the js expression to the interpreter
+ # after having url unescaping the content. This may make appear some
+ # quote or other special characters that will break the js expression.
extraparams.setdefault('fname', 'view')
url = self.build_url('json', **extraparams)
- return "javascript: $('#%s').%s; noop()" % (
- nodeid, js.loadxhtml(url, None, 'get', replacemode))
+ cbname = build_cb_uid(url[:50])
+ jscode = 'function %s() { $("#%s").%s; }' % (
+ cbname, nodeid, js.loadxhtml(url, None, 'get', replacemode))
+ self.html_headers.add_post_inline_script(jscode)
+ return "javascript: %s()" % cbname
# urls/path management ####################################################
--- a/web/test/test_views.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/test_views.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,21 +20,12 @@
from cubicweb.devtools.testlib import CubicWebTC, AutoPopulateTest, AutomaticWebTest
from cubicweb.view import AnyRsetView
-AutomaticWebTest.application_rql = [
- 'Any L,F WHERE E is CWUser, E login L, E firstname F',
- 'Any L,F,E WHERE E is CWUser, E login L, E firstname F',
- 'Any COUNT(X) WHERE X is CWUser',
- ]
-
-class ComposityCopy(CubicWebTC):
-
- def test_regr_copy_view(self):
- """regression test: make sure we can ask a copy of a
- composite entity
- """
- rset = self.execute('CWUser X WHERE X login "admin"')
- self.view('copy', rset)
-
+class AutomaticWebTest(AutomaticWebTest):
+ application_rql = [
+ 'Any L,F WHERE E is CWUser, E login L, E firstname F',
+ 'Any L,F,E WHERE E is CWUser, E login L, E firstname F',
+ 'Any COUNT(X) WHERE X is CWUser',
+ ]
class SomeView(AnyRsetView):
@@ -46,8 +37,13 @@
class ManualCubicWebTCs(AutoPopulateTest):
- def setup_database(self):
- self.auto_populate(10)
+
+ def test_regr_copy_view(self):
+ """regression test: make sure we can ask a copy of a
+ composite entity
+ """
+ rset = self.execute('CWUser X WHERE X login "admin"')
+ self.view('copy', rset)
def test_manual_tests(self):
rset = self.execute('Any P,F,S WHERE P is CWUser, P firstname F, P surname S')
@@ -68,10 +64,6 @@
source = self.view('someview', rset).source
self.assertEqual(source.count('spam.js'), 1)
-
-
-class ExplicitViewsTest(CubicWebTC):
-
def test_unrelateddivs(self):
rset = self.execute('Any X WHERE X is CWUser, X login "admin"')
group = self.request().create_entity('CWGroup', name=u'R&D')
--- a/web/test/test_windmill.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/test_windmill.py Fri Mar 11 09:46:45 2011 +0100
@@ -2,5 +2,7 @@
from cubicweb.devtools.cwwindmill import (CubicWebWindmillUseCase,
unittest_main)
+class CubicWebWindmillUseCase(CubicWebWindmillUseCase): pass
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_application.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_application.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,6 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for cubicweb.web.application"""
+from __future__ import with_statement
+
import base64, Cookie
import sys
from urllib import unquote
@@ -298,8 +300,9 @@
def test_login_not_available_to_authenticated(self):
req = self.request()
- ex = self.assertRaises(Unauthorized, self.app_publish, req, 'login')
- self.assertEqual(str(ex), 'log out first')
+ with self.assertRaises(Unauthorized) as cm:
+ self.app_publish(req, 'login')
+ self.assertEqual(str(cm.exception), 'log out first')
def test_fb_login_concept(self):
"""see data/views.py"""
@@ -367,8 +370,9 @@
# preparing the suite of the test
# set session id in cookie
cookie = Cookie.SimpleCookie()
- cookie['__session'] = req.session.sessionid
- req._headers['Cookie'] = cookie['__session'].OutputString()
+ sessioncookie = self.app.session_handler.session_cookie(req)
+ cookie[sessioncookie] = req.session.sessionid
+ req._headers['Cookie'] = cookie[sessioncookie].OutputString()
clear_cache(req, 'get_authorization')
# reset session as if it was a new incoming request
req.session = req.cnx = None
--- a/web/test/unittest_breadcrumbs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_breadcrumbs.py Fri Mar 11 09:46:45 2011 +0100
@@ -31,8 +31,10 @@
self.assertEqual(f2.view('breadcrumbs'),
'<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&ld</a>' % f2.eid)
childrset = f2.as_rset()
- ibc = self.vreg['components'].select('breadcrumbs', self.request(), rset=childrset)
- self.assertEqual(ibc.render(),
+ ibc = self.vreg['ctxcomponents'].select('breadcrumbs', self.request(), rset=childrset)
+ l = []
+ ibc.render(l.append)
+ self.assertEqual(''.join(l),
"""<span id="breadcrumbs" class="pathbar"> > <a href="http://testing.fr/cubicweb/Folder">folder_plural</a> > <a href="http://testing.fr/cubicweb/folder/%s" title="">par&ent</a> > 
<a href="http://testing.fr/cubicweb/folder/%s" title="">chi&ld</a></span>""" % (f1.eid, f2.eid))
--- a/web/test/unittest_facet.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_facet.py Fri Mar 11 09:46:45 2011 +0100
@@ -14,26 +14,33 @@
self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
return req, rset, rqlst, mainvar
- def test_relation_simple(self):
+ def _in_group_facet(self, cls=facet.RelationFacet, no_relation=False):
req, rset, rqlst, mainvar = self.prepare_rqlst()
- f = facet.RelationFacet(req, rset=rset,
- rqlst=rqlst.children[0],
- filtered_variable=mainvar)
+ cls.no_relation = no_relation
+ f = cls(req, rset=rset, rqlst=rqlst.children[0],
+ filtered_variable=mainvar)
+ f.__regid__ = 'in_group'
f.rtype = 'in_group'
f.role = 'subject'
f.target_attr = 'name'
guests, managers = [eid for eid, in self.execute('CWGroup G ORDERBY GN '
'WHERE G name GN, G name IN ("guests", "managers")')]
+ groups = [eid for eid, in self.execute('CWGroup G ORDERBY GN '
+ 'WHERE G name GN, G name IN ("guests", "managers")')]
+ return f, groups
+
+ def test_relation_simple(self):
+ f, (guests, managers) = self._in_group_facet()
self.assertEqual(f.vocabulary(),
- [(u'guests', guests), (u'managers', managers)])
+ [(u'guests', guests), (u'managers', managers)])
# ensure rqlst is left unmodified
- self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
+ self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
#rqlst = rset.syntax_tree()
self.assertEqual(f.possible_values(),
[str(guests), str(managers)])
# ensure rqlst is left unmodified
- self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
- req.form[f.__regid__] = str(guests)
+ self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
+ f._cw.form[f.__regid__] = str(guests)
f.add_rql_restrictions()
# selection is cluttered because rqlst has been prepared for facet (it
# is not in real life)
@@ -72,25 +79,47 @@
self.assertEqual(f.rqlst.as_string(),
'DISTINCT Any GROUPBY X WHERE X in_group G?, G name GN, NOT G name "users", X in_group D, D eid %s' % guests)
+ def test_relation_no_relation_1(self):
+ f, (guests, managers) = self._in_group_facet(no_relation=True)
+ self.assertEqual(f.vocabulary(),
+ [(u'guests', guests), (u'managers', managers)])
+ self.assertEqual(f.possible_values(),
+ [str(guests), str(managers)])
+ f._cw.create_entity('CWUser', login=u'hop', upassword='toto')
+ self.assertEqual(f.vocabulary(),
+ [(u'<no relation>', ''), (u'guests', guests), (u'managers', managers)])
+ self.assertEqual(f.possible_values(),
+ [str(guests), str(managers), ''])
+ f._cw.form[f.__regid__] = ''
+ f.add_rql_restrictions()
+ self.assertEqual(f.rqlst.as_string(),
+ 'DISTINCT Any WHERE X is CWUser, NOT X in_group G')
+
+ def test_relation_no_relation_2(self):
+ f, (guests, managers) = self._in_group_facet(no_relation=True)
+ f._cw.form[f.__regid__] = ['', guests]
+ f.rqlst.save_state()
+ f.add_rql_restrictions()
+ self.assertEqual(f.rqlst.as_string(),
+ 'DISTINCT Any WHERE X is CWUser, (NOT X in_group B) OR (X in_group A, A eid %s)' % guests)
+ f.rqlst.recover()
+ self.assertEqual(f.rqlst.as_string(),
+ 'DISTINCT Any WHERE X is CWUser')
+
+
def test_relationattribute(self):
- req, rset, rqlst, mainvar = self.prepare_rqlst()
- f = facet.RelationAttributeFacet(req, rset=rset,
- rqlst=rqlst.children[0],
- filtered_variable=mainvar)
- f.rtype = 'in_group'
- f.role = 'subject'
- f.target_attr = 'name'
+ f, (guests, managers) = self._in_group_facet(cls=facet.RelationAttributeFacet)
self.assertEqual(f.vocabulary(),
[(u'guests', u'guests'), (u'managers', u'managers')])
# ensure rqlst is left unmodified
- self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
+ self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
#rqlst = rset.syntax_tree()
self.assertEqual(f.possible_values(),
['guests', 'managers'])
# ensure rqlst is left unmodified
- self.assertEqual(rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
- req.form[f.__regid__] = 'guests'
+ self.assertEqual(f.rqlst.as_string(), 'DISTINCT Any WHERE X is CWUser')
+ f._cw.form[f.__regid__] = 'guests'
f.add_rql_restrictions()
# selection is cluttered because rqlst has been prepared for facet (it
# is not in real life)
--- a/web/test/unittest_form.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_form.py Fri Mar 11 09:46:45 2011 +0100
@@ -143,7 +143,7 @@
state.eid = 'S'
form = RTFForm(self.req, redirect_path='perdu.com', entity=state)
# make it think it can use fck editor anyway
- form.field_by_name('description', 'subject').format = lambda x: 'text/html'
+ form.field_by_name('description', 'subject').format = lambda form, field=None: 'text/html'
self.assertMultiLineEqual(self._render_entity_field('description', form),
expected % {'eid': state.eid})
--- a/web/test/unittest_formfields.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_formfields.py Fri Mar 11 09:46:45 2011 +0100
@@ -29,9 +29,11 @@
from cubes.file.entities import File
-config = TestServerConfiguration('data')
-config.bootstrap_cubes()
-schema = config.load_schema()
+def setUpModule(*args):
+ global schema
+ config = TestServerConfiguration('data', apphome=GuessFieldTC.datadir)
+ config.bootstrap_cubes()
+ schema = config.load_schema()
class GuessFieldTC(TestCase):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_formwidgets.py Fri Mar 11 09:46:45 2011 +0100
@@ -0,0 +1,44 @@
+# copyright 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 <http://www.gnu.org/licenses/>.
+"""unittests for cw.web.formwidgets"""
+
+from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
+
+from cubicweb.devtools import TestServerConfiguration, fake
+from cubicweb.web import uicfg, formwidgets, formfields
+
+from cubes.file.entities import File
+
+def setUpModule(*args):
+ global schema
+ config = TestServerConfiguration('data', apphome=WidgetsTC.datadir)
+ config.bootstrap_cubes()
+ schema = config.load_schema()
+
+class WidgetsTC(TestCase):
+
+ def test_state_fields(self):
+ field = formfields.guess_field(schema['Bookmark'], schema['path'])
+ widget = formwidgets.EditableURLWidget()
+ req = fake.FakeRequest(form={'path-subjectfqs:A': 'param=value&vid=view'})
+ form = mock(_cw=req, formvalues={}, edited_entity=mock(eid='A'))
+ self.assertEqual(widget.process_field_data(form, field),
+ '?param=value%26vid%3Dview')
+
+if __name__ == '__main__':
+ unittest_main()
--- a/web/test/unittest_reledit.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_reledit.py Fri Mar 11 09:46:45 2011 +0100
@@ -43,7 +43,8 @@
if rschema not in reledit:
continue
rtype = rschema.type
- self.assertMultiLineEqual(reledit[rtype] % {'eid': self.proj.eid}, self.proj.view('reledit', rtype=rtype, role=role), rtype)
+ self.assertMultiLineEqual(reledit[rtype] % {'eid': self.proj.eid},
+ self.proj.view('reledit', rtype=rtype, role=role), rtype)
def test_default_forms(self):
doreledit = {'title': """<div id="title-subject-%(eid)s-reledit" onmouseout="jQuery('#title-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#title-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="title-subject-%(eid)s-value" class="editableFieldValue">cubicweb-world-domination</div><form action="http://testing.fr/cubicweb/validateform?__onsuccess=window.parent.cw.reledit.onSuccess" method="post" enctype="application/x-www-form-urlencoded" id="title-subject-%(eid)s-form" onsubmit="return freezeFormButtons('title-subject-%(eid)s-form');" class="releditForm" cubicweb:target="eformframe">
@@ -65,18 +66,14 @@
<fieldset class="default">
<table class="">
<tr class="title_subject_row">
-<td
->
+<td>
<input id="title-subject:%(eid)s" maxlength="32" name="title-subject:%(eid)s" size="32" tabindex="1" type="text" value="cubicweb-world-domination" />
</td></tr>
</table></fieldset>
<table class="buttonbar">
<tr>
-
<td><button class="validateButton" tabindex="2" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-
<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel('title-subject-%(eid)s')" tabindex="3" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
-
</tr></table>
</fieldset>
</form><div id="title-subject-%(eid)s" class="editableField hidden"><div id="title-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'title', 'subject', 'title-subject-%(eid)s', false, '');" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
@@ -103,30 +100,24 @@
<table class="attributeForm">
<tr class="title_subject_row">
<th class="labelCol"><label class="required" for="title-subject:A">title</label></th>
-<td
->
+<td>
<input id="title-subject:A" maxlength="50" name="title-subject:A" size="45" tabindex="4" type="text" value="" />
</td></tr>
<tr class="description_subject_row">
<th class="labelCol"><label for="description-subject:A">description</label></th>
-<td
->
+<td>
<input name="description_format-subject:A" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:A" name="description-subject:A" onkeyup="autogrow(this)" rows="2" tabindex="5"></textarea>
</td></tr>
<tr class="rss_url_subject_row">
<th class="labelCol"><label for="rss_url-subject:A">rss_url</label></th>
-<td
->
+<td>
<input id="rss_url-subject:A" maxlength="128" name="rss_url-subject:A" size="45" tabindex="6" type="text" value="" />
</td></tr>
</table></fieldset>
<table class="buttonbar">
<tr>
-
<td><button class="validateButton" tabindex="7" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-
<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel('long_desc-subject-%(eid)s')" tabindex="8" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
-
</tr></table>
</fieldset>
</form><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-add" class="editableField" onclick="cw.reledit.loadInlineEditionForm('edition', %(eid)s, 'long_desc', 'subject', 'long_desc-subject-%(eid)s', false, 'autolimited');" title="click to add a value"><img title="click to add a value" src="data/plus.png" alt="click to add a value"/></div></div></div>""",
@@ -152,8 +143,7 @@
<fieldset class="default">
<table class="">
<tr class="manager_subject_row">
-<td
->
+<td>
<select id="manager-subject:%(eid)s" name="manager-subject:%(eid)s" size="1" tabindex="9">
<option value="__cubicweb_internal_field__"></option>
<option value="%(toto)s">Toto</option>
@@ -162,11 +152,8 @@
</table></fieldset>
<table class="buttonbar">
<tr>
-
<td><button class="validateButton" tabindex="10" type="submit" value="button_ok"><img alt="OK_ICON" src="http://testing.fr/cubicweb/data/ok.png" />button_ok</button></td>
-
<td><button class="validateButton" onclick="cw.reledit.cleanupAfterCancel('manager-subject-%(eid)s')" tabindex="11" type="button" value="button_cancel"><img alt="CANCEL_ICON" src="http://testing.fr/cubicweb/data/cancel.png" />button_cancel</button></td>
-
</tr></table>
</fieldset>
</form><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'manager', 'subject', 'manager-subject-%(eid)s', false, 'autolimited');" title="click to edit this field"><img title="click to edit this field" src="data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
--- a/web/test/unittest_session.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_session.py Fri Mar 11 09:46:45 2011 +0100
@@ -7,10 +7,11 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.web import InvalidSession
class SessionTC(CubicWebTC):
- def test_auto_reconnection(self):
+ def test_session_expiration(self):
sm = self.app.session_handler.session_manager
# make is if the web session has been opened by the session manager
sm._sessions[self.cnx.sessionid] = self.websession
@@ -23,11 +24,8 @@
# fake an incoming http query with sessionid in session cookie
# don't use self.request() which try to call req.set_session
req = self.requestcls(self.vreg)
- websession = sm.get_session(req, sessionid)
- self.assertEqual(len(sm._sessions), 1)
- self.assertIs(websession, self.websession)
- self.assertEqual(websession.sessionid, sessionid)
- self.assertNotEquals(websession.sessionid, websession.cnx.sessionid)
+ self.assertRaises(InvalidSession, sm.get_session, req, sessionid)
+ self.assertEqual(len(sm._sessions), 0)
finally:
# avoid error in tearDown by telling this connection is closed...
self.cnx._closed = True
--- a/web/test/unittest_urlpublisher.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_urlpublisher.py Fri Mar 11 09:46:45 2011 +0100
@@ -77,7 +77,7 @@
self.assertEqual(ctrl, 'view')
self.assertEqual(len(rset), 1)
self.assertEqual(rset.description[0][0], 'CWUser')
- self.assertEqual(rset.printable_rql(), 'Any X,AA,AB,AC,AD WHERE X eid 5, X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD')
+ self.assertEqual(rset.printable_rql(), 'Any X,AA,AB,AC,AD WHERE X eid %s, X is CWUser, X login AA, X firstname AB, X surname AC, X modification_date AD' % rset[0][0])
# test non-ascii paths
ctrl, rset = self.process('CWUser/login/%C3%BFsa%C3%BFe')
self.assertEqual(ctrl, 'view')
--- a/web/test/unittest_views_basecontrollers.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_views_basecontrollers.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,6 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""cubicweb.web.views.basecontrollers unit tests"""
+from __future__ import with_statement
+
from logilab.common.testlib import unittest_main, mock_object
from cubicweb import Binary, NoSelectableObject, ValidationError
@@ -47,8 +49,9 @@
def test_noparam_edit(self):
"""check behaviour of this controller without any form parameter
"""
- ex = self.assertRaises(ValidationError, self.ctrl_publish, self.request())
- self.assertEqual(ex.errors, {None: u'no selected entities'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(self.request())
+ self.assertEqual(cm.exception.errors, {None: u'no selected entities'})
def test_validation_unique(self):
"""test creation of two linked entities
@@ -61,8 +64,9 @@
'upassword-subject:X': u'toto',
'upassword-subject-confirm:X': u'toto',
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'login-subject': 'the value "admin" is already used, use another one'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'login-subject': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
"""checking that a manager user can edit itself
@@ -205,8 +209,9 @@
'login-subject:X': u'toto',
'upassword-subject:X': u'toto',
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'upassword-subject': u'password and confirmation don\'t match'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'upassword-subject': u'password and confirmation don\'t match'})
req = self.request()
req.form = {'__cloned_eid:X': u(user.eid),
'eid': 'X', '__type:X': 'CWUser',
@@ -215,8 +220,9 @@
'upassword-subject:X': u'toto',
'upassword-subject-confirm:X': u'tutu',
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'upassword-subject': u'password and confirmation don\'t match'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'upassword-subject': u'password and confirmation don\'t match'})
def test_interval_bound_constraint_success(self):
@@ -230,8 +236,9 @@
'amount-subject:X': u'-10',
'described_by_test-subject:X': u(feid),
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'amount-subject': 'value must be >= 0'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'amount-subject': 'value must be >= 0'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
@@ -239,8 +246,9 @@
'amount-subject:X': u'110',
'described_by_test-subject:X': u(feid),
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'amount-subject': 'value must be <= 100'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'amount-subject': 'value must be <= 100'})
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
@@ -421,8 +429,9 @@
'alias-subject:Y': u'',
'use_email-object:Y': 'X',
}
- ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
- self.assertEqual(ex.errors, {'address-subject': u'required field'})
+ with self.assertRaises(ValidationError) as cm:
+ self.ctrl_publish(req)
+ self.assertEqual(cm.exception.errors, {'address-subject': u'required field'})
def test_nonregr_copy(self):
user = self.user()
--- a/web/test/unittest_views_editforms.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_views_editforms.py Fri Mar 11 09:46:45 2011 +0100
@@ -64,10 +64,10 @@
])
self.assertListEqual(rbc(e, 'main', 'metadata'),
[('last_login_time', 'subject'),
+ ('creation_date', 'subject'),
+ ('cwuri', 'subject'),
('modification_date', 'subject'),
('created_by', 'subject'),
- ('creation_date', 'subject'),
- ('cwuri', 'subject'),
('owned_by', 'subject'),
('bookmarked_by', 'object'),
])
@@ -77,8 +77,8 @@
self.assertListEqual([x for x in rbc(e, 'main', 'relations')
if x != ('tags', 'object')],
[('primary_email', 'subject'),
+ ('connait', 'subject'),
('custom_workflow', 'subject'),
- ('connait', 'subject'),
('checked_by', 'object'),
])
self.assertListEqual(rbc(e, 'main', 'inlined'),
--- a/web/test/unittest_views_navigation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_views_navigation.py Fri Mar 11 09:46:45 2011 +0100
@@ -58,7 +58,7 @@
navcomp = self.vreg['components'].select('navigation', req, rset=rset, page_size=20)
self.assertIsInstance(navcomp, PageNavigationSelect)
- def test_navigation_selection_not_enough(self):
+ def test_navigation_selection_not_enough_1(self):
req = self.request()
rset = self.execute('Any X,N LIMIT 10 WHERE X name N')
navcomp = self.vreg['components'].select_or_none('navigation', req, rset=rset)
@@ -68,7 +68,7 @@
self.assertEqual(navcomp, None)
req.set_search_state('normal')
- def test_navigation_selection_not_enough(self):
+ def test_navigation_selection_not_enough_2(self):
req = self.request()
rset = self.execute('Any N, COUNT(RDEF) GROUPBY N ORDERBY N WHERE RDEF relation_type RT, RT name N')
navcomp = self.vreg['components'].select('navigation', req, rset=rset)
@@ -122,26 +122,26 @@
# view = mock_object(is_primary=lambda x: True)
# rset = self.execute('CWUser X LIMIT 1')
# req = self.request()
- # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # objs = self.vreg['ctxcomponents'].poss_visible_objects(
# req, rset=rset, view=view, context='navtop')
# # breadcrumbs should be in headers by default
# clsids = set(obj.id for obj in objs)
# self.failUnless('breadcrumbs' in clsids)
- # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # objs = self.vreg['ctxcomponents'].poss_visible_objects(
# req, rset=rset, view=view, context='navbottom')
# # breadcrumbs should _NOT_ be in footers by default
# clsids = set(obj.id for obj in objs)
# self.failIf('breadcrumbs' in clsids)
- # self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+ # self.execute('INSERT CWProperty P: P pkey "ctxcomponents.breadcrumbs.context", '
# 'P value "navbottom"')
# # breadcrumbs should now be in footers
# req.cnx.commit()
- # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # objs = self.vreg['ctxcomponents'].poss_visible_objects(
# req, rset=rset, view=view, context='navbottom')
# clsids = [obj.id for obj in objs]
# self.failUnless('breadcrumbs' in clsids)
- # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # objs = self.vreg['ctxcomponents'].poss_visible_objects(
# req, rset=rset, view=view, context='navtop')
# clsids = [obj.id for obj in objs]
--- a/web/test/unittest_viewselector.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_viewselector.py Fri Mar 11 09:46:45 2011 +0100
@@ -468,19 +468,18 @@
def test_properties(self):
self.assertEqual(sorted(k for k in self.vreg['propertydefs'].keys()
- if k.startswith('boxes.edit_box')),
- ['boxes.edit_box.context',
- 'boxes.edit_box.order',
- 'boxes.edit_box.visible'])
+ if k.startswith('ctxcomponents.edit_box')),
+ ['ctxcomponents.edit_box.context',
+ 'ctxcomponents.edit_box.order',
+ 'ctxcomponents.edit_box.visible'])
self.assertEqual([k for k in self.vreg['propertyvalues'].keys()
if not k.startswith('system.version')],
[])
- self.assertEqual(self.vreg.property_value('boxes.edit_box.visible'), True)
- self.assertEqual(self.vreg.property_value('boxes.edit_box.order'), 2)
- self.assertEqual(self.vreg.property_value('boxes.possible_views_box.visible'), False)
- self.assertEqual(self.vreg.property_value('boxes.possible_views_box.order'), 10)
- self.assertRaises(UnknownProperty, self.vreg.property_value, 'boxes.actions_box')
-
+ self.assertEqual(self.vreg.property_value('ctxcomponents.edit_box.visible'), True)
+ self.assertEqual(self.vreg.property_value('ctxcomponents.edit_box.order'), 2)
+ self.assertEqual(self.vreg.property_value('ctxcomponents.possible_views_box.visible'), False)
+ self.assertEqual(self.vreg.property_value('ctxcomponents.possible_views_box.order'), 10)
+ self.assertRaises(UnknownProperty, self.vreg.property_value, 'ctxcomponents.actions_box')
--- a/web/test/unittest_web.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/unittest_web.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,15 +21,25 @@
class AjaxReplaceUrlTC(TestCase):
- def test_ajax_replace_url(self):
+ def test_ajax_replace_url_1(self):
+ self._test_arurl("fname=view&rql=Person%20P&vid=list",
+ rql='Person P', vid='list')
+
+ def test_ajax_replace_url_2(self):
+ self._test_arurl("age=12&fname=view&name=bar&rql=Person%20P&vid=oneline",
+ rql='Person P', vid='oneline', name='bar', age=12)
+
+ def _test_arurl(self, qs, **kwargs):
req = FakeRequest()
arurl = req.ajax_replace_url
# NOTE: for the simplest use cases, we could use doctest
- self.assertEqual(arurl('foo', rql='Person P', vid='list'),
- """javascript: $('#foo').loadxhtml("http://testing.fr/cubicweb/json?rql=Person%20P&fname=view&vid=list",null,"get","replace"); noop()""")
- self.assertEqual(arurl('foo', rql='Person P', vid='oneline', name='bar', age=12),
- """javascript: $('#foo').loadxhtml("http://testing.fr/cubicweb/json?name=bar&age=12&rql=Person%20P&fname=view&vid=oneline",null,"get","replace"); noop()""")
-
+ url = arurl('foo', **kwargs)
+ self.failUnless(url.startswith('javascript:'))
+ self.failUnless(url.endswith('()'))
+ cbname = url.split()[1][:-2]
+ self.assertMultiLineEqual(
+ 'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/json?%s",null,"get","replace"); }' % (cbname, qs),
+ req.html_headers.post_inlined_scripts[0])
if __name__ == '__main__':
unittest_main()
--- a/web/test/windmill/test_connexion.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/windmill/test_connexion.py Fri Mar 11 09:46:45 2011 +0100
@@ -4,7 +4,6 @@
# Generated by the windmill services transformer
from windmill.authoring import WindmillTestClient
-
def test_connect():
client = WindmillTestClient(__name__)
@@ -16,6 +15,7 @@
client.execJS(js=u"$('#loginForm').submit()")
client.waits.forPageLoad(timeout=u'20000')
+ client.waits.sleep(milliseconds=u'5000')
client.asserts.assertJS(js=u'$(\'.message\').text() == "welcome %s !"' % LOGIN)
client.open(url=u'/logout')
client.waits.forPageLoad(timeout=u'20000')
--- a/web/test/windmill/test_creation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/windmill/test_creation.py Fri Mar 11 09:46:45 2011 +0100
@@ -26,7 +26,7 @@
client.type(text=u'myuser', id=u'upassword-subject:A')
client.type(text=u'myuser', name=u'upassword-subject-confirm:A')
client.type(text=u'myuser', id=u'firstname-subject:A')
- client.select(val=u'4', id=u'in_group-subject:A')
+ client.select(option=u'managers', id=u'in_group-subject:A')
client.waits.forPageLoad(timeout=u'20000')
client.click(id=u'adduse_email:Alink')
client.waits.forPageLoad(timeout=u'20000')
@@ -34,6 +34,7 @@
client.waits.forPageLoad(timeout=u'20000')
client.click(value=u'button_ok')
client.waits.forPageLoad(timeout=u'20000')
+ client.waits.sleep(milliseconds=u'5000')
client.asserts.assertJS(js=u'$(\'.message\').text() == "entity created"')
client.open(url=u'/?rql=Any U WHERE U is CWUser, U login "myuser"')
client.waits.forPageLoad(timeout=u'20000')
--- a/web/test/windmill/test_edit_relation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/test/windmill/test_edit_relation.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,8 +22,8 @@
client.type(text=u'folder1', id=u'name-subject:A')
client.click(value=u'button_ok')
client.waits.forPageLoad(timeout=u'20000')
- client.waits.forElement(link=u'add add Folder filed_under Folder object', timeout=u'8000')
- client.click(link=u'add add Folder filed_under Folder object')
+ client.waits.forElement(link=u'add Folder filed_under Folder object', timeout=u'8000')
+ client.click(link=u'add Folder filed_under Folder object')
client.waits.forPageLoad(timeout=u'20000')
client.waits.forElement(timeout=u'8000', id=u'name-subject:A')
client.click(id=u'name-subject:A')
@@ -44,8 +44,8 @@
client.click(link=u'x')
client.click(value=u'button_ok')
client.waits.forPageLoad(timeout=u'20000')
- client.waits.forElement(link=u'add add Folder filed_under Folder object', timeout=u'8000')
- client.click(link=u'add add Folder filed_under Folder object')
+ client.waits.forElement(link=u'add Folder filed_under Folder object', timeout=u'8000')
+ client.click(link=u'add Folder filed_under Folder object')
client.waits.forPageLoad(timeout=u'20000')
client.type(text=u'subfolder2', id=u'name-subject:A')
client.click(value=u'button_ok')
--- a/web/uicfg.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/uicfg.py Fri Mar 11 09:46:45 2011 +0100
@@ -53,7 +53,7 @@
from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
RelationTagsDict, NoTargetRelationTagsDict,
register_rtag, _ensure_str_key)
-from cubicweb.schema import META_RTYPES
+from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
# primary view configuration ##################################################
@@ -120,6 +120,8 @@
continue
if eschema.schema_entity():
self.setdefault(eschema, 'schema')
+ elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES:
+ self.setdefault(eschema, 'system')
elif eschema.is_subobject(strict=True):
self.setdefault(eschema, 'subobject')
else:
@@ -127,13 +129,9 @@
indexview_etype_section = InitializableDict(
EmailAddress='subobject',
+ Bookmark='system',
# entity types in the 'system' table by default (managers only)
CWUser='system', CWGroup='system',
- CWPermission='system',
- CWCache='system',
- Workflow='system',
- ExternalUri='system',
- Bookmark='system',
)
# autoform.AutomaticEntityForm configuration ##################################
@@ -404,23 +402,22 @@
return super(ReleditTags, self).tag_relation(key, tag)
def init_reledit_ctrl(rtag, sschema, rschema, oschema, role):
- if rschema.final:
- return
- composite = rschema.rdef(sschema, oschema).composite == role
- if role == 'subject':
- oschema = '*'
- else:
- sschema = '*'
values = rtag.get(sschema, rschema, oschema, role)
- edittarget = values.get('edit_target')
- if edittarget not in (None, 'rtype', 'related'):
- rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
- rschema, edittarget)
- edittarget = None
- if not edittarget:
- edittarget = 'related' if composite else 'rtype'
- rtag.tag_relation((sschema, rschema, oschema, role),
- {'edit_target': edittarget})
+ if not rschema.final:
+ composite = rschema.rdef(sschema, oschema).composite == role
+ if role == 'subject':
+ oschema = '*'
+ else:
+ sschema = '*'
+ edittarget = values.get('edit_target')
+ if edittarget not in (None, 'rtype', 'related'):
+ rtag.warning('reledit: wrong value for edit_target on relation %s: %s',
+ rschema, edittarget)
+ edittarget = None
+ if not edittarget:
+ edittarget = 'related' if composite else 'rtype'
+ rtag.tag_relation((sschema, rschema, oschema, role),
+ {'edit_target': edittarget})
if not 'novalue_include_rtype' in values:
showlabel = primaryview_display_ctrl.get(
sschema, rschema, oschema, role).get('showlabel', True)
--- a/web/views/__init__.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/__init__.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Views, forms, actions... for the CubicWeb web client
+"""Views, forms, actions... for the CubicWeb web client"""
-"""
__docformat__ = "restructuredtext en"
import os
--- a/web/views/actions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/actions.py Fri Mar 11 09:46:45 2011 +0100
@@ -47,7 +47,7 @@
# if user has no update right but it can modify some relation,
# display action anyway
form = entity._cw.vreg['forms'].select('edition', entity._cw,
- entity=entity)
+ entity=entity, mainform=False)
for dummy in form.editable_relations():
return 1
try:
@@ -407,7 +407,7 @@
category = 'footer'
order = 3
- title = _('powered by CubicWeb')
+ title = _('Powered by CubicWeb')
def url(self):
return 'http://www.cubicweb.org'
--- a/web/views/ajaxedit.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/ajaxedit.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,21 +15,19 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Set of views allowing edition of entities/relations using ajax
+"""Set of views allowing edition of entities/relations using ajax"""
-"""
__docformat__ = "restructuredtext en"
from cubicweb import role
+from cubicweb.view import View
from cubicweb.selectors import match_form_params, match_kwargs
-from cubicweb.web.box import EditRelationBoxTemplate
+from cubicweb.web import component, stdmsgs, formwidgets as fw
-class AddRelationView(EditRelationBoxTemplate):
- """base class for view which let add entities linked
- by a given relation
+class AddRelationView(component.EditRelationMixIn, View):
+ """base class for view which let add entities linked by a given relation
- subclasses should define at least id, rtype and target
- class attributes.
+ subclasses should define at least id, rtype and target class attributes.
"""
__registry__ = 'views'
__regid__ = 'xaddrelation'
@@ -38,7 +36,7 @@
cw_property_defs = {} # don't want to inherit this from Box
expected_kwargs = form_params = ('rtype', 'target')
- build_js = EditRelationBoxTemplate.build_reload_js_call
+ build_js = component.EditRelationMixIn.build_reload_js_call
def cell_call(self, row, col, rtype=None, target=None, etype=None):
self.rtype = rtype or self._cw.form['rtype']
@@ -53,13 +51,13 @@
etypes = rschema.subjects(entity.e_schema)
if len(etypes) == 1:
self.etype = etypes[0]
- self.w(u'<div id="%s">' % self.__regid__)
+ self.w(u'<div id="%s">' % self.domid)
self.w(u'<h1>%s</h1>' % self._cw._('relation %(relname)s of %(ent)s')
% {'relname': rschema.display_name(self._cw, role(self)),
'ent': entity.view('incontext')})
self.w(u'<ul>')
for boxitem in self.unrelated_boxitems(entity):
- boxitem.render(self.w)
+ self.w('<li class="invisible">%s</li>' % boxitem)
self.w(u'</ul></div>')
def unrelated_entities(self, entity):
@@ -74,11 +72,42 @@
ordermethod='fetch_order')
self.pagination(self._cw, rset, w=self.w)
return rset.entities()
- # in other cases, use vocabulary functions
- entities = []
- # XXX to update for 3.2
- for _, eid in entity.vocabulary(self.rtype, role(self)):
- if eid is not None:
- rset = self._cw.eid_rset(eid)
- entities.append(rset.get_entity(0, 0))
- return entities
+ super(AddRelationView, self).unrelated_entities(self)
+
+
+def ajax_composite_form(container, entity, rtype, okjs, canceljs,
+ entityfkwargs=None):
+ """
+ * if entity is None, edit container (assert container.has_eid())
+ * if entity has not eid, will be created
+ * if container has not eid, will be created (see vcreview InsertionPoint)
+ """
+ req = container._cw
+ parentexists = entity is None or container.has_eid()
+ buttons = [fw.Button(onclick=okjs),
+ fw.Button(stdmsgs.BUTTON_CANCEL, onclick=canceljs)]
+ freg = req.vreg['forms']
+ # main form kwargs
+ mkwargs = dict(action='#', domid='%sForm%s' % (rtype, container.eid),
+ form_buttons=buttons,
+ onsubmit='javascript: %s; return false' % okjs)
+ # entity form kwargs
+ # use formtype=inlined to skip the generic relations edition section
+ fkwargs = dict(entity=entity or container, formtype='inlined')
+ if entityfkwargs is not None:
+ fkwargs.update(entityfkwargs)
+ # form values
+ formvalues = {}
+ if entity is not None: # creation
+ formvalues[rtype] = container.eid
+ if parentexists: # creation / edition
+ mkwargs.update(fkwargs)
+ # use formtype=inlined to avoid viewing the relation edition section
+ form = freg.select('edition', req, **mkwargs)
+ else: # creation of both container and comment entities
+ form = freg.select('composite', req, form_renderer_id='default',
+ **mkwargs)
+ form.add_subform(freg.select('edition', req, entity=container,
+ mainform=False, mainentity=True))
+ form.add_subform(freg.select('edition', req, mainform=False, **fkwargs))
+ return form, formvalues
--- a/web/views/authentication.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/authentication.py Fri Mar 11 09:46:45 2011 +0100
@@ -37,6 +37,7 @@
class WebAuthInfoRetreiver(Component):
__registry__ = 'webauth'
order = None
+ __abstract__ = True
def authentication_information(self, req):
"""retreive authentication information from the given request, raise
@@ -51,6 +52,18 @@
"""
pass
+ def request_has_auth_info(self, req):
+ """tells from the request if it has enough information
+ to proceed to authentication, would the current session
+ be invalidated
+ """
+ raise NotImplementedError()
+
+ def revalidate_login(self, req):
+ """returns a login string or None, for repository session validation
+ purposes
+ """
+ raise NotImplementedError()
class LoginPasswordRetreiver(WebAuthInfoRetreiver):
__regid__ = 'loginpwdauth'
@@ -65,6 +78,11 @@
raise NoAuthInfo()
return login, {'password': password}
+ def request_has_auth_info(self, req):
+ return req.get_authorization()[0] is not None
+
+ def revalidate_login(self, req):
+ return req.get_authorization()[0]
class RepositoryAuthenticationManager(AbstractAuthenticationManager):
"""authenticate user associated to a request and check session validity"""
@@ -73,8 +91,8 @@
super(RepositoryAuthenticationManager, self).__init__(vreg)
self.repo = vreg.config.repository(vreg)
self.log_queries = vreg.config['query-log-file']
- self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg),
- key=lambda x: x.order)
+ self.authinforetrievers = sorted(vreg['webauth'].possible_objects(vreg),
+ key=lambda x: x.order)
# 2-uple login / password, login is None when no anonymous access
# configured
self.anoninfo = vreg.config.anonymous_user()
@@ -88,35 +106,30 @@
raise :exc:`InvalidSession` if session is corrupted for a reason or
another and should be closed
+
+ also invoked while going from anonymous to logged in
"""
# with this authentication manager, session is actually a dbapi
# connection
- login = req.get_authorization()[0]
+ for retriever in self.authinforetrievers:
+ if retriever.request_has_auth_info(req):
+ login = retriever.revalidate_login(req)
+ return self._validate_session(req, session, login)
+ # let's try with the current session
+ return self._validate_session(req, session, None)
+
+ def _validate_session(self, req, session, login):
# check session.login and not user.login, since in case of login by
# email, login and cnx.login are the email while user.login is the
# actual user login
if login and session.login != login:
raise InvalidSession('login mismatch')
try:
- lock = session.reconnection_lock
- except AttributeError:
- lock = session.reconnection_lock = Lock()
- # need to be locked two avoid duplicated reconnections on concurrent
- # requests
- with lock:
- cnx = session.cnx
- try:
- # calling cnx.user() check connection validity, raise
- # BadConnectionId on failure
- user = cnx.user(req)
- except BadConnectionId:
- # check if a connection should be automatically restablished
- if (login is None or login == session.login):
- cnx = self._authenticate(session.login, session.authinfo)
- user = cnx.user(req)
- session.cnx = cnx
- else:
- raise InvalidSession('bad connection id')
+ # calling cnx.user() check connection validity, raise
+ # BadConnectionId on failure
+ user = session.cnx.user(req)
+ except BadConnectionId:
+ raise InvalidSession('bad connection id')
return user
def authenticate(self, req):
@@ -128,18 +141,19 @@
raise :exc:`cubicweb.AuthenticationError` if authentication failed
(no authentication info found or wrong user/password)
"""
- for retreiver in self.authinforetreivers:
+ for retriever in self.authinforetrievers:
try:
- login, authinfo = retreiver.authentication_information(req)
+ login, authinfo = retriever.authentication_information(req)
except NoAuthInfo:
continue
try:
cnx = self._authenticate(login, authinfo)
except AuthenticationError:
continue # the next one may succeed
- for retreiver_ in self.authinforetreivers:
- retreiver_.authenticated(retreiver, req, cnx, login, authinfo)
+ for retriever_ in self.authinforetrievers:
+ retriever_.authenticated(retriever, req, cnx, login, authinfo)
return cnx, login, authinfo
+
# false if no authentication info found, eg this is not an
# authentication failure
if 'login' in locals():
--- a/web/views/autoform.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/autoform.py Fri Mar 11 09:46:45 2011 +0100
@@ -267,9 +267,9 @@
self._cw.data[countkey] += 1
except KeyError:
self._cw.data[countkey] = 1
- self.w(self.form.render(
- divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
- counter=self._cw.data[countkey] , **kwargs))
+ self.form.render(w=self.w, divid=divid, title=title, removejs=removejs,
+ i18nctx=i18nctx, counter=self._cw.data[countkey] ,
+ **kwargs)
def form_title(self, entity, i18nctx):
return self._cw.pgettext(i18nctx, entity.__regid__)
@@ -550,6 +550,7 @@
pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
for pendingid in pending_inserts:
eidfrom, rtype, eidto = pendingid.split(':')
+ pendingid = 'id' + pendingid
if typed_eid(eidfrom) == entity.eid: # subject
label = display_name(form._cw, rtype, 'subject',
entity.__regid__)
@@ -784,7 +785,8 @@
def editable_attributes(self, strict=False):
"""return a list of (relation schema, role) to edit for the entity"""
if self.display_fields is not None:
- return self.display_fields
+ schema = self._cw.vreg.schema
+ return [(schema[rtype], role) for rtype, role in self.display_fields]
if self.edited_entity.has_eid() and not self.edited_entity.cw_has_perm('update'):
return []
# XXX we should simply put eid in the generated section, no?
@@ -825,41 +827,42 @@
'inlined form but there is multiple target types, '
'dunno what to do', rschema)
continue
- ttype = ttypes[0].type
- if self.should_inline_relation_form(rschema, ttype, role):
- formviews = list(self.inline_edition_form_view(rschema, ttype, role))
- card = rschema.role_rdef(entity.e_schema, ttype, role).role_cardinality(role)
- # there is no related entity and we need at least one: we need to
- # display one explicit inline-creation view
- if self.should_display_inline_creation_form(rschema, formviews, card):
- formviews += self.inline_creation_form_view(rschema, ttype, role)
- # we can create more than one related entity, we thus display a link
- # to add new related entities
- if self.should_display_add_new_relation_link(rschema, formviews, card):
+ tschema = ttypes[0]
+ ttype = tschema.type
+ formviews = list(self.inline_edition_form_view(rschema, ttype, role))
+ card = rschema.role_rdef(entity.e_schema, ttype, role).role_cardinality(role)
+ # there is no related entity and we need at least one: we need to
+ # display one explicit inline-creation view
+ if self.should_display_inline_creation_form(rschema, formviews, card):
+ formviews += self.inline_creation_form_view(rschema, ttype, role)
+ # we can create more than one related entity, we thus display a link
+ # to add new related entities
+ if self.should_display_add_new_relation_link(rschema, formviews, card):
+ rdef = entity.e_schema.rdef(rschema, role, ttype)
+ if entity.has_eid():
+ if role == 'subject':
+ rdefkwargs = {'fromeid': entity.eid}
+ else:
+ rdefkwargs = {'toeid': entity.eid}
+ else:
+ rdefkwargs = {}
+ if (tschema.has_perm(self._cw, 'add')
+ and rdef.has_perm(self._cw, 'add', **rdefkwargs)):
addnewlink = self._cw.vreg['views'].select(
'inline-addnew-link', self._cw,
etype=ttype, rtype=rschema, role=role, card=card,
peid=self.edited_entity.eid,
petype=self.edited_entity.e_schema, pform=self)
formviews.append(addnewlink)
- allformviews += formviews
+ allformviews += formviews
return allformviews
- def should_inline_relation_form(self, rschema, targettype, role):
- """return true if the given relation with entity has role and a
- targettype target should be inlined
-
- At this point we now relation has inlined_attributes tag (eg is returned
- by `inlined_relations()`. Overrides this for more finer control.
- """
- return True
-
def should_display_inline_creation_form(self, rschema, existant, card):
"""return true if a creation form should be inlined
by default true if there is no related entity and we need at least one
"""
- return not existant and card in '1+' or self._cw.form.has_key('force_%s_display' % rschema)
+ return not existant and card in '1+'
def should_display_add_new_relation_link(self, rschema, existant, card):
"""return true if we should add a link to add a new creation form
@@ -868,7 +871,7 @@
by default true if there is no related entity or if the relation has
multiple cardinality
"""
- return not existant or card in '+*' # XXX add target type permisssions
+ return not existant or card in '+*'
def should_hide_add_new_relation_link(self, rschema, card):
"""return true if once an inlined creation form is added, the 'add new'
@@ -911,13 +914,12 @@
_AFS.tag_attribute(('*', 'eid'), 'main', 'attributes')
_AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes')
_AFS.tag_attribute(('*', 'description'), 'main', 'attributes')
-_AFS.tag_attribute(('*', 'creation_date'), 'main', 'metadata')
-_AFS.tag_attribute(('*', 'modification_date'), 'main', 'metadata')
-_AFS.tag_attribute(('*', 'cwuri'), 'main', 'metadata')
_AFS.tag_attribute(('*', 'has_text'), 'main', 'hidden')
_AFS.tag_subject_of(('*', 'in_state', '*'), 'main', 'hidden')
-_AFS.tag_subject_of(('*', 'owned_by', '*'), 'main', 'metadata')
-_AFS.tag_subject_of(('*', 'created_by', '*'), 'main', 'metadata')
+for rtype in ('creation_date', 'modification_date', 'cwuri',
+ 'owned_by', 'created_by', 'cw_source'):
+ _AFS.tag_subject_of(('*', rtype, '*'), 'main', 'metadata')
+
_AFS.tag_subject_of(('*', 'require_permission', '*'), 'main', 'hidden')
_AFS.tag_subject_of(('*', 'by_transition', '*'), 'main', 'attributes')
_AFS.tag_subject_of(('*', 'by_transition', '*'), 'muledit', 'attributes')
--- a/web/views/basecomponents.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/basecomponents.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,20 +20,23 @@
* the rql input form
* the logged user link
"""
+from __future__ import with_statement
__docformat__ = "restructuredtext en"
_ = unicode
from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_renamed
from rql import parse
from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params,
+ match_context, configuration_values,
anonymous_user, authenticated_user)
from cubicweb.schema import display_name
+from cubicweb.utils import wrap_on_write
from cubicweb.uilib import toggle_action
-from cubicweb.web import component
-from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
- BoxLink)
+from cubicweb.web import component, uicfg
+from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu
VISIBLE_PROP_DEF = {
_('visible'): dict(type='Boolean', default=True,
@@ -68,69 +71,107 @@
self.w(u'</form></div>')
-class ApplLogo(component.Component):
- """build the instance logo, usually displayed in the header"""
- __regid__ = 'logo'
- cw_property_defs = VISIBLE_PROP_DEF
- # don't want user to hide this component using an cwproperty
- site_wide = True
- def call(self):
- self.w(u'<a href="%s"><img id="logo" src="%s" alt="logo"/></a>'
- % (self._cw.base_url(), self._cw.uiprops['LOGO']))
-
-
-class ApplHelp(component.Component):
- """build the help button, usually displayed in the header"""
- __regid__ = 'help'
- cw_property_defs = VISIBLE_PROP_DEF
- def call(self):
- self.w(u'<a href="%s" class="help" title="%s"> </a>'
- % (self._cw.build_url(_restpath='doc/main'),
- self._cw._(u'help'),))
-
-
-class _UserLink(component.Component):
+class HeaderComponent(component.CtxComponent): # XXX rename properly along with related context
"""if the user is the anonymous user, build a link to login else display a menu
with user'action (preference, logout, etc...)
"""
- cw_property_defs = VISIBLE_PROP_DEF
+ __abstract__ = True
+ cw_property_defs = component.override_ctx(
+ component.CtxComponent,
+ vocabulary=['header-left', 'header-right'])
# don't want user to hide this component using an cwproperty
site_wide = True
- __regid__ = 'loggeduserlink'
+ context = _('header-left')
+
+
+class ApplLogo(HeaderComponent):
+ """build the instance logo, usually displayed in the header"""
+ __regid__ = 'logo'
+ order = -1
+
+ def render(self, w):
+ w(u'<a href="%s"><img id="logo" src="%s" alt="logo"/></a>'
+ % (self._cw.base_url(), self._cw.uiprops['LOGO']))
+
+
+class ApplicationName(HeaderComponent):
+ """display the instance name"""
+ __regid__ = 'appliname'
+
+ def render(self, w):
+ title = self._cw.property_value('ui.site-title')
+ if title:
+ w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
+ self._cw.base_url(), xml_escape(title)))
-class AnonUserLink(_UserLink):
- __select__ = _UserLink.__select__ & anonymous_user()
+class CookieLoginComponent(HeaderComponent):
+ __regid__ = 'anonuserlink'
+ __select__ = (HeaderComponent.__select__ & anonymous_user()
+ & configuration_values('auth-mode', 'cookie'))
+ context = 'header-right'
+ loginboxid = 'popupLoginBox'
+ _html = u"""[<a class="logout" title="%s" href="javascript:
+cw.htmlhelpers.popupLoginBox('%s', '__login');">%s</a>]"""
+
+ def render(self, w):
+ # XXX bw compat, though should warn about subclasses redefining call
+ self.w = w
+ self.call()
+
def call(self):
- if self._cw.vreg.config['auth-mode'] == 'cookie':
- self.w(self._cw._('anonymous'))
- self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
- % (self._cw._('i18n_login_popup')))
- else:
- self.w(self._cw._('anonymous'))
- self.w(u' [<a class="logout" href="%s">%s</a>]'
- % (self._cw.build_url('login'), self._cw._('login')))
+ self.w(self._html % (self._cw._('login / password'),
+ self.loginboxid, self._cw._('i18n_login_popup')))
+ self._cw.view('logform', rset=self.cw_rset, id=self.loginboxid,
+ klass='%s hidden' % self.loginboxid, title=False,
+ showmessage=False, w=self.w)
-class UserLink(_UserLink):
- __select__ = _UserLink.__select__ & authenticated_user()
+class HTTPLoginComponent(CookieLoginComponent):
+ __select__ = (HeaderComponent.__select__ & anonymous_user()
+ & configuration_values('auth-mode', 'http'))
+
+ def render(self, w):
+ # this redirects to the 'login' controller which in turn
+ # will raise a 401/Unauthorized
+ req = self._cw
+ w(u'[<a class="logout" title="%s" href="%s">%s</a>]'
+ % (req._('login / password'), req.build_url('login'), req._('login')))
+
- def call(self):
+_UserLink = class_renamed('_UserLink', HeaderComponent)
+AnonUserLink = class_renamed('AnonUserLink', CookieLoginComponent)
+AnonUserLink.__abstract__ = True
+AnonUserLink.__select__ &= yes(1)
+
+
+class AnonUserStatusLink(HeaderComponent):
+ __regid__ = 'userstatus'
+ __select__ = HeaderComponent.__select__ & anonymous_user()
+ context = _('header-right')
+ order = HeaderComponent.order - 10
+
+ def render(self, w):
+ w(u'<span class="caption">%s</span>' % self._cw._('anonymous'))
+
+
+class AuthenticatedUserStatus(AnonUserStatusLink):
+ __select__ = HeaderComponent.__select__ & authenticated_user()
+
+ def render(self, w):
# display useractions and siteactions
actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
box = MenuWidget('', 'userActionsBox', _class='', islist=False)
menu = PopupBoxMenu(self._cw.user.login, isitem=False)
box.append(menu)
for action in actions.get('useractions', ()):
- menu.append(BoxLink(action.url(), self._cw._(action.title),
- action.html_class()))
+ menu.append(self.action_link(action))
if actions.get('useractions') and actions.get('siteactions'):
- menu.append(BoxSeparator())
+ menu.append(self.separator())
for action in actions.get('siteactions', ()):
- menu.append(BoxLink(action.url(), self._cw._(action.title),
- action.html_class()))
- box.render(w=self.w)
+ menu.append(self.action_link(action))
+ box.render(w=w)
class ApplicationMessage(component.Component):
@@ -148,37 +189,10 @@
self.w(u'<div id="appMsg" onclick="%s" class="%s">\n' %
(toggle_action('appMsg'), (msgs and ' ' or 'hidden')))
for msg in msgs:
- self.w(u'<div class="message" id="%s">%s</div>' % (
- self.div_id(), msg))
+ self.w(u'<div class="message" id="%s">%s</div>' % (self.domid, msg))
self.w(u'</div>')
-class ApplicationName(component.Component):
- """display the instance name"""
- __regid__ = 'appliname'
- cw_property_defs = VISIBLE_PROP_DEF
- # don't want user to hide this component using an cwproperty
- site_wide = True
-
- def call(self):
- title = self._cw.property_value('ui.site-title')
- if title:
- self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
- self._cw.base_url(), xml_escape(title)))
-
-
-class SeeAlsoVComponent(component.RelatedObjectsVComponent):
- """display any entity's see also"""
- __regid__ = 'seealso'
- context = 'navcontentbottom'
- rtype = 'see_also'
- role = 'subject'
- order = 40
- # register msg not generated since no entity use see_also in cubicweb itself
- title = _('contentnavigation_seealso')
- help = _('contentnavigation_seealso_description')
-
-
class EtypeRestrictionComponent(component.Component):
"""displays the list of entity types contained in the resultset
to be able to filter accordingly.
@@ -230,17 +244,29 @@
self.w(u' | '.join(html))
self.w(u'</div>')
+# contextual components ########################################################
-class MetaDataComponent(component.EntityVComponent):
+
+class MetaDataComponent(component.EntityCtxComponent):
__regid__ = 'metadata'
context = 'navbottom'
order = 1
- def cell_call(self, row, col, view=None):
- self.wview('metadata', self.cw_rset, row=row, col=col)
+ def render_body(self, w):
+ self.entity.view('metadata', w=w)
-def registration_callback(vreg):
- vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
- if 'see_also' in vreg.schema:
- vreg.register(SeeAlsoVComponent)
+class SectionLayout(component.Layout):
+ __select__ = match_context('navtop', 'navbottom',
+ 'navcontenttop', 'navcontentbottom')
+ cssclass = 'section'
+
+ def render(self, w):
+ if self.init_rendering():
+ view = self.cw_extra_kwargs['view']
+ w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass,
+ view.domid))
+ with wrap_on_write(w, '<h4>') as wow:
+ view.render_title(wow)
+ view.render_body(w)
+ w(u'</div>\n')
--- a/web/views/basecontrollers.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/basecontrollers.py Fri Mar 11 09:46:45 2011 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
@@ -21,12 +20,14 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.common.date import strptime
from cubicweb import (NoSelectableObject, ObjectNotFound, ValidationError,
AuthenticationError, typed_eid)
-from cubicweb.utils import json, json_dumps
+from cubicweb.utils import UStringIO, json, json_dumps
+from cubicweb.uilib import exc_message
from cubicweb.selectors import authenticated_user, anonymous_user, match_form_params
from cubicweb.mail import format_mail
from cubicweb.web import Redirect, RemoteCallFailed, DirectResponse
@@ -120,7 +121,7 @@
self.validate_cache(view)
template = self.appli.main_template_id(self._cw)
return self._cw.vreg['views'].main_template(self._cw, template,
- rset=rset, view=view)
+ rset=rset, view=view)
def _select_view_and_rset(self, rset):
req = self._cw
@@ -130,16 +131,6 @@
rset = self.process_rql()
else:
rset = None
- if rset and rset.rowcount == 1 and '__method' in req.form:
- entity = rset.get_entity(0, 0)
- try:
- method = getattr(entity, req.form.pop('__method'))
- method()
- except Redirect: # propagate redirect that might occur in method()
- raise
- except Exception, ex:
- self.exception('while handling __method')
- req.set_message(req._("error while handling __method: %s") % req._(ex))
vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.vreg.schema)
try:
view = self._cw.vreg['views'].select(vid, req, rset=rset)
@@ -262,6 +253,7 @@
# we receive unicode keys which is not supported by the **syntax
return dict((str(key), value) for key, value in extraargs.iteritems())
+
class JSonController(Controller):
__regid__ = 'json'
@@ -291,15 +283,15 @@
except ValueError, exc:
self.exception('error while decoding json arguments for js_%s: %s',
fname, args, exc)
- raise RemoteCallFailed(repr(exc))
+ raise RemoteCallFailed(exc_message(exc, self._cw.encoding))
try:
result = func(*args)
except (RemoteCallFailed, DirectResponse):
raise
- except Exception, ex:
+ except Exception, exc:
self.exception('an exception occurred while calling js_%s(%s): %s',
- fname, args, ex)
- raise RemoteCallFailed(repr(ex))
+ fname, args, exc)
+ raise RemoteCallFailed(exc_message(exc, self._cw.encoding))
if result is None:
return ''
# get unicode on @htmlize methods, encoded string on @jsonize methods
@@ -345,9 +337,14 @@
return None
def _call_view(self, view, paginate=False, **kwargs):
- # set stream first, in case we need to call pagination
- stream = view.set_stream()
divid = self._cw.form.get('divid')
+ # we need to call pagination before with the stream set
+ try:
+ stream = view.set_stream()
+ except AttributeError:
+ stream = UStringIO()
+ kwargs['w'] = stream.write
+ assert not paginate
if divid == 'pageContent':
# ensure divid isn't reused by the view (e.g. table view)
del self._cw.form['divid']
@@ -422,13 +419,14 @@
**optional_kwargs(extraargs))
#except NoSelectableObject:
# raise RemoteCallFailed('unselectable')
- return self._call_view(comp, **extraargs)
+ return self._call_view(comp, **optional_kwargs(extraargs))
@xhtmlize
def js_render(self, registry, oid, eid=None,
selectargs=None, renderargs=None):
if eid is not None:
rset = self._cw.eid_rset(eid)
+ # XXX set row=0
elif self._cw.form.get('rql'):
rset = self._cw.execute(self._cw.form['rql'])
else:
@@ -589,7 +587,9 @@
def publish(self, rset=None):
body = self._cw.form['description']
- self.sendmail(self._cw.config['submit-mail'], _('%s error report') % self._cw.config.appid, body)
+ self.sendmail(self._cw.config['submit-mail'],
+ self._cw._('%s error report') % self._cw.config.appid,
+ body)
url = self._cw.build_url(__message=self._cw._('bug report sent'))
raise Redirect(url)
@@ -601,11 +601,10 @@
def publish(self, rset=None):
txuuid = self._cw.form['txuuid']
errors = self._cw.cnx.undo_transaction(txuuid)
- if errors:
- self.w(self._cw._('some errors occurred:'))
- self.wview('pyvalist', pyvalue=errors)
- else:
+ if not errors:
self.redirect()
+ return self._cw._('some errors occurred:') + self.view('pyvalist',
+ pyvalue=errors)
def redirect(self):
req = self._cw
--- a/web/views/basetemplates.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/basetemplates.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""default templates for CubicWeb web client"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_renamed
@@ -73,7 +74,7 @@
# FIXME Deprecated code ?
msg = self._cw._('you have been logged out')
w(u'<h2>%s</h2>\n' % msg)
- if self._cw.vreg.config['anonymous-user']:
+ if self._cw.vreg.config.anonymous_user()[0]:
indexurl = self._cw.build_url('view', vid='index', __message=msg)
w(u'<p><a href="%s">%s</a><p>' % (
xml_escape(indexurl),
@@ -127,7 +128,7 @@
w(u'<div id="pageContent">\n')
vtitle = self._cw.form.get('vtitle')
if vtitle:
- w(u'<h1 class="vtitle">%s</h1>\n' % xml_escape(vtitle))
+ w(u'<div class="vtitle">%s</div>\n' % xml_escape(vtitle))
# display entity type restriction component
etypefilter = self._cw.vreg['components'].select_or_none(
'etypenavigation', self._cw, rset=self.cw_rset)
@@ -188,9 +189,10 @@
self.w(u'</body>')
def nav_column(self, view, context):
- boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+ boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
self._cw, rset=self.cw_rset, view=view, context=context))
if boxes:
+ getlayout = self._cw.vreg['components'].select
self.w(u'<td id="navColumn%s"><div class="navboxes">\n' % context.capitalize())
for box in boxes:
box.render(w=self.w, view=view)
@@ -257,7 +259,7 @@
w(u'<table width="100%" height="100%" border="0"><tr>\n')
w(u'<td id="navColumnLeft">\n')
self.topleft_header()
- boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+ boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
self._cw, rset=self.cw_rset, view=view, context='left'))
if boxes:
w(u'<div class="navboxes">\n')
@@ -269,17 +271,18 @@
w(u'<div id="pageContent">\n')
vtitle = self._cw.form.get('vtitle')
if vtitle:
- w(u'<h1 class="vtitle">%s</h1>' % xml_escape(vtitle))
+ w(u'<div class="vtitle">%s</div>' % xml_escape(vtitle))
def topleft_header(self):
logo = self._cw.vreg['components'].select_or_none('logo', self._cw,
rset=self.cw_rset)
if logo and logo.cw_propval('visible'):
- self.w(u'<table id="header"><tr>\n')
- self.w(u'<td>')
- logo.render(w=self.w)
- self.w(u'</td>\n')
- self.w(u'</tr></table>\n')
+ w = self.w
+ w(u'<table id="header"><tr>\n')
+ w(u'<td>')
+ logo.render(w=w)
+ w(u'</td>\n')
+ w(u'</tr></table>\n')
# page parts templates ########################################################
@@ -325,6 +328,8 @@
"""default html page header"""
__regid__ = 'header'
main_cell_components = ('appliname', 'breadcrumbs')
+ headers = (('headtext', 'header-left'),
+ ('header-right', 'header-right'))
def call(self, view, **kwargs):
self.main_header(view)
@@ -334,35 +339,17 @@
def main_header(self, view):
"""build the top menu with authentification info and the rql box"""
- self.w(u'<table id="header"><tr>\n')
- self.w(u'<td id="firstcolumn">')
- logo = self._cw.vreg['components'].select_or_none(
- 'logo', self._cw, rset=self.cw_rset)
- if logo and logo.cw_propval('visible'):
- logo.render(w=self.w)
- self.w(u'</td>\n')
- # appliname and breadcrumbs
- self.w(u'<td id="headtext">')
- for cid in self.main_cell_components:
- comp = self._cw.vreg['components'].select_or_none(
- cid, self._cw, rset=self.cw_rset)
- if comp and comp.cw_propval('visible'):
- comp.render(w=self.w)
- self.w(u'</td>')
- # logged user and help
- self.w(u'<td>\n')
- comp = self._cw.vreg['components'].select_or_none(
- 'loggeduserlink', self._cw, rset=self.cw_rset)
- if comp and comp.cw_propval('visible'):
- comp.render(w=self.w)
- self.w(u'</td>')
- # lastcolumn
- self.w(u'<td id="lastcolumn">')
- self.w(u'</td>\n')
- self.w(u'</tr></table>\n')
- if self._cw.session.anonymous_session:
- self.wview('logform', rset=self.cw_rset, id='popupLoginBox',
- klass='hidden', title=False, showmessage=False)
+ w = self.w
+ w(u'<table id="header"><tr>\n')
+ for colid, context in self.headers:
+ w(u'<td id="%s">' % colid)
+ components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context=context)
+ for comp in components:
+ comp.render(w=w)
+ w(u' ')
+ w(u'</td>')
+ w(u'</tr></table>\n')
def state_header(self):
state = self._cw.search_state
@@ -379,27 +366,24 @@
return self.w(u'<div class="stateMessage">%s</div>' % msg)
-
class HTMLPageFooter(View):
- """default html page footer: include footer actions
- """
+ """default html page footer: include footer actions"""
__regid__ = 'footer'
def call(self, **kwargs):
- req = self._cw
self.w(u'<div id="footer">')
+ self.footer_content()
+ self.w(u'</div>')
+
+ def footer_content(self):
actions = self._cw.vreg['actions'].possible_actions(self._cw,
rset=self.cw_rset)
footeractions = actions.get('footer', ())
for i, action in enumerate(footeractions):
- self.w(u'<a href="%s"' % action.url())
- if getattr(action, 'html_class'):
- self.w(u' class="%s"' % action.html_class())
- self.w(u'>%s</a>' % self._cw._(action.title))
+ self.w(u'<a href="%s">%s</a>' % (action.url(),
+ self._cw._(action.title)))
if i < (len(footeractions) - 1):
self.w(u' | ')
- self.w(u'</div>')
-
class HTMLContentHeader(View):
"""default html page content header:
@@ -410,7 +394,7 @@
def call(self, view, **kwargs):
"""by default, display informal messages in content header"""
- components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+ components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
self._cw, rset=self.cw_rset, view=view, context='navtop')
if components:
self.w(u'<div id="contentheader">')
@@ -426,7 +410,7 @@
__regid__ = 'contentfooter'
def call(self, view, **kwargs):
- components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+ components = self._cw.vreg['ctxcomponents'].poss_visible_objects(
self._cw, rset=self.cw_rset, view=view, context='navbottom')
if components:
self.w(u'<div id="contentfooter">')
@@ -439,12 +423,16 @@
__regid__ = 'logform'
domid = 'loginForm'
needs_css = ('cubicweb.login.css',)
+ onclick = "javascript: cw.htmlhelpers.popupLoginBox('%s', '%s');"
# XXX have to recall fields name since python is mangling __login/__password
__login = ff.StringField('__login', widget=fw.TextInput({'class': 'data'}))
__password = ff.StringField('__password', label=_('password'),
widget=fw.PasswordSingleInput({'class': 'data'}))
form_buttons = [fw.SubmitButton(label=_('log in'),
- attrs={'class': 'loginButton'})]
+ attrs={'class': 'loginButton'}),
+ fw.ResetButton(label=_('cancel'),
+ attrs={'class': 'loginButton',
+ 'onclick': onclick % ('popupLoginBox', '__login')}),]
def form_action(self):
if self.action is None:
@@ -453,32 +441,35 @@
class LogFormView(View):
+ # XXX an awfull lot of hardcoded assumptions there
+ # makes it unobvious to reuse/specialize
__regid__ = 'logform'
__select__ = match_kwargs('id', 'klass')
title = 'log in'
def call(self, id, klass, title=True, showmessage=True):
- self.w(u'<div id="%s" class="%s">' % (id, klass))
+ w = self.w
+ w(u'<div id="%s" class="%s">' % (id, klass))
if title:
stitle = self._cw.property_value('ui.site-title')
if stitle:
stitle = xml_escape(stitle)
else:
stitle = u' '
- self.w(u'<div id="loginTitle">%s</div>' % stitle)
- self.w(u'<div id="loginContent">\n')
+ w(u'<div class="loginTitle">%s</div>' % stitle)
+ w(u'<div class="loginContent">\n')
if showmessage and self._cw.message:
- self.w(u'<div class="loginMessage">%s</div>\n' % self._cw.message)
+ w(u'<div class="loginMessage">%s</div>\n' % self._cw.message)
config = self._cw.vreg.config
if config['auth-mode'] != 'http':
self.login_form(id) # Cookie authentication
- self.w(u'</div>')
+ w(u'</div>')
if self._cw.https and config.anonymous_user()[0]:
path = xml_escape(config['base-url'] + self._cw.relative_path())
- self.w(u'<div class="loginMessage"><a href="%s">%s</a></div>\n'
- % (path, self._cw._('No account? Try public access at %s') % path))
- self.w(u'</div>\n')
+ w(u'<div class="loginMessage"><a href="%s">%s</a></div>\n'
+ % (path, self._cw._('No account? Try public access at %s') % path))
+ w(u'</div>\n')
def login_form(self, id):
cw = self._cw
@@ -488,7 +479,7 @@
else:
label = cw.pgettext('CWUser', 'login')
form.field_by_name('__login').label = label
- self.w(form.render(table_class='', display_progress_div=False))
+ form.render(w=self.w, table_class='', display_progress_div=False)
cw.html_headers.add_onload('jQuery("#__login:visible").focus()')
LogFormTemplate = class_renamed('LogFormTemplate', LogFormView)
--- a/web/views/baseviews.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/baseviews.py Fri Mar 11 09:46:45 2011 +0100
@@ -33,11 +33,12 @@
from logilab.mtconverter import TransformError, xml_escape, xml_escape
-from cubicweb import NoSelectableObject
-from cubicweb.selectors import yes, empty_rset, one_etype_rset
+from cubicweb import NoSelectableObject, tags
+from cubicweb.selectors import yes, empty_rset, one_etype_rset, match_kwargs
from cubicweb.schema import display_name
from cubicweb.view import EntityView, AnyRsetView, View
from cubicweb.uilib import cut, printable_value
+from cubicweb.web.views import calendar
class NullView(AnyRsetView):
@@ -187,7 +188,7 @@
def cell_call(self, row, col):
_ = self._cw._
entity = self.cw_rset.get_entity(row, col)
- self.w(u'<div class="metadata">')
+ self.w(u'<div>')
if self.show_eid:
self.w(u'%s #%s - ' % (entity.dc_type(), entity.eid))
if entity.modification_date != entity.creation_date:
@@ -358,7 +359,7 @@
def call(self, subvid=None, **kwargs):
if subvid is None and 'vid' in kwargs:
- warn("should give a 'subvid' argument instead of 'vid'",
+ warn("[3.9] should give a 'subvid' argument instead of 'vid'",
DeprecationWarning, stacklevel=2)
else:
kwargs['vid'] = subvid
@@ -436,3 +437,109 @@
RssView = class_moved(xmlrss.RSSView)
RssItemView = class_moved(xmlrss.RSSItemView)
+
+class GroupByView(EntityView):
+ """grouped view of a result set. The `group_key` method return the group
+ key of an entities (a string or tuple of string).
+
+ For each group, display a link to entities of this group by generating url
+ like <basepath>/<key> or <basepath>/<key item 1>/<key item 2>.
+ """
+ __abstrack__ = True
+ __select__ = EntityView.__select__ & match_kwargs('basepath')
+ entity_attribute = None
+ reversed = False
+
+ def index_url(self, basepath, key, **kwargs):
+ if isinstance(key, (list, tuple)):
+ key = '/'.join(key)
+ return self._cw.build_url('%s/%s' % (basepath, key),
+ **kwargs)
+
+ def index_link(self, basepath, key, items):
+ url = self.index_url(basepath, key)
+ if isinstance(key, (list, tuple)):
+ key = ' '.join(key)
+ return tags.a(key, href=url)
+
+ def group_key(self, entity, **kwargs):
+ value = getattr(entity, self.entity_attribute)
+ if callable(value):
+ value = value()
+ return value
+
+ def call(self, basepath, maxentries=None, **kwargs):
+ index = {}
+ for entity in self.cw_rset.entities():
+ index.setdefault(self.group_key(entity, **kwargs), []).append(entity)
+ displayed = sorted(index)
+ if self.reversed:
+ displayed = reversed(displayed)
+ if maxentries is None:
+ needmore = False
+ else:
+ needmore = len(index) > maxentries
+ displayed = tuple(displayed)[:maxentries]
+ w = self.w
+ w(u'<ul class="boxListing">')
+ for key in displayed:
+ w(u'<li>%s</li>\n' %
+ self.index_link(basepath, key, index[key]))
+ if needmore:
+ url = self._cw.build_url('view', vid=self.__regid__,
+ rql=self.cw_rset.printable_rql())
+ w( u'<li>%s</li>\n' % tags.a(u'[%s]' % self._cw._('see more'),
+ href=url))
+ w(u'</ul>\n')
+
+
+class ArchiveView(GroupByView):
+ """archive view of a result set. Links to months are built using a basepath
+ parameters, eg using url like <basepath>/<year>/<month>
+ """
+ __regid__ = 'cw.archive.by_date'
+ entity_attribute = 'creation_date'
+ reversed = True
+
+ def group_key(self, entity, **kwargs):
+ value = super(ArchiveView, self).group_key(entity, **kwargs)
+ return '%04d' % value.year, '%02d' % value.month
+
+ def index_link(self, basepath, key, items):
+ """represent a single month entry"""
+ year, month = key
+ label = u'%s %s [%s]' % (self._cw._(calendar.MONTHNAMES[int(month)-1]),
+ year, len(items))
+ etypes = set(entity.__regid__ for entity in items)
+ vtitle = '%s %s' % (', '.join(display_name(self._cw, etype, 'plural')
+ for etype in etypes),
+ label)
+ title = self._cw._('archive for %(month)s/%(year)s') % {
+ 'month': month, 'year': year}
+ url = self.index_url(basepath, key, vtitle=vtitle)
+ return tags.a(label, href=url, title=title)
+
+
+class AuthorView(GroupByView):
+ """author view of a result set. Links to month are built using a basepath
+ parameters, eg using url like <basepath>/<author>
+ """
+ __regid__ = 'cw.archive.by_author'
+ entity_attribute = 'creator'
+
+ def group_key(self, entity, **kwargs):
+ value = super(AuthorView, self).group_key(entity, **kwargs)
+ if value:
+ return value.login
+ return value
+
+ def index_link(self, basepath, key, items):
+ label = u'%s [%s]' % (key, len(items))
+ etypes = set(entity.__regid__ for entity in items)
+ vtitle = self._cw._('%(etype)s by %(author)s') % {
+ 'etype': ', '.join(display_name(self._cw, etype, 'plural')
+ for etype in etypes),
+ 'author': label}
+ url = self.index_url(basepath, key, vtitle=vtitle)
+ title = self._cw._('archive for %(author)s') % {'author': key}
+ return tags.a(label, href=url, title=title)
--- a/web/views/bookmark.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/bookmark.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,17 +15,17 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Primary view for bookmarks + user's bookmarks box
+"""Primary view for bookmarks + user's bookmarks box"""
-"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized
from cubicweb.selectors import is_instance, one_line_rset
-from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
-from cubicweb.web import action, box, uicfg, formwidgets as fw
+from cubicweb.web import (action, component, uicfg, htmlwidgets,
+ formwidgets as fw)
from cubicweb.web.views import primary
_abaa = uicfg.actionbox_appearsin_addmenu
@@ -70,58 +70,55 @@
self.w(u'</div>')
-class BookmarksBox(box.UserRQLBoxTemplate):
+class BookmarksBox(component.CtxComponent):
"""display a box containing all user's bookmarks"""
__regid__ = 'bookmarks_box'
+
+ title = _('bookmarks')
order = 40
- title = _('bookmarks')
rql = ('Any B,T,P ORDERBY lower(T) '
'WHERE B is Bookmark,B title T, B path P, B bookmarked_by U, '
'U eid %(x)s')
- etype = 'Bookmark'
- rtype = 'bookmarked_by'
+ def init_rendering(self):
+ ueid = self._cw.user.eid
+ self.bookmarks_rset = self._cw.execute(self.rql, {'x': ueid})
+ rschema = self._cw.vreg.schema.rschema('bookmarked_by')
+ eschema = self._cw.vreg.schema.eschema('Bookmark')
+ self.can_delete = rschema.has_perm(self._cw, 'delete', toeid=ueid)
+ self.can_edit = (eschema.has_perm(self._cw, 'add') and
+ rschema.has_perm(self._cw, 'add', toeid=ueid))
+ if not self.bookmarks_rset and not self.can_edit:
+ raise component.EmptyComponent()
+ self.items = []
- def call(self, **kwargs):
+ def render_body(self, w):
+ ueid = self._cw.user.eid
req = self._cw
- ueid = req.user.eid
- try:
- rset = req.execute(self.rql, {'x': ueid})
- except Unauthorized:
- # can't access to something in the query, forget this box
- return
- box = BoxWidget(req._(self.title), self.__regid__)
- box.listing_class = 'sideBox'
- rschema = self._cw.vreg.schema.rschema(self.rtype)
- eschema = self._cw.vreg.schema.eschema(self.etype)
- candelete = rschema.has_perm(req, 'delete', toeid=ueid)
- if candelete:
+ if self.can_delete:
req.add_js('cubicweb.ajax.js')
- else:
- dlink = None
- for bookmark in rset.entities():
- label = '<a href="%s">%s</a>' % (xml_escape(bookmark.action_url()),
- xml_escape(bookmark.title))
- if candelete:
- dlink = u'[<a href="javascript:removeBookmark(%s)" title="%s">-</a>]' % (
- bookmark.eid, _('delete this bookmark'))
- label = '%s %s' % (dlink, label)
- box.append(RawBoxItem(label))
- if eschema.has_perm(req, 'add') and rschema.has_perm(req, 'add', toeid=ueid):
- boxmenu = BoxMenu(req._('manage bookmarks'))
+ for bookmark in self.bookmarks_rset.entities():
+ label = self.link(bookmark.title, bookmark.action_url())
+ if self.can_delete:
+ dlink = u'[<a class="action" href="javascript:removeBookmark(%s)" title="%s">-</a>]' % (
+ bookmark.eid, req._('delete this bookmark'))
+ label = '<div>%s %s</div>' % (dlink, label)
+ self.append(label)
+ if self.can_edit:
+ menu = htmlwidgets.BoxMenu(req._('manage bookmarks'))
linkto = 'bookmarked_by:%s:subject' % ueid
# use a relative path so that we can move the instance without
# loosing bookmarks
path = req.relative_path()
- # XXX if vtitle specified in params, extract it and use it as default value
- # for bookmark's title
- url = self.create_url(self.etype, __linkto=linkto, path=path)
- boxmenu.append(self.mk_action(req._('bookmark this page'), url,
- category='manage', id='bookmark'))
- if rset:
+ # XXX if vtitle specified in params, extract it and use it as
+ # default value for bookmark's title
+ url = req.vreg['etypes'].etype_class('Bookmark').cw_create_url(
+ req, __linkto=linkto, path=path)
+ menu.append(self.link(req._('bookmark this page'), url))
+ if self.bookmarks_rset:
if req.user.is_in_group('managers'):
bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, U eid %s' % ueid
- erset = rset
+ erset = self.bookmarks_rset
else:
# we can't edit shared bookmarks we don't own
bookmarksrql = 'Bookmark B WHERE B bookmarked_by U, B owned_by U, U eid %(x)s'
@@ -129,11 +126,10 @@
build_descr=False)
bookmarksrql %= {'x': ueid}
if erset:
- url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
- boxmenu.append(self.mk_action(self._cw._('edit bookmarks'), url, category='manage'))
+ url = req.build_url(vid='muledit', rql=bookmarksrql)
+ menu.append(self.link(req._('edit bookmarks'), url))
url = req.user.absolute_url(vid='xaddrelation', rtype='bookmarked_by',
target='subject')
- boxmenu.append(self.mk_action(self._cw._('pick existing bookmarks'), url, category='manage'))
- box.append(boxmenu)
- if not box.is_empty():
- box.render(self.w)
+ menu.append(self.link(req._('pick existing bookmarks'), url))
+ self.append(menu)
+ self.render_items(w)
--- a/web/views/boxes.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/boxes.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,12 +18,14 @@
"""Generic boxes for CubicWeb web client:
* actions box
-* possible views box
+* search box
-additional (disabled by default) boxes
+Additional boxes (disabled by default):
* schema box
+* possible views box
* startup views box
"""
+from __future__ import with_statement
__docformat__ = "restructuredtext en"
_ = unicode
@@ -31,42 +33,43 @@
from warnings import warn
from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import class_deprecated
-from cubicweb.selectors import match_user_groups, non_final_entity
+from cubicweb import Unauthorized
+from cubicweb.selectors import (match_user_groups, match_kwargs,
+ non_final_entity, nonempty_rset,
+ match_context, contextual)
+from cubicweb.utils import wrap_on_write
from cubicweb.view import EntityView
from cubicweb.schema import display_name
-from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web.box import BoxTemplate
+from cubicweb.web import component, box, htmlwidgets
+# XXX bw compat, some cubes import this class from here
+BoxTemplate = box.BoxTemplate
+BoxHtml = htmlwidgets.BoxHtml
-class EditBox(BoxTemplate): # XXX rename to ActionsBox
+class EditBox(component.CtxComponent): # XXX rename to ActionsBox
"""
box with all actions impacting the entity displayed: edit, copy, delete
- change state, add related entities
+ change state, add related entities...
"""
__regid__ = 'edit_box'
- __select__ = BoxTemplate.__select__ & non_final_entity()
+ __select__ = component.CtxComponent.__select__ & non_final_entity()
title = _('actions')
order = 2
+ contextual = True
- def call(self, view=None, **kwargs):
+ def init_rendering(self):
+ super(EditBox, self).init_rendering()
_ = self._cw._
- title = _(self.title)
- if self.cw_rset:
- etypes = self.cw_rset.column_types(0)
- if len(etypes) == 1:
- plural = self.cw_rset.rowcount > 1 and 'plural' or ''
- etypelabel = display_name(self._cw, iter(etypes).next(), plural)
- title = u'%s - %s' % (title, etypelabel.lower())
- box = BoxWidget(title, self.__regid__, _class="greyBoxFrame")
self._menus_in_order = []
self._menus_by_id = {}
# build list of actions
actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
- view=view)
+ **self.cw_extra_kwargs)
other_menu = self._get_menu('moreactions', _('more actions'))
- for category, defaultmenu in (('mainactions', box),
+ for category, defaultmenu in (('mainactions', self),
('moreactions', other_menu),
('addrelated', None)):
for action in actions.get(category, ()):
@@ -81,16 +84,28 @@
menu = defaultmenu
action.fill_menu(self, menu)
# if we've nothing but actions in the other_menu, add them directly into the box
- if box.is_empty() and len(self._menus_by_id) == 1 and not other_menu.is_empty():
- box.items = other_menu.items
- other_menu.items = []
+ if not self.items and len(self._menus_by_id) == 1 and not other_menu.is_empty():
+ self.items = other_menu.items
else: # ensure 'more actions' menu appears last
self._menus_in_order.remove(other_menu)
self._menus_in_order.append(other_menu)
- for submenu in self._menus_in_order:
- self.add_submenu(box, submenu)
- if not box.is_empty():
- box.render(self.w)
+ for submenu in self._menus_in_order:
+ self.add_submenu(self, submenu)
+ if not self.items:
+ raise component.EmptyComponent()
+
+ def render_title(self, w):
+ title = self._cw._(self.title)
+ if self.cw_rset:
+ etypes = self.cw_rset.column_types(0)
+ if len(etypes) == 1:
+ plural = self.cw_rset.rowcount > 1 and 'plural' or ''
+ etypelabel = display_name(self._cw, iter(etypes).next(), plural)
+ title = u'%s - %s' % (title, etypelabel.lower())
+ w(title)
+
+ def render_body(self, w):
+ self.render_items(w)
def _get_menu(self, id, title=None, label_prefix=None):
try:
@@ -98,7 +113,7 @@
except KeyError:
if title is None:
title = self._cw._(id)
- self._menus_by_id[id] = menu = BoxMenu(title)
+ self._menus_by_id[id] = menu = htmlwidgets.BoxMenu(title)
menu.label_prefix = label_prefix
self._menus_in_order.append(menu)
return menu
@@ -108,19 +123,22 @@
if len(submenu.items) == 1 and not appendanyway:
boxlink = submenu.items[0]
if submenu.label_prefix:
- boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
+ # XXX iirk
+ if hasattr(boxlink, 'label'):
+ boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label)
+ else:
+ boxlink = u'%s %s' % (submenu.label_prefix, boxlink)
box.append(boxlink)
elif submenu.items:
box.append(submenu)
elif appendanyway:
- box.append(RawBoxItem(xml_escape(submenu.label)))
+ box.append(xml_escape(submenu.label))
-class SearchBox(BoxTemplate):
+class SearchBox(component.CtxComponent):
"""display a box with a simple search form"""
__regid__ = 'search_box'
- visible = True # enabled by default
title = _('search')
order = 0
formdef = u"""<form action="%s">
@@ -130,77 +148,132 @@
<input type="hidden" name="subvid" value="tsearch" />
</td><td>
<input tabindex="%s" type="submit" id="rqlboxsubmit" class="rqlsubmit" value="" />
-</td></tr></table>
-</form>"""
+ </td></tr></table>
+ </form>"""
- def call(self, view=None, **kwargs):
- req = self._cw
- if req.form.pop('__fromsearchbox', None):
- rql = req.form.get('rql', '')
+ def render_title(self, w):
+ w(u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>"""
+ % self._cw._(self.title))
+
+ def render_body(self, w):
+ if self._cw.form.pop('__fromsearchbox', None):
+ rql = self._cw.form.get('rql', '')
else:
rql = ''
- form = self.formdef % (req.build_url('view'), req.next_tabindex(),
- xml_escape(rql), req.next_tabindex())
- title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
- box = BoxWidget(title, self.__regid__, _class="searchBoxFrame", islist=False, escape=False)
- box.append(BoxHtml(form))
- box.render(self.w)
+ w(self.formdef % (self._cw.build_url('view'), self._cw.next_tabindex(),
+ xml_escape(rql), self._cw.next_tabindex()))
# boxes disabled by default ###################################################
-class PossibleViewsBox(BoxTemplate):
+class PossibleViewsBox(component.CtxComponent):
"""display a box containing links to all possible views"""
__regid__ = 'possible_views_box'
- __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
- visible = False
+ contextual = True
title = _('possible views')
order = 10
+ visible = False # disabled by default
- def call(self, **kwargs):
- box = BoxWidget(self._cw._(self.title), self.__regid__)
- views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
- rset=self.cw_rset)
- if v.category != 'startupview']
- for category, views in self.sort_actions(views):
- menu = BoxMenu(category)
+ def init_rendering(self):
+ self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
+ rset=self.cw_rset)
+ if v.category != 'startupview']
+ if not self.views:
+ raise component.EmptyComponent()
+ self.items = []
+
+ def render_body(self, w):
+ for category, views in box.sort_by_category(self.views):
+ menu = htmlwidgets.BoxMenu(category)
for view in views:
- menu.append(self.box_action(view))
- box.append(menu)
- if not box.is_empty():
- box.render(self.w)
+ menu.append(self.action_link(view))
+ self.append(menu)
+ self.render_items(w)
-class StartupViewsBox(BoxTemplate):
+class StartupViewsBox(PossibleViewsBox):
"""display a box containing links to all startup views"""
__regid__ = 'startup_views_box'
- visible = False # disabled by default
+
+ contextual = False
title = _('startup views')
order = 70
+ visible = False # disabled by default
- def call(self, **kwargs):
- box = BoxWidget(self._cw._(self.title), self.__regid__)
- for view in self._cw.vreg['views'].possible_views(self._cw, None):
- if view.category == 'startupview':
- box.append(self.box_action(view))
- if not box.is_empty():
- box.render(self.w)
+ def init_rendering(self):
+ self.views = [v for v in self._cw.vreg['views'].possible_views(self._cw)
+ if v.category == 'startupview']
+ if not self.views:
+ raise component.EmptyComponent()
+ self.items = []
-# helper classes ##############################################################
+class RsetBox(component.CtxComponent):
+ """helper view class to display an rset in a sidebox"""
+ __select__ = nonempty_rset() & match_kwargs('title', 'vid')
+ __regid__ = 'rsetbox'
+ cw_property_defs = {}
+ context = 'incontext'
+
+ @property
+ def domid(self):
+ return super(RsetBox, self).domid + unicode(abs(id(self))) + unicode(abs(id(self.cw_rset)))
+
+ def render_title(self, w):
+ w(self.cw_extra_kwargs['title'])
+
+ def render_body(self, w):
+ if 'dispctrl' in self.cw_extra_kwargs:
+ # XXX do not modify dispctrl!
+ self.cw_extra_kwargs['dispctrl'].setdefault('subvid', 'outofcontext')
+ self.cw_extra_kwargs['dispctrl'].setdefault('use_list_limit', 1)
+ self._cw.view(self.cw_extra_kwargs['vid'], self.cw_rset, w=w,
+ initargs=self.cw_extra_kwargs)
+
+ # helper classes ##############################################################
class SideBoxView(EntityView):
"""helper view class to display some entities in a sidebox"""
+ __metaclass__ = class_deprecated
+ __deprecation_warning__ = '[3.10] SideBoxView is deprecated, use RsetBox instead (%(cls)s)'
+
__regid__ = 'sidebox'
- def call(self, boxclass='sideBox', title=u''):
+ def call(self, title=u'', **kwargs):
"""display a list of entities by calling their <item_vid> view"""
- if title:
- self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
if 'dispctrl' in self.cw_extra_kwargs:
# XXX do not modify dispctrl!
self.cw_extra_kwargs['dispctrl'].setdefault('subvid', 'outofcontext')
- self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
- self.wview('autolimited', self.cw_rset, **self.cw_extra_kwargs)
- self.w(u'</div>\n</div>\n')
+ self.cw_extra_kwargs['dispctrl'].setdefault('use_list_limit', 1)
+ if title:
+ self.cw_extra_kwargs['title'] = title
+ self.cw_extra_kwargs.setdefault('context', 'incontext')
+ box = self._cw.vreg['ctxcomponents'].select(
+ 'rsetbox', self._cw, rset=self.cw_rset, vid='autolimited',
+ **self.cw_extra_kwargs)
+ box.render(self.w)
+
+
+class ContextualBoxLayout(component.Layout):
+ __select__ = match_context('incontext', 'left', 'right') & contextual()
+ # predefined class in cubicweb.css: contextualBox | contextFreeBox
+ cssclass = 'contextualBox'
+
+ def render(self, w):
+ if self.init_rendering():
+ view = self.cw_extra_kwargs['view']
+ w(u'<div class="%s %s" id="%s">' % (self.cssclass, view.cssclass,
+ view.domid))
+ with wrap_on_write(w, u'<div class="boxTitle"><span>',
+ u'</span></div>') as wow:
+ view.render_title(wow)
+ w(u'<div class="boxBody">')
+ view.render_body(w)
+ # boxFooter div is a CSS place holder (for shadow for example)
+ w(u'</div><div class="boxFooter"></div></div>\n')
+
+
+class ContextFreeBoxLayout(ContextualBoxLayout):
+ __select__ = match_context('incontext', 'left', 'right') & ~contextual()
+ cssclass = 'contextFreeBox'
--- a/web/views/calendar.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/calendar.py Fri Mar 11 09:46:45 2011 +0100
@@ -31,6 +31,7 @@
class ICalendarableAdapter(EntityAdapter):
+ __needs_bw_compat__ = True
__regid__ = 'ICalendarable'
__select__ = implements(ICalendarable, warn=False) # XXX for bw compat, should be abstract
--- a/web/views/csvexport.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/csvexport.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,10 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""csv export views
+"""csv export views"""
-"""
__docformat__ = "restructuredtext en"
+_ = unicode
from cubicweb.schema import display_name
from cubicweb.uilib import UnicodeCSVWriter
--- a/web/views/cwproperties.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/cwproperties.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""Specific views for CWProperty
+"""Specific views for CWProperty (eg site/user preferences"""
-"""
__docformat__ = "restructuredtext en"
_ = unicode
@@ -45,7 +44,7 @@
_('ui')
_('boxes')
_('components')
-_('contentnavigation')
+_('ctxcomponents')
_('navigation.combobox-limit')
_('navigation.page-size')
_('navigation.related-limit')
@@ -66,8 +65,8 @@
def make_togglable_link(nodeid, label):
"""builds a HTML link that switches the visibility & remembers it"""
- action = u"javascript: togglePrefVisibility('%s')" % nodeid
- return u'<a href="%s">%s</a>' % (action, label)
+ return u'<a href="javascript: togglePrefVisibility(\'%s\')">%s</a>' % (
+ nodeid, label)
def css_class(someclass):
return someclass and 'class="%s"' % someclass or ''
@@ -111,7 +110,8 @@
return status
def call(self, **kwargs):
- self._cw.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
+ self._cw.add_js(('cubicweb.preferences.js',
+ 'cubicweb.edition.js', 'cubicweb.ajax.js'))
self._cw.add_css('cubicweb.preferences.css')
vreg = self._cw.vreg
values = self.defined_keys
@@ -135,7 +135,7 @@
for group, objects in groupedopts.items():
for oid, keys in objects.items():
- groupedopts[group][oid] = self.form(group + '-' + oid, keys, True)
+ groupedopts[group][oid] = self.form(group + '_' + oid, keys, True)
w = self.w
req = self._cw
@@ -162,10 +162,10 @@
for o, f in objects.iteritems())
for label, oid, form in sorted_objects:
w(u'<div class="component">')
- w(u'''<div class="componentLink"><a href="javascript:noop();"
+ w(u'''<div class="componentLink"><a href="javascript:$.noop();"
onclick="javascript:toggleVisibility('field_%(oid)s_%(group)s')"
class="componentTitle">%(label)s</a>''' % {'label':label, 'oid':oid, 'group':group})
- w(u''' (<div class="openlink"><a href="javascript:noop();"
+ w(u''' (<div class="openlink"><a href="javascript:$.noop();"
onclick="javascript:openFieldset('fieldset_%(group)s')">%(label)s</a></div>)'''
% {'label':_('open all'), 'group':group})
w(u'</div>')
@@ -200,8 +200,8 @@
else:
entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
entity.eid = self._cw.varmaker.next()
- entity['pkey'] = key
- entity['value'] = self._cw.vreg.property_value(key)
+ entity.cw_attr_cache['pkey'] = key
+ entity.cw_attr_cache['value'] = self._cw.vreg.property_value(key)
return entity
def form(self, formid, keys, splitlabel=False):
@@ -219,7 +219,9 @@
self.form_row(form, key, splitlabel)
renderer = self._cw.vreg['formrenderers'].select('cwproperties', self._cw,
display_progress_div=False)
- return form.render(renderer=renderer)
+ data = []
+ form.render(w=data.append, renderer=renderer)
+ return u'\n'.join(data)
def form_row(self, form, key, splitlabel):
entity = self.entity_for_key(key)
@@ -329,7 +331,7 @@
def form_init(self, form):
entity = form.edited_entity
- if not (entity.has_eid() or 'pkey' in entity):
+ if not (entity.has_eid() or 'pkey' in entity.cw_attr_cache):
# no key set yet, just include an empty div which will be filled
# on key selection
return
@@ -353,7 +355,7 @@
if vocab is not None:
if callable(vocab):
# list() just in case its a generator function
- self.choices = list(vocab(form._cw))
+ self.choices = list(vocab())
else:
self.choices = vocab
wdg = Select()
--- a/web/views/cwuser.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/cwuser.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""Specific views for users and groups"""
__docformat__ = "restructuredtext en"
+_ = unicode
import hashlib
@@ -25,7 +26,7 @@
from cubicweb.selectors import one_line_rset, is_instance, match_user_groups
from cubicweb.view import EntityView
-from cubicweb.web import action, uicfg
+from cubicweb.web import action, uicfg, formwidgets
from cubicweb.web.views import tabs
_pvs = uicfg.primaryview_section
@@ -38,6 +39,11 @@
_pvs.tag_object_of(('*', 'in_group', 'CWGroup'), 'relations')
_pvs.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
+_affk = uicfg.autoform_field_kwargs
+
+_affk.tag_subject_of(('CWUser', 'in_group', 'CWGroup'),
+ {'widget': formwidgets.InOutWidget})
+
class UserPreferencesEntityAction(action.Action):
__regid__ = 'prefs'
__select__ = (one_line_rset() & is_instance('CWUser') &
@@ -107,6 +113,7 @@
__select__ = tabs.PrimaryTab.__select__ & is_instance('CWGroup')
def render_entity_attributes(self, entity):
+ _ = self._cw._
rql = 'Any U, FN, LN, CD, LL ORDERBY L WHERE U in_group G, ' \
'U login L, U firstname FN, U surname LN, U creation_date CD, ' \
'U last_login_time LL, G eid %(x)s'
--- a/web/views/debug.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/debug.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,11 +18,13 @@
"""management and error screens"""
__docformat__ = "restructuredtext en"
+_ = unicode
from time import strftime, localtime
from logilab.mtconverter import xml_escape
+from cubicweb import BadConnectionId
from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.view import StartupView
from cubicweb.web.views import actions
@@ -119,10 +121,15 @@
if sessions:
w(u'<ul>')
for session in sessions:
+ try:
+ last_usage_time = session.cnx.check()
+ except BadConnectionId:
+ w(u'<li>%s (INVALID)</li>' % session.sessionid)
+ continue
w(u'<li>%s (%s: %s)<br/>' % (
session.sessionid,
_('last usage'),
- strftime(dtformat, localtime(session.last_usage_time))))
+ strftime(dtformat, localtime(last_usage_time))))
dict_to_html(w, session.data)
w(u'</li>')
w(u'</ul>')
@@ -139,12 +146,14 @@
cache_max_age = 0
def call(self, **kwargs):
- self.w(u'<h1>%s</h1>' % _("Registry's content"))
+ self.w(u'<h1>%s</h1>' % self._cw._("Registry's content"))
keys = sorted(self._cw.vreg)
url = xml_escape(self._cw.url())
self.w(u'<p>%s</p>\n' % ' - '.join('<a href="%s#%s">%s</a>'
% (url, key, key) for key in keys))
for key in keys:
+ if key in ('boxes', 'contentnavigation'): # those are bw compat registries
+ continue
self.w(u'<h2 id="%s">%s</h2>' % (key, key))
if self._cw.vreg[key]:
values = sorted(self._cw.vreg[key].iteritems())
@@ -173,20 +182,20 @@
Connection, Cursor,
CubicWebRequestBase)
try:
- from cubicweb.server.session import Session, ChildSession, InternalSession
- lookupclasses += (InternalSession, ChildSession, Session)
+ from cubicweb.server.session import Session, InternalSession
+ lookupclasses += (InternalSession, Session)
except ImportError:
pass # no server part installed
self.w(u'<h1>%s</h1>' % _('Garbage collection information'))
counters, ocounters, garbage = gc_info(lookupclasses,
viewreferrersclasses=())
- self.w(u'<h3>%s</h3>' % _('Looked up classes'))
+ self.w(u'<h3>%s</h3>' % self._cw._('Looked up classes'))
values = sorted(counters.iteritems(), key=lambda x: x[1], reverse=True)
self.wview('pyvaltable', pyvalue=values)
- self.w(u'<h3>%s</h3>' % _('Most referenced classes'))
+ self.w(u'<h3>%s</h3>' % self._cw._('Most referenced classes'))
values = sorted(ocounters.iteritems(), key=lambda x: x[1], reverse=True)
self.wview('pyvaltable', pyvalue=values[:self._cw.form.get('nb', 20)])
if garbage:
- self.w(u'<h3>%s</h3>' % _('Unreachable objects'))
+ self.w(u'<h3>%s</h3>' % self._cw._('Unreachable objects'))
values = sorted(xml_escape(repr(o)) for o in garbage)
self.wview('pyvallist', pyvalue=values)
--- a/web/views/editcontroller.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/editcontroller.py Fri Mar 11 09:46:45 2011 +0100
@@ -34,6 +34,7 @@
class IEditControlAdapter(EntityAdapter):
+ __needs_bw_compat__ = True
__regid__ = 'IEditControl'
__select__ = is_instance('Any')
@@ -115,19 +116,14 @@
form = req.form
# so we're able to know the main entity from the repository side
if '__maineid' in form:
- req.set_shared_data('__maineid', form['__maineid'], querydata=True)
+ req.set_shared_data('__maineid', form['__maineid'], txdata=True)
# no specific action, generic edition
self._to_create = req.data['eidmap'] = {}
self._pending_fields = req.data['pendingfields'] = set()
try:
- methodname = req.form.pop('__method', None)
for eid in req.edited_eids():
# __type and eid
formparams = req.extract_entity_params(eid, minparams=2)
- if methodname is not None:
- entity = req.entity_from_eid(eid)
- method = getattr(entity, methodname)
- method(formparams)
eid = self.edit_entity(formparams)
except (RequestError, NothingToEdit), ex:
if '__linkto' in req.form and 'eid' in req.form:
--- a/web/views/editforms.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/editforms.py Fri Mar 11 09:46:45 2011 +0100
@@ -90,7 +90,7 @@
w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
href=entity.absolute_url()))
w(u'</ul>\n')
- w(form.render())
+ form.render(w=self.w)
class EditionFormView(FormViewMixIn, EntityView):
@@ -112,7 +112,7 @@
form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity,
submitmsg=self.submited_message())
self.init_form(form, entity)
- self.w(form.render())
+ form.render(w=self.w)
def init_form(self, form, entity):
"""customize your form before rendering here"""
@@ -169,7 +169,9 @@
def url(self):
"""return the url associated with this view"""
- return self.create_url(self._cw.form.get('etype'))
+ req = self._cw
+ return req.vreg["etypes"].etype_class(req.form['etype']).cw_create_url(
+ req)
def submited_message(self):
"""return the message that will be displayed on successful edition"""
@@ -256,7 +258,7 @@
rset=self.cw_rset,
copy_nav_params=True,
formvid='edition')
- self.w(form.render())
+ form.render(w=self.w)
# click and edit handling ('reledit') ##########################################
--- a/web/views/embedding.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/embedding.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,6 +20,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
import re
from urlparse import urljoin
@@ -40,6 +41,7 @@
class IEmbedableAdapter(EntityAdapter):
"""interface for embedable entities"""
+ __needs_bw_compat__ = True
__regid__ = 'IEmbedable'
__select__ = implements(IEmbedable, warn=False) # XXX for bw compat, should be abstract
--- a/web/views/facets.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/facets.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""the facets box and some basic facets"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import xml_escape
@@ -25,10 +26,7 @@
from cubicweb.selectors import (non_final_entity, multi_lines_rset,
match_context_prop, yes, relation_possible)
from cubicweb.utils import json_dumps
-from cubicweb.web.box import BoxTemplate
-from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
- prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
- _prepare_vocabulary_rqlst)
+from cubicweb.web import component, facet
@objectify_selector
def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
@@ -38,14 +36,13 @@
return 0
-class FilterBox(BoxTemplate):
+class FilterBox(component.CtxComponent):
"""filter results of a query"""
- __regid__ = 'filter_box'
- __select__ = (((non_final_entity() & multi_lines_rset())
- | contextview_selector()
- ) & match_context_prop())
- context = 'left'
- title = _('boxes_filter_box')
+ __regid__ = 'facet.filters'
+ __select__ = ((non_final_entity() & multi_lines_rset())
+ | contextview_selector())
+ context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right'
+ title = _('facet.filters')
visible = True # functionality provided by the search box by default
order = 1
roundcorners = True
@@ -61,7 +58,8 @@
"""
return {}
- def _get_context(self, view):
+ def _get_context(self):
+ view = self.cw_extra_kwargs.get('view')
context = getattr(view, 'filter_box_context_info', lambda: None)()
if context:
rset, vid, divid, paginate = context
@@ -71,14 +69,15 @@
paginate = view and view.paginable
return rset, vid, divid, paginate
- def call(self, view=None):
+ def render(self, w, **kwargs):
req = self._cw
req.add_js( self.needs_js )
req.add_css( self.needs_css)
if self.roundcorners:
req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");')
- rset, vid, divid, paginate = self._get_context(view)
- if rset.rowcount < 2: # XXX done by selectors, though maybe necessary when rset has been hijacked
+ rset, vid, divid, paginate = self._get_context()
+ # XXX done by selectors, though maybe necessary when rset has been hijacked
+ if rset.rowcount < 2:
return
rqlst = rset.syntax_tree()
# union not yet supported
@@ -86,20 +85,18 @@
return ()
rqlst = rqlst.copy()
req.vreg.rqlhelper.annotate(rqlst)
- mainvar, baserql = prepare_facets_rqlst(rqlst, rset.args)
+ mainvar, baserql = facet.prepare_facets_rqlst(rqlst, rset.args)
widgets = []
- for facet in self.get_facets(rset, rqlst.children[0], mainvar):
- if facet.cw_propval('visible'):
- wdg = facet.get_widget()
- if wdg is not None:
- widgets.append(wdg)
+ for facetobj in self.get_facets(rset, rqlst.children[0], mainvar):
+ wdg = facetobj.get_widget()
+ if wdg is not None:
+ widgets.append(wdg)
if not widgets:
return
if vid is None:
vid = req.form.get('vid')
- if self.bk_linkbox_template:
- self.display_bookmark_link(rset)
- w = self.w
+ if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'):
+ w(self.bookmark_link(rset))
w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">' % (
divid, xml_escape(json_dumps([divid, vid, paginate, self.facetargs()]))))
w(u'<fieldset>')
@@ -108,27 +105,27 @@
for param in ('subvid', 'vtitle'):
if param in req.form:
hiddens[param] = req.form[param]
- filter_hiddens(w, **hiddens)
+ facet.filter_hiddens(w, **hiddens)
for wdg in widgets:
- wdg.render(w=self.w)
+ wdg.render(w=w)
w(u'</fieldset>\n</form>\n')
- def display_bookmark_link(self, rset):
- eschema = self._cw.vreg.schema.eschema('Bookmark')
- if eschema.has_perm(self._cw, 'add'):
- bk_path = 'rql=%s' % self._cw.url_quote(rset.printable_rql())
- if self._cw.form.get('vid'):
- bk_path += '&vid=%s' % self._cw.url_quote(self._cw.form['vid'])
- bk_path = 'view?' + bk_path
- bk_title = self._cw._('my custom search')
- linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
- bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
- bk_base_url = self._cw.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
- bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
- xml_escape(bk_base_url),
- xml_escape(bk_add_url),
- self._cw._('bookmark this search'))
- self.w(self.bk_linkbox_template % bk_link)
+ def bookmark_link(self, rset):
+ req = self._cw
+ bk_path = u'rql=%s' % req.url_quote(rset.printable_rql())
+ if req.form.get('vid'):
+ bk_path += u'&vid=%s' % req.url_quote(req.form['vid'])
+ bk_path = u'view?' + bk_path
+ bk_title = req._('my custom search')
+ linkto = u'bookmarked_by:%s:subject' % req.user.eid
+ bkcls = req.vreg['etypes'].etype_class('Bookmark')
+ bk_add_url = bkcls.cw_create_url(req, path=bk_path, title=bk_title,
+ __linkto=linkto)
+ bk_base_url = bkcls.cw_create_url(req, title=bk_title, __linkto=linkto)
+ bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
+ xml_escape(bk_base_url), xml_escape(bk_add_url),
+ req._('bookmark this search'))
+ return self.bk_linkbox_template % bk_link
def get_facets(self, rset, rqlst, mainvar):
return self._cw.vreg['facets'].poss_visible_objects(
@@ -137,23 +134,28 @@
# facets ######################################################################
-class CreatedByFacet(RelationFacet):
+class CWSourceFacet(facet.RelationFacet):
+ __regid__ = 'cw_source-facet'
+ rtype = 'cw_source'
+ target_attr = 'name'
+
+class CreatedByFacet(facet.RelationFacet):
__regid__ = 'created_by-facet'
rtype = 'created_by'
target_attr = 'login'
-class InGroupFacet(RelationFacet):
+class InGroupFacet(facet.RelationFacet):
__regid__ = 'in_group-facet'
rtype = 'in_group'
target_attr = 'name'
-class InStateFacet(RelationFacet):
+class InStateFacet(facet.RelationAttributeFacet):
__regid__ = 'in_state-facet'
rtype = 'in_state'
target_attr = 'name'
# inherit from RelationFacet to benefit from its possible_values implementation
-class ETypeFacet(RelationFacet):
+class ETypeFacet(facet.RelationFacet):
__regid__ = 'etype-facet'
__select__ = yes()
order = 1
@@ -184,8 +186,9 @@
rqlst = self.rqlst
rqlst.save_state()
try:
- _cleanup_rqlst(rqlst, self.filtered_variable)
- etype_var = _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, self.role)
+ facet._cleanup_rqlst(rqlst, self.filtered_variable)
+ etype_var = facet._prepare_vocabulary_rqlst(
+ rqlst, self.filtered_variable, self.rtype, self.role)
attrvar = rqlst.make_variable()
rqlst.add_selected(attrvar)
rqlst.add_relation(etype_var, 'name', attrvar)
@@ -193,7 +196,7 @@
finally:
rqlst.recover()
-class HasTextFacet(AbstractFacet):
+class HasTextFacet(facet.AbstractFacet):
__select__ = relation_possible('has_text', 'subject') & match_context_prop()
__regid__ = 'has_text-facet'
rtype = 'has_text'
@@ -209,7 +212,7 @@
default implentation expects a .vocabulary method on the facet and
return a combobox displaying this vocabulary
"""
- return FacetStringWidget(self)
+ return facet.FacetStringWidget(self)
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
--- a/web/views/formrenderers.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/formrenderers.py Fri Mar 11 09:46:45 2011 +0100
@@ -31,7 +31,9 @@
.. autoclass:: cubicweb.web.views.formrenderers.EntityInlinedFormRenderer
"""
+
__docformat__ = "restructuredtext en"
+_ = unicode
from warnings import warn
@@ -41,7 +43,7 @@
from cubicweb import tags
from cubicweb.appobject import AppObject
from cubicweb.selectors import is_instance, yes
-from cubicweb.utils import json_dumps
+from cubicweb.utils import json_dumps, support_args
from cubicweb.web import eid_param, formwidgets as fwdgs
@@ -53,6 +55,8 @@
name, value, checked, attrs)
def field_label(form, field):
+ if callable(field.label):
+ return field.label(form, field)
# XXX with 3.6 we can now properly rely on 'if field.role is not None' and
# stop having a tuple for label
if isinstance(field.label, tuple): # i.e. needs contextual translation
@@ -102,23 +106,23 @@
# renderer interface ######################################################
- def render(self, form, values):
+ def render(self, w, form, values):
self._set_options(values)
form.add_media()
data = []
- w = data.append
- w(self.open_form(form, values))
+ _w = data.append
+ _w(self.open_form(form, values))
if self.display_progress_div:
- w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
- w(u'<fieldset>')
- self.render_fields(w, form, values)
- self.render_buttons(w, form)
- w(u'</fieldset>')
- w(self.close_form(form, values))
+ _w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
+ _w(u'\n<fieldset>\n')
+ self.render_fields(_w, form, values)
+ self.render_buttons(_w, form)
+ _w(u'\n</fieldset>\n')
+ _w(self.close_form(form, values))
errormsg = self.error_message(form)
if errormsg:
data.insert(0, errormsg)
- return '\n'.join(data)
+ w(''.join(data))
def render_label(self, form, field):
if field.label is None:
@@ -133,7 +137,12 @@
help = []
descr = field.help
if callable(descr):
- descr = descr(form)
+ if support_args(descr, 'form', 'field'):
+ descr = descr(form, field)
+ else:
+ warn("[3.10] field's help callback must now take form and field as argument (%s)"
+ % field, DeprecationWarning)
+ descr = descr(form)
if descr:
help.append('<div class="helper">%s</div>' % self._cw._(descr))
example = field.example_format(self._cw)
@@ -172,29 +181,29 @@
def open_form(self, form, values):
if form.needs_multipart:
- enctype = 'multipart/form-data'
+ enctype = u'multipart/form-data'
else:
- enctype = 'application/x-www-form-urlencoded'
- tag = ('<form action="%s" method="post" enctype="%s"' % (
+ enctype = u'application/x-www-form-urlencoded'
+ tag = (u'<form action="%s" method="post" enctype="%s"' % (
xml_escape(form.form_action() or '#'), enctype))
if form.domid:
- tag += ' id="%s"' % form.domid
+ tag += u' id="%s"' % form.domid
if form.onsubmit:
- tag += ' onsubmit="%s"' % xml_escape(form.onsubmit % dictattr(form))
+ tag += u' onsubmit="%s"' % xml_escape(form.onsubmit % dictattr(form))
if form.cssstyle:
- tag += ' style="%s"' % xml_escape(form.cssstyle)
+ tag += u' style="%s"' % xml_escape(form.cssstyle)
if form.cssclass:
- tag += ' class="%s"' % xml_escape(form.cssclass)
+ tag += u' class="%s"' % xml_escape(form.cssclass)
if form.cwtarget:
- tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
- return tag + '>'
+ tag += u' cubicweb:target="%s"' % xml_escape(form.cwtarget)
+ return tag + u'>'
def close_form(self, form, values):
"""seems dumb but important for consistency w/ close form, and necessary
for form renderers overriding open_form to use something else or more than
and <form>
"""
- return '</form>'
+ return u'</form>'
def render_fields(self, w, form, values):
fields = self._render_hidden_fields(w, form)
@@ -212,6 +221,7 @@
for field in form.fields:
if not field.is_visible():
w(field.render(form, self))
+ w(u'\n')
fields.remove(field)
return fields
@@ -229,28 +239,29 @@
except KeyError:
self.warning('no such fieldset: %s (%s)', fieldset, form)
continue
- w(u'<fieldset class="%s">' % (fieldset or u'default'))
+ w(u'<fieldset class="%s">\n' % (fieldset or u'default'))
if fieldset:
w(u'<legend>%s</legend>' % self._cw._(fieldset))
- w(u'<table class="%s">' % self.table_class)
+ w(u'<table class="%s">\n' % self.table_class)
for field in fields:
- w(u'<tr class="%s_%s_row">' % (field.name, field.role))
+ w(u'<tr class="%s_%s_row">\n' % (field.name, field.role))
if self.display_label and field.label is not None:
- w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- w('<td')
+ w(u'<th class="labelCol">%s</th>\n' % self.render_label(form, field))
+ w(u'<td')
if field.label is None:
- w(' colspan="2"')
+ w(u' colspan="2"')
error = form.field_error(field)
if error:
w(u' class="error"')
- w(u'>')
+ w(u'>\n')
w(field.render(form, self))
+ w(u'\n')
if error:
self.render_error(w, error)
if self.display_help:
w(self.render_help(form, field))
- w(u'</td></tr>')
- w(u'</table></fieldset>')
+ w(u'</td></tr>\n')
+ w(u'</table></fieldset>\n')
if byfieldset:
self.warning('unused fieldsets: %s', ', '.join(byfieldset))
@@ -441,10 +452,8 @@
"""
__regid__ = 'inline'
- def render(self, form, values):
+ def render(self, w, form, values):
form.add_media()
- data = []
- w = data.append
try:
w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
except KeyError:
@@ -458,7 +467,7 @@
values['removemsg'] = self._cw._('remove-inlined-entity-form')
w(u'<div class="iformTitle"><span>%(title)s</span> '
'#<span class="icounter">%(counter)s</span> '
- '[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
+ '[<a href="javascript: %(removejs)s;$.noop();">%(removemsg)s</a>]</div>'
% values)
else:
w(u'<div class="iformTitle"><span>%(title)s</span> '
@@ -470,7 +479,6 @@
values.pop(key, None)
self.render_fields(w, form, values)
w(u'</div></div>')
- return '\n'.join(data)
def render_fields(self, w, form, values):
w(u'<fieldset id="fs-%(divid)s">' % values)
--- a/web/views/forms.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/forms.py Fri Mar 11 09:46:45 2011 +0100
@@ -50,6 +50,7 @@
from logilab.common.deprecation import deprecated
from cubicweb import typed_eid
+from cubicweb.utils import support_args
from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
from cubicweb.web import uicfg, form, formwidgets as fwdgs
from cubicweb.web.formfields import relvoc_unrelated, guess_field
@@ -168,10 +169,25 @@
warn('[3.6] rendervalues argument is deprecated, all named arguments will be given instead',
DeprecationWarning, stacklevel=2)
kwargs = rendervalues
+ w = kwargs.pop('w', None)
+ if w is None:
+ warn('[3.10] you should specify "w" to form.render() named arguments',
+ DeprecationWarning, stacklevel=2)
+ data = []
+ w = data.append
+ else:
+ data = None
self.build_context(formvalues)
if renderer is None:
renderer = self.default_renderer()
- return renderer.render(self, kwargs)
+ if support_args(renderer.render, 'w'):
+ renderer.render(w, self, kwargs)
+ else:
+ warn('[3.10] you should add "w" as first argument o %s.render()'
+ % renderer.__class__, DeprecationWarning)
+ w(renderer.render(self, kwargs))
+ if data is not None:
+ return '\n'.join(data)
def default_renderer(self):
return self._cw.vreg['formrenderers'].select(
@@ -270,7 +286,8 @@
super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs)
self.add_hidden('__type', self.edited_entity.__regid__, eidparam=True)
self.add_hidden('eid', self.edited_entity.eid)
- if kwargs.get('mainform', True): # mainform default to true in parent
+ # mainform default to true in parent, hence default to True
+ if kwargs.get('mainform', True) or kwargs.get('mainentity', False):
self.add_hidden(u'__maineid', self.edited_entity.eid)
# If we need to directly attach the new object to another one
if self._cw.list_form_param('__linkto'):
--- a/web/views/ibreadcrumbs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/ibreadcrumbs.py Fri Mar 11 09:46:45 2011 +0100
@@ -25,12 +25,13 @@
from logilab.mtconverter import xml_escape
#from cubicweb.interfaces import IBreadCrumbs
+from cubicweb import tags, uilib
+from cubicweb.entity import Entity
from cubicweb.selectors import (is_instance, one_line_rset, adaptable,
one_etype_rset, multi_lines_rset, any_rset)
-from cubicweb.view import EntityView, Component, EntityAdapter
+from cubicweb.view import EntityView, EntityAdapter
+from cubicweb.web.views import basecomponents
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
-from cubicweb.entity import Entity
-from cubicweb import tags, uilib
# ease bw compat
@@ -51,7 +52,7 @@
__select__ = is_instance('Any', accept_none=False)
def parent_entity(self):
- if hasattr(self.entity, 'parent'):
+ if hasattr(self.entity, 'parent') and callable(self.entity.parent):
warn('[3.9] parent() method is deprecated, define a '
'custom IBreadCrumbsAdapter/ITreeAdapter for %s instead'
% self.entity.__class__, DeprecationWarning)
@@ -61,7 +62,7 @@
return itree.parent()
return None
- def breadcrumbs(self, view=None, recurs=False):
+ def breadcrumbs(self, view=None, recurs=None):
"""return a list containing some:
* tuple (url, label)
@@ -70,14 +71,30 @@
defining path from a root to the current view
- the main view is given as argument so breadcrumbs may vary according
- to displayed view (may be None). When recursing on a parent entity,
- the `recurs` argument should be set to True.
+ the main view is given as argument so breadcrumbs may vary according to
+ displayed view (may be None). When recursing on a parent entity, the
+ `recurs` argument should be a set of already traversed nodes (infinite
+ loop safety belt).
"""
parent = self.parent_entity()
if parent is not None:
+ if recurs is True:
+ _recurs = set()
+ warn('[3.10] recurs argument should be a set() or None',
+ DeprecationWarning, stacklevel=2)
+ elif recurs:
+ _recurs = recurs
+ else:
+ if recurs is False:
+ warn('[3.10] recurs argument should be a set() or None',
+ DeprecationWarning, stacklevel=2)
+ _recurs = set()
+ if _recurs and parent.eid in _recurs:
+ self.error('cycle in breadcrumbs for entity %s' % self.entity)
+ return []
+ _recurs.add(parent.eid)
adapter = ibreadcrumb_adapter(parent)
- path = adapter.breadcrumbs(view, True) + [self.entity]
+ path = adapter.breadcrumbs(view, _recurs) + [self.entity]
else:
path = [self.entity]
if not recurs:
@@ -90,84 +107,83 @@
return path
-class BreadCrumbEntityVComponent(Component):
+class BreadCrumbEntityVComponent(basecomponents.HeaderComponent):
__regid__ = 'breadcrumbs'
- __select__ = one_line_rset() & adaptable('IBreadCrumbs')
-
- cw_property_defs = {
- _('visible'): dict(type='Boolean', default=True,
- help=_('display the component or not')),
- }
- title = _('contentnavigation_breadcrumbs')
- help = _('contentnavigation_breadcrumbs_description')
+ __select__ = (basecomponents.HeaderComponent.__select__
+ & one_line_rset() & adaptable('IBreadCrumbs'))
+ order = basecomponents.ApplicationName.order + 1
+ context = basecomponents.ApplicationName.context
separator = u' > '
link_template = u'<a href="%s">%s</a>'
+ first_separator = True
- def call(self, view=None, first_separator=True):
+ def render(self, w):
entity = self.cw_rset.get_entity(0, 0)
adapter = ibreadcrumb_adapter(entity)
+ view = self.cw_extra_kwargs.get('view')
path = adapter.breadcrumbs(view)
if path:
- self.open_breadcrumbs()
- if first_separator:
- self.w(self.separator)
- self.render_breadcrumbs(entity, path)
- self.close_breadcrumbs()
+ self.open_breadcrumbs(w)
+ if self.first_separator:
+ w(self.separator)
+ self.render_breadcrumbs(w, entity, path)
+ self.close_breadcrumbs(w)
- def open_breadcrumbs(self):
- self.w(u'<span id="breadcrumbs" class="pathbar">')
+ def open_breadcrumbs(self, w):
+ w(u'<span id="breadcrumbs" class="pathbar">')
- def close_breadcrumbs(self):
- self.w(u'</span>')
+ def close_breadcrumbs(self, w):
+ w(u'</span>')
- def render_breadcrumbs(self, contextentity, path):
+ def render_breadcrumbs(self, w, contextentity, path):
root = path.pop(0)
if isinstance(root, Entity):
- self.w(self.link_template % (self._cw.build_url(root.__regid__),
+ w(self.link_template % (self._cw.build_url(root.__regid__),
root.dc_type('plural')))
- self.w(self.separator)
- self.wpath_part(root, contextentity, not path)
+ w(self.separator)
+ self.wpath_part(w, root, contextentity, not path)
for i, parent in enumerate(path):
- self.w(self.separator)
- self.w(u"\n")
- self.wpath_part(parent, contextentity, i == len(path) - 1)
+ w(self.separator)
+ w(u"\n")
+ self.wpath_part(w, parent, contextentity, i == len(path) - 1)
- def wpath_part(self, part, contextentity, last=False): # XXX deprecates last argument?
+ def wpath_part(self, w, part, contextentity, last=False): # XXX deprecates last argument?
if isinstance(part, Entity):
- self.w(part.view('breadcrumbs'))
+ w(part.view('breadcrumbs'))
elif isinstance(part, tuple):
url, title = part
textsize = self._cw.property_value('navigation.short-line-size')
- self.w(self.link_template % (
+ w(self.link_template % (
xml_escape(url), xml_escape(uilib.cut(title, textsize))))
else:
textsize = self._cw.property_value('navigation.short-line-size')
- self.w(uilib.cut(unicode(part), textsize))
+ w(uilib.cut(unicode(part), textsize))
class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent):
- __select__ = multi_lines_rset() & one_etype_rset() & \
- adaptable('IBreadCrumbs')
+ __select__ = (basecomponents.HeaderComponent.__select__
+ & multi_lines_rset() & one_etype_rset()
+ & adaptable('IBreadCrumbs'))
- def render_breadcrumbs(self, contextentity, path):
+ def render_breadcrumbs(self, w, contextentity, path):
# XXX hack: only display etype name or first non entity path part
root = path.pop(0)
if isinstance(root, Entity):
- self.w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
- root.dc_type('plural')))
+ w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
+ root.dc_type('plural')))
else:
- self.wpath_part(root, contextentity, not path)
+ self.wpath_part(w, root, contextentity, not path)
class BreadCrumbAnyRSetVComponent(BreadCrumbEntityVComponent):
- __select__ = any_rset()
+ __select__ = basecomponents.HeaderComponent.__select__ & any_rset()
- def call(self, view=None, first_separator=True):
- self.w(u'<span id="breadcrumbs" class="pathbar">')
- if first_separator:
- self.w(self.separator)
- self.w(self._cw._('search'))
- self.w(u'</span>')
+ def render(self, w):
+ w(u'<span id="breadcrumbs" class="pathbar">')
+ if self.first_separator:
+ w(self.separator)
+ w(self._cw._('search'))
+ w(u'</span>')
class BreadCrumbView(EntityView):
--- a/web/views/idownloadable.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/idownloadable.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,16 +21,18 @@
_ = unicode
from logilab.mtconverter import BINARY_ENCODINGS, TransformError, xml_escape
+from logilab.common.deprecation import class_renamed, deprecated
from cubicweb import tags
from cubicweb.view import EntityView
from cubicweb.selectors import (one_line_rset, is_instance, match_context_prop,
adaptable, has_mimetype)
from cubicweb.mttransforms import ENGINE
-from cubicweb.web import box, httpcache
+from cubicweb.web import component, httpcache
from cubicweb.web.views import primary, baseviews
+@deprecated('[3.10] use a custom IDownloadable adapter instead')
def download_box(w, entity, title=None, label=None, footer=u''):
req = entity._cw
w(u'<div class="sideBox">')
@@ -42,22 +44,30 @@
w(u'<a href="%s"><img src="%s" alt="%s"/> %s</a>'
% (xml_escape(entity.cw_adapt_to('IDownloadable').download_url()),
req.uiprops['DOWNLOAD_ICON'],
- _('download icon'), xml_escape(label or entity.dc_title())))
+ req._('download icon'), xml_escape(label or entity.dc_title())))
w(u'%s</div>' % footer)
w(u'</div></div>\n')
-class DownloadBox(box.EntityBoxTemplate):
- __regid__ = 'download_box'
- # no download box for images
- # XXX primary_view selector ?
- __select__ = (one_line_rset() & match_context_prop()
- & adaptable('IDownloadable') & ~has_mimetype('image/'))
+class DownloadBox(component.EntityCtxComponent):
+ __regid__ = 'download_box' # no download box for images
+ __select__ = (component.EntityCtxComponent.__select__ &
+ adaptable('IDownloadable') & ~has_mimetype('image/'))
+
order = 10
+ title = _('download')
- def cell_call(self, row, col, title=None, label=None, **kwargs):
- entity = self.cw_rset.get_entity(row, col)
- download_box(self.w, entity, title, label)
+ def init_rendering(self):
+ self.items = [self.entity]
+
+ def render_body(self, w):
+ for item in self.items:
+ idownloadable = item.cw_adapt_to('IDownloadable')
+ w(u'<a href="%s"><img src="%s" alt="%s"/> %s</a>'
+ % (xml_escape(idownloadable.download_url()),
+ self._cw.uiprops['DOWNLOAD_ICON'],
+ self._cw._('download icon'),
+ xml_escape(idownloadable.download_file_name())))
class DownloadView(EntityView):
@@ -146,7 +156,7 @@
return False
-class IDownloadableLineView(baseviews.OneLineView):
+class IDownloadableOneLineView(baseviews.OneLineView):
__select__ = adaptable('IDownloadable')
def cell_call(self, row, col, title=None, **kwargs):
@@ -154,11 +164,15 @@
entity = self.cw_rset.get_entity(row, col)
url = xml_escape(entity.absolute_url())
adapter = entity.cw_adapt_to('IDownloadable')
- name = xml_escape(title or adapter.download_file_name())
+ name = xml_escape(title or entity.dc_title())
durl = xml_escape(adapter.download_url())
self.w(u'<a href="%s">%s</a> [<a href="%s">%s</a>]' %
(url, name, durl, self._cw._('download')))
+IDownloadableLineView = class_renamed(
+ 'IDownloadableLineView', IDownloadableOneLineView,
+ '[3.10] IDownloadableLineView is deprecated, use IDownloadableOneLineView')
+
class AbstractEmbeddedView(EntityView):
__abstract__ = True
--- a/web/views/igeocodable.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/igeocodable.py Fri Mar 11 09:46:45 2011 +0100
@@ -26,6 +26,7 @@
class IGeocodableAdapter(EntityAdapter):
"""interface required by geocoding views such as gmap-view"""
+ __needs_bw_compat__ = True
__regid__ = 'IGeocodable'
__select__ = implements(IGeocodable, warn=False) # XXX for bw compat, should be abstract
--- a/web/views/isioc.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/isioc.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,6 +21,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import xml_escape
@@ -31,6 +32,7 @@
class ISIOCItemAdapter(EntityAdapter):
"""interface for entities which may be represented as an ISIOC items"""
+ __needs_bw_compat__ = True
__regid__ = 'ISIOCItem'
__select__ = implements(ISiocItem, warn=False) # XXX for bw compat, should be abstract
@@ -62,6 +64,7 @@
class ISIOCContainerAdapter(EntityAdapter):
"""interface for entities which may be represented as an ISIOC container"""
+ __needs_bw_compat__ = True
__regid__ = 'ISIOCContainer'
__select__ = implements(ISiocContainer, warn=False) # XXX for bw compat, should be abstract
--- a/web/views/management.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/management.py Fri Mar 11 09:46:45 2011 +0100
@@ -24,7 +24,7 @@
from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView, View
-from cubicweb.uilib import html_traceback, rest_traceback
+from cubicweb.uilib import html_traceback, rest_traceback, exc_message
from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field
from cubicweb.web.views.schema import SecurityViewMixIn
@@ -84,7 +84,7 @@
__redirectpath=entity.rest_path())
field = guess_field(entity.e_schema, self._cw.vreg.schema.rschema('owned_by'))
form.append_field(field)
- self.w(form.render(display_progress_div=False))
+ form.render(w=self.w, display_progress_div=False)
def owned_by_information(self, entity):
ownersrset = entity.related('owned_by')
@@ -154,7 +154,7 @@
form.append_field(field)
renderer = self._cw.vreg['formrenderers'].select(
'htable', self._cw, rset=None, display_progress_div=False)
- self.w(form.render(renderer=renderer))
+ form.render(w=self.w, renderer=renderer)
class ErrorView(AnyRsetView):
@@ -217,17 +217,8 @@
form.add_hidden('__bugreporting', '1')
form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
form.action = req.build_url('reportbug')
- w(form.render())
-
+ form.render(w=w)
-def exc_message(ex, encoding):
- try:
- return unicode(ex)
- except:
- try:
- return unicode(str(ex), encoding, 'replace')
- except:
- return unicode(repr(ex), encoding, 'replace')
def text_error_description(ex, excinfo, req, eversion, cubes):
binfo = rest_traceback(excinfo, xml_escape(ex))
--- a/web/views/massmailing.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/massmailing.py Fri Mar 11 09:46:45 2011 +0100
@@ -65,13 +65,13 @@
sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
label=_('From:'),
- value=lambda f: '%s <%s>' % (
- f._cw.user.dc_title(),
- f._cw.user.cw_adapt_to('IEmailable').get_email()))
+ value=lambda form, field: '%s <%s>' % (
+ form._cw.user.dc_title(),
+ form._cw.user.cw_adapt_to('IEmailable').get_email()))
recipient = ff.StringField(widget=CheckBox(), label=_('Recipients:'),
choices=recipient_vocabulary,
- value= lambda f: [entity.eid for entity in f.cw_rset.entities()
- if entity.cw_adapt_to('IEmailable').get_email()])
+ value= lambda form, field: [entity.eid for entity in form.cw_rset.entities()
+ if entity.cw_adapt_to('IEmailable').get_email()])
subject = ff.StringField(label=_('Subject:'), max_length=256)
mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
inputid='mailbody'))
@@ -146,7 +146,7 @@
def call(self):
form = self._cw.vreg['forms'].select('massmailing', self._cw,
rset=self.cw_rset)
- self.w(form.render())
+ form.render(w=self.w)
class SendMailController(controller.Controller):
--- a/web/views/navigation.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/navigation.py Fri Mar 11 09:46:45 2011 +0100
@@ -29,7 +29,7 @@
adaptable, implements)
from cubicweb.uilib import cut
from cubicweb.view import EntityAdapter, implements_adapter_compat
-from cubicweb.web.component import EntityVComponent, NavigationComponent
+from cubicweb.web.component import EmptyComponent, EntityCtxComponent, NavigationComponent
class PageNavigation(NavigationComponent):
@@ -187,6 +187,7 @@
"""interface for entities which can be linked to a previous and/or next
entity
"""
+ __needs_bw_compat__ = True
__regid__ = 'IPrevNext'
__select__ = implements(IPrevNext, warn=False) # XXX for bw compat, else should be abstract
@@ -201,59 +202,55 @@
raise NotImplementedError
-class NextPrevNavigationComponent(EntityVComponent):
+class NextPrevNavigationComponent(EntityCtxComponent):
__regid__ = 'prevnext'
# register msg not generated since no entity implements IPrevNext in cubicweb
# itself
- title = _('contentnavigation_prevnext')
- help = _('contentnavigation_prevnext_description')
- __select__ = EntityVComponent.__select__ & adaptable('IPrevNext')
+ help = _('ctxcomponents_prevnext_description')
+ __select__ = EntityCtxComponent.__select__ & adaptable('IPrevNext')
context = 'navbottom'
order = 10
- def call(self, view=None):
- self.cell_call(0, 0, view=view)
+ def init_rendering(self):
+ adapter = self.entity.cw_adapt_to('IPrevNext')
+ self.previous = adapter.previous_entity()
+ self.next = adapter.next_entity()
+ if not (self.previous or self.next):
+ raise EmptyComponent()
- def cell_call(self, row, col, view=None):
- entity = self.cw_rset.get_entity(row, col)
- adapter = entity.cw_adapt_to('IPrevNext')
- previous = adapter.previous_entity()
- next = adapter.next_entity()
- if previous or next:
- textsize = self._cw.property_value('navigation.short-line-size')
- self.w(u'<div class="prevnext">')
- if previous:
- self.previous_div(previous, textsize)
- if next:
- self.next_div(next, textsize)
- self.w(u'</div>')
- self.w(u'<div class="clear"></div>')
+ def render_body(self, w):
+ w(u'<div class="prevnext">')
+ self.prevnext(w)
+ w(u'</div>')
+ w(u'<div class="clear"></div>')
+
+ def prevnext(self, w):
+ if self.previous:
+ self.prevnext_entity(w, self.previous, 'prev')
+ if self.next:
+ self.prevnext_entity(w, self.next, 'next')
- def previous_div(self, previous, textsize):
- self.w(u'<div class="previousEntity left">')
- self.w(self.previous_link(previous, textsize))
- self.w(u'</div>')
- self._cw.html_headers.add_raw('<link rel="prev" href="%s" />'
- % xml_escape(previous.absolute_url()))
-
- def previous_link(self, previous, textsize):
- return u'<a href="%s" title="%s"><< %s</a>' % (
- xml_escape(previous.absolute_url()),
- self._cw._('i18nprevnext_previous'),
- xml_escape(cut(previous.dc_title(), textsize)))
+ def prevnext_entity(self, w, entity, type):
+ textsize = self._cw.property_value('navigation.short-line-size')
+ if type == 'prev':
+ title = self._cw._('i18nprevnext_previous')
+ icon = u'<< '
+ cssclass = u'previousEntity left'
+ else:
+ title = self._cw._('i18nprevnext_next')
+ icon = u'>> '
+ cssclass = u'nextEntity right'
+ self.prevnext_div(w, type, cssclass, entity.absolute_url(),
+ title, icon + xml_escape(cut(entity.dc_title(), textsize)))
- def next_div(self, next, textsize):
- self.w(u'<div class="nextEntity right">')
- self.w(self.next_link(next, textsize))
- self.w(u'</div>')
- self._cw.html_headers.add_raw('<link rel="next" href="%s" />'
- % xml_escape(next.absolute_url()))
-
- def next_link(self, next, textsize):
- return u'<a href="%s" title="%s">%s >></a>' % (
- xml_escape(next.absolute_url()),
- self._cw._('i18nprevnext_next'),
- xml_escape(cut(next.dc_title(), textsize)))
+ def prevnext_div(self, w, type, cssclass, url, title, content):
+ w(u'<div class="%s">' % cssclass)
+ w(u'<a href="%s" title="%s">%s</a>' % (xml_escape(url),
+ xml_escape(title),
+ content))
+ w(u'</div>')
+ self._cw.html_headers.add_raw('<link rel="%s" href="%s" />' % (
+ type, xml_escape(url)))
def do_paginate(view, rset=None, w=None, show_all_option=True, page_size=None):
--- a/web/views/old_calendar.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/old_calendar.py Fri Mar 11 09:46:45 2011 +0100
@@ -17,6 +17,9 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""html calendar views"""
+__docformat__ = "restructuredtext en"
+_ = unicode
+
from datetime import date, time, timedelta
from logilab.mtconverter import xml_escape
@@ -29,6 +32,7 @@
class ICalendarViewsAdapter(EntityAdapter):
"""calendar views interface"""
+ __needs_bw_compat__ = True
__regid__ = 'ICalendarViews'
__select__ = implements(ICalendarViews, warn=False) # XXX for bw compat, should be abstract
--- a/web/views/plots.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/plots.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,10 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""basic plot views
+"""basic plot views"""
-"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.common.date import datetime2ticks
from logilab.mtconverter import xml_escape
--- a/web/views/primary.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/primary.py Fri Mar 11 09:46:45 2011 +0100
@@ -22,13 +22,15 @@
from warnings import warn
+from logilab.common.deprecation import deprecated
from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized, NoSelectableObject
-from cubicweb.selectors import match_kwargs
+from cubicweb.utils import support_args
+from cubicweb.selectors import match_kwargs, match_context
from cubicweb.view import EntityView
-from cubicweb.schema import VIRTUAL_RTYPES, display_name
-from cubicweb.web import uicfg
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
+from cubicweb.web import uicfg, component
class PrimaryView(EntityView):
@@ -67,7 +69,15 @@
boxes = None
if boxes or hasattr(self, 'render_side_related'):
self.w(u'<table width="100%"><tr><td style="width: 75%">')
- self.render_entity_summary(entity)
+ if hasattr(self, 'render_entity_summary'):
+ warn('[3.10] render_entity_summary method is deprecated (%s)' % self,
+ DeprecationWarning)
+ self.render_entity_summary(entity)
+ summary = self.summary(entity)
+ if summary:
+ warn('[3.10] summary method is deprecated (%s)' % self,
+ DeprecationWarning)
+ self.w(u'<div class="summary">%s</div>' % summary)
self.w(u'<div class="mainInfo">')
self.content_navigation_components('navcontenttop')
self.render_entity_attributes(entity)
@@ -88,14 +98,13 @@
def content_navigation_components(self, context):
self.w(u'<div class="%s">' % context)
- for comp in self._cw.vreg['contentnavigation'].poss_visible_objects(
- self._cw, rset=self.cw_rset, row=self.cw_row, view=self, context=context):
+ for comp in self._cw.vreg['ctxcomponents'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=self, context=context):
+ # XXX bw compat code
try:
comp.render(w=self.w, row=self.cw_row, view=self)
- except NotImplementedError:
- warn('[3.2] component %s doesnt implement cell_call, please update'
- % comp.__class__, DeprecationWarning)
- comp.render(w=self.w, view=self)
+ except TypeError:
+ comp.render(w=self.w)
self.w(u'</div>')
def render_entity_title(self, entity):
@@ -112,14 +121,10 @@
def render_entity_toolbox(self, entity):
self.content_navigation_components('ctxtoolbar')
+ @deprecated('[3.8] render_entity_metadata method is deprecated')
def render_entity_metadata(self, entity):
entity.view('metadata', w=self.w)
- def render_entity_summary(self, entity):
- summary = self.summary(entity) # deprecate summary?
- if summary:
- self.w(u'<div class="summary">%s</div>' % summary)
-
def summary(self, entity):
"""default implementation return an empty string"""
return u''
@@ -142,20 +147,20 @@
if display_attributes:
self.w(u'<table>')
for rschema, role, dispctrl, value in display_attributes:
- try:
- self._render_attribute(dispctrl, rschema, value,
- role=role, table=True)
+ 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)
- except TypeError:
+ 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)
- except AttributeError:
- label = self._rel_label(entity, rschema, role, dispctrl)
- self.render_attribute(label, value, table=True)
self.w(u'</table>')
def render_attribute(self, label, value, table=False):
@@ -179,12 +184,12 @@
if not rset:
continue
if hasattr(self, '_render_relation'):
- try:
+ if not support_args(self._render_relation, 'showlabel'):
self._render_relation(dispctrl, rset, 'autolimited')
warn('[3.9] _render_relation prototype has changed and has '
'been renamed to render_relation, please update %s'
% self.__class__, DeprecationWarning)
- except TypeError:
+ else:
self._render_relation(rset, dispctrl, 'autolimited',
self.show_rel_label)
warn('[3.6] _render_relation prototype has changed and has '
@@ -217,42 +222,42 @@
try:
label, rset, vid, dispctrl = box
except ValueError:
- warn('[3.5] box views should now be defined as a 4-uple (label, rset, vid, dispctrl), '
- 'please update %s' % self.__class__.__name__,
- DeprecationWarning)
label, rset, vid = box
dispctrl = {}
+ warn('[3.10] box views should now be a RsetBox instance, '
+ 'please update %s' % self.__class__.__name__,
+ DeprecationWarning)
self.w(u'<div class="sideBox">')
self.wview(vid, rset, title=label, initargs={'dispctrl': dispctrl})
self.w(u'</div>')
else:
- try:
- box.render(w=self.w, row=self.cw_row)
- except NotImplementedError:
- # much probably a context insensitive box, which only implements
- # .call() and not cell_call()
- box.render(w=self.w)
+ try:
+ box.render(w=self.w, row=self.cw_row)
+ except TypeError:
+ box.render(w=self.w)
def _prepare_side_boxes(self, entity):
sideboxes = []
+ boxesreg = self._cw.vreg['ctxcomponents']
for rschema, tschemas, role, dispctrl in self._section_def(entity, 'sideboxes'):
rset = self._relation_rset(entity, rschema, role, dispctrl)
if not rset:
continue
- label = display_name(self._cw, rschema.type, role)
- vid = dispctrl.get('vid', 'sidebox')
- sideboxes.append( (label, rset, vid, dispctrl) )
- sideboxes += self._cw.vreg['boxes'].poss_visible_objects(
- self._cw, rset=self.cw_rset, row=self.cw_row, view=self,
- context='incontext')
+ label = self._rel_label(entity, rschema, role, dispctrl)
+ vid = dispctrl.get('vid', 'autolimited')
+ box = boxesreg.select('rsetbox', self._cw, rset=rset,
+ vid=vid, title=label, dispctrl=dispctrl,
+ context='incontext')
+ sideboxes.append(box)
+ sideboxes += boxesreg.poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=self,
+ context='incontext')
# XXX since we've two sorted list, it may be worth using bisect
def get_order(x):
- if isinstance(x, tuple):
- # x is a view box (label, rset, vid, dispctrl)
- # default to 1000 so view boxes occurs after component boxes
- return x[-1].get('order', 1000)
- # x is a component box
- return x.cw_propval('order')
+ if 'order' in x.cw_property_defs:
+ return x.cw_propval('order')
+ # default to 9999 so view boxes occurs after component boxes
+ return x.cw_extra_kwargs.get('dispctrl', {}).get('order', 9999)
return sorted(sideboxes, key=get_order)
def _section_def(self, entity, where):
@@ -328,15 +333,15 @@
# else show links to display related entities
else:
rql = self.cw_rset.printable_rql()
- self.cw_rset.limit(limit) # remove extra entity
+ rset = self.cw_rset.limit(limit) # remove extra entity
if list_limit is None:
- self.wview('csv', self.cw_rset, subvid=subvid)
+ self.wview('csv', rset, subvid=subvid)
self.w(u'[<a href="%s">%s</a>]' % (
xml_escape(self._cw.build_url(rql=rql, vid=subvid)),
self._cw._('see them all')))
else:
self.w(u'<div>')
- self.wview('simplelist', self.cw_rset, subvid=subvid)
+ self.wview('simplelist', rset, subvid=subvid)
self.w(u'[<a href="%s">%s</a>]' % (
xml_escape(self._cw.build_url(rql=rql, vid=subvid)),
self._cw._('see them all')))
@@ -367,23 +372,31 @@
__regid__ = 'attribute'
__select__ = EntityView.__select__ & match_kwargs('rtype')
- def cell_call(self, row, col, rtype, **kwargs):
+ def cell_call(self, row, col, rtype, role, **kwargs):
entity = self.cw_rset.get_entity(row, col)
if self._cw.vreg.schema.rschema(rtype).final:
self.w(entity.printable_value(rtype))
else:
dispctrl = uicfg.primaryview_display_ctrl.etype_get(
- entity.e_schema, rtype, kwargs['role'], '*')
+ entity.e_schema, rtype, role, '*')
rset = entity.related(rtype, role)
if rset:
self.wview('autolimited', rset, initargs={'dispctrl': dispctrl})
+
+class ToolbarLayout(component.Layout):
+ __select__ = match_context('ctxtoolbar')
+
+ def render(self, w):
+ if self.init_rendering():
+ self.cw_extra_kwargs['view'].render_body(w)
+
## default primary ui configuration ###########################################
_pvs = uicfg.primaryview_section
-for rtype in ('eid', 'creation_date', 'modification_date', 'cwuri',
- 'is', 'is_instance_of', 'identity', 'owned_by', 'created_by',
- 'require_permission', 'see_also'):
+for rtype in META_RTYPES:
_pvs.tag_subject_of(('*', rtype, '*'), 'hidden')
_pvs.tag_object_of(('*', rtype, '*'), 'hidden')
+_pvs.tag_subject_of(('*', 'require_permission', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'require_permission', '*'), 'hidden')
--- a/web/views/pyviews.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/pyviews.py Fri Mar 11 09:46:45 2011 +0100
@@ -24,6 +24,10 @@
class PyValTableView(View):
+ """display a list of list of values into an html table.
+
+ Take care, content is NOT xml-escaped.
+ """
__regid__ = 'pyvaltable'
__select__ = match_kwargs('pyvalue')
@@ -50,6 +54,10 @@
class PyValListView(View):
+ """display a list of values into an html list.
+
+ Take care, content is NOT xml-escaped.
+ """
__regid__ = 'pyvallist'
__select__ = match_kwargs('pyvalue')
--- a/web/views/reledit.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/reledit.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,8 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""the 'reedit' feature (eg edit attribute/relation from primary view)
-"""
+"""the 'reedit' feature (eg edit attribute/relation from primary view"""
+
+__docformat__ = "restructuredtext en"
+_ = unicode
import copy
from warnings import warn
@@ -76,10 +78,10 @@
assert rtype
assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
self._cw.add_css('cubicweb.form.css')
- self._cw.add_js('cubicweb.reledit.js', 'cubicweb.edition.js')
+ self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
entity = self.cw_rset.get_entity(row, col)
rschema = self._cw.vreg.schema[rtype]
- self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
+ self._rules = rctrl.etype_get(entity.e_schema, rschema, role, '*')
if rvid is not None or default_value is not None:
warn('[3.9] specifying rvid/default_value on select is deprecated, '
'reledit_ctrl rtag to control this' % self, DeprecationWarning)
@@ -209,7 +211,7 @@
return rschema.has_perm(self._cw, 'delete', **kwargs)
def _build_edit_zone(self):
- return self._editzone % {'msg' : xml_escape(_(self._cw._(self._editzonemsg)))}
+ return self._editzone % {'msg' : xml_escape(self._cw._(self._editzonemsg))}
def _build_delete_zone(self):
return self._deletezone % {'msg': xml_escape(self._cw._(self._deletemsg))}
@@ -322,7 +324,7 @@
w(u'<div id="%s-value" class="editableFieldValue">' % divid)
w(value)
w(u'</div>')
- w(form.render(renderer=renderer))
+ form.render(w=w, renderer=renderer)
w(u'<div id="%s" class="editableField hidden">' % divid)
def _edit_action(self, divid, args, edit_related, add_related, _delete_related):
--- a/web/views/schema.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/schema.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""Specific views for schema related entities"""
__docformat__ = "restructuredtext en"
+_ = unicode
from itertools import cycle
@@ -44,7 +45,7 @@
ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
SKIP_TYPES = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
| INTERNAL_TYPES)
-SKIP_TYPES.update(set(('CWUser', 'CWGroup')))
+SKIP_TYPES.update(set(('CWUser', 'CWGroup', 'EmailAddress', 'Bookmark')))
def skip_types(req):
if int(req.form.get('skipmeta', True)):
@@ -125,6 +126,7 @@
# set layout permissions in a table for each group of relation
# definition
w = self.w
+ _ = self._cw._
w(u'<div style="margin: 0px 1.5em">')
tmpl = u'<strong>%s</strong> %s <strong>%s</strong>'
for perm, rdefs in perms.iteritems():
@@ -147,7 +149,7 @@
default_tab = 'schema-diagram'
def call(self):
- self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
+ self.w(u'<h1>%s</h1>' % self._cw._('Schema of the data model'))
self.render_tabs(self.tabs, self.default_tab)
@@ -155,9 +157,11 @@
__regid__ = 'schema-diagram'
def call(self):
- self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
- u'meta-data, but you can also display a <a href="%s">complete '
- u'schema with meta-data</a>.</div>')
+ _ = self._cw._
+ self.w(self._cw._(
+ u'<div>This schema of the data model <em>excludes</em> the '
+ 'meta-data, but you can also display a <a href="%s">complete '
+ 'schema with meta-data</a>.</div>')
% xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=0)))
self.w(u'<div><a href="%s">%s</a></div>' %
(self._cw.build_url('view', vid='owl'),
@@ -397,15 +401,16 @@
def cell_call(self, row, col):
entity = self.cw_rset.get_entity(row, col)
eschema = self._cw.vreg.schema.eschema(entity.name)
- self.w(u'<h4>%s</h4>' % _('This entity type permissions:').capitalize())
+ self.w(u'<h4>%s</h4>' % self._cw._('This entity type permissions:'))
self.permissions_table(eschema)
self.w(u'<div style="margin: 0px 1.5em">')
- self.w(u'<h4>%s</h4>' % _('Attributes permissions:').capitalize())
+ self.w(u'<h4>%s</h4>' % self._cw._('Attributes permissions:'))
for attr, etype in eschema.attribute_definitions():
if attr not in META_RTYPES:
rdef = eschema.rdef(attr)
attrtype = str(rdef.rtype)
- self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attrtype, _(attrtype)))
+ self.w(u'<h4 class="schema">%s (%s)</h4> '
+ % (attrtype, self._cw._(attrtype)))
self.permissions_table(rdef)
self.w(u'</div>')
--- a/web/views/sessions.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/sessions.py Fri Mar 11 09:46:45 2011 +0100
@@ -21,7 +21,8 @@
__docformat__ = "restructuredtext en"
-from cubicweb.web import InvalidSession
+from cubicweb import RepositoryError, Unauthorized
+from cubicweb.web import InvalidSession, Redirect
from cubicweb.web.application import AbstractSessionManager
from cubicweb.dbapi import DBAPISession
@@ -51,9 +52,6 @@
if not sessionid in self._sessions:
raise InvalidSession()
session = self._sessions[sessionid]
- if self.has_expired(session):
- self.close_session(session)
- raise InvalidSession()
try:
user = self.authmanager.validate_session(req, session)
except InvalidSession:
@@ -78,6 +76,44 @@
req.set_session(session)
return session
+ def postlogin(self, req):
+ """postlogin: the user has been authenticated, redirect to the original
+ page (index by default) with a welcome message
+ """
+ # Update last connection date
+ # XXX: this should be in a post login hook in the repository, but there
+ # we can't differentiate actual login of automatic session
+ # reopening. Is it actually a problem?
+ if 'last_login_time' in req.vreg.schema:
+ self._update_last_login_time(req)
+ args = req.form
+ for forminternal_key in ('__form_id', '__domid', '__errorurl'):
+ args.pop(forminternal_key, None)
+ args['__message'] = req._('welcome %s !') % req.user.login
+ if 'vid' in req.form:
+ args['vid'] = req.form['vid']
+ if 'rql' in req.form:
+ args['rql'] = req.form['rql']
+ path = req.relative_path(False)
+ if path == 'login':
+ path = 'view'
+ raise Redirect(req.build_url(path, **args))
+
+ def _update_last_login_time(self, req):
+ # XXX should properly detect missing permission / non writeable source
+ # and avoid "except (RepositoryError, Unauthorized)" below
+ if req.user.cw_metainformation()['source']['type'] == 'ldapuser':
+ return
+ try:
+ req.execute('SET X last_login_time NOW WHERE X eid %(x)s',
+ {'x' : req.user.eid})
+ req.cnx.commit()
+ except (RepositoryError, Unauthorized):
+ req.cnx.rollback()
+ except:
+ req.cnx.rollback()
+ raise
+
def close_session(self, session):
"""close session on logout or on invalid session detected (expired out,
corrupted...)
--- a/web/views/sparql.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/sparql.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,10 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""SPARQL integration
+"""SPARQL integration"""
-"""
__docformat__ = "restructuredtext en"
+_ = unicode
from yams import xy
from rql import TypeResolverException
@@ -52,7 +52,7 @@
__regid__ = 'sparql'
def call(self):
form = self._cw.vreg['forms'].select('sparql', self._cw)
- self.w(form.render())
+ form.render(w=self.w)
sparql = self._cw.form.get('sparql')
vid = self._cw.form.get('resultvid', 'table')
if sparql:
@@ -67,7 +67,7 @@
else:
rql, args = qinfo.finalize()
if vid == 'sparqlxml':
- url = self._cw.build_url('view', rql=(rql,args), vid=vid)
+ url = self._cw.build_url('view', rql=rql % args, vid=vid)
raise Redirect(url)
rset = self._cw.execute(rql, args)
self.wview(vid, rset, 'null')
--- a/web/views/startup.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/startup.py Fri Mar 11 09:46:45 2011 +0100
@@ -84,7 +84,7 @@
def create_links(self):
self.w(u'<ul class="createLink">')
for etype in self.add_etype_links:
- eschema = self.schema.eschema(etype)
+ eschema = self._cw.vreg.schema.eschema(etype)
if eschema.has_perm(self._cw, 'add'):
self.w(u'<li><a href="%s">%s</a></li>' % (
self._cw.build_url('add/%s' % eschema),
@@ -159,15 +159,14 @@
url = self._cw.build_url(etype)
etypelink = u' <a href="%s">%s</a> (%d)' % (
xml_escape(url), label, nb)
- yield (label, etypelink, self.add_entity_link(eschema, req))
+ if eschema.has_perm(req, 'add'):
+ yield (label, etypelink, self.add_entity_link(etype))
- def add_entity_link(self, eschema, req):
- """creates a [+] link for adding an entity if user has permission to do so"""
- if not eschema.has_perm(req, 'add'):
- return u''
+ def add_entity_link(self, etype):
+ """creates a [+] link for adding an entity"""
+ url = self._cw.vreg["etypes"].etype_class(etype).cw_create_url(self._cw)
return u'[<a href="%s" title="%s">+</a>]' % (
- xml_escape(self.create_url(eschema.type)),
- self._cw.__('add a %s' % eschema))
+ xml_escape(url), self._cw.__('add a %s' % etype))
class IndexView(ManageView):
--- a/web/views/tableview.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/tableview.py Fri Mar 11 09:46:45 2011 +0100
@@ -18,6 +18,7 @@
"""generic table view, including filtering abilities"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import xml_escape
@@ -27,8 +28,9 @@
from cubicweb import tags
from cubicweb.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
+from cubicweb.web.component import Link
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
- PopupBoxMenu, BoxLink)
+ PopupBoxMenu)
from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
class TableView(AnyRsetView):
@@ -211,7 +213,7 @@
ident='%sActions' % divid)
box.append(menu)
for url, label, klass, ident in actions:
- menu.append(BoxLink(url, label, klass, ident=ident, escape=True))
+ menu.append(Link(url, label, klass=klass, id=ident))
box.render(w=self.w)
self.w(u'<div class="clear"/>')
--- a/web/views/tabs.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/tabs.py Fri Mar 11 09:46:45 2011 +0100
@@ -15,11 +15,10 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""base classes to handle tabbed views
-
-"""
+"""base classes to handle tabbed views"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.common.deprecation import class_renamed
from logilab.mtconverter import xml_escape
@@ -48,7 +47,8 @@
""" a lazy version of wview """
w = w or self.w
self._cw.add_js('cubicweb.lazy.js')
- urlparams = {'vid' : vid, 'fname' : 'view'}
+ urlparams = self._cw.form.copy()
+ urlparams.update({'vid' : vid, 'fname' : 'view'})
if rql:
urlparams['rql'] = rql
elif eid:
@@ -203,7 +203,7 @@
class TabbedPrimaryView(TabsMixin, primary.PrimaryView):
__abstract__ = True # don't register
- tabs = ['main_tab']
+ tabs = [_('main_tab')]
default_tab = 'main_tab'
def cell_call(self, row, col):
@@ -217,7 +217,7 @@
class PrimaryTab(primary.PrimaryView):
__regid__ = 'main_tab'
- title = None
+ title = None # should not appear in possible views
def is_primary(self):
return True
--- a/web/views/timetable.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/timetable.py Fri Mar 11 09:46:45 2011 +0100
@@ -54,7 +54,7 @@
for row in xrange(self.cw_rset.rowcount):
task = self.cw_rset.get_entity(row, 0)
icalendarable = task.cw_adapt_to('ICalendarable')
- if len(self.cw_rset[row]) > 1:
+ if len(self.cw_rset[row]) > 1 and self.cw_rset.description[row][1] == 'CWUser':
user = self.cw_rset.get_entity(row, 1)
else:
user = ALL_USERS
--- a/web/views/treeview.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/treeview.py Fri Mar 11 09:46:45 2011 +0100
@@ -20,6 +20,7 @@
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from warnings import warn
@@ -110,7 +111,7 @@
__regid__ = 'treeview'
itemvid = 'treeitemview'
subvid = 'oneline'
- css_classes = 'treeview widget'
+ cssclass = 'treeview widget'
title = _('tree view')
def _init_params(self, subvid, treeid, initial_load, initial_thru_ajax, morekwargs):
@@ -144,7 +145,7 @@
if toplevel:
self._init_headers(treeid, toplevel_thru_ajax)
ulid = ' id="tree-%s"' % treeid
- self.w(u'<ul%s class="%s">' % (ulid, self.css_classes))
+ self.w(u'<ul%s class="%s">' % (ulid, self.cssclass))
# XXX force sorting on x.sortvalue() (which return dc_title by default)
# we need proper ITree & co specification to avoid this.
# (pb when type ambiguity at the other side of the tree relation,
@@ -171,7 +172,7 @@
"""specific version of the treeview to display file trees
"""
__regid__ = 'filetree'
- css_classes = 'treeview widget filetree'
+ cssclass = 'treeview widget filetree'
title = _('file tree view')
def call(self, subvid=None, treeid=None, initial_load=True, **kwargs):
--- a/web/views/wdoc.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/wdoc.py Fri Mar 11 09:46:45 2011 +0100
@@ -92,9 +92,14 @@
return index
def title_for_lang(node, lang):
+ fallback_title = None
for title in node.findall('title'):
- if title.attrib['{http://www.w3.org/XML/1998/namespace}lang'] == lang:
+ title_lang = title.attrib['{http://www.w3.org/XML/1998/namespace}lang']
+ if title_lang == lang:
return unicode(title.text)
+ if title_lang == 'en':
+ fallback_title = unicode(title.text)
+ return fallback_title
def subsections(node):
return [child for child in node if child.tag == 'section']
@@ -283,7 +288,7 @@
category = 'footer'
order = 2
- title = _('about this site')
+ title = _('About this site')
def url(self):
return self._cw.build_url('doc/about')
--- a/web/views/workflow.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/workflow.py Fri Mar 11 09:46:45 2011 +0100
@@ -25,6 +25,7 @@
_ = unicode
import os
+from warnings import warn
from logilab.mtconverter import xml_escape
from logilab.common.graph import escape
@@ -73,13 +74,16 @@
_afs = uicfg.autoform_section
_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden')
_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden')
+_afs.tag_attribute(('TrInfo', 'tr_count'), 'main', 'hidden')
_afs.tag_object_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
# IWorkflowable views #########################################################
class ChangeStateForm(forms.CompositeEntityForm):
- __regid__ = 'changestate'
+ # set dom id to ensure there is no conflict with edition form (see
+ # session_key() implementation)
+ __regid__ = domid = 'changestate'
form_renderer_id = 'base' # don't want EntityFormRenderer
form_buttons = [fwdgs.SubmitButton(),
@@ -103,7 +107,7 @@
'st1': entity.cw_adapt_to('IWorkflowable').printable_state,
'st2': self._cw._(transition.destination(entity).name)}
self.w(u'<p>%s</p>\n' % msg)
- self.w(form.render())
+ form.render(w=self.w)
def redirectpath(self, entity):
return entity.rest_path()
@@ -133,7 +137,7 @@
title = _('Workflow history')
- def cell_call(self, row, col, view=None):
+ def cell_call(self, row, col, view=None, title=title):
_ = self._cw._
eid = self.cw_rset[row][col]
sel = 'Any FS,TS,WF,D'
@@ -156,19 +160,27 @@
except Unauthorized:
return
if rset:
- self.wview('table', rset, title=_(self.title), displayactions=False,
+ if title:
+ title = _(title)
+ self.wview('table', rset, title=title, displayactions=False,
displaycols=displaycols, headers=headers)
-class WFHistoryVComponent(component.EntityVComponent):
+class WFHistoryVComponent(component.EntityCtxComponent):
"""display the workflow history for entities supporting it"""
__regid__ = 'wfhistory'
- __select__ = component.EntityVComponent.__select__ & WFHistoryView.__select__
+ __select__ = component.EntityCtxComponent.__select__ & WFHistoryView.__select__
context = 'navcontentbottom'
title = _('Workflow history')
- def cell_call(self, row, col, view=None):
- self.wview('wfhistory', self.cw_rset, row=row, col=col, view=view)
+ def render_body(self, w):
+ if hasattr(self, 'cell_call'):
+ warn('[3.10] %s should now implement render_body instead of cell_call'
+ % self.__class__, DeprecationWarning)
+ self.w = w
+ self.cell_call(self.entity.cw_row, self.entity.cw_col)
+ else:
+ self.entity.view('wfhistory', w=w, title=None)
# workflow actions #############################################################
--- a/web/views/xmlrss.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/views/xmlrss.py Fri Mar 11 09:46:45 2011 +0100
@@ -29,7 +29,7 @@
from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
from cubicweb.view import implements_adapter_compat
from cubicweb.uilib import simple_sgml_tag
-from cubicweb.web import httpcache, box
+from cubicweb.web import httpcache, component
# base xml views ##############################################################
@@ -68,7 +68,7 @@
value = entity.eid
else:
try:
- value = entity[attr]
+ value = entity.cw_attr_cache[attr]
except KeyError:
# Bytes
continue
@@ -122,6 +122,7 @@
# RSS stuff ###################################################################
class IFeedAdapter(EntityAdapter):
+ __needs_bw_compat__ = True
__regid__ = 'IFeed'
__select__ = is_instance('Any')
@@ -148,25 +149,25 @@
return entity.cw_adapt_to('IFeed').rss_feed_url()
-class RSSIconBox(box.BoxTemplate):
+class RSSIconBox(component.CtxComponent):
"""just display the RSS icon on uniform result set"""
__regid__ = 'rss'
- __select__ = (box.BoxTemplate.__select__
+ __select__ = (component.CtxComponent.__select__
& appobject_selectable('components', 'rss_feed_url'))
visible = False
order = 999
- def call(self, **kwargs):
+ def render(self, w, **kwargs):
try:
rss = self._cw.uiprops['RSS_LOGO']
except KeyError:
self.error('missing RSS_LOGO external resource')
return
urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
- rset=self.cw_rset)
+ rset=self.cw_rset)
url = urlgetter.feed_url()
- self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
+ w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
class RSSView(XMLView):
--- a/web/wdoc/ChangeLog_en Fri Dec 10 12:17:18 2010 +0100
+++ b/web/wdoc/ChangeLog_en Fri Mar 11 09:46:45 2011 +0100
@@ -4,7 +4,27 @@
.. _SPARQL: http://www.w3.org/TR/rdf-sparql-query/
.. _schema: schema
.. _OWL: http://www.w3.org/TR/owl-features/
-.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
+.. _CWSource: cwetype/CWSource
+
+2010-10-17 -- 3.10.0
+
+ * facets on optional relations now propose to search to entities
+ which miss the relation
+
+ * box and content navigation components have been merged, so this
+ simplify the configuration and you should be able to move
+ boxes/components in various places of the interface
+
+ * global / context dependant boxes should now be more
+ distinguishable to make the interface more intuitive
+
+ * upgraded jQuery and jQuery UI respectively to version 1.4.2 and
+ 1.8.
+
+ * data sources are now stored as CWSource_ entities in the database.
+ This allows on multi-sources instance to filter search results
+ according to the source entities are coming from.
+
2010-06-11 -- 3.8.4
* support full text prefix search for instances using postgres > 8.4 as database: try it
--- a/web/wdoc/ChangeLog_fr Fri Dec 10 12:17:18 2010 +0100
+++ b/web/wdoc/ChangeLog_fr Fri Mar 11 09:46:45 2011 +0100
@@ -5,7 +5,28 @@
.. _schema: schema
.. _OWL: http://www.w3.org/TR/owl-features/
.. _pdfexport: http://www.cubicweb.org/project/cubicweb-pdfexport
+.. _CWSource: cwetype/CWSource
+2010-10-07 -- 3.10.0
+
+ * les facettes sur les relations optionnelles proposent maintenant
+ de filter les entité qui n'ont *pas* la relation
+
+ * les boîtes et composants contextuels ont été fusionnés, permettant
+ de simplifier la configuration et de placer ces nouveaux composants
+ comme vous le désirez
+
+ * les boîtes globales ou dépendantes du contexte sont plus
+ facilement distinguable pour rendre l'interface plus intuitive
+
+ * passage à jQuery 1.4.2, et jQuery UI 1.8
+
+ * les sources de données sont maintenant stockées dans la base de
+ données sous forme d'entités CWSource_. Cela permet sur les
+ instances multi-source de filter les résultats de recherche en
+ fonction de la source dont viennent les entités.
+
+
2010-06-11 -- 3.8.4
* support pour la recherche de préfixe pour les instances utilisant postgres > 8.4 :
essayez en cherchant par ex. 'cubic*'
--- a/web/webconfig.py Fri Dec 10 12:17:18 2010 +0100
+++ b/web/webconfig.py Fri Mar 11 09:46:45 2011 +0100
@@ -135,17 +135,6 @@
"Should be 0 or greater than repository\'s session-time.",
'group': 'web', 'level': 2,
}),
- ('cleanup-session-time',
- {'type' : 'time',
- 'default': '24h',
- 'help': 'duration of inactivity after which a connection '
- 'will be closed, to limit memory consumption (avoid sessions that '
- 'never expire and cause memory leak when http-session-time is 0). '
- 'So even if http-session-time is 0 and the user don\'t close his '
- 'browser, he will have to reauthenticate after this time of '
- 'inactivity. Default to 24h.',
- 'group': 'web', 'level': 3,
- }),
('cleanup-anonymous-session-time',
{'type' : 'time',
'default': '5min',
@@ -218,8 +207,8 @@
def fckeditor_installed(self):
return exists(self.uiprops['FCKEDITOR_PATH'])
- def eproperty_definitions(self):
- for key, pdef in super(WebConfiguration, self).eproperty_definitions():
+ def cwproperty_definitions(self):
+ for key, pdef in super(WebConfiguration, self).cwproperty_definitions():
if key == 'ui.fckeditor' and not self.fckeditor_installed():
continue
yield key, pdef