--- a/.hgtags Thu Apr 21 17:25:32 2011 +0200
+++ b/.hgtags Wed Apr 27 09:55:17 2011 +0200
@@ -188,3 +188,9 @@
77318f1ec4aae3523d455e884daf3708c3c79af7 cubicweb-debian-version-3.11.1-1
56ae3cd5f8553678a2b1d4121b61241598d0ca68 cubicweb-version-3.11.2
954b5b51cd9278eb45d66be1967064d01ab08453 cubicweb-debian-version-3.11.2-1
+fd502219eb76f4bfd239d838a498a1d1e8204baf cubicweb-version-3.12.0
+92b56939b7c77bbf443b893c495a20f19bc30702 cubicweb-debian-version-3.12.0-1
+59701627adba73ee97529f6ea0e250a0f3748e32 cubicweb-version-3.12.1
+07e2c9c7df2617c5ecfa84cb819b3ee8ef91d1f2 cubicweb-debian-version-3.12.1-1
+5a9b6bc5653807500c30a7eb0e95b90fd714fec3 cubicweb-version-3.12.2
+6d418fb3ffed273562aae411efe323d5138b592a cubicweb-debian-version-3.12.2-1
--- a/__pkginfo__.py Thu Apr 21 17:25:32 2011 +0200
+++ b/__pkginfo__.py Wed Apr 27 09:55:17 2011 +0200
@@ -22,7 +22,7 @@
modname = distname = "cubicweb"
-numversion = (3, 11, 2)
+numversion = (3, 12, 2)
version = '.'.join(str(num) for num in numversion)
description = "a repository of entities / relations for knowledge management"
@@ -43,7 +43,7 @@
'logilab-common': '>= 0.55.2',
'logilab-mtconverter': '>= 0.8.0',
'rql': '>= 0.28.0',
- 'yams': '>= 0.30.4',
+ 'yams': '>= 0.32.0',
'docutils': '>= 0.6',
#gettext # for xgettext, msgcat, etc...
# web dependancies
@@ -52,7 +52,7 @@
'Twisted': '',
# XXX graphviz
# server dependencies
- 'logilab-database': '>= 1.4.0',
+ 'logilab-database': '>= 1.5.0',
'pysqlite': '>= 2.5.5', # XXX install pysqlite2
}
--- a/cwvreg.py Thu Apr 21 17:25:32 2011 +0200
+++ b/cwvreg.py Wed Apr 27 09:55:17 2011 +0200
@@ -312,6 +312,10 @@
kwargs['clear'] = True
super(ETypeRegistry, self).register(obj, **kwargs)
+ def iter_classes(self):
+ for etype in self.vreg.schema.entities():
+ yield self.etype_class(etype)
+
@cached
def parent_classes(self, etype):
if etype == 'Any':
@@ -835,18 +839,24 @@
return self['views'].select(__vid, req, rset=rset, **kwargs)
+import decimal
from datetime import datetime, date, time, timedelta
-YAMS_TO_PY = {
- 'Boolean': bool,
+YAMS_TO_PY = { # XXX unify with yams.constraints.BASE_CONVERTERS?
'String' : unicode,
+ 'Bytes': Binary,
'Password': str,
- 'Bytes': Binary,
+
+ 'Boolean': bool,
'Int': int,
'Float': float,
- 'Date': date,
- 'Datetime': datetime,
- 'Time': time,
- 'Interval': timedelta,
+ 'Decimal': decimal.Decimal,
+
+ 'Date': date,
+ 'Datetime': datetime,
+ 'TZDatetime': datetime,
+ 'Time': time,
+ 'TZTime': time,
+ 'Interval': timedelta,
}
--- a/dataimport.py Thu Apr 21 17:25:32 2011 +0200
+++ b/dataimport.py Wed Apr 27 09:55:17 2011 +0200
@@ -133,7 +133,7 @@
"""
for idx, item in enumerate(iterable):
yield item
- if idx % number:
+ if not idx % number:
func()
func()
--- a/debian/changelog Thu Apr 21 17:25:32 2011 +0200
+++ b/debian/changelog Wed Apr 27 09:55:17 2011 +0200
@@ -1,6 +1,24 @@
+cubicweb (3.12.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Nicolas Chauvat <nicolas.chauvat@logilab.fr> Mon, 11 Apr 2011 22:15:21 +0200
+
+cubicweb (3.12.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 06 Apr 2011 23:25:12 +0200
+
+cubicweb (3.12.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Alexandre Fayolle <alexandre.fayolle@logilab.fr> Fri, 01 Apr 2011 15:59:37 +0200
+
cubicweb (3.11.2-1) unstable; urgency=low
- * new upstream release
+ * new upstream release
-- Nicolas Chauvat <nicolas.chauvat@logilab.fr> Mon, 28 Mar 2011 19:18:54 +0200
--- a/debian/control Thu Apr 21 17:25:32 2011 +0200
+++ b/debian/control Wed Apr 27 09:55:17 2011 +0200
@@ -33,7 +33,7 @@
Conflicts: cubicweb-multisources
Replaces: cubicweb-multisources
Provides: cubicweb-multisources
-Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.4.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
+Depends: ${misc:Depends}, ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.5.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
Recommends: pyro (<< 4.0.0), cubicweb-documentation (= ${source:Version})
Description: server part of the CubicWeb framework
CubicWeb is a semantic web application framework.
@@ -97,7 +97,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.30.4), python-rql (>= 0.28.0), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.55.2), python-yams (>= 0.32.0), python-rql (>= 0.28.0), python-lxml
Recommends: python-simpletal (>= 4.0), python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/devtools/__init__.py Thu Apr 21 17:25:32 2011 +0200
+++ b/devtools/__init__.py Wed Apr 27 09:55:17 2011 +0200
@@ -384,7 +384,7 @@
def get_cnx(self):
- """return Connection object ont he current repository"""
+ """return Connection object on the current repository"""
from cubicweb.dbapi import in_memory_cnx
repo = self.get_repo()
sources = self.config.sources()
--- a/devtools/fill.py Thu Apr 21 17:25:32 2011 +0200
+++ b/devtools/fill.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,5 +1,5 @@
# -*- coding: iso-8859-1 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -152,6 +152,8 @@
base = datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
return self._constrained_generate(entity, attrname, base, timedelta(hours=1), index)
+ generate_tzdatetime = generate_datetime # XXX implementation should add a timezone
+
def generate_date(self, entity, attrname, index):
"""generates a random date (format is 'yyyy-mm-dd')"""
base = date(randint(2000, 2010), 1, 1) + timedelta(randint(1, 365))
@@ -166,6 +168,8 @@
"""generates a random time (format is ' HH:MM')"""
return time(11, index%60) #'11:%02d' % (index % 60)
+ generate_tztime = generate_time # XXX implementation should add a timezone
+
def generate_bytes(self, entity, attrname, index, format=None):
fakefile = Binary("%s%s" % (attrname, index))
fakefile.filename = u"file_%s" % attrname
@@ -441,7 +445,7 @@
constraints = [c for c in rdef.constraints
if isinstance(c, RQLConstraint)]
if constraints:
- restrictions = ', '.join(c.restriction for c in constraints)
+ restrictions = ', '.join(c.expression for c in constraints)
q += ', %s' % restrictions
# restrict object eids if possible
# XXX the attempt to restrict below in completely wrong
--- a/devtools/testlib.py Thu Apr 21 17:25:32 2011 +0200
+++ b/devtools/testlib.py Wed Apr 27 09:55:17 2011 +0200
@@ -37,7 +37,7 @@
from logilab.common.pytest import nocoverage, pause_tracing, resume_tracing
from logilab.common.debugger import Debugger
from logilab.common.umessage import message_from_string
-from logilab.common.decorators import cached, classproperty, clear_cache
+from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod
from logilab.common.deprecation import deprecated, class_deprecated
from logilab.common.shellutils import getlogin
@@ -46,7 +46,7 @@
from cubicweb.dbapi import ProgrammingError, DBAPISession, repo_connect
from cubicweb.sobjects import notification
from cubicweb.web import Redirect, application
-from cubicweb.server.session import security_enabled
+from cubicweb.server.session import Session, security_enabled
from cubicweb.server.hook import SendMailOp
from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
from cubicweb.devtools import BASE_URL, fake, htmlparser, DEFAULT_EMPTY_DB_ID
@@ -354,13 +354,24 @@
else:
return req.user
- def create_user(self, login, groups=('users',), password=None, req=None,
+ @iclassmethod # XXX turn into a class method
+ def create_user(self, req, login=None, groups=('users',), password=None,
commit=True, **kwargs):
"""create and return a new user entity"""
+ if isinstance(req, basestring):
+ warn('[3.12] create_user arguments are now (req, login[, groups, password, commit, **kwargs])',
+ DeprecationWarning, stacklevel=1)
+ if not isinstance(groups, (tuple, list)):
+ password = groups
+ groups = login
+ elif isinstance(login, tuple):
+ groups = login
+ login = req
+ if req is None:
+ assert not isinstance(self, type)
+ req = self._orig_cnx[0].request()
if password is None:
password = login.encode('utf8')
- if req is None:
- req = self._orig_cnx[0].request()
user = req.create_entity('CWUser', login=unicode(login),
upassword=password, **kwargs)
req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
@@ -368,9 +379,37 @@
{'x': user.eid})
user.cw_clear_relation_cache('in_group', 'subject')
if commit:
- req.cnx.commit()
+ try:
+ req.commit() # req is a session
+ except AttributeError:
+ req.cnx.commit()
return user
+ @iclassmethod # XXX turn into a class method
+ def grant_permission(self, session, entity, group, pname=None, plabel=None):
+ """insert a permission on an entity. Will have to commit the main
+ connection to be considered
+ """
+ if not isinstance(session, Session):
+ warn('[3.12] grant_permission arguments are now (session, entity, group, pname[, plabel])',
+ DeprecationWarning, stacklevel=1)
+ plabel = pname
+ pname = group
+ group = entity
+ entity = session
+ assert not isinstance(self, type)
+ session = self.session
+ pname = unicode(pname)
+ plabel = plabel and unicode(plabel) or unicode(group)
+ e = entity.eid
+ with security_enabled(session, False, False):
+ peid = session.execute(
+ 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,'
+ 'X require_group G, E require_permission X '
+ 'WHERE G name %(group)s, E eid %(e)s',
+ locals())[0][0]
+ return peid
+
def login(self, login, **kwargs):
"""return a connection for the given login/password"""
if login == self.admlogin:
@@ -439,21 +478,6 @@
# other utilities #########################################################
- def grant_permission(self, entity, group, pname, plabel=None):
- """insert a permission on an entity. Will have to commit the main
- connection to be considered
- """
- pname = unicode(pname)
- plabel = plabel and unicode(plabel) or unicode(group)
- e = entity.eid
- with security_enabled(self.session, False, False):
- peid = self.execute(
- 'INSERT CWPermission X: X name %(pname)s, X label %(plabel)s,'
- 'X require_group G, E require_permission X '
- 'WHERE G name %(group)s, E eid %(e)s',
- locals())[0][0]
- return peid
-
@contextmanager
def temporary_appobjects(self, *appobjects):
self.vreg._loadedmods.setdefault(self.__module__, {})
--- a/doc/book/en/annexes/faq.rst Thu Apr 21 17:25:32 2011 +0200
+++ b/doc/book/en/annexes/faq.rst Wed Apr 27 09:55:17 2011 +0200
@@ -380,11 +380,14 @@
You can prefer use a migration script similar to this shell invocation instead::
$ cubicweb-ctl shell <instance>
+ >>> from cubicweb import Binary
>>> from cubicweb.server.utils import crypt_password
>>> crypted = crypt_password('joepass')
>>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
>>> joe = rset.get_entity(0,0)
- >>> joe.set_attributes(upassword=crypted)
+ >>> joe.set_attributes(upassword=Binary(crypted))
+
+Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
The more experimented people would use RQL request directly::
--- a/doc/tools/pyjsrest.py Thu Apr 21 17:25:32 2011 +0200
+++ b/doc/tools/pyjsrest.py Wed Apr 27 09:55:17 2011 +0200
@@ -153,9 +153,6 @@
'jquery.flot.js',
'jquery.corner.js',
'jquery.ui.js',
- 'ui.core.js',
- 'ui.tabs.js',
- 'ui.slider.js',
'excanvas.js',
'gmap.utility.labeledmarker.js',
--- a/entities/test/unittest_base.py Thu Apr 21 17:25:32 2011 +0200
+++ b/entities/test/unittest_base.py Wed Apr 27 09:55:17 2011 +0200
@@ -34,7 +34,8 @@
class BaseEntityTC(CubicWebTC):
def setup_database(self):
- self.member = self.create_user('member')
+ req = self.request()
+ self.member = self.create_user(req, 'member')
--- a/entities/test/unittest_wfobjs.py Thu Apr 21 17:25:32 2011 +0200
+++ b/entities/test/unittest_wfobjs.py Wed Apr 27 09:55:17 2011 +0200
@@ -95,13 +95,15 @@
class WorkflowTC(CubicWebTC):
def setup_database(self):
+ req = self.request()
rschema = self.schema['in_state']
for rdef in rschema.rdefs.values():
self.assertEqual(rdef.cardinality, '1*')
- self.member = self.create_user('member')
+ self.member = self.create_user(req, 'member')
def test_workflow_base(self):
- e = self.create_user('toto')
+ req = self.request()
+ e = self.create_user(req, 'toto')
iworkflowable = e.cw_adapt_to('IWorkflowable')
self.assertEqual(iworkflowable.state, 'activated')
iworkflowable.change_state('deactivated', u'deactivate 1')
@@ -170,13 +172,14 @@
self.assertEqual(trinfo.transition.name, 'deactivate')
def test_goback_transition(self):
+ req = self.request()
wf = self.session.user.cw_adapt_to('IWorkflowable').current_workflow
asleep = wf.add_state('asleep')
wf.add_transition('rest', (wf.state_by_name('activated'),
wf.state_by_name('deactivated')),
asleep)
wf.add_transition('wake up', asleep)
- user = self.create_user('stduser')
+ user = self.create_user(req, 'stduser')
iworkflowable = user.cw_adapt_to('IWorkflowable')
iworkflowable.fire_transition('rest')
self.commit()
@@ -196,7 +199,8 @@
def _test_stduser_deactivate(self):
ueid = self.member.eid
- self.create_user('tutu')
+ req = self.request()
+ self.create_user(req, 'tutu')
cnx = self.login('tutu')
req = self.request()
iworkflowable = req.entity_from_eid(self.member.eid).cw_adapt_to('IWorkflowable')
@@ -393,7 +397,8 @@
class CustomWorkflowTC(CubicWebTC):
def setup_database(self):
- self.member = self.create_user('member')
+ req = self.request()
+ self.member = self.create_user(req, 'member')
def test_custom_wf_replace_state_no_history(self):
"""member in inital state with no previous history, state is simply
@@ -493,7 +498,8 @@
def test_auto_transition_fired(self):
wf = self.setup_custom_wf()
- user = self.create_user('member')
+ req = self.request()
+ user = self.create_user(req, 'member')
iworkflowable = user.cw_adapt_to('IWorkflowable')
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': user.eid})
@@ -523,7 +529,8 @@
def test_auto_transition_custom_initial_state_fired(self):
wf = self.setup_custom_wf()
- user = self.create_user('member', surname=u'toto')
+ req = self.request()
+ user = self.create_user(req, 'member', surname=u'toto')
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': user.eid})
self.commit()
@@ -538,7 +545,8 @@
type=u'auto', conditions=({'expr': u'X surname "toto"',
'mainvars': u'X'},))
self.commit()
- user = self.create_user('member', surname=u'toto')
+ req = self.request()
+ user = self.create_user(req, 'member', surname=u'toto')
self.commit()
iworkflowable = user.cw_adapt_to('IWorkflowable')
self.assertEqual(iworkflowable.state, 'dead')
@@ -554,7 +562,8 @@
self.s_deactivated = self.wf.state_by_name('deactivated').eid
self.s_dummy = self.wf.add_state(u'dummy').eid
self.wf.add_transition(u'dummy', (self.s_deactivated,), self.s_dummy)
- ueid = self.create_user('stduser', commit=False).eid
+ req = self.request()
+ ueid = self.create_user(req, 'stduser', commit=False).eid
# test initial state is set
rset = self.execute('Any N WHERE S name N, X in_state S, X eid %(x)s',
{'x' : ueid})
--- a/entity.py Thu Apr 21 17:25:32 2011 +0200
+++ b/entity.py Wed Apr 27 09:55:17 2011 +0200
@@ -28,7 +28,7 @@
from rql.utils import rqlvar_maker
-from cubicweb import Unauthorized, typed_eid
+from cubicweb import Unauthorized, typed_eid, neg_role
from cubicweb.rset import ResultSet
from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
@@ -157,6 +157,7 @@
def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
settype=True, ordermethod='fetch_order'):
"""return a rql to fetch all entities of the class type"""
+ # XXX update api and implementation to AST manipulation (see unrelated rql)
restrictions = restriction or []
if settype:
restrictions.append('%s is %s' % (mainvar, cls.__regid__))
@@ -165,6 +166,7 @@
selection = [mainvar]
orderby = []
# start from 26 to avoid possible conflicts with X
+ # XXX not enough to be sure it'll be no conflicts
varmaker = rqlvar_maker(index=26)
cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
orderby, restrictions, user, ordermethod)
@@ -762,7 +764,7 @@
# generic vocabulary methods ##############################################
def cw_unrelated_rql(self, rtype, targettype, role, ordermethod=None,
- vocabconstraints=True):
+ vocabconstraints=True):
"""build a rql to fetch `targettype` entities unrelated to this entity
using (rtype, role) relation.
@@ -772,58 +774,83 @@
ordermethod = ordermethod or 'fetch_unrelated_order'
if isinstance(rtype, basestring):
rtype = self._cw.vreg.schema.rschema(rtype)
+ rdef = rtype.role_rdef(self.e_schema, targettype, role)
+ rewriter = RQLRewriter(self._cw)
+ # initialize some variables according to the `role` of `self` in the
+ # relation:
+ # * variable for myself (`evar`) and searched entities (`searchvedvar`)
+ # * entity type of the subject (`subjtype`) and of the object
+ # (`objtype`) of the relation
if role == 'subject':
evar, searchedvar = 'S', 'O'
subjtype, objtype = self.e_schema, targettype
else:
searchedvar, evar = 'S', 'O'
objtype, subjtype = self.e_schema, targettype
+ # initialize some variables according to `self` existance
+ if rdef.role_cardinality(neg_role(role)) in '?1':
+ # if cardinality in '1?', we want a target entity which isn't
+ # already linked using this relation
+ if searchedvar == 'S':
+ restriction = ['NOT S %s ZZ' % rtype]
+ else:
+ restriction = ['NOT ZZ %s O' % rtype]
+ elif self.has_eid():
+ # elif we have an eid, we don't want a target entity which is
+ # already linked to ourself through this relation
+ restriction = ['NOT S %s O' % rtype]
+ else:
+ restriction = []
if self.has_eid():
- restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
+ restriction += ['%s eid %%(x)s' % evar]
args = {'x': self.eid}
if role == 'subject':
- securitycheck_args = {'fromeid': self.eid}
+ sec_check_args = {'fromeid': self.eid}
else:
- securitycheck_args = {'toeid': self.eid}
+ sec_check_args = {'toeid': self.eid}
+ existant = None # instead of 'SO', improve perfs
else:
- restriction = []
args = {}
- securitycheck_args = {}
- rdef = rtype.role_rdef(self.e_schema, targettype, role)
- insertsecurity = (rdef.has_local_role('add') and not
- rdef.has_perm(self._cw, 'add', **securitycheck_args))
- # XXX consider constraint.mainvars to check if constraint apply
+ sec_check_args = {}
+ existant = searchedvar
+ # retreive entity class for targettype to compute base rql
+ etypecls = self._cw.vreg['etypes'].etype_class(targettype)
+ rql = etypecls.fetch_rql(self._cw.user, restriction,
+ mainvar=searchedvar, ordermethod=ordermethod)
+ select = self._cw.vreg.parse(self._cw, rql, args).children[0]
+ # insert RQL expressions for schema constraints into the rql syntax tree
if vocabconstraints:
# RQLConstraint is a subclass for RQLVocabularyConstraint, so they
# will be included as well
- restriction += [cstr.restriction for cstr in rdef.constraints
- if isinstance(cstr, RQLVocabularyConstraint)]
+ cstrcls = RQLVocabularyConstraint
else:
- restriction += [cstr.restriction for cstr in rdef.constraints
- if isinstance(cstr, RQLConstraint)]
- etypecls = self._cw.vreg['etypes'].etype_class(targettype)
- rql = etypecls.fetch_rql(self._cw.user, restriction,
- mainvar=searchedvar, ordermethod=ordermethod)
+ cstrcls = RQLConstraint
+ for cstr in rdef.constraints:
+ # consider constraint.mainvars to check if constraint apply
+ if isinstance(cstr, cstrcls) and searchedvar in cstr.mainvars:
+ if not self.has_eid() and evar in cstr.mainvars:
+ continue
+ # compute a varmap suitable to RQLRewriter.rewrite argument
+ varmap = dict((v, v) for v in 'SO' if v in select.defined_vars
+ and v in cstr.mainvars)
+ # rewrite constraint by constraint since we want a AND between
+ # expressions.
+ rewriter.rewrite(select, [(varmap, (cstr,))], select.solutions,
+ args, existant)
+ # insert security RQL expressions granting the permission to 'add' the
+ # relation into the rql syntax tree, if necessary
+ rqlexprs = rdef.get_rqlexprs('add')
+ if rqlexprs and not rdef.has_perm(self._cw, 'add', **sec_check_args):
+ # compute a varmap suitable to RQLRewriter.rewrite argument
+ varmap = dict((v, v) for v in 'SO' if v in select.defined_vars)
+ # rewrite all expressions at once since we want a OR between them.
+ rewriter.rewrite(select, [(varmap, rqlexprs)], select.solutions,
+ args, existant)
# ensure we have an order defined
- if not ' ORDERBY ' in rql:
- before, after = rql.split(' WHERE ', 1)
- rql = '%s ORDERBY %s WHERE %s' % (before, searchedvar, after)
- if insertsecurity:
- rqlexprs = rdef.get_rqlexprs('add')
- rewriter = RQLRewriter(self._cw)
- rqlst = self._cw.vreg.parse(self._cw, rql, args)
- if not self.has_eid():
- existant = searchedvar
- else:
- existant = None # instead of 'SO', improve perfs
- for select in rqlst.children:
- varmap = {}
- for var in 'SO':
- if var in select.defined_vars:
- varmap[var] = var
- rewriter.rewrite(select, [(varmap, rqlexprs)],
- select.solutions, args, existant)
- rql = rqlst.as_string()
+ if not select.orderby:
+ select.add_sort_var(select.defined_vars[searchedvar])
+ # we're done, turn the rql syntax tree as a string
+ rql = select.as_string()
return rql, args
def unrelated(self, rtype, targettype, role='subject', limit=None,
@@ -835,6 +862,7 @@
rql, args = self.cw_unrelated_rql(rtype, targettype, role, ordermethod)
except Unauthorized:
return self._cw.empty_rset()
+ # XXX should be set in unrelated rql when manipulating the AST
if limit is not None:
before, after = rql.split(' WHERE ', 1)
rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
--- a/hooks/metadata.py Thu Apr 21 17:25:32 2011 +0200
+++ b/hooks/metadata.py Wed Apr 27 09:55:17 2011 +0200
@@ -67,13 +67,12 @@
def precommit_event(self):
session = self.session
- for eid in self.get_data():
- if session.deleted_in_transaction(eid):
- # entity have been created and deleted in the same transaction
- continue
- entity = session.entity_from_eid(eid)
- if not entity.created_by:
- session.add_relation(eid, 'created_by', session.user.eid)
+ relations = [(eid, session.user.eid) for eid in self.get_data()
+ # don't consider entities that have been created and
+ # deleted in the same transaction
+ if not session.deleted_in_transaction(eid) and \
+ not session.entity_from_eid(eid).created_by]
+ session.add_relations([('created_by', relations)])
class SetOwnershipHook(MetaDataHook):
--- a/hooks/test/unittest_hooks.py Thu Apr 21 17:25:32 2011 +0200
+++ b/hooks/test/unittest_hooks.py Wed Apr 27 09:55:17 2011 +0200
@@ -42,25 +42,31 @@
def test_html_tidy_hook(self):
req = self.request()
- entity = req.create_entity('Workflow', name=u'wf1', description_format=u'text/html',
- description=u'yo')
+ entity = req.create_entity('Workflow', name=u'wf1',
+ description_format=u'text/html',
+ description=u'yo')
self.assertEqual(entity.description, u'yo')
- entity = req.create_entity('Workflow', name=u'wf2', description_format=u'text/html',
- description=u'<b>yo')
+ entity = req.create_entity('Workflow', name=u'wf2',
+ description_format=u'text/html',
+ description=u'<b>yo')
self.assertEqual(entity.description, u'<b>yo</b>')
- entity = req.create_entity('Workflow', name=u'wf3', description_format=u'text/html',
- description=u'<b>yo</b>')
+ entity = req.create_entity('Workflow', name=u'wf3',
+ description_format=u'text/html',
+ description=u'<b>yo</b>')
self.assertEqual(entity.description, u'<b>yo</b>')
- entity = req.create_entity('Workflow', name=u'wf4', description_format=u'text/html',
- description=u'<b>R&D</b>')
+ entity = req.create_entity('Workflow', name=u'wf4',
+ description_format=u'text/html',
+ description=u'<b>R&D</b>')
self.assertEqual(entity.description, u'<b>R&D</b>')
- entity = req.create_entity('Workflow', name=u'wf5', description_format=u'text/html',
- description=u"<div>c'est <b>l'été")
+ entity = req.create_entity('Workflow', name=u'wf5',
+ description_format=u'text/html',
+ description=u"<div>c'est <b>l'été")
self.assertEqual(entity.description, u"<div>c'est <b>l'été</b></div>")
def test_nonregr_html_tidy_hook_no_update(self):
- entity = self.request().create_entity('Workflow', name=u'wf1', description_format=u'text/html',
- description=u'yo')
+ entity = self.request().create_entity('Workflow', name=u'wf1',
+ description_format=u'text/html',
+ description=u'yo')
entity.set_attributes(name=u'wf2')
self.assertEqual(entity.description, u'yo')
entity.set_attributes(description=u'R&D<p>yo')
@@ -90,7 +96,8 @@
self.assertEqual(entity.owned_by[0].eid, self.session.user.eid)
def test_user_login_stripped(self):
- u = self.create_user(' joe ')
+ req = self.request()
+ u = self.create_user(req, ' joe ')
tname = self.execute('Any L WHERE E login L, E eid %(e)s',
{'e': u.eid})[0][0]
self.assertEqual(tname, 'joe')
@@ -104,7 +111,8 @@
class UserGroupHooksTC(CubicWebTC):
def test_user_synchronization(self):
- self.create_user('toto', password='hop', commit=False)
+ req = self.request()
+ self.create_user(req, 'toto', password='hop', commit=False)
self.assertRaises(AuthenticationError,
self.repo.connect, u'toto', password='hop')
self.commit()
@@ -129,7 +137,8 @@
self.assertEqual(user.groups, set(('managers',)))
def test_user_composite_owner(self):
- ueid = self.create_user('toto').eid
+ req = self.request()
+ ueid = self.create_user(req, 'toto').eid
# composite of euser should be owned by the euser regardless of who created it
self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", U use_email X '
'WHERE U login "toto"')
--- a/hooks/test/unittest_syncschema.py Thu Apr 21 17:25:32 2011 +0200
+++ b/hooks/test/unittest_syncschema.py Wed Apr 27 09:55:17 2011 +0200
@@ -251,7 +251,8 @@
'RT name "surname", E name "CWUser"')
self.commit()
# should not be able anymore to add cwuser without surname
- self.assertRaises(ValidationError, self.create_user, "toto")
+ req = self.request()
+ self.assertRaises(ValidationError, self.create_user, req, "toto")
self.rollback()
self.execute('SET DEF cardinality "?1" '
'WHERE DEF relation_type RT, DEF from_entity E,'
--- a/i18n/de.po Thu Apr 21 17:25:32 2011 +0200
+++ b/i18n/de.po Wed Apr 27 09:55:17 2011 +0200
@@ -737,6 +737,18 @@
msgid "Submit bug report by mail"
msgstr "Diesen Bericht als E-Mail senden"
+msgid "TZDatetime"
+msgstr ""
+
+msgid "TZDatetime_plural"
+msgstr ""
+
+msgid "TZTime"
+msgstr ""
+
+msgid "TZTime_plural"
+msgstr ""
+
#, python-format
msgid "The view %s can not be applied to this query"
msgstr "Die Ansicht %s ist auf diese Anfrage nicht anwendbar."
@@ -2743,6 +2755,9 @@
"zeigt an, welcher Zustand standardmäßig benutzt werden soll, wenn eine "
"Entität erstellt wird"
+msgid "indifferent"
+msgstr "gleichgültig"
+
msgid "info"
msgstr "Information"
--- a/i18n/en.po Thu Apr 21 17:25:32 2011 +0200
+++ b/i18n/en.po Wed Apr 27 09:55:17 2011 +0200
@@ -711,6 +711,18 @@
msgid "Submit bug report by mail"
msgstr ""
+msgid "TZDatetime"
+msgstr "International date and time"
+
+msgid "TZDatetime_plural"
+msgstr "International dates and times"
+
+msgid "TZTime"
+msgstr "International time"
+
+msgid "TZTime_plural"
+msgstr "International times"
+
#, python-format
msgid "The view %s can not be applied to this query"
msgstr ""
@@ -2668,6 +2680,9 @@
"is created"
msgstr ""
+msgid "indifferent"
+msgstr "indifferent"
+
msgid "info"
msgstr ""
--- a/i18n/es.po Thu Apr 21 17:25:32 2011 +0200
+++ b/i18n/es.po Wed Apr 27 09:55:17 2011 +0200
@@ -732,6 +732,18 @@
msgid "Submit bug report by mail"
msgstr "Enviar este reporte por email"
+msgid "TZDatetime"
+msgstr ""
+
+msgid "TZDatetime_plural"
+msgstr ""
+
+msgid "TZTime"
+msgstr ""
+
+msgid "TZTime_plural"
+msgstr ""
+
#, python-format
msgid "The view %s can not be applied to this query"
msgstr "La vista %s no puede ser aplicada a esta búsqueda"
@@ -2773,6 +2785,9 @@
msgstr ""
"Indica cual estado deberá ser utilizado por defecto al crear una entidad"
+msgid "indifferent"
+msgstr "indifferente"
+
msgid "info"
msgstr "Información del Sistema"
--- a/i18n/fr.po Thu Apr 21 17:25:32 2011 +0200
+++ b/i18n/fr.po Wed Apr 27 09:55:17 2011 +0200
@@ -449,7 +449,7 @@
msgstr "Date et heure"
msgid "Datetime_plural"
-msgstr "Date et heure"
+msgstr "Dates et heures"
msgid "Decimal"
msgstr "Nombre décimal"
@@ -736,6 +736,18 @@
msgid "Submit bug report by mail"
msgstr "Soumettre ce rapport par email"
+msgid "TZDatetime"
+msgstr "Date et heure internationale"
+
+msgid "TZDatetime_plural"
+msgstr "Dates et heures internationales"
+
+msgid "TZTime"
+msgstr "Heure internationale"
+
+msgid "TZTime_plural"
+msgstr "Heures internationales"
+
#, python-format
msgid "The view %s can not be applied to this query"
msgstr "La vue %s ne peut être appliquée à cette requête"
@@ -2783,6 +2795,9 @@
msgstr ""
"indique quel état devrait être utilisé par défaut lorsqu'une entité est créée"
+msgid "indifferent"
+msgstr "indifférent"
+
msgid "info"
msgstr "information"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.12.0_Any.py Wed Apr 27 09:55:17 2011 +0200
@@ -0,0 +1,2 @@
+add_entity_type('TZDatetime')
+add_entity_type('TZTime')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/scripts/chpasswd.py Wed Apr 27 09:55:17 2011 +0200
@@ -0,0 +1,48 @@
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import getpass
+
+from cubicweb import Binary
+from cubicweb.server.utils import crypt_password
+
+
+if __args__:
+ login = __args__.pop()
+else:
+ login = raw_input("login ? ")
+
+rset = rql('Any U WHERE U is CWUser, U login %(login)s', {'login': login})
+
+if len(rset) != 1:
+ sys.exit("user '%s' does not exist!" % login)
+
+pass1 = getpass.getpass(prompt='Enter new password ? ')
+pass2 = getpass.getpass(prompt='Confirm ? ')
+
+if pass1 != pass2:
+ sys.exit("passwords don't match!")
+
+crypted = crypt_password(pass1)
+
+cwuser = rset.get_entity(0,0)
+cwuser.set_attributes(upassword=Binary(crypted))
+commit()
+
+print("password updated.")
--- a/schema.py Thu Apr 21 17:25:32 2011 +0200
+++ b/schema.py Wed Apr 27 09:55:17 2011 +0200
@@ -28,6 +28,7 @@
from logilab.common.decorators import cached, clear_cache, monkeypatch
from logilab.common.logging_ext import set_log_methods
from logilab.common.deprecation import deprecated, class_moved
+from logilab.common.textutils import splitstrip
from logilab.common.graph import get_cycles
from logilab.common.compat import any
@@ -179,35 +180,6 @@
__builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name)
-# rql expression utilities function ############################################
-
-def guess_rrqlexpr_mainvars(expression):
- defined = set(split_expression(expression))
- mainvars = []
- if 'S' in defined:
- mainvars.append('S')
- if 'O' in defined:
- mainvars.append('O')
- if 'U' in defined:
- mainvars.append('U')
- if not mainvars:
- raise Exception('unable to guess selection variables')
- return ','.join(sorted(mainvars))
-
-def split_expression(rqlstring):
- for expr in rqlstring.split(','):
- for noparen in expr.split('('):
- for word in noparen.split():
- yield word
-
-def normalize_expression(rqlstring):
- """normalize an rql expression to ease schema synchronization (avoid
- suppressing and reinserting an expression if only a space has been added/removed
- for instance)
- """
- return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
-
-
# Schema objects definition ###################################################
def ERSchema_display_name(self, req, form='', context=None):
@@ -640,175 +612,57 @@
def schema_by_eid(self, eid):
return self._eid_index[eid]
-
-# Possible constraints ########################################################
-
-class BaseRQLConstraint(BaseConstraint):
- """base class for rql constraints
- """
- distinct_query = None
-
- def __init__(self, restriction, mainvars=None):
- self.restriction = normalize_expression(restriction)
- if mainvars is None:
- mainvars = guess_rrqlexpr_mainvars(restriction)
- else:
- normmainvars = []
- for mainvar in mainvars.split(','):
- mainvar = mainvar.strip()
- if not mainvar.isalpha():
- raise Exception('bad mainvars %s' % mainvars)
- normmainvars.append(mainvar)
- assert mainvars, 'bad mainvars %s' % mainvars
- mainvars = ','.join(sorted(normmainvars))
- self.mainvars = mainvars
-
- def serialize(self):
- # start with a comma for bw compat, see below
- return ';' + self.mainvars + ';' + self.restriction
-
- @classmethod
- def deserialize(cls, value):
- # XXX < 3.5.10 bw compat
- if not value.startswith(';'):
- return cls(value)
- _, mainvars, restriction = value.split(';', 2)
- return cls(restriction, mainvars)
-
- def check(self, entity, rtype, value):
- """return true if the value satisfy the constraint, else false"""
- # implemented as a hook in the repository
- return 1
-
- def repo_check(self, session, eidfrom, rtype, eidto):
- """raise ValidationError if the relation doesn't satisfy the constraint
- """
- pass # this is a vocabulary constraint, not enforce XXX why?
-
- def __str__(self):
- if self.distinct_query:
- selop = 'Any'
- else:
- selop = 'DISTINCT Any'
- return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop,
- self.mainvars, self.restriction)
-
- def __repr__(self):
- return '<%s @%#x>' % (self.__str__(), id(self))
-
-
-class RQLVocabularyConstraint(BaseRQLConstraint):
- """the rql vocabulary constraint :
-
- limit the proposed values to a set of entities returned by a rql query,
- but this is not enforced at the repository level
-
- restriction is additional rql restriction that will be added to
- a predefined query, where the S and O variables respectivly represent
- the subject and the object of the relation
-
- mainvars is a string that should be used as selection variable (eg
- `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
- done to guess it according to variable used in the expression.
- """
-
-
-class RepoEnforcedRQLConstraintMixIn(object):
+# Bases for manipulating RQL in schema #########################################
- def __init__(self, restriction, mainvars=None, msg=None):
- super(RepoEnforcedRQLConstraintMixIn, self).__init__(restriction, mainvars)
- self.msg = msg
-
- def serialize(self):
- # start with a semicolon for bw compat, see below
- return ';%s;%s\n%s' % (self.mainvars, self.restriction,
- self.msg or '')
-
- def deserialize(cls, value):
- # XXX < 3.5.10 bw compat
- if not value.startswith(';'):
- return cls(value)
- value, msg = value.split('\n', 1)
- _, mainvars, restriction = value.split(';', 2)
- return cls(restriction, mainvars, msg)
- deserialize = classmethod(deserialize)
+def guess_rrqlexpr_mainvars(expression):
+ defined = set(split_expression(expression))
+ mainvars = set()
+ if 'S' in defined:
+ mainvars.add('S')
+ if 'O' in defined:
+ mainvars.add('O')
+ if 'U' in defined:
+ mainvars.add('U')
+ if not mainvars:
+ raise Exception('unable to guess selection variables')
+ return mainvars
- def repo_check(self, session, eidfrom, rtype, eidto=None):
- """raise ValidationError if the relation doesn't satisfy the constraint
- """
- if not self.match_condition(session, eidfrom, eidto):
- # XXX at this point if both or neither of S and O are in mainvar we
- # dunno if the validation error `occurred` on eidfrom or eidto (from
- # user interface point of view)
- #
- # possible enhancement: check entity being created, it's probably
- # the main eid unless this is a composite relation
- if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
- maineid = eidfrom
- qname = role_name(rtype, 'subject')
- else:
- maineid = eidto
- qname = role_name(rtype, 'object')
- if self.msg:
- msg = session._(self.msg)
- else:
- msg = '%(constraint)s %(restriction)s failed' % {
- 'constraint': session._(self.type()),
- 'restriction': self.restriction}
- raise ValidationError(maineid, {qname: msg})
+def split_expression(rqlstring):
+ for expr in rqlstring.split(','):
+ for noparen in expr.split('('):
+ for word in noparen.split():
+ yield word
- def exec_query(self, session, eidfrom, eidto):
- if eidto is None:
- # checking constraint for an attribute relation
- restriction = 'S eid %(s)s, ' + self.restriction
- args = {'s': eidfrom}
- else:
- restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
- args = {'s': eidfrom, 'o': eidto}
- rql = 'Any %s WHERE %s' % (self.mainvars, restriction)
- if self.distinct_query:
- rql = 'DISTINCT ' + rql
- return session.execute(rql, args, build_descr=False)
-
-
-class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
- """the rql constraint is similar to the RQLVocabularyConstraint but
- are also enforced at the repository level
+def normalize_expression(rqlstring):
+ """normalize an rql expression to ease schema synchronization (avoid
+ suppressing and reinserting an expression if only a space has been
+ added/removed for instance)
"""
- distinct_query = False
-
- def match_condition(self, session, eidfrom, eidto):
- return self.exec_query(session, eidfrom, eidto)
-
-
-class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
- """the unique rql constraint check that the result of the query isn't
- greater than one.
-
- You *must* specify mainvars when instantiating the constraint since there is
- no way to guess it correctly (e.g. if using S,O or U the constraint will
- always be satisfied because we've to use a DISTINCT query).
- """
- # XXX turns mainvars into a required argument in __init__
- distinct_query = True
-
- def match_condition(self, session, eidfrom, eidto):
- return len(self.exec_query(session, eidfrom, eidto)) <= 1
+ return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
class RQLExpression(object):
+ """Base class for RQL expression used in schema (constraints and
+ permissions)
+ """
+ # these are overridden by set_log_methods below
+ # only defining here to prevent pylint from complaining
+ info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
def __init__(self, expression, mainvars, eid):
self.eid = eid # eid of the entity representing this rql expression
- if not isinstance(mainvars, unicode):
- mainvars = unicode(mainvars)
+ assert mainvars, 'bad mainvars %s' % mainvars
+ if isinstance(mainvars, basestring):
+ mainvars = set(splitstrip(mainvars))
+ elif not isinstance(mainvars, set):
+ mainvars = set(mainvars)
self.mainvars = mainvars
self.expression = normalize_expression(expression)
try:
self.rqlst = parse(self.full_rql, print_errors=False).children[0]
except RQLSyntaxError:
raise RQLSyntaxError(expression)
- for mainvar in mainvars.split(','):
+ for mainvar in mainvars:
if len(self.rqlst.defined_vars[mainvar].references()) <= 2:
_LOGGER.warn('You did not use the %s variable in your RQL '
'expression %s', mainvar, self)
@@ -832,6 +686,8 @@
def __setstate__(self, state):
self.__init__(*state)
+ # permission rql expression specific stuff #################################
+
@cached
def transform_has_permission(self):
found = None
@@ -942,12 +798,10 @@
@property
def minimal_rql(self):
- return 'Any %s WHERE %s' % (self.mainvars, self.expression)
+ return 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)),
+ self.expression)
- # these are overridden by set_log_methods below
- # only defining here to prevent pylint from complaining
- info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
-
+# rql expressions for use in permission definition #############################
class ERQLExpression(RQLExpression):
def __init__(self, expression, mainvars=None, eid=None):
@@ -1024,12 +878,153 @@
kwargs['o'] = toeid
return self._check(session, **kwargs)
+
# in yams, default 'update' perm for attributes granted to managers and owners.
# Within cw, we want to default to users who may edit the entity holding the
# attribute.
ybo.DEFAULT_ATTRPERMS['update'] = (
'managers', ERQLExpression('U has_update_permission X'))
+# additional cw specific constraints ###########################################
+
+class BaseRQLConstraint(RRQLExpression, BaseConstraint):
+ """base class for rql constraints"""
+ distinct_query = None
+
+ def serialize(self):
+ # start with a comma for bw compat,see below
+ return ';' + ','.join(sorted(self.mainvars)) + ';' + self.expression
+
+ @classmethod
+ def deserialize(cls, value):
+ # XXX < 3.5.10 bw compat
+ if not value.startswith(';'):
+ return cls(value)
+ _, mainvars, expression = value.split(';', 2)
+ return cls(expression, mainvars)
+
+ def check(self, entity, rtype, value):
+ """return true if the value satisfy the constraint, else false"""
+ # implemented as a hook in the repository
+ return 1
+
+ def __str__(self):
+ if self.distinct_query:
+ selop = 'Any'
+ else:
+ selop = 'DISTINCT Any'
+ return '%s(%s %s WHERE %s)' % (self.__class__.__name__, selop,
+ ','.join(sorted(self.mainvars)),
+ self.expression)
+
+ def __repr__(self):
+ return '<%s @%#x>' % (self.__str__(), id(self))
+
+
+class RQLVocabularyConstraint(BaseRQLConstraint):
+ """the rql vocabulary constraint:
+
+ limit the proposed values to a set of entities returned by a rql query,
+ but this is not enforced at the repository level
+
+ `expression` is additional rql restriction that will be added to
+ a predefined query, where the S and O variables respectivly represent
+ the subject and the object of the relation
+
+ `mainvars` is a set of variables that should be used as selection variable
+ (eg `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
+ done to guess it according to variable used in the expression.
+ """
+
+ def repo_check(self, session, eidfrom, rtype, eidto):
+ """raise ValidationError if the relation doesn't satisfy the constraint
+ """
+ pass # this is a vocabulary constraint, not enforce
+
+
+class RepoEnforcedRQLConstraintMixIn(object):
+
+ def __init__(self, expression, mainvars=None, msg=None):
+ super(RepoEnforcedRQLConstraintMixIn, self).__init__(expression, mainvars)
+ self.msg = msg
+
+ def serialize(self):
+ # start with a semicolon for bw compat, see below
+ return ';%s;%s\n%s' % (','.join(sorted(self.mainvars)), self.expression,
+ self.msg or '')
+
+ def deserialize(cls, value):
+ # XXX < 3.5.10 bw compat
+ if not value.startswith(';'):
+ return cls(value)
+ value, msg = value.split('\n', 1)
+ _, mainvars, expression = value.split(';', 2)
+ return cls(expression, mainvars, msg)
+ deserialize = classmethod(deserialize)
+
+ def repo_check(self, session, eidfrom, rtype, eidto=None):
+ """raise ValidationError if the relation doesn't satisfy the constraint
+ """
+ if not self.match_condition(session, eidfrom, eidto):
+ # XXX at this point if both or neither of S and O are in mainvar we
+ # dunno if the validation error `occurred` on eidfrom or eidto (from
+ # user interface point of view)
+ #
+ # possible enhancement: check entity being created, it's probably
+ # the main eid unless this is a composite relation
+ if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
+ maineid = eidfrom
+ qname = role_name(rtype, 'subject')
+ else:
+ maineid = eidto
+ qname = role_name(rtype, 'object')
+ if self.msg:
+ msg = session._(self.msg)
+ else:
+ msg = '%(constraint)s %(expression)s failed' % {
+ 'constraint': session._(self.type()),
+ 'expression': self.expression}
+ raise ValidationError(maineid, {qname: msg})
+
+ def exec_query(self, session, eidfrom, eidto):
+ if eidto is None:
+ # checking constraint for an attribute relation
+ expression = 'S eid %(s)s, ' + self.expression
+ args = {'s': eidfrom}
+ else:
+ expression = 'S eid %(s)s, O eid %(o)s, ' + self.expression
+ args = {'s': eidfrom, 'o': eidto}
+ rql = 'Any %s WHERE %s' % (','.join(sorted(self.mainvars)), expression)
+ if self.distinct_query:
+ rql = 'DISTINCT ' + rql
+ return session.execute(rql, args, build_descr=False)
+
+
+class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
+ """the rql constraint is similar to the RQLVocabularyConstraint but
+ are also enforced at the repository level
+ """
+ distinct_query = False
+
+ def match_condition(self, session, eidfrom, eidto):
+ return self.exec_query(session, eidfrom, eidto)
+
+
+class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
+ """the unique rql constraint check that the result of the query isn't
+ greater than one.
+
+ You *must* specify `mainvars` when instantiating the constraint since there
+ is no way to guess it correctly (e.g. if using S,O or U the constraint will
+ always be satisfied because we've to use a DISTINCT query).
+ """
+ # XXX turns mainvars into a required argument in __init__
+ distinct_query = True
+
+ def match_condition(self, session, eidfrom, eidto):
+ return len(self.exec_query(session, eidfrom, eidto)) <= 1
+
+
# workflow extensions #########################################################
from yams.buildobjs import _add_relation as yams_add_relation
--- a/server/hook.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/hook.py Wed Apr 27 09:55:17 2011 +0200
@@ -270,13 +270,17 @@
'session_open', 'session_close'))
ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
-def _iter_kwargs(entities, kwargs):
- if not entities:
+def _iter_kwargs(entities, eids_from_to, kwargs):
+ if not entities and not eids_from_to:
yield kwargs
- else:
+ elif entities:
for entity in entities:
kwargs['entity'] = entity
yield kwargs
+ else:
+ for subject, object in eids_from_to:
+ kwargs.update({'eidfrom': subject, 'eidto': object})
+ yield kwargs
class HooksRegistry(CWRegistry):
@@ -304,12 +308,19 @@
if 'entities' in kwargs:
assert 'entity' not in kwargs, \
'can\'t pass "entities" and "entity" arguments simultaneously'
+ assert 'eids_from_to' not in kwargs, \
+ 'can\'t pass "entities" and "eids_from_to" arguments simultaneously'
entities = kwargs.pop('entities')
+ eids_from_to = []
+ elif 'eids_from_to' in kwargs:
+ entities = []
+ eids_from_to = kwargs.pop('eids_from_to')
else:
entities = []
+ eids_from_to = []
# by default, hooks are executed with security turned off
with security_enabled(session, read=False):
- for _kwargs in _iter_kwargs(entities, kwargs):
+ for _kwargs in _iter_kwargs(entities, eids_from_to, kwargs):
hooks = sorted(self.possible_objects(session, **_kwargs),
key=lambda x: x.order)
with security_enabled(session, write=False):
--- a/server/migractions.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/migractions.py Wed Apr 27 09:55:17 2011 +0200
@@ -438,7 +438,8 @@
'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
'WHERE T eid %%(x)s' % perm,
{'expr': expr, 'exprtype': exprtype,
- 'vars': expression.mainvars, 'x': teid},
+ 'vars': u','.join(sorted(expression.mainvars)),
+ 'x': teid},
ask_confirm=False)
def _synchronize_rschema(self, rtype, syncrdefs=True,
@@ -757,9 +758,9 @@
targeted type is known
"""
instschema = self.repo.schema
- assert not etype in instschema, \
+ eschema = self.fs_schema.eschema(etype)
+ assert eschema.final or not etype in instschema, \
'%s already defined in the instance schema' % etype
- eschema = self.fs_schema.eschema(etype)
confirm = self.verbosity >= 2
groupmap = self.group_mapping()
cstrtypemap = self.cstrtype_mapping()
--- a/server/querier.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/querier.py Wed Apr 27 09:55:17 2011 +0200
@@ -556,6 +556,8 @@
def insert_relation_defs(self):
session = self.session
repo = session.repo
+ edited_entities = {}
+ relations = {}
for subj, rtype, obj in self.relation_defs():
# if a string is given into args instead of an int, we get it here
if isinstance(subj, basestring):
@@ -567,12 +569,21 @@
elif not isinstance(obj, (int, long)):
obj = obj.entity.eid
if repo.schema.rschema(rtype).inlined:
- entity = session.entity_from_eid(subj)
- edited = EditedEntity(entity)
+ if subj not in edited_entities:
+ entity = session.entity_from_eid(subj)
+ edited = EditedEntity(entity)
+ edited_entities[subj] = edited
+ else:
+ edited = edited_entities[subj]
edited.edited_attribute(rtype, obj)
- repo.glob_update_entity(session, edited)
else:
- repo.glob_add_relation(session, subj, rtype, obj)
+ if rtype in relations:
+ relations[rtype].append((subj, obj))
+ else:
+ relations[rtype] = [(subj, obj)]
+ repo.glob_add_relations(session, relations)
+ for edited in edited_entities.itervalues():
+ repo.glob_update_entity(session, edited)
class QuerierHelper(object):
--- a/server/repository.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/repository.py Wed Apr 27 09:55:17 2011 +0200
@@ -1375,19 +1375,45 @@
def glob_add_relation(self, session, subject, rtype, object):
"""add a relation to the repository"""
- if server.DEBUG & server.DBG_REPO:
- print 'ADD relation', subject, rtype, object
- source = self.locate_relation_source(session, subject, rtype, object)
- if source.should_call_hooks:
- del_existing_rel_if_needed(session, subject, rtype, object)
- self.hm.call_hooks('before_add_relation', session,
- eidfrom=subject, rtype=rtype, eidto=object)
- source.add_relation(session, subject, rtype, object)
- rschema = self.schema.rschema(rtype)
- session.update_rel_cache_add(subject, rtype, object, rschema.symmetric)
- if source.should_call_hooks:
- self.hm.call_hooks('after_add_relation', session,
- eidfrom=subject, rtype=rtype, eidto=object)
+ self.glob_add_relations(session, {rtype: [(subject, object)]})
+
+ def glob_add_relations(self, session, relations):
+ """add several relations to the repository
+
+ relations is a dictionary rtype: [(subj_eid, obj_eid), ...]
+ """
+ sources = {}
+ for rtype, eids_subj_obj in relations.iteritems():
+ if server.DEBUG & server.DBG_REPO:
+ for subject, object in relations:
+ print 'ADD relation', subject, rtype, object
+ for subject, object in eids_subj_obj:
+ source = self.locate_relation_source(session, subject, rtype, object)
+ if source not in sources:
+ relations_by_rtype = {}
+ sources[source] = relations_by_rtype
+ else:
+ relations_by_rtype = sources[source]
+ if rtype in relations_by_rtype:
+ relations_by_rtype[rtype].append((subject, object))
+ else:
+ relations_by_rtype[rtype] = [(subject, object)]
+ for source, relations_by_rtype in sources.iteritems():
+ if source.should_call_hooks:
+ for rtype, source_relations in relations_by_rtype.iteritems():
+ for subject, object in source_relations:
+ del_existing_rel_if_needed(session, subject, rtype, object)
+ self.hm.call_hooks('before_add_relation', session,
+ rtype=rtype, eids_from_to=source_relations)
+ for rtype, source_relations in relations_by_rtype.iteritems():
+ source.add_relations(session, rtype, source_relations)
+ rschema = self.schema.rschema(rtype)
+ for subject, object in source_relations:
+ session.update_rel_cache_add(subject, rtype, object, rschema.symmetric)
+ if source.should_call_hooks:
+ for rtype, source_relations in relations_by_rtype.iteritems():
+ self.hm.call_hooks('after_add_relation', session,
+ rtype=rtype, eids_from_to=source_relations)
def glob_delete_relation(self, session, subject, rtype, object):
"""delete a relation from the repository"""
--- a/server/schemaserial.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/schemaserial.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -566,7 +566,7 @@
yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
'E mainvars %%(v)s, X %s_permission E WHERE X eid %%(x)s' % action,
{'e': unicode(rqlexpr.expression),
- 'v': unicode(rqlexpr.mainvars),
+ 'v': unicode(','.join(sorted(rqlexpr.mainvars))),
't': unicode(rqlexpr.__class__.__name__)})
# update functions
--- a/server/session.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/session.py Wed Apr 27 09:55:17 2011 +0200
@@ -213,14 +213,34 @@
You may use this in hooks when you know both eids of the relation you
want to add.
"""
+ self.add_relations([(rtype, [(fromeid, toeid)])])
+
+ def add_relations(self, relations):
+ '''set many relation using a shortcut similar to the one in add_relation
+
+ relations is a list of 2-uples, the first element of each
+ 2-uple is the rtype, and the second is a list of (fromeid,
+ toeid) tuples
+ '''
+ edited_entities = {}
+ relations_dict = {}
with security_enabled(self, False, False):
- if self.vreg.schema[rtype].inlined:
- entity = self.entity_from_eid(fromeid)
- edited = EditedEntity(entity)
- edited.edited_attribute(rtype, toeid)
+ for rtype, eids in relations:
+ if self.vreg.schema[rtype].inlined:
+ for fromeid, toeid in eids:
+ if fromeid not in edited_entities:
+ entity = self.entity_from_eid(fromeid)
+ edited = EditedEntity(entity)
+ edited_entities[fromeid] = edited
+ else:
+ edited = edited_entities[fromeid]
+ edited.edited_attribute(rtype, toeid)
+ else:
+ relations_dict[rtype] = eids
+ self.repo.glob_add_relations(self, relations_dict)
+ for edited in edited_entities.itervalues():
self.repo.glob_update_entity(self, edited)
- else:
- self.repo.glob_add_relation(self, fromeid, rtype, toeid)
+
def delete_relation(self, fromeid, rtype, toeid):
"""provide direct access to the repository method to delete a relation.
--- a/server/sources/__init__.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/sources/__init__.py Wed Apr 27 09:55:17 2011 +0200
@@ -434,6 +434,13 @@
"""add a relation to the source"""
raise NotImplementedError()
+ def add_relations(self, session, rtype, subj_obj_list):
+ """add a relations to the source"""
+ # override in derived classes if you feel you can
+ # optimize
+ for subject, object in subj_obj_list:
+ self.add_relation(session, subject, rtype, object)
+
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
raise NotImplementedError()
--- a/server/sources/native.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/sources/native.py Wed Apr 27 09:55:17 2011 +0200
@@ -628,22 +628,42 @@
def add_relation(self, session, subject, rtype, object, inlined=False):
"""add a relation to the source"""
- self._add_relation(session, subject, rtype, object, inlined)
+ self._add_relations(session, rtype, [(subject, object)], inlined)
if session.undoable_action('A', rtype):
self._record_tx_action(session, 'tx_relation_actions', 'A',
eid_from=subject, rtype=rtype, eid_to=object)
- def _add_relation(self, session, subject, rtype, object, inlined=False):
+ def add_relations(self, session, rtype, subj_obj_list, inlined=False):
+ """add a relations to the source"""
+ self._add_relations(session, rtype, subj_obj_list, inlined)
+ if session.undoable_action('A', rtype):
+ for subject, object in subj_obj_list:
+ self._record_tx_action(session, 'tx_relation_actions', 'A',
+ eid_from=subject, rtype=rtype, eid_to=object)
+
+ def _add_relations(self, session, rtype, subj_obj_list, inlined=False):
"""add a relation to the source"""
+ sql = []
if inlined is False:
- attrs = {'eid_from': subject, 'eid_to': object}
- sql = self.sqlgen.insert('%s_relation' % rtype, attrs)
+ attrs = [{'eid_from': subject, 'eid_to': object}
+ for subject, object in subj_obj_list]
+ sql.append((self.sqlgen.insert('%s_relation' % rtype, attrs[0]), attrs))
else: # used by data import
- etype = session.describe(subject)[0]
- attrs = {'cw_eid': subject, SQL_PREFIX + rtype: object}
- sql = self.sqlgen.update(SQL_PREFIX + etype, attrs,
- ['cw_eid'])
- self.doexec(session, sql, attrs)
+ etypes = {}
+ for subject, object in subj_obj_list:
+ etype = session.describe(subject)[0]
+ if etype in etypes:
+ etypes[etype].append((subject, object))
+ else:
+ etypes[etype] = [(subject, object)]
+ for subj_etype, subj_obj_list in etypes.iteritems():
+ attrs = [{'cw_eid': subject, SQL_PREFIX + rtype: object}
+ for subject, object in subj_obj_list]
+ sql.append((self.sqlgen.update(SQL_PREFIX + etype, attrs[0],
+ ['cw_eid']),
+ attrs))
+ for statement, attrs in sql:
+ self.doexecmany(session, statement, attrs)
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
@@ -1232,7 +1252,7 @@
self.repo.hm.call_hooks('before_add_relation', session,
eidfrom=subj, rtype=rtype, eidto=obj)
# add relation in the database
- self._add_relation(session, subj, rtype, obj, rdef.rtype.inlined)
+ self._add_relations(session, rtype, [(subj, obj)], rdef.rtype.inlined)
# set related cache
session.update_rel_cache_add(subj, rtype, obj, rdef.rtype.symmetric)
self.repo.hm.call_hooks('after_add_relation', session,
--- a/server/sources/rql2sql.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/sources/rql2sql.py Wed Apr 27 09:55:17 2011 +0200
@@ -50,7 +50,9 @@
__docformat__ = "restructuredtext en"
import threading
+from datetime import datetime, time
+from logilab.common.date import utcdatetime, utctime
from logilab.database import FunctionDescr, SQL_FUNCTIONS_REGISTRY
from rql import BadRQLQuery, CoercionError
@@ -82,6 +84,7 @@
newvar.prepare_annotation()
newvar.stinfo['scope'] = select
newvar._q_invariant = False
+ select.selection.append(VariableRef(newvar))
return newvar
def _fill_to_wrap_rel(var, newselect, towrap, schema):
@@ -91,10 +94,12 @@
towrap.add( (var, rel) )
for vref in rel.children[1].iget_nodes(VariableRef):
newivar = _new_var(newselect, vref.name)
- newselect.selection.append(VariableRef(newivar))
_fill_to_wrap_rel(vref.variable, newselect, towrap, schema)
elif rschema.final:
towrap.add( (var, rel) )
+ for vref in rel.children[1].iget_nodes(VariableRef):
+ newivar = _new_var(newselect, vref.name)
+ newivar.stinfo['attrvar'] = (var, rel.r_type)
def rewrite_unstable_outer_join(select, solutions, unstable, schema):
"""if some optional variables are unstable, they should be selected in a
@@ -114,11 +119,6 @@
# extract aliases / selection
newvar = _new_var(newselect, var.name)
newselect.selection = [VariableRef(newvar)]
- for avar in select.defined_vars.itervalues():
- if avar.stinfo['attrvar'] is var:
- newavar = _new_var(newselect, avar.name)
- newavar.stinfo['attrvar'] = newvar
- newselect.selection.append(VariableRef(newavar))
towrap_rels = set()
_fill_to_wrap_rel(var, newselect, towrap_rels, schema)
# extract relations
@@ -1424,6 +1424,14 @@
_id = value
if isinstance(_id, unicode):
_id = _id.encode()
+ # convert timestamp to utc.
+ # expect SET TiME ZONE to UTC at connection opening time.
+ # This shouldn't change anything for datetime without TZ.
+ value = self._args[_id]
+ if isinstance(value, datetime) and value.tzinfo is not None:
+ self._query_attrs[_id] = utcdatetime(value)
+ elif isinstance(value, time) and value.tzinfo is not None:
+ self._query_attrs[_id] = utctime(value)
else:
_id = str(id(constant)).replace('-', '', 1)
self._query_attrs[_id] = value
--- a/server/sqlutils.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/sqlutils.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -25,7 +25,7 @@
from logilab import database as db, common as lgc
from logilab.common.shellutils import ProgressBar
-from logilab.common.date import todate, todatetime
+from logilab.common.date import todate, todatetime, utcdatetime, utctime
from logilab.database.sqlgen import SQLGenerator
from cubicweb import Binary, ConfigurationError
@@ -274,10 +274,15 @@
value = crypt_password(value)
value = self._binary(value)
# XXX needed for sqlite but I don't think it is for other backends
- elif atype == 'Datetime' and isinstance(value, date):
+ # Note: use is __class__ since issubclass(datetime, date)
+ elif atype in ('Datetime', 'TZDatetime') and type(value) is date:
value = todatetime(value)
elif atype == 'Date' and isinstance(value, datetime):
value = todate(value)
+ elif atype == 'TZDatetime' and getattr(value, 'tzinfo', None):
+ value = utcdatetime(value)
+ elif atype == 'TZTime' and getattr(value, 'tzinfo', None):
+ value = utctime(value)
elif isinstance(value, Binary):
value = self._binary(value.getvalue())
attrs[SQL_PREFIX+str(attr)] = value
@@ -326,3 +331,13 @@
sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
sqlite_hooks.append(init_sqlite_connexion)
+
+
+def init_postgres_connexion(cnx):
+ cnx.cursor().execute('SET TIME ZONE UTC')
+ # commit is needed, else setting are lost if the connection is first
+ # rollbacked
+ cnx.commit()
+
+postgres_hooks = SQL_CONNECT_HOOKS.setdefault('postgres', [])
+postgres_hooks.append(init_postgres_connexion)
--- a/server/ssplanner.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/ssplanner.py Wed Apr 27 09:55:17 2011 +0200
@@ -559,6 +559,7 @@
session = self.plan.session
repo = session.repo
edefs = {}
+ relations = {}
# insert relations
if self.children:
result = self.execute_child()
@@ -578,9 +579,14 @@
edefs[eid] = edited = EditedEntity(edef)
edited.edited_attribute(str(rschema), rhsval)
else:
- repo.glob_add_relation(session, lhsval, str(rschema), rhsval)
+ str_rschema = str(rschema)
+ if str_rschema in relations:
+ relations[str_rschema].append((lhsval, rhsval))
+ else:
+ relations[str_rschema] = [(lhsval, rhsval)]
result[i] = newrow
# update entities
+ repo.glob_add_relations(session, relations)
for eid, edited in edefs.iteritems():
repo.glob_update_entity(session, edited)
return result
--- a/server/test/data/schema.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/data/schema.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,7 +18,7 @@
from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
SubjectRelation, RichString, String, Int, Float,
- Boolean, Datetime)
+ Boolean, Datetime, TZDatetime)
from yams.constraints import SizeConstraint
from cubicweb.schema import (WorkflowableEntityType,
RQLConstraint, RQLUniqueConstraint,
@@ -114,6 +114,7 @@
tel = Int()
fax = Int()
datenaiss = Datetime()
+ tzdatenaiss = TZDatetime()
test = Boolean(__permissions__={
'read': ('managers', 'users', 'guests'),
'update': ('managers',),
@@ -219,3 +220,26 @@
class require_state(RelationDefinition):
subject = 'CWPermission'
object = 'State'
+
+class personne_composite(RelationDefinition):
+ subject='Personne'
+ object='Personne'
+ composite='subject'
+
+class personne_inlined(RelationDefinition):
+ subject='Personne'
+ object='Personne'
+ cardinality='?*'
+ inlined=True
+
+
+class login_user(RelationDefinition):
+ subject = 'Personne'
+ object = 'CWUser'
+ cardinality = '??'
+
+class ambiguous_inlined(RelationDefinition):
+ subject = ('Affaire', 'Note')
+ object = 'CWUser'
+ inlined = True
+ cardinality = '?*'
--- a/server/test/data/sources_fti Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-[system]
-
-db-driver = postgres
-db-host = localhost
-db-port =
-adapter = native
-db-name = cw_fti_test
-db-encoding = UTF-8
-db-user = syt
-db-password = syt
-
-[admin]
-login = admin
-password = gingkow
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/sources_postgres Wed Apr 27 09:55:17 2011 +0200
@@ -0,0 +1,14 @@
+[system]
+
+db-driver = postgres
+db-host = localhost
+db-port = 5433
+adapter = native
+db-name = cw_fti_test
+db-encoding = UTF-8
+db-user = syt
+db-password = syt
+
+[admin]
+login = admin
+password = gingkow
--- a/server/test/unittest_fti.py Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-from __future__ import with_statement
-
-import socket
-
-from logilab.common.testlib import SkipTest
-
-from cubicweb.devtools import ApptestConfiguration
-from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.selectors import is_instance
-from cubicweb.entities.adapters import IFTIndexableAdapter
-
-AT_LOGILAB = socket.gethostname().endswith('.logilab.fr')
-
-
-class PostgresFTITC(CubicWebTC):
- config = ApptestConfiguration('data', sourcefile='sources_fti')
-
- @classmethod
- def setUpClass(cls):
- if not AT_LOGILAB:
- raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
-
- def test_occurence_count(self):
- req = self.request()
- c1 = req.create_entity('Card', title=u'c1',
- content=u'cubicweb cubicweb cubicweb')
- c2 = req.create_entity('Card', title=u'c3',
- content=u'cubicweb')
- c3 = req.create_entity('Card', title=u'c2',
- content=u'cubicweb cubicweb')
- self.commit()
- self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
- [[c1.eid], [c3.eid], [c2.eid]])
-
-
- def test_attr_weight(self):
- class CardIFTIndexableAdapter(IFTIndexableAdapter):
- __select__ = is_instance('Card')
- attr_weight = {'title': 'A'}
- with self.temporary_appobjects(CardIFTIndexableAdapter):
- req = self.request()
- c1 = req.create_entity('Card', title=u'c1',
- content=u'cubicweb cubicweb cubicweb')
- c2 = req.create_entity('Card', title=u'c2',
- content=u'cubicweb cubicweb')
- c3 = req.create_entity('Card', title=u'cubicweb',
- content=u'autre chose')
- self.commit()
- self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
- [[c3.eid], [c1.eid], [c2.eid]])
-
- def test_entity_weight(self):
- class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
- __select__ = is_instance('Personne')
- entity_weight = 2.0
- with self.temporary_appobjects(PersonneIFTIndexableAdapter):
- req = self.request()
- c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
- c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
- c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
- self.commit()
- self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
- [[c1.eid], [c3.eid], [c2.eid]])
-
-
-if __name__ == '__main__':
- from logilab.common.testlib import unittest_main
- unittest_main()
--- a/server/test/unittest_ldapuser.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_ldapuser.py Wed Apr 27 09:55:17 2011 +0200
@@ -260,7 +260,8 @@
self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
def test_multiple_entities_from_different_sources(self):
- self.create_user('cochon')
+ req = self.request()
+ self.create_user(req, 'cochon')
self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}))
def test_exists1(self):
@@ -274,16 +275,18 @@
self.assertEqual(rset.rows, [['admin', 'activated'], [SYT, 'activated']])
def test_exists2(self):
- self.create_user('comme')
- self.create_user('cochon')
+ req = self.request()
+ self.create_user(req, 'comme')
+ self.create_user(req, 'cochon')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
rset = self.sexecute('Any GN ORDERBY GN WHERE X in_group G, G name GN, '
'(G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon")))')
self.assertEqual(rset.rows, [['managers'], ['users']])
def test_exists3(self):
- self.create_user('comme')
- self.create_user('cochon')
+ req = self.request()
+ self.create_user(req, 'comme')
+ self.create_user(req, 'cochon')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
@@ -293,9 +296,10 @@
self.assertEqual(sorted(rset.rows), [['managers', 'admin'], ['users', 'comme'], ['users', SYT]])
def test_exists4(self):
- self.create_user('comme')
- self.create_user('cochon', groups=('users', 'guests'))
- self.create_user('billy')
+ req = self.request()
+ self.create_user(req, 'comme')
+ self.create_user(req, 'cochon', groups=('users', 'guests'))
+ self.create_user(req, 'billy')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
@@ -315,9 +319,10 @@
self.assertEqual(sorted(rset.rows), sorted(all.rows))
def test_exists5(self):
- self.create_user('comme')
- self.create_user('cochon', groups=('users', 'guests'))
- self.create_user('billy')
+ req = self.request()
+ self.create_user(req, 'comme')
+ self.create_user(req, 'cochon', groups=('users', 'guests'))
+ self.create_user(req, 'billy')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
self.sexecute('SET X copain Y WHERE X login "comme", Y login "billy"')
@@ -347,7 +352,8 @@
sorted(r[0] for r in afeids + ueids))
def _init_security_test(self):
- self.create_user('iaminguestsgrouponly', groups=('guests',))
+ req = self.request()
+ self.create_user(req, 'iaminguestsgrouponly', groups=('guests',))
cnx = self.login('iaminguestsgrouponly')
return cnx.cursor()
--- a/server/test/unittest_migractions.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_migractions.py Wed Apr 27 09:55:17 2011 +0200
@@ -364,8 +364,9 @@
'X from_entity FE, FE name "Personne",'
'X ordernum O')]
expected = [u'nom', u'prenom', u'sexe', u'promo', u'ass', u'adel', u'titre',
- u'web', u'tel', u'fax', u'datenaiss', u'test', 'description', u'firstname',
- u'creation_date', 'cwuri', u'modification_date']
+ u'web', u'tel', u'fax', u'datenaiss', u'tzdatenaiss', u'test',
+ u'description', u'firstname',
+ u'creation_date', u'cwuri', u'modification_date']
self.assertEqual(rinorder, expected)
# test permissions synchronization ####################################
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_postgres.py Wed Apr 27 09:55:17 2011 +0200
@@ -0,0 +1,83 @@
+from __future__ import with_statement
+
+import socket
+from datetime import datetime
+
+from logilab.common.testlib import SkipTest
+
+from cubicweb.devtools import ApptestConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.selectors import is_instance
+from cubicweb.entities.adapters import IFTIndexableAdapter
+
+AT_LOGILAB = socket.gethostname().endswith('.logilab.fr') # XXX
+
+from unittest_querier import FixedOffset
+
+class PostgresFTITC(CubicWebTC):
+ @classmethod
+ def setUpClass(cls):
+ if not AT_LOGILAB: # XXX here until we can raise SkipTest in setUp to detect we can't connect to the db
+ raise SkipTest('XXX %s: require logilab configuration' % cls.__name__)
+ cls.config = ApptestConfiguration('data', sourcefile='sources_postgres',
+ apphome=cls.datadir)
+
+ def test_occurence_count(self):
+ req = self.request()
+ c1 = req.create_entity('Card', title=u'c1',
+ content=u'cubicweb cubicweb cubicweb')
+ c2 = req.create_entity('Card', title=u'c3',
+ content=u'cubicweb')
+ c3 = req.create_entity('Card', title=u'c2',
+ content=u'cubicweb cubicweb')
+ self.commit()
+ self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+ [[c1.eid], [c3.eid], [c2.eid]])
+
+
+ def test_attr_weight(self):
+ class CardIFTIndexableAdapter(IFTIndexableAdapter):
+ __select__ = is_instance('Card')
+ attr_weight = {'title': 'A'}
+ with self.temporary_appobjects(CardIFTIndexableAdapter):
+ req = self.request()
+ c1 = req.create_entity('Card', title=u'c1',
+ content=u'cubicweb cubicweb cubicweb')
+ c2 = req.create_entity('Card', title=u'c2',
+ content=u'cubicweb cubicweb')
+ c3 = req.create_entity('Card', title=u'cubicweb',
+ content=u'autre chose')
+ self.commit()
+ self.assertEqual(req.execute('Card X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+ [[c3.eid], [c1.eid], [c2.eid]])
+
+ def test_entity_weight(self):
+ class PersonneIFTIndexableAdapter(IFTIndexableAdapter):
+ __select__ = is_instance('Personne')
+ entity_weight = 2.0
+ with self.temporary_appobjects(PersonneIFTIndexableAdapter):
+ req = self.request()
+ c1 = req.create_entity('Personne', nom=u'c1', prenom=u'cubicweb')
+ c2 = req.create_entity('Comment', content=u'cubicweb cubicweb', comments=c1)
+ c3 = req.create_entity('Comment', content=u'cubicweb cubicweb cubicweb', comments=c1)
+ self.commit()
+ self.assertEqual(req.execute('Any X ORDERBY FTIRANK(X) DESC WHERE X has_text "cubicweb"').rows,
+ [[c1.eid], [c3.eid], [c2.eid]])
+
+
+ def test_tz_datetime(self):
+ self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+ {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+ datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+ self.assertEqual(datenaiss.tzinfo, None)
+ self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+ self.commit()
+ self.execute("INSERT Personne X: X nom 'boby', X tzdatenaiss %(date)s",
+ {'date': datetime(1977, 6, 7, 2, 0)})
+ datenaiss = self.execute("Any XD WHERE X nom 'boby', X tzdatenaiss XD")[0][0]
+ self.assertEqual(datenaiss.tzinfo, None)
+ self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 2, 0))
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
--- a/server/test/unittest_querier.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_querier.py Wed Apr 27 09:55:17 2011 +0200
@@ -18,7 +18,7 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""unit tests for modules cubicweb.server.querier and cubicweb.server.ssplanner
"""
-from datetime import date, datetime
+from datetime import date, datetime, timedelta, tzinfo
from logilab.common.testlib import TestCase, unittest_main
from rql import BadRQLQuery, RQLSyntaxError
@@ -32,6 +32,14 @@
from cubicweb.devtools.repotest import tuplify, BaseQuerierTC
from unittest_session import Variable
+class FixedOffset(tzinfo):
+ def __init__(self, hours=0):
+ self.hours = hours
+ def utcoffset(self, dt):
+ return timedelta(hours=self.hours)
+ def dst(self, dt):
+ return timedelta(0)
+
# register priority/severity sorting registered procedure
from rql.utils import register_function, FunctionDescr
@@ -761,18 +769,20 @@
rset = self.execute('Any N WHERE X is CWEType, X name N, X final %(val)s',
{'val': True})
self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
- 'Date', 'Datetime',
- 'Decimal', 'Float',
- 'Int', 'Interval',
- 'Password', 'String',
- 'Time'])
+ 'Date', 'Datetime',
+ 'Decimal', 'Float',
+ 'Int', 'Interval',
+ 'Password', 'String',
+ 'TZDatetime', 'TZTime',
+ 'Time'])
rset = self.execute('Any N WHERE X is CWEType, X name N, X final TRUE')
self.assertEqual(sorted(r[0] for r in rset.rows), ['Boolean', 'Bytes',
- 'Date', 'Datetime',
- 'Decimal', 'Float',
- 'Int', 'Interval',
- 'Password', 'String',
- 'Time'])
+ 'Date', 'Datetime',
+ 'Decimal', 'Float',
+ 'Int', 'Interval',
+ 'Password', 'String',
+ 'TZDatetime', 'TZTime',
+ 'Time'])
def test_select_constant(self):
rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup')
@@ -1213,11 +1223,11 @@
self.assertEqual(rset.description, [('CWUser',)])
def test_update_upassword(self):
- cursor = self.pool['system']
rset = self.execute("INSERT CWUser X: X login 'bob', X upassword %(pwd)s", {'pwd': 'toto'})
self.assertEqual(rset.description[0][0], 'CWUser')
rset = self.execute("SET X upassword %(pwd)s WHERE X is CWUser, X login 'bob'",
{'pwd': 'tutu'})
+ cursor = self.pool['system']
cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'"
% (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX))
passwd = str(cursor.fetchone()[0])
@@ -1227,7 +1237,16 @@
self.assertEqual(len(rset.rows), 1)
self.assertEqual(rset.description, [('CWUser',)])
- # non regression tests ####################################################
+ # ZT datetime tests ########################################################
+
+ def test_tz_datetime(self):
+ self.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s",
+ {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))})
+ datenaiss = self.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0]
+ self.assertEqual(datenaiss.tzinfo, None)
+ self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0))
+
+ # non regression tests #####################################################
def test_nonregr_1(self):
teid = self.execute("INSERT Tag X: X name 'tag'")[0][0]
--- a/server/test/unittest_repository.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_repository.py Wed Apr 27 09:55:17 2011 +0200
@@ -69,11 +69,12 @@
cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
% (namecol, table, finalcol, namecol), {'final': 'TRUE'})
self.assertEqual(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
- (u'Date',), (u'Datetime',),
- (u'Decimal',),(u'Float',),
- (u'Int',),
- (u'Interval',), (u'Password',),
- (u'String',), (u'Time',)])
+ (u'Date',), (u'Datetime',),
+ (u'Decimal',),(u'Float',),
+ (u'Int',),
+ (u'Interval',), (u'Password',),
+ (u'String',),
+ (u'TZDatetime',), (u'TZTime',), (u'Time',)])
sql = ("SELECT etype.cw_eid, etype.cw_name, cstr.cw_eid, rel.eid_to "
"FROM cw_CWUniqueTogetherConstraint as cstr, "
" relations_relation as rel, "
@@ -327,7 +328,7 @@
self.assertEqual(len(constraints), 1)
cstr = constraints[0]
self.assert_(isinstance(cstr, RQLConstraint))
- self.assertEqual(cstr.restriction, 'O final TRUE')
+ self.assertEqual(cstr.expression, 'O final TRUE')
ownedby = schema.rschema('owned_by')
self.assertEqual(ownedby.objects('CWEType'), ('CWUser',))
@@ -684,5 +685,160 @@
req.cnx.commit()
self.assertEqual(cm.exception.errors, {'inline1-subject': u'RQLUniqueConstraint S type T, S inline1 A1, A1 todo_by C, Y type T, Y inline1 A2, A2 todo_by C failed'})
+ def test_add_relations_at_creation_with_del_existing_rel(self):
+ req = self.request()
+ person = req.create_entity('Personne', nom=u'Toto', prenom=u'Lanturlu', sexe=u'M')
+ users_rql = 'Any U WHERE U is CWGroup, U name "users"'
+ users = self.execute(users_rql).get_entity(0, 0)
+ req.create_entity('CWUser',
+ login=u'Toto',
+ upassword=u'firstname',
+ firstname=u'firstname',
+ surname=u'surname',
+ reverse_login_user=person,
+ in_group=users)
+ self.commit()
+
+
+class PerformanceTest(CubicWebTC):
+ def setup_database(self):
+ import logging
+ logger = logging.getLogger('cubicweb.session')
+ #logger.handlers = [logging.StreamHandler(sys.stdout)]
+ logger.setLevel(logging.INFO)
+ self.info = logger.info
+
+ def test_composite_deletion(self):
+ req = self.request()
+ personnes = []
+ t0 = time.time()
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
+ for j in xrange(0, 2000, 100):
+ abraham.set_relations(personne_composite=personnes[j:j+100])
+ t1 = time.time()
+ self.info('creation: %.2gs', (t1 - t0))
+ req.cnx.commit()
+ t2 = time.time()
+ self.info('commit creation: %.2gs', (t2 - t1))
+ self.execute('DELETE Personne P WHERE P eid %(eid)s', {'eid': abraham.eid})
+ t3 = time.time()
+ self.info('deletion: %.2gs', (t3 - t2))
+ req.cnx.commit()
+ t4 = time.time()
+ self.info("commit deletion: %2gs", (t4 - t3))
+
+ def test_add_relation_non_inlined(self):
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ req.cnx.commit()
+ t0 = time.time()
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M',
+ personne_composite=personnes[:100])
+ t1 = time.time()
+ self.info('creation: %.2gs', (t1 - t0))
+ for j in xrange(100, 2000, 100):
+ abraham.set_relations(personne_composite=personnes[j:j+100])
+ t2 = time.time()
+ self.info('more relations: %.2gs', (t2-t1))
+ req.cnx.commit()
+ t3 = time.time()
+ self.info('commit creation: %.2gs', (t3 - t2))
+
+ def test_add_relation_inlined(self):
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ req.cnx.commit()
+ t0 = time.time()
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M',
+ personne_inlined=personnes[:100])
+ t1 = time.time()
+ self.info('creation: %.2gs', (t1 - t0))
+ for j in xrange(100, 2000, 100):
+ abraham.set_relations(personne_inlined=personnes[j:j+100])
+ t2 = time.time()
+ self.info('more relations: %.2gs', (t2-t1))
+ req.cnx.commit()
+ t3 = time.time()
+ self.info('commit creation: %.2gs', (t3 - t2))
+
+
+ def test_session_add_relation(self):
+ """ to be compared with test_session_add_relations"""
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
+ req.cnx.commit()
+ t0 = time.time()
+ add_relation = self.session.add_relation
+ for p in personnes:
+ add_relation(abraham.eid, 'personne_composite', p.eid)
+ req.cnx.commit()
+ t1 = time.time()
+ self.info('add relation: %.2gs', t1-t0)
+
+ def test_session_add_relations (self):
+ """ to be compared with test_session_add_relation"""
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
+ req.cnx.commit()
+ t0 = time.time()
+ add_relations = self.session.add_relations
+ relations = [('personne_composite', [(abraham.eid, p.eid) for p in personnes])]
+ add_relations(relations)
+ req.cnx.commit()
+ t1 = time.time()
+ self.info('add relations: %.2gs', t1-t0)
+ def test_session_add_relation_inlined(self):
+ """ to be compared with test_session_add_relations"""
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
+ req.cnx.commit()
+ t0 = time.time()
+ add_relation = self.session.add_relation
+ for p in personnes:
+ add_relation(abraham.eid, 'personne_inlined', p.eid)
+ req.cnx.commit()
+ t1 = time.time()
+ self.info('add relation (inlined): %.2gs', t1-t0)
+
+ def test_session_add_relations_inlined (self):
+ """ to be compared with test_session_add_relation"""
+ req = self.request()
+ personnes = []
+ for i in xrange(2000):
+ p = req.create_entity('Personne', nom=u'Doe%03d'%i, prenom=u'John', sexe=u'M')
+ personnes.append(p)
+ abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
+ req.cnx.commit()
+ t0 = time.time()
+ add_relations = self.session.add_relations
+ relations = [('personne_inlined', [(abraham.eid, p.eid) for p in personnes])]
+ add_relations(relations)
+ req.cnx.commit()
+ t1 = time.time()
+ self.info('add relations (inlined): %.2gs', t1-t0)
+
+
+
if __name__ == '__main__':
unittest_main()
--- a/server/test/unittest_rql2sql.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_rql2sql.py Wed Apr 27 09:55:17 2011 +0200
@@ -909,7 +909,28 @@
('Any O,AD WHERE NOT S inline1 O, S eid 123, O todo_by AD?',
'''SELECT _O.cw_eid, rel_todo_by0.eid_to
FROM cw_Note AS _S, cw_Affaire AS _O LEFT OUTER JOIN todo_by_relation AS rel_todo_by0 ON (rel_todo_by0.eid_from=_O.cw_eid)
-WHERE (_S.cw_inline1 IS NULL OR _S.cw_inline1!=_O.cw_eid) AND _S.cw_eid=123''')
+WHERE (_S.cw_inline1 IS NULL OR _S.cw_inline1!=_O.cw_eid) AND _S.cw_eid=123'''),
+
+ ('Any X,AE WHERE X multisource_inlined_rel S?, S ambiguous_inlined A, A modification_date AE',
+ '''SELECT _X.cw_eid, _T0.C2
+FROM cw_Card AS _X LEFT OUTER JOIN (SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2
+FROM cw_Affaire AS _S, cw_CWUser AS _A
+WHERE _S.cw_ambiguous_inlined=_A.cw_eid
+UNION ALL
+SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2
+FROM cw_CWUser AS _A, cw_Note AS _S
+WHERE _S.cw_ambiguous_inlined=_A.cw_eid) AS _T0 ON (_X.cw_multisource_inlined_rel=_T0.C0)
+UNION ALL
+SELECT _X.cw_eid, _T0.C2
+FROM cw_Note AS _X LEFT OUTER JOIN (SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2
+FROM cw_Affaire AS _S, cw_CWUser AS _A
+WHERE _S.cw_ambiguous_inlined=_A.cw_eid
+UNION ALL
+SELECT _S.cw_eid AS C0, _A.cw_eid AS C1, _A.cw_modification_date AS C2
+FROM cw_CWUser AS _A, cw_Note AS _S
+WHERE _S.cw_ambiguous_inlined=_A.cw_eid) AS _T0 ON (_X.cw_multisource_inlined_rel=_T0.C0)'''
+ ),
+
]
VIRTUAL_VARS = [
@@ -1225,9 +1246,13 @@
yield self._check, rql, sql
def _checkall(self, rql, sql):
+ if isinstance(rql, tuple):
+ rql, args = rql
+ else:
+ args = None
try:
rqlst = self._prepare(rql)
- r, args, cbs = self.o.generate(rqlst)
+ r, args, cbs = self.o.generate(rqlst, args)
self.assertEqual((r.strip(), args), sql)
except Exception, ex:
print rql
@@ -1239,7 +1264,7 @@
return
def test1(self):
- self._checkall('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s',
+ self._checkall(('Any count(RDEF) WHERE RDEF relation_type X, X eid %(x)s', {'x': None}),
("""SELECT COUNT(T1.C0) FROM (SELECT _RDEF.cw_eid AS C0
FROM cw_CWAttribute AS _RDEF
WHERE _RDEF.cw_relation_type=%(x)s
@@ -1250,7 +1275,7 @@
)
def test2(self):
- self._checkall('Any X WHERE C comments X, C eid %(x)s',
+ self._checkall(('Any X WHERE C comments X, C eid %(x)s', {'x': None}),
('''SELECT rel_comments0.eid_to
FROM comments_relation AS rel_comments0
WHERE rel_comments0.eid_from=%(x)s''', {})
--- a/server/test/unittest_security.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_security.py Wed Apr 27 09:55:17 2011 +0200
@@ -29,7 +29,8 @@
def setup_database(self):
super(BaseSecurityTC, self).setup_database()
- self.create_user('iaminusersgrouponly')
+ req = self.request()
+ self.create_user(req, 'iaminusersgrouponly')
readoriggroups = self.schema['Personne'].permissions['read']
addoriggroups = self.schema['Personne'].permissions['add']
def fix_perm():
@@ -260,7 +261,8 @@
def test_user_can_change_its_upassword(self):
- ueid = self.create_user('user').eid
+ req = self.request()
+ ueid = self.create_user(req, 'user').eid
cnx = self.login('user')
cu = cnx.cursor()
cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
@@ -271,7 +273,8 @@
cnx.close()
def test_user_cant_change_other_upassword(self):
- ueid = self.create_user('otheruser').eid
+ req = self.request()
+ ueid = self.create_user(req, 'otheruser').eid
cnx = self.login('iaminusersgrouponly')
cu = cnx.cursor()
cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
--- a/server/test/unittest_undo.py Thu Apr 21 17:25:32 2011 +0200
+++ b/server/test/unittest_undo.py Wed Apr 27 09:55:17 2011 +0200
@@ -23,9 +23,11 @@
class UndoableTransactionTC(CubicWebTC):
+
def setup_database(self):
+ req = self.request()
self.session.undo_actions = set('CUDAR')
- self.toto = self.create_user('toto', password='toto', groups=('users',),
+ self.toto = self.create_user(req, 'toto', password='toto', groups=('users',),
commit=False)
self.txuuid = self.commit()
@@ -246,7 +248,8 @@
def test_undo_creation_integrity_1(self):
session = self.session
- tutu = self.create_user('tutu', commit=False)
+ req = self.request()
+ tutu = self.create_user(req, 'tutu', commit=False)
txuuid = self.commit()
email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
--- a/sobjects/test/unittest_email.py Thu Apr 21 17:25:32 2011 +0200
+++ b/sobjects/test/unittest_email.py Wed Apr 27 09:55:17 2011 +0200
@@ -54,7 +54,8 @@
self.failIf(rset.rowcount != 1, rset)
def test_security_check(self):
- self.create_user('toto')
+ req = self.request()
+ self.create_user(req, 'toto')
email1 = self.execute('INSERT EmailAddress E: E address "client@client.com", U use_email E WHERE U login "admin"')[0][0]
self.commit()
cnx = self.login('toto')
--- a/sobjects/test/unittest_notification.py Thu Apr 21 17:25:32 2011 +0200
+++ b/sobjects/test/unittest_notification.py Wed Apr 27 09:55:17 2011 +0200
@@ -84,7 +84,7 @@
def test_status_change_view(self):
req = self.request()
- u = self.create_user('toto', req=req)
+ u = self.create_user(req, 'toto')
u.cw_adapt_to('IWorkflowable').fire_transition('deactivate', comment=u'yeah')
self.failIf(MAILBOX)
self.commit()
--- a/test/data/schema.py Thu Apr 21 17:25:32 2011 +0200
+++ b/test/data/schema.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,13 +15,11 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
from yams.buildobjs import (EntityType, String, SubjectRelation,
RelationDefinition)
-from cubicweb.schema import WorkflowableEntityType
+from cubicweb.schema import (WorkflowableEntityType,
+ RQLConstraint, RQLVocabularyConstraint)
class Personne(EntityType):
nom = String(required=True)
@@ -29,7 +27,14 @@
type = String()
travaille = SubjectRelation('Societe')
evaluee = SubjectRelation(('Note', 'Personne'))
- connait = SubjectRelation('Personne', symmetric=True)
+ connait = SubjectRelation(
+ 'Personne', symmetric=True,
+ constraints=[
+ RQLConstraint('NOT S identity O'),
+ # conflicting constraints, see cw_unrelated_rql tests in
+ # unittest_entity.py
+ RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'),
+ RQLVocabularyConstraint('S travaille P, P nom "tutu"')])
class Societe(EntityType):
nom = String()
--- a/test/unittest_entity.py Thu Apr 21 17:25:32 2011 +0200
+++ b/test/unittest_entity.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -19,7 +19,7 @@
"""unit tests for cubicweb.web.views.entities module"""
from datetime import datetime
-
+from logilab.common import tempattr
from cubicweb import Binary, Unauthorized
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.mttransforms import HAS_TAL
@@ -29,6 +29,17 @@
class EntityTC(CubicWebTC):
+ def setUp(self):
+ super(EntityTC, self).setUp()
+ self.backup_dict = {}
+ for cls in self.vreg['etypes'].iter_classes():
+ self.backup_dict[cls] = (cls.fetch_attrs, cls.fetch_order)
+
+ def tearDown(self):
+ super(EntityTC, self).tearDown()
+ for cls in self.vreg['etypes'].iter_classes():
+ cls.fetch_attrs, cls.fetch_order = self.backup_dict[cls]
+
def test_boolean_value(self):
e = self.vreg['etypes'].etype_class('CWUser')(self.request())
self.failUnless(e)
@@ -136,17 +147,19 @@
Note = self.vreg['etypes'].etype_class('Note')
peschema = Personne.e_schema
seschema = Societe.e_schema
- peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '1*'
- peschema.subjrels['connait'].rdef(peschema, peschema).cardinality = '11'
- peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema).cardinality = '1*'
- seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema).cardinality = '1*'
- # testing basic fetch_attrs attribute
- self.assertEqual(Personne.fetch_rql(user),
- 'Any X,AA,AB,AC ORDERBY AA ASC '
- 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
- pfetch_attrs = Personne.fetch_attrs
- sfetch_attrs = Societe.fetch_attrs
+ torestore = []
+ for rdef, card in [(peschema.subjrels['travaille'].rdef(peschema, seschema), '1*'),
+ (peschema.subjrels['connait'].rdef(peschema, peschema), '11'),
+ (peschema.subjrels['evaluee'].rdef(peschema, Note.e_schema), '1*'),
+ (seschema.subjrels['evaluee'].rdef(seschema, Note.e_schema), '1*')]:
+ cm = tempattr(rdef, 'cardinality', card)
+ cm.__enter__()
+ torestore.append(cm)
try:
+ # testing basic fetch_attrs attribute
+ self.assertEqual(Personne.fetch_rql(user),
+ 'Any X,AA,AB,AC ORDERBY AA ASC '
+ 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
# testing unknown attributes
Personne.fetch_attrs = ('bloug', 'beep')
self.assertEqual(Personne.fetch_rql(user), 'Any X WHERE X is Personne')
@@ -185,8 +198,9 @@
'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
# XXX test unauthorized attribute
finally:
- Personne.fetch_attrs = pfetch_attrs
- Societe.fetch_attrs = sfetch_attrs
+ # fetch_attrs restored by generic tearDown
+ for cm in torestore:
+ cm.__exit__(None, None, None)
def test_related_rql_base(self):
Personne = self.vreg['etypes'].etype_class('Personne')
@@ -227,52 +241,90 @@
user = self.request().user
rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
- 'WHERE NOT S use_email O, S eid %(x)s, '
+ 'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, '
'O is EmailAddress, O address AA, O alias AB, O modification_date AC')
def test_unrelated_rql_security_1_user(self):
- self.create_user('toto')
+ req = self.request()
+ self.create_user(req, 'toto')
self.login('toto')
- user = self.request().user
+ user = req.user
rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
- 'WHERE NOT S use_email O, S eid %(x)s, '
+ 'WHERE NOT EXISTS(ZZ use_email O), S eid %(x)s, '
'O is EmailAddress, O address AA, O alias AB, O modification_date AC')
user = self.execute('Any X WHERE X login "admin"').get_entity(0, 0)
rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
- self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
- 'NOT EXISTS(S use_email O), S eid %(x)s, '
- 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, '
- 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+ self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
+ 'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, '
+ 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, '
+ 'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
def test_unrelated_rql_security_1_anon(self):
self.login('anon')
user = self.request().user
rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0]
- self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
- 'NOT EXISTS(S use_email O), S eid %(x)s, '
- 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, '
- 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+ self.assertEqual(rql, 'Any O,AA,AB,AC ORDERBY AC DESC '
+ 'WHERE NOT EXISTS(ZZ use_email O, ZZ is CWUser), S eid %(x)s, '
+ 'O is EmailAddress, O address AA, O alias AB, O modification_date AC, A eid %(B)s, '
+ 'EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
def test_unrelated_rql_security_2(self):
email = self.execute('INSERT EmailAddress X: X address "hop"').get_entity(0, 0)
rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
- self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA ASC '
- 'WHERE NOT S use_email O, O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD')
+ self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
+ 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, '
+ 'S login AA, S firstname AB, S surname AC, S modification_date AD')
self.login('anon')
email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0)
rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
- 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
- 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+ 'WHERE NOT EXISTS(S use_email O), O eid %(x)s, S is CWUser, '
+ 'S login AA, S firstname AB, S surname AC, S modification_date AD, '
+ 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
def test_unrelated_rql_security_nonexistant(self):
self.login('anon')
email = self.vreg['etypes'].etype_class('EmailAddress')(self.request())
rql = email.cw_unrelated_rql('use_email', 'CWUser', 'object')[0]
self.assertEqual(rql, 'Any S,AA,AB,AC,AD ORDERBY AA '
- 'WHERE S is CWUser, S login AA, S firstname AB, S surname AC, S modification_date AD, '
- 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+ 'WHERE S is CWUser, '
+ 'S login AA, S firstname AB, S surname AC, S modification_date AD, '
+ 'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
+
+ def test_unrelated_rql_constraints_creation_subject(self):
+ person = self.vreg['etypes'].etype_class('Personne')(self.request())
+ rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0]
+ self.assertEqual(
+ rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
+ 'O is Personne, O nom AA, O prenom AB, O modification_date AC')
+
+ def test_unrelated_rql_constraints_creation_object(self):
+ person = self.vreg['etypes'].etype_class('Personne')(self.request())
+ rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0]
+ self.assertEqual(
+ rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE '
+ 'S is Personne, S nom AA, S prenom AB, S modification_date AC, '
+ 'NOT (S connait A, A nom "toto"), A is Personne, EXISTS(S travaille B, B nom "tutu")')
+
+ def test_unrelated_rql_constraints_edition_subject(self):
+ person = self.request().create_entity('Personne', nom=u'sylvain')
+ rql = person.cw_unrelated_rql('connait', 'Personne', 'subject')[0]
+ self.assertEqual(
+ rql, 'Any O,AA,AB,AC ORDERBY AC DESC WHERE '
+ 'NOT EXISTS(S connait O), S eid %(x)s, O is Personne, '
+ 'O nom AA, O prenom AB, O modification_date AC, '
+ 'NOT S identity O')
+
+ def test_unrelated_rql_constraints_edition_object(self):
+ person = self.request().create_entity('Personne', nom=u'sylvain')
+ rql = person.cw_unrelated_rql('connait', 'Personne', 'object')[0]
+ self.assertEqual(
+ rql, 'Any S,AA,AB,AC ORDERBY AC DESC WHERE '
+ 'NOT EXISTS(S connait O), O eid %(x)s, S is Personne, '
+ 'S nom AA, S prenom AB, S modification_date AC, '
+ 'NOT S identity O, NOT (S connait A, A nom "toto"), '
+ 'EXISTS(S travaille B, B nom "tutu")')
def test_unrelated_base(self):
req = self.request()
@@ -302,7 +354,8 @@
user = self.request().user
rset = user.unrelated('use_email', 'EmailAddress', 'subject')
self.assertEqual([x.address for x in rset.entities()], [u'hop'])
- self.create_user('toto')
+ req = self.request()
+ self.create_user(req, 'toto')
self.login('toto')
email = self.execute('Any X WHERE X eid %(x)s', {'x': email.eid}).get_entity(0, 0)
rset = email.unrelated('use_email', 'CWUser', 'object')
--- a/test/unittest_rqlrewrite.py Thu Apr 21 17:25:32 2011 +0200
+++ b/test/unittest_rqlrewrite.py Wed Apr 27 09:55:17 2011 +0200
@@ -353,14 +353,21 @@
self.failUnlessEqual(rqlst.as_string(),
u"Any C WHERE C is Card, EXISTS(C owned_by A, A is CWUser)")
- def test_rqlexpr_not_relation1(self):
+ def test_rqlexpr_not_relation_1_1(self):
constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)')
rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X')
self.failUnlessEqual(rqlst.as_string(),
u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire')
- def test_rqlexpr_not_relation2(self):
+ def test_rqlexpr_not_relation_1_2(self):
+ constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
+ rqlst = parse('Affaire A WHERE NOT EXISTS(A documented_by C)')
+ rewrite(rqlst, {('A', 'X'): (constraint,)}, {}, 'X')
+ self.failUnlessEqual(rqlst.as_string(),
+ u'Any A WHERE NOT EXISTS(A documented_by C, C is Card), A is Affaire, EXISTS(A owned_by B, B login "hop", B is CWUser)')
+
+ def test_rqlexpr_not_relation_2(self):
constraint = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
rqlst = rqlhelper.parse('Affaire A WHERE NOT A documented_by C', annotate=False)
rewrite(rqlst, {('C', 'X'): (constraint,)}, {}, 'X')
--- a/test/unittest_schema.py Thu Apr 21 17:25:32 2011 +0200
+++ b/test/unittest_schema.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -169,7 +169,7 @@
'Password', 'Personne',
'RQLExpression',
'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint',
- 'Tag', 'Time', 'Transition', 'TrInfo',
+ 'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo',
'Workflow', 'WorkflowTransition']
self.assertListEqual(sorted(expected_entities), entities)
relations = sorted([str(r) for r in schema.relations()])
@@ -239,7 +239,7 @@
self.failUnlessEqual(len(constraints), 1, constraints)
constraint = constraints[0]
self.failUnless(isinstance(constraint, RQLConstraint))
- self.failUnlessEqual(constraint.restriction, 'O final TRUE')
+ self.failUnlessEqual(constraint.expression, 'O final TRUE')
def test_fulltext_container(self):
schema = loader.load(config)
@@ -315,7 +315,7 @@
class GuessRrqlExprMainVarsTC(TestCase):
def test_exists(self):
mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3)'))
- self.assertEqual(mainvars, 'O')
+ self.assertEqual(mainvars, set(['O']))
if __name__ == '__main__':
--- a/test/unittest_selectors.py Thu Apr 21 17:25:32 2011 +0200
+++ b/test/unittest_selectors.py Wed Apr 27 09:55:17 2011 +0200
@@ -328,7 +328,8 @@
self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
try:
# login as a simple user
- self.create_user('john')
+ req = self.request()
+ self.create_user(req, 'john')
self.login('john')
# it should not be possible to use SomeAction not owned objects
req = self.request()
--- a/uilib.py Thu Apr 21 17:25:32 2011 +0200
+++ b/uilib.py Wed Apr 27 09:55:17 2011 +0200
@@ -62,9 +62,9 @@
return value
if attrtype == 'Date':
return ustrftime(value, req.property_value('ui.date-format'))
- if attrtype == 'Time':
+ if attrtype in ('Time', 'TZTime'):
return ustrftime(value, req.property_value('ui.time-format'))
- if attrtype == 'Datetime':
+ if attrtype in ('Datetime', 'TZDatetime'):
if displaytime:
return ustrftime(value, req.property_value('ui.datetime-format'))
return ustrftime(value, req.property_value('ui.date-format'))
@@ -72,8 +72,9 @@
if value:
return req._('yes')
return req._('no')
- if attrtype == 'Float':
+ if attrtype in ('Float', 'Decimal'):
value = req.property_value('ui.float-format') % value
+ # XXX Interval
return unicode(value)
--- a/utils.py Thu Apr 21 17:25:32 2011 +0200
+++ b/utils.py Wed Apr 27 09:55:17 2011 +0200
@@ -361,17 +361,43 @@
self.doctype = u''
# xmldecl and html opening tag
self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
- self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
- 'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
- 'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
+ self._namespaces = [('xmlns', 'http://www.w3.org/1999/xhtml'),
+ ('xmlns:cubicweb','http://www.logilab.org/2008/cubicweb')]
+ self._htmlattrs = [('xml:lang', req.lang),
+ ('lang', req.lang)]
# keep main_stream's reference on req for easier text/html demoting
req.main_stream = self
+ def add_namespace(self, prefix, uri):
+ self._namespaces.append( (prefix, uri) )
+
+ def set_namespaces(self, namespaces):
+ self._namespaces = namespaces
+
+ def add_htmlattr(self, attrname, attrvalue):
+ self._htmlattrs.append( (attrname, attrvalue) )
+
+ def set_htmlattrs(self, attrs):
+ self._htmlattrs = attrs
+
+ def set_doctype(self, doctype, reset_xmldecl=True):
+ self.doctype = doctype
+ if reset_xmldecl:
+ self.xmldecl = u''
+
def write(self, data):
"""StringIO interface: this method will be assigned to self.w
"""
self.body.write(data)
+ @property
+ def htmltag(self):
+ attrs = ' '.join('%s="%s"' % (attr, xml_escape(value))
+ for attr, value in (self._namespaces + self._htmlattrs))
+ if attrs:
+ return '<html %s>' % attrs
+ return '<html>'
+
def getvalue(self):
"""writes HTML headers, closes </head> tag and writes HTML body"""
return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
@@ -379,7 +405,6 @@
self.head.getvalue(),
self.body.getvalue())
-
try:
# may not be there if cubicweb-web not installed
if sys.version_info < (2, 6):
--- a/web/data/cubicweb.css Thu Apr 21 17:25:32 2011 +0200
+++ b/web/data/cubicweb.css Wed Apr 27 09:55:17 2011 +0200
@@ -993,23 +993,49 @@
background-image: none;
}
-/* ui.tabs.css */
-ul.ui-tabs-nav,
-div.ui-tabs-panel {
- font-family: %(defaultFontFamily)s;
- font-size: %(defaultSize)s;
+/* jquery-ui tabs */
+
+div.ui-tabs.ui-widget-content {
+ background:none;
+ border:none;
+ color:inherit;
+}
+
+div.ui-tabs ul.ui-tabs-nav {
+ padding-left: 0.5em;
+}
+
+div.ui-tabs ul.ui-tabs-nav a {
+ color:#27537A;
+ padding: 0.3em 0.6em;
+}
+
+div.ui-tabs ul.ui-tabs-nav li.ui-tabs-selected a {
+ color:black;
}
-div.ui-tabs-panel {
- border-top:1px solid #b6b6b6;
+div.ui-tabs ul.ui-tabs-nav li.ui-state-hover {
+ background:none;
+}
+
+div.ui-tabs .ui-widget-header {
+ background:none;
+ border:none;
}
-ul.ui-tabs-nav a {
- color: #3d3d3d;
+div.ui-tabs .ui-widget-header li {
+ border-color:#333333;
}
-ul.ui-tabs-nav a:hover {
- color: #000;
+div.ui-tabs .ui-tabs-panel {
+ border-top:1px solid #97A5B0;
+ padding-left:0.5em;
+ color:inherit;
+}
+
+div.ui-tabs .ui-tabs-nav, div.ui-tabs .ui-tabs-panel {
+ font-family: %(defaultFontFamily)s;
+ font-size: %(defaultSize)s;
}
img.ui-datepicker-trigger {
--- a/web/data/cubicweb.lazy.js Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/web/data/cubicweb.old.css Thu Apr 21 17:25:32 2011 +0200
+++ b/web/data/cubicweb.old.css Wed Apr 27 09:55:17 2011 +0200
@@ -976,3 +976,42 @@
/* remove background image (orange bullet) for autocomplete suggestions */
background-image: none;
}
+
+div.ui-tabs.ui-widget-content {
+ background:none;
+ border:none;
+ color:inherit;
+}
+
+div.ui-tabs ul.ui-tabs-nav {
+ padding-left: 0.5em;
+}
+
+div.ui-tabs ul.ui-tabs-nav a {
+ color:#27537A;
+ padding: 0.3em 0.6em;
+ outline:0;
+}
+
+div.ui-tabs ul.ui-tabs-nav li.ui-tabs-selected a {
+ color:black;
+}
+
+div.ui-tabs ul.ui-tabs-nav li.ui-state-hover, div.ui-tabs ul.ui-tabs-nav li.ui-state-focus {
+ background:white;
+}
+
+div.ui-tabs .ui-widget-header {
+ background:none;
+ border:none;
+}
+
+div.ui-tabs .ui-widget-header li {
+ border-color:#333333;
+}
+
+div.ui-tabs .ui-tabs-panel {
+ border-top:1px solid #97A5B0;
+ padding-left:0.5em;
+ color:inherit;
+}
--- a/web/data/cubicweb.reledit.js Thu Apr 21 17:25:32 2011 +0200
+++ b/web/data/cubicweb.reledit.js Wed Apr 27 09:55:17 2011 +0200
@@ -18,6 +18,7 @@
cleanupAfterCancel: function (divid) {
jQuery('#appMsg').hide();
jQuery('div.errorMessage').remove();
+ // plus re-set inline style ?
jQuery('#' + divid).show();
jQuery('#' + divid + '-value').show();
jQuery('#' + divid + '-form').hide();
@@ -63,9 +64,9 @@
* @param reload: boolean to reload page if true (when changing URL dependant data)
* @param default_value : value if the field is empty
*/
- loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid) {
+ loadInlineEditionForm: function(formid, eid, rtype, role, divid, reload, vid, action) {
var args = {fname: 'reledit_form', rtype: rtype, role: role,
- pageid: pageid,
+ pageid: pageid, action: action,
eid: eid, divid: divid, formid: formid,
reload: reload, vid: vid};
var d = jQuery('#'+divid+'-reledit').loadxhtml(JSON_BASE_URL, args, 'post');
--- a/web/data/cubicweb.tabs.js Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/web/data/jquery.corner.js Thu Apr 21 17:25:32 2011 +0200
+++ b/web/data/jquery.corner.js Wed Apr 27 09:55:17 2011 +0200
@@ -1,178 +1,247 @@
-/*
- * jQuery corner plugin
- *
- * version 1.92 (12/18/2007)
- *
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- */
-
-/**
- * The corner() method provides a simple way of styling DOM elements.
- *
- * corner() takes a single string argument: $().corner("effect corners width")
- *
- * effect: The name of the effect to apply, such as round or bevel.
- * If you don't specify an effect, rounding is used.
- *
- * corners: The corners can be one or more of top, bottom, tr, tl, br, or bl.
- * By default, all four corners are adorned.
- *
- * width: The width specifies the width of the effect; in the case of rounded corners this
- * will be the radius of the width.
- * Specify this value using the px suffix such as 10px, and yes it must be pixels.
- *
- * For more details see: http://methvin.com/jquery/jq-corner.html
- * For a full demo see: http://malsup.com/jquery/corner/
- *
- *
- * @example $('.adorn').corner();
- * @desc Create round, 10px corners
- *
- * @example $('.adorn').corner("25px");
- * @desc Create round, 25px corners
- *
- * @example $('.adorn').corner("notch bottom");
- * @desc Create notched, 10px corners on bottom only
- *
- * @example $('.adorn').corner("tr dog 25px");
- * @desc Create dogeared, 25px corner on the top-right corner only
- *
- * @example $('.adorn').corner("round 8px").parent().css('padding', '4px').corner("round 10px");
- * @desc Create a rounded border effect by styling both the element and its parent
- *
- * @name corner
- * @type jQuery
- * @param String options Options which control the corner style
- * @cat Plugins/Corner
- * @return jQuery
- * @author Dave Methvin (dave.methvin@gmail.com)
- * @author Mike Alsup (malsup@gmail.com)
- */
-(function($) {
-
-$.fn.corner = function(o) {
- var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent);
- function sz(el, p) { return parseInt($.css(el,p))||0; };
- function hex2(s) {
- var s = parseInt(s).toString(16);
- return ( s.length < 2 ) ? '0'+s : s;
- };
- function gpc(node) {
- for ( ; node && node.nodeName.toLowerCase() != 'html'; node = node.parentNode ) {
- var v = $.css(node,'backgroundColor');
- if ( v.indexOf('rgb') >= 0 ) {
- if ($.browser.safari && v == 'rgba(0, 0, 0, 0)')
- continue;
- var rgb = v.match(/\d+/g);
- return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]);
- }
- if ( v && v != 'transparent' )
- return v;
- }
- return '#ffffff';
- };
- function getW(i) {
- switch(fx) {
- case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width))));
- case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width))));
- case 'sharp': return Math.round(width*(1-Math.cos(Math.acos(i/width))));
- case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width))));
- case 'slide': return Math.round(width*(Math.atan2(i,width/i)));
- case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1))));
- case 'curl': return Math.round(width*(Math.atan(i)));
- case 'tear': return Math.round(width*(Math.cos(i)));
- case 'wicked': return Math.round(width*(Math.tan(i)));
- case 'long': return Math.round(width*(Math.sqrt(i)));
- case 'sculpt': return Math.round(width*(Math.log((width-i-1),width)));
- case 'dog': return (i&1) ? (i+1) : width;
- case 'dog2': return (i&2) ? (i+1) : width;
- case 'dog3': return (i&3) ? (i+1) : width;
- case 'fray': return (i%2)*width;
- case 'notch': return width;
- case 'bevel': return i+1;
- }
- };
- o = (o||"").toLowerCase();
- var keep = /keep/.test(o); // keep borders?
- var cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]); // corner color
- var sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]); // strip color
- var width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10; // corner width
- var re = /round|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dog/;
- var fx = ((o.match(re)||['round'])[0]);
- var edges = { T:0, B:1 };
- var opts = {
- TL: /top|tl/.test(o), TR: /top|tr/.test(o),
- BL: /bottom|bl/.test(o), BR: /bottom|br/.test(o)
- };
- if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR )
- opts = { TL:1, TR:1, BL:1, BR:1 };
- var strip = document.createElement('div');
- strip.style.overflow = 'hidden';
- strip.style.height = '1px';
- strip.style.backgroundColor = sc || 'transparent';
- strip.style.borderStyle = 'solid';
- return this.each(function(index){
- var pad = {
- T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0,
- B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0
- };
-
- if ($.browser.msie) this.style.zoom = 1; // force 'hasLayout' in IE
- if (!keep) this.style.border = 'none';
- strip.style.borderColor = cc || gpc(this.parentNode);
- var cssHeight = $.curCSS(this, 'height');
-
- for (var j in edges) {
- var bot = edges[j];
- // only add stips if needed
- if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) {
- strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none');
- var d = document.createElement('div');
- $(d).addClass('jquery-corner');
- var ds = d.style;
-
- bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild);
-
- if (bot && cssHeight != 'auto') {
- if ($.css(this,'position') == 'static')
- this.style.position = 'relative';
- ds.position = 'absolute';
- ds.bottom = ds.left = ds.padding = ds.margin = '0';
- if (($.browser.msie) && ($.browser.version < 8.0))
- ds.setExpression('width', 'this.parentNode.offsetWidth');
- else
- ds.width = '100%';
- }
- else if (!bot && $.browser.msie) {
- if ($.css(this,'position') == 'static')
- this.style.position = 'relative';
- ds.position = 'absolute';
- ds.top = ds.left = ds.right = ds.padding = ds.margin = '0';
-
- // fix ie6 problem when blocked element has a border width
- var bw = 0;
- if (ie6 || !$.boxModel)
- bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth');
- ie6 ? ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"') : ds.width = '100%';
- }
- else {
- ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' :
- (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px';
- }
-
- for (var i=0; i < width; i++) {
- var w = Math.max(0,getW(i));
- var e = strip.cloneNode(false);
- e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px';
- bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild);
- }
- }
- }
- });
-};
-
-$.fn.uncorner = function(o) { return $('.jquery-corner', this).remove(); };
-
-})(jQuery);
+/*!
+ * jQuery corner plugin: simple corner rounding
+ * Examples and documentation at: http://jquery.malsup.com/corner/
+ * version 2.11 (15-JUN-2010)
+ * Requires jQuery v1.3.2 or later
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ * Authors: Dave Methvin and Mike Alsup
+ */
+
+/**
+ * corner() takes a single string argument: $('#myDiv').corner("effect corners width")
+ *
+ * effect: name of the effect to apply, such as round, bevel, notch, bite, etc (default is round).
+ * corners: one or more of: top, bottom, tr, tl, br, or bl. (default is all corners)
+ * width: width of the effect; in the case of rounded corners this is the radius.
+ * specify this value using the px suffix such as 10px (yes, it must be pixels).
+ */
+;(function($) {
+
+var style = document.createElement('div').style,
+ moz = style['MozBorderRadius'] !== undefined,
+ webkit = style['WebkitBorderRadius'] !== undefined,
+ radius = style['borderRadius'] !== undefined || style['BorderRadius'] !== undefined,
+ mode = document.documentMode || 0,
+ noBottomFold = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8),
+
+ expr = $.browser.msie && (function() {
+ var div = document.createElement('div');
+ try { div.style.setExpression('width','0+0'); div.style.removeExpression('width'); }
+ catch(e) { return false; }
+ return true;
+ })();
+
+$.support = $.support || {};
+$.support.borderRadius = moz || webkit || radius; // so you can do: if (!$.support.borderRadius) $('#myDiv').corner();
+
+function sz(el, p) {
+ return parseInt($.css(el,p))||0;
+};
+function hex2(s) {
+ var s = parseInt(s).toString(16);
+ return ( s.length < 2 ) ? '0'+s : s;
+};
+function gpc(node) {
+ while(node) {
+ var v = $.css(node,'backgroundColor'), rgb;
+ if (v && v != 'transparent' && v != 'rgba(0, 0, 0, 0)') {
+ if (v.indexOf('rgb') >= 0) {
+ rgb = v.match(/\d+/g);
+ return '#'+ hex2(rgb[0]) + hex2(rgb[1]) + hex2(rgb[2]);
+ }
+ return v;
+ }
+ if (node.nodeName.toLowerCase() == 'html')
+ break;
+ node = node.parentNode; // keep walking if transparent
+ }
+ return '#ffffff';
+};
+
+function getWidth(fx, i, width) {
+ switch(fx) {
+ case 'round': return Math.round(width*(1-Math.cos(Math.asin(i/width))));
+ case 'cool': return Math.round(width*(1+Math.cos(Math.asin(i/width))));
+ case 'sharp': return Math.round(width*(1-Math.cos(Math.acos(i/width))));
+ case 'bite': return Math.round(width*(Math.cos(Math.asin((width-i-1)/width))));
+ case 'slide': return Math.round(width*(Math.atan2(i,width/i)));
+ case 'jut': return Math.round(width*(Math.atan2(width,(width-i-1))));
+ case 'curl': return Math.round(width*(Math.atan(i)));
+ case 'tear': return Math.round(width*(Math.cos(i)));
+ case 'wicked': return Math.round(width*(Math.tan(i)));
+ case 'long': return Math.round(width*(Math.sqrt(i)));
+ case 'sculpt': return Math.round(width*(Math.log((width-i-1),width)));
+ case 'dogfold':
+ case 'dog': return (i&1) ? (i+1) : width;
+ case 'dog2': return (i&2) ? (i+1) : width;
+ case 'dog3': return (i&3) ? (i+1) : width;
+ case 'fray': return (i%2)*width;
+ case 'notch': return width;
+ case 'bevelfold':
+ case 'bevel': return i+1;
+ }
+};
+
+$.fn.corner = function(options) {
+ // in 1.3+ we can fix mistakes with the ready state
+ if (this.length == 0) {
+ if (!$.isReady && this.selector) {
+ var s = this.selector, c = this.context;
+ $(function() {
+ $(s,c).corner(options);
+ });
+ }
+ return this;
+ }
+
+ return this.each(function(index){
+ var $this = $(this),
+ // meta values override options
+ o = [$this.attr($.fn.corner.defaults.metaAttr) || '', options || ''].join(' ').toLowerCase(),
+ keep = /keep/.test(o), // keep borders?
+ cc = ((o.match(/cc:(#[0-9a-f]+)/)||[])[1]), // corner color
+ sc = ((o.match(/sc:(#[0-9a-f]+)/)||[])[1]), // strip color
+ width = parseInt((o.match(/(\d+)px/)||[])[1]) || 10, // corner width
+ re = /round|bevelfold|bevel|notch|bite|cool|sharp|slide|jut|curl|tear|fray|wicked|sculpt|long|dog3|dog2|dogfold|dog/,
+ fx = ((o.match(re)||['round'])[0]),
+ fold = /dogfold|bevelfold/.test(o),
+ edges = { T:0, B:1 },
+ opts = {
+ TL: /top|tl|left/.test(o), TR: /top|tr|right/.test(o),
+ BL: /bottom|bl|left/.test(o), BR: /bottom|br|right/.test(o)
+ },
+ // vars used in func later
+ strip, pad, cssHeight, j, bot, d, ds, bw, i, w, e, c, common, $horz;
+
+ if ( !opts.TL && !opts.TR && !opts.BL && !opts.BR )
+ opts = { TL:1, TR:1, BL:1, BR:1 };
+
+ // support native rounding
+ if ($.fn.corner.defaults.useNative && fx == 'round' && (radius || moz || webkit) && !cc && !sc) {
+ if (opts.TL)
+ $this.css(radius ? 'border-top-left-radius' : moz ? '-moz-border-radius-topleft' : '-webkit-border-top-left-radius', width + 'px');
+ if (opts.TR)
+ $this.css(radius ? 'border-top-right-radius' : moz ? '-moz-border-radius-topright' : '-webkit-border-top-right-radius', width + 'px');
+ if (opts.BL)
+ $this.css(radius ? 'border-bottom-left-radius' : moz ? '-moz-border-radius-bottomleft' : '-webkit-border-bottom-left-radius', width + 'px');
+ if (opts.BR)
+ $this.css(radius ? 'border-bottom-right-radius' : moz ? '-moz-border-radius-bottomright' : '-webkit-border-bottom-right-radius', width + 'px');
+ return;
+ }
+
+ strip = document.createElement('div');
+ $(strip).css({
+ overflow: 'hidden',
+ height: '1px',
+ minHeight: '1px',
+ fontSize: '1px',
+ backgroundColor: sc || 'transparent',
+ borderStyle: 'solid'
+ });
+
+ pad = {
+ T: parseInt($.css(this,'paddingTop'))||0, R: parseInt($.css(this,'paddingRight'))||0,
+ B: parseInt($.css(this,'paddingBottom'))||0, L: parseInt($.css(this,'paddingLeft'))||0
+ };
+
+ if (typeof this.style.zoom != undefined) this.style.zoom = 1; // force 'hasLayout' in IE
+ if (!keep) this.style.border = 'none';
+ strip.style.borderColor = cc || gpc(this.parentNode);
+ cssHeight = $(this).outerHeight();
+
+ for (j in edges) {
+ bot = edges[j];
+ // only add stips if needed
+ if ((bot && (opts.BL || opts.BR)) || (!bot && (opts.TL || opts.TR))) {
+ strip.style.borderStyle = 'none '+(opts[j+'R']?'solid':'none')+' none '+(opts[j+'L']?'solid':'none');
+ d = document.createElement('div');
+ $(d).addClass('jquery-corner');
+ ds = d.style;
+
+ bot ? this.appendChild(d) : this.insertBefore(d, this.firstChild);
+
+ if (bot && cssHeight != 'auto') {
+ if ($.css(this,'position') == 'static')
+ this.style.position = 'relative';
+ ds.position = 'absolute';
+ ds.bottom = ds.left = ds.padding = ds.margin = '0';
+ if (expr)
+ ds.setExpression('width', 'this.parentNode.offsetWidth');
+ else
+ ds.width = '100%';
+ }
+ else if (!bot && $.browser.msie) {
+ if ($.css(this,'position') == 'static')
+ this.style.position = 'relative';
+ ds.position = 'absolute';
+ ds.top = ds.left = ds.right = ds.padding = ds.margin = '0';
+
+ // fix ie6 problem when blocked element has a border width
+ if (expr) {
+ bw = sz(this,'borderLeftWidth') + sz(this,'borderRightWidth');
+ ds.setExpression('width', 'this.parentNode.offsetWidth - '+bw+'+ "px"');
+ }
+ else
+ ds.width = '100%';
+ }
+ else {
+ ds.position = 'relative';
+ ds.margin = !bot ? '-'+pad.T+'px -'+pad.R+'px '+(pad.T-width)+'px -'+pad.L+'px' :
+ (pad.B-width)+'px -'+pad.R+'px -'+pad.B+'px -'+pad.L+'px';
+ }
+
+ for (i=0; i < width; i++) {
+ w = Math.max(0,getWidth(fx,i, width));
+ e = strip.cloneNode(false);
+ e.style.borderWidth = '0 '+(opts[j+'R']?w:0)+'px 0 '+(opts[j+'L']?w:0)+'px';
+ bot ? d.appendChild(e) : d.insertBefore(e, d.firstChild);
+ }
+
+ if (fold && $.support.boxModel) {
+ if (bot && noBottomFold) continue;
+ for (c in opts) {
+ if (!opts[c]) continue;
+ if (bot && (c == 'TL' || c == 'TR')) continue;
+ if (!bot && (c == 'BL' || c == 'BR')) continue;
+
+ common = { position: 'absolute', border: 'none', margin: 0, padding: 0, overflow: 'hidden', backgroundColor: strip.style.borderColor };
+ $horz = $('<div/>').css(common).css({ width: width + 'px', height: '1px' });
+ switch(c) {
+ case 'TL': $horz.css({ bottom: 0, left: 0 }); break;
+ case 'TR': $horz.css({ bottom: 0, right: 0 }); break;
+ case 'BL': $horz.css({ top: 0, left: 0 }); break;
+ case 'BR': $horz.css({ top: 0, right: 0 }); break;
+ }
+ d.appendChild($horz[0]);
+
+ var $vert = $('<div/>').css(common).css({ top: 0, bottom: 0, width: '1px', height: width + 'px' });
+ switch(c) {
+ case 'TL': $vert.css({ left: width }); break;
+ case 'TR': $vert.css({ right: width }); break;
+ case 'BL': $vert.css({ left: width }); break;
+ case 'BR': $vert.css({ right: width }); break;
+ }
+ d.appendChild($vert[0]);
+ }
+ }
+ }
+ }
+ });
+};
+
+$.fn.uncorner = function() {
+ if (radius || moz || webkit)
+ this.css(radius ? 'border-radius' : moz ? '-moz-border-radius' : '-webkit-border-radius', 0);
+ $('div.jquery-corner', this).remove();
+ return this;
+};
+
+// expose options
+$.fn.corner.defaults = {
+ useNative: true, // true if plugin should attempt to use native browser support for border radius rounding
+ metaAttr: 'data-corner' // name of meta attribute to use for options
+};
+
+})(jQuery);
Binary file web/data/tab.png has changed
--- a/web/data/ui.core.js Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-/*
- * jQuery UI @VERSION
- *
- * Copyright (c) 2010 Paul Bakaus (ui.jquery.com)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI
- */
-;(function($) {
-
-/** jQuery core modifications and additions **/
-
-var _remove = $.fn.remove;
-$.fn.remove = function() {
- $("*", this).add(this).triggerHandler("remove");
- return _remove.apply(this, arguments );
-};
-
-function isVisible(element) {
- function checkStyles(element) {
- var style = element.style;
- return (style.display != 'none' && style.visibility != 'hidden');
- }
-
- var visible = checkStyles(element);
-
- (visible && $.each($.dir(element, 'parentNode'), function() {
- return (visible = checkStyles(this));
- }));
-
- return visible;
-}
-
-$.extend($.expr[':'], {
- data: function(a, i, m) {
- return $.data(a, m[3]);
- },
-
- // TODO: add support for object, area
- tabbable: function(a, i, m) {
- var nodeName = a.nodeName.toLowerCase();
-
- return (
- // in tab order
- a.tabIndex >= 0 &&
-
- ( // filter node types that participate in the tab order
-
- // anchor tag
- ('a' == nodeName && a.href) ||
-
- // enabled form element
- (/input|select|textarea|button/.test(nodeName) &&
- 'hidden' != a.type && !a.disabled)
- ) &&
-
- // visible on page
- isVisible(a)
- );
- }
-});
-
-$.keyCode = {
- BACKSPACE: 8,
- CAPS_LOCK: 20,
- COMMA: 188,
- CONTROL: 17,
- DELETE: 46,
- DOWN: 40,
- END: 35,
- ENTER: 13,
- ESCAPE: 27,
- HOME: 36,
- INSERT: 45,
- LEFT: 37,
- NUMPAD_ADD: 107,
- NUMPAD_DECIMAL: 110,
- NUMPAD_DIVIDE: 111,
- NUMPAD_ENTER: 108,
- NUMPAD_MULTIPLY: 106,
- NUMPAD_SUBTRACT: 109,
- PAGE_DOWN: 34,
- PAGE_UP: 33,
- PERIOD: 190,
- RIGHT: 39,
- SHIFT: 16,
- SPACE: 32,
- TAB: 9,
- UP: 38
-};
-
-// $.widget is a factory to create jQuery plugins
-// taking some boilerplate code out of the plugin code
-// created by Scott González and Jörn Zaefferer
-function getter(namespace, plugin, method, args) {
- function getMethods(type) {
- var methods = $[namespace][plugin][type] || [];
- return (typeof methods == 'string' ? methods.split(/,?\s+/) : methods);
- }
-
- var methods = getMethods('getter');
- if (args.length == 1 && typeof args[0] == 'string') {
- methods = methods.concat(getMethods('getterSetter'));
- }
- return ($.inArray(method, methods) != -1);
-}
-
-$.widget = function(name, prototype) {
- var namespace = name.split(".")[0];
- name = name.split(".")[1];
-
- // create plugin method
- $.fn[name] = function(options) {
- var isMethodCall = (typeof options == 'string'),
- args = Array.prototype.slice.call(arguments, 1);
-
- // prevent calls to internal methods
- if (isMethodCall && options.substring(0, 1) == '_') {
- return this;
- }
-
- // handle getter methods
- if (isMethodCall && getter(namespace, name, options, args)) {
- var instance = $.data(this[0], name);
- return (instance ? instance[options].apply(instance, args)
- : undefined);
- }
-
- // handle initialization and non-getter methods
- return this.each(function() {
- var instance = $.data(this, name);
-
- // constructor
- (!instance && !isMethodCall &&
- $.data(this, name, new $[namespace][name](this, options)));
-
- // method call
- (instance && isMethodCall && $.isFunction(instance[options]) &&
- instance[options].apply(instance, args));
- });
- };
-
- // create widget constructor
- $[namespace][name] = function(element, options) {
- var self = this;
-
- this.widgetName = name;
- this.widgetEventPrefix = $[namespace][name].eventPrefix || name;
- this.widgetBaseClass = namespace + '-' + name;
-
- this.options = $.extend({},
- $.widget.defaults,
- $[namespace][name].defaults,
- $.metadata && $.metadata.get(element)[name],
- options);
-
- this.element = $(element)
- .bind('setData.' + name, function(e, key, value) {
- return self._setData(key, value);
- })
- .bind('getData.' + name, function(e, key) {
- return self._getData(key);
- })
- .bind('remove', function() {
- return self.destroy();
- });
-
- this._init();
- };
-
- // add widget prototype
- $[namespace][name].prototype = $.extend({}, $.widget.prototype, prototype);
-
- // TODO: merge getter and getterSetter properties from widget prototype
- // and plugin prototype
- $[namespace][name].getterSetter = 'option';
-};
-
-$.widget.prototype = {
- _init: function() {},
- destroy: function() {
- this.element.removeData(this.widgetName);
- },
-
- option: function(key, value) {
- var options = key,
- self = this;
-
- if (typeof key == "string") {
- if (value === undefined) {
- return this._getData(key);
- }
- options = {};
- options[key] = value;
- }
-
- $.each(options, function(key, value) {
- self._setData(key, value);
- });
- },
- _getData: function(key) {
- return this.options[key];
- },
- _setData: function(key, value) {
- this.options[key] = value;
-
- if (key == 'disabled') {
- this.element[value ? 'addClass' : 'removeClass'](
- this.widgetBaseClass + '-disabled');
- }
- },
-
- enable: function() {
- this._setData('disabled', false);
- },
- disable: function() {
- this._setData('disabled', true);
- },
-
- _trigger: function(type, e, data) {
- var eventName = (type == this.widgetEventPrefix
- ? type : this.widgetEventPrefix + type);
- e = e || $.event.fix({ type: eventName, target: this.element[0] });
- return this.element.triggerHandler(eventName, [e, data], this.options[type]);
- }
-};
-
-$.widget.defaults = {
- disabled: false
-};
-
-
-/** jQuery UI core **/
-
-$.ui = {
- plugin: {
- add: function(module, option, set) {
- var proto = $.ui[module].prototype;
- for(var i in set) {
- proto.plugins[i] = proto.plugins[i] || [];
- proto.plugins[i].push([option, set[i]]);
- }
- },
- call: function(instance, name, args) {
- var set = instance.plugins[name];
- if(!set) { return; }
-
- for (var i = 0; i < set.length; i++) {
- if (instance.options[set[i][0]]) {
- set[i][1].apply(instance.element, args);
- }
- }
- }
- },
- cssCache: {},
- css: function(name) {
- if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
- var tmp = $('<div class="ui-gen">').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');
-
- //if (!$.browser.safari)
- //tmp.appendTo('body');
-
- //Opera and Safari set width and height to 0px instead of auto
- //Safari returns rgba(0,0,0,0) when bgcolor is not set
- $.ui.cssCache[name] = !!(
- (!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) ||
- !(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
- );
- try { $('body').get(0).removeChild(tmp.get(0)); } catch(e){}
- return $.ui.cssCache[name];
- },
- disableSelection: function(el) {
- $(el)
- .attr('unselectable', 'on')
- .css('MozUserSelect', 'none')
- .bind('selectstart.ui', function() { return false; });
- },
- enableSelection: function(el) {
- $(el)
- .attr('unselectable', 'off')
- .css('MozUserSelect', '')
- .unbind('selectstart.ui');
- },
- hasScroll: function(e, a) {
- var scroll = (a && a == 'left') ? 'scrollLeft' : 'scrollTop',
- has = false;
-
- if (e[scroll] > 0) { return true; }
-
- // TODO: determine which cases actually cause this to happen
- // if the element doesn't have the scroll set, see if it's possible to
- // set the scroll
- e[scroll] = 1;
- has = (e[scroll] > 0);
- e[scroll] = 0;
- return has;
- }
-};
-
-
-/** Mouse Interaction Plugin **/
-
-$.ui.mouse = {
- _mouseInit: function() {
- var self = this;
-
- this.element.bind('mousedown.'+this.widgetName, function(e) {
- return self._mouseDown(e);
- });
-
- // Prevent text selection in IE
- if ($.browser.msie) {
- this._mouseUnselectable = this.element.attr('unselectable');
- this.element.attr('unselectable', 'on');
- }
-
- this.started = false;
- },
-
- // TODO: make sure destroying one instance of mouse doesn't mess with
- // other instances of mouse
- _mouseDestroy: function() {
- this.element.unbind('.'+this.widgetName);
-
- // Restore text selection in IE
- ($.browser.msie
- && this.element.attr('unselectable', this._mouseUnselectable));
- },
-
- _mouseDown: function(e) {
- // we may have missed mouseup (out of window)
- (this._mouseStarted && this._mouseUp(e));
-
- this._mouseDownEvent = e;
-
- var self = this,
- btnIsLeft = (e.which == 1),
- elIsCancel = (typeof this.options.cancel == "string" ? $(e.target).parents().add(e.target).filter(this.options.cancel).length : false);
- if (!btnIsLeft || elIsCancel || !this._mouseCapture(e)) {
- return true;
- }
-
- this.mouseDelayMet = !this.options.delay;
- if (!this.mouseDelayMet) {
- this._mouseDelayTimer = setTimeout(function() {
- self.mouseDelayMet = true;
- }, this.options.delay);
- }
-
- if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) {
- this._mouseStarted = (this._mouseStart(e) !== false);
- if (!this._mouseStarted) {
- e.preventDefault();
- return true;
- }
- }
-
- // these delegates are required to keep context
- this._mouseMoveDelegate = function(e) {
- return self._mouseMove(e);
- };
- this._mouseUpDelegate = function(e) {
- return self._mouseUp(e);
- };
- $(document)
- .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
- .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
-
- return false;
- },
-
- _mouseMove: function(e) {
- // IE mouseup check - mouseup happened when mouse was out of window
- if ($.browser.msie && !e.button) {
- return this._mouseUp(e);
- }
-
- if (this._mouseStarted) {
- this._mouseDrag(e);
- return false;
- }
-
- if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) {
- this._mouseStarted =
- (this._mouseStart(this._mouseDownEvent, e) !== false);
- (this._mouseStarted ? this._mouseDrag(e) : this._mouseUp(e));
- }
-
- return !this._mouseStarted;
- },
-
- _mouseUp: function(e) {
- $(document)
- .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
- .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
-
- if (this._mouseStarted) {
- this._mouseStarted = false;
- this._mouseStop(e);
- }
-
- return false;
- },
-
- _mouseDistanceMet: function(e) {
- return (Math.max(
- Math.abs(this._mouseDownEvent.pageX - e.pageX),
- Math.abs(this._mouseDownEvent.pageY - e.pageY)
- ) >= this.options.distance
- );
- },
-
- _mouseDelayMet: function(e) {
- return this.mouseDelayMet;
- },
-
- // These are placeholder methods, to be overriden by extending plugin
- _mouseStart: function(e) {},
- _mouseDrag: function(e) {},
- _mouseStop: function(e) {},
- _mouseCapture: function(e) { return true; }
-};
-
-$.ui.mouse.defaults = {
- cancel: null,
- distance: 1,
- delay: 0
-};
-
-})(jQuery);
--- a/web/data/ui.slider.js Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/*
- * jQuery UI 1.7.1
- *
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI
- */
jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;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.1
- *
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Draggables
- *
- * Depends:
- * ui.core.js
- */
(function(a){a.widget("ui.draggable",a.extend({},a.ui.mouse,{_init:function(){if(this.options.helper=="original"&&!(/^(?:r|a|f)/).test(this.element.css("position"))){this.element[0].style.position="relative"}(this.options.addClasses&&this.element.addClass("ui-draggable"));(this.options.disabled&&this.element.addClass("ui-draggable-disabled"));this._mouseInit()},destroy:function(){if(!this.element.data("draggable")){return}this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled");this._mouseDestroy()},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle")){return false}this.handle=this._getHandle(b);if(!this.handle){return false}return true},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b);this._cacheHelperProportions();if(a.ui.ddmanager){a.ui.ddmanager.current=this}this._cacheMargins();this.cssPosition=this.helper.css("position");this.scrollParent=this.helper.scrollParent();this.offset=this.element.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(b);this.originalPageX=b.pageX;this.originalPageY=b.pageY;if(c.cursorAt){this._adjustOffsetFromHelper(c.cursorAt)}if(c.containment){this._setContainment()}this._trigger("start",b);this._cacheHelperProportions();if(a.ui.ddmanager&&!c.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,b)}this.helper.addClass("ui-draggable-dragging");this._mouseDrag(b,true);return true},_mouseDrag:function(b,d){this.position=this._generatePosition(b);this.positionAbs=this._convertPositionTo("absolute");if(!d){var c=this._uiHash();this._trigger("drag",b,c);this.position=c.position}if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}if(a.ui.ddmanager){a.ui.ddmanager.drag(this,b)}return false},_mouseStop:function(c){var d=false;if(a.ui.ddmanager&&!this.options.dropBehaviour){d=a.ui.ddmanager.drop(this,c)}if(this.dropped){d=this.dropped;this.dropped=false}if((this.options.revert=="invalid"&&!d)||(this.options.revert=="valid"&&d)||this.options.revert===true||(a.isFunction(this.options.revert)&&this.options.revert.call(this.element,d))){var b=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){b._trigger("stop",c);b._clear()})}else{this._trigger("stop",c);this._clear()}return false},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?true:false;a(this.options.handle,this.element).find("*").andSelf().each(function(){if(this==b.target){c=true}});return c},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c])):(d.helper=="clone"?this.element.clone():this.element);if(!b.parents("body").length){b.appendTo((d.appendTo=="parent"?this.element[0].parentNode:d.appendTo))}if(b[0]!=this.element[0]&&!(/(fixed|absolute)/).test(b.css("position"))){b.css("position","absolute")}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.element.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.element.css("marginLeft"),10)||0),top:(parseInt(this.element.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)&&e.containment.constructor!=Array){var c=a(e.containment)[0];if(!c){return}var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}else{if(e.containment.constructor==Array){this.containment=e.containment}}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.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.1",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.1
- *
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Droppables
- *
- * Depends:
- * ui.core.js
- * 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.1",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 Slider 1.7.1
- *
- * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Slider
- *
- * Depends:
- * ui.core.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(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((e==0&&d>=b)||(e==1&&d<=b)){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(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.1",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);;
\ No newline at end of file
--- a/web/data/ui.tabs.css Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-/* Caution! Ensure accessibility in print and other media types... */
-@media projection, screen { /* Use class for showing/hiding tab content, so that visibility can be better controlled in different media types... */
- .ui-tabs-hide {
- display: none;
- }
-}
-
-/* Hide useless elements in print layouts... */
-@media print {
- .ui-tabs-nav {
- display: none;
- }
-}
-
-/* Skin */
-.ui-tabs-nav, .ui-tabs-panel {
- font-family: "Trebuchet MS", Trebuchet, Verdana, Helvetica, Arial, sans-serif;
- font-size: 12px;
-
-}
-.ui-tabs-nav {
- list-style: none;
- margin: 0px;
- padding: 0px 0px 0px 4px;
-
-}
-.ui-tabs-nav:after { /* clearing without presentational markup, IE gets extra treatment */
- display: block;
- clear: both;
- content: " ";
-}
-.ui-tabs-nav li {
- float: left;
- margin: 0 0 0 1px;
- min-width: 84px; /* be nice to Opera */
- list-style: none;
- background: none;
- padding: 0px 0px 1px 1px;
-}
-.ui-tabs-nav a, .ui-tabs-nav a span {
- display: block;
- padding: 0 10px;
- background: url(tab.png) no-repeat;
-}
-.ui-tabs-nav a {
- margin: 1px 0 0; /* position: relative makes opacity fail for disabled tab in IE */
- padding-left: 0;
- color: #27537a;
- font-weight: bold;
- line-height: 1.2;
- text-align: center;
- text-decoration: none;
- white-space: nowrap; /* required in IE 6 */
- outline: 0; /* prevent dotted border in Firefox */
-}
-.ui-tabs-nav .ui-tabs-selected a {
- position: relative;
- top: 1px;
- z-index: 2;
- margin-top: 0;
- color: #000;
-}
-.ui-tabs-nav a span {
- width: 64px; /* IE 6 treats width as min-width */
- min-width: 64px;
- height: 18px; /* IE 6 treats height as min-height */
- min-height: 18px;
- padding-top: 6px;
- padding-right: 0;
-}
-*>.ui-tabs-nav a span { /* hide from IE 6 */
- width: auto;
- height: auto;
-}
-.ui-tabs-nav .ui-tabs-selected a span {
- padding-bottom: 1px;
-}
-.ui-tabs-nav .ui-tabs-selected a, .ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active {
- background-position: 100% -150px;
-}
-.ui-tabs-nav a, .ui-tabs-nav .ui-tabs-disabled a:hover, .ui-tabs-nav .ui-tabs-disabled a:focus, .ui-tabs-nav .ui-tabs-disabled a:active {
- background-position: 100% -100px;
-}
-.ui-tabs-nav .ui-tabs-selected a span, .ui-tabs-nav a:hover span, .ui-tabs-nav a:focus span, .ui-tabs-nav a:active span {
- background-position: 0 -50px;
-}
-.ui-tabs-nav a span, .ui-tabs-nav .ui-tabs-disabled a:hover span, .ui-tabs-nav .ui-tabs-disabled a:focus span, .ui-tabs-nav .ui-tabs-disabled a:active span {
- background-position: 0 0;
-}
-.ui-tabs-nav .ui-tabs-selected a:link, .ui-tabs-nav .ui-tabs-selected a:visited, .ui-tabs-nav .ui-tabs-disabled a:link, .ui-tabs-nav .ui-tabs-disabled a:visited { /* @ Opera, use pseudo classes otherwise it confuses cursor... */
- cursor: text;
-}
-.ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active,
-.ui-tabs-nav .ui-tabs-unselect a:hover, .ui-tabs-nav .ui-tabs-unselect a:focus, .ui-tabs-nav .ui-tabs-unselect a:active { /* @ Opera, we need to be explicit again here now... */
- cursor: pointer;
-}
-.ui-tabs-disabled {
- opacity: .4;
- filter: alpha(opacity=40);
-}
-.ui-tabs-panel {
- border-top: 1px solid #97a5b0;
- padding: 1em 8px;
- margin-top:-1px; /* Logilab style */
- background: #fff; /* declare background color for container to avoid distorted fonts in IE while fading */
-}
-.ui-tabs-loading em {
- padding: 0 0 0 20px;
- background: url(loading.gif) no-repeat 0 50%;
-}
-
-/* Additional IE specific bug fixes... */
-* html .ui-tabs-nav { /* auto clear, @ IE 6 & IE 7 Quirks Mode */
- display: inline-block;
-}
-*:first-child+html .ui-tabs-nav { /* @ IE 7 Standards Mode - do not group selectors, otherwise IE 6 will ignore complete rule (because of the unknown + combinator)... */
- display: inline-block;
-}
-
-/* ========= Lobilab styles =========== */
-
-/* added by katia */
-* html .ui-tabs-panel{
- width:100%;
-}
\ No newline at end of file
--- a/web/data/ui.tabs.js Thu Apr 21 17:25:32 2011 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,587 +0,0 @@
-/*
- * jQuery UI Tabs @VERSION
- *
- * Copyright (c) 2007, 2010 Klaus Hartl (stilbuero.de)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- * http://docs.jquery.com/UI/Tabs
- *
- * Depends:
- * ui.core.js
- */
-(function($) {
-
-$.widget("ui.tabs", {
- _init: function() {
- this.options.event += '.tabs'; // namespace event
-
- // create tabs
- this._tabify(true);
- },
- _setData: function(key, value) {
- if ((/^selected/).test(key))
- this.select(value);
- else {
- this.options[key] = value;
- this._tabify();
- }
- },
- length: function() {
- return this.$tabs.length;
- },
- _tabId: function(a) {
- return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
- || this.options.idPrefix + $.data(a);
- },
- ui: function(tab, panel) {
- return {
- options: this.options,
- tab: tab,
- panel: panel,
- index: this.$tabs.index(tab)
- };
- },
- _tabify: function(init) {
-
- this.$lis = $('li:has(a[href])', this.element);
- this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
- this.$panels = $([]);
-
- var self = this, o = this.options;
-
- this.$tabs.each(function(i, a) {
- // inline tab
- if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
- self.$panels = self.$panels.add(a.hash);
- // remote tab
- else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
- $.data(a, 'href.tabs', a.href); // required for restore on destroy
- $.data(a, 'load.tabs', a.href); // mutable
- var id = self._tabId(a);
- a.href = '#' + id;
- var $panel = $('#' + id);
- if (!$panel.length) {
- $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
- .insertAfter( self.$panels[i - 1] || self.element );
- $panel.data('destroy.tabs', true);
- }
- self.$panels = self.$panels.add( $panel );
- }
- // invalid tab href
- else
- o.disabled.push(i + 1);
- });
-
- // initialization from scratch
- if (init) {
-
- // attach necessary classes for styling if not present
- this.element.addClass(o.navClass);
- this.$panels.each(function() {
- var $this = $(this);
- $this.addClass(o.panelClass);
- });
-
- // Selected tab
- // use "selected" option or try to retrieve:
- // 1. from fragment identifier in url
- // 2. from cookie
- // 3. from selected class attribute on <li>
- if (o.selected === undefined) {
- if (location.hash) {
- this.$tabs.each(function(i, a) {
- if (a.hash == location.hash) {
- o.selected = i;
- // prevent page scroll to fragment
- if ($.browser.msie || $.browser.opera) { // && !o.remote
- var $toShow = $(location.hash), toShowId = $toShow.attr('id');
- $toShow.attr('id', '');
- setTimeout(function() {
- $toShow.attr('id', toShowId); // restore id
- }, 500);
- }
- scrollTo(0, 0);
- return false; // break
- }
- });
- }
- else if (o.cookie) {
- var index = parseInt($.cookie('ui-tabs-' + $.data(self.element[0])), 10);
- if (index && self.$tabs[index])
- o.selected = index;
- }
- else if (self.$lis.filter('.' + o.selectedClass).length)
- o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
- }
- o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
-
- // Take disabling tabs via class attribute from HTML
- // into account and update option properly.
- // A selected tab cannot become disabled.
- o.disabled = $.unique(o.disabled.concat(
- $.map(this.$lis.filter('.' + o.disabledClass),
- function(n, i) { return self.$lis.index(n); } )
- )).sort();
- if ($.inArray(o.selected, o.disabled) != -1)
- o.disabled.splice($.inArray(o.selected, o.disabled), 1);
-
- // highlight selected tab
- this.$panels.addClass(o.hideClass);
- this.$lis.removeClass(o.selectedClass);
- if (o.selected !== null) {
- this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before
- this.$lis.eq(o.selected).addClass(o.selectedClass);
-
- // seems to be expected behavior that the show callback is fired
- var onShow = function() {
- self._trigger('show', null,
- self.ui(self.$tabs[o.selected], self.$panels[o.selected]));
- };
-
- // load if remote tab
- if ($.data(this.$tabs[o.selected], 'load.tabs'))
- this.load(o.selected, onShow);
- // just trigger show event
- else
- onShow();
- }
-
- // clean up to avoid memory leaks in certain versions of IE 6
- $(window).bind('unload', function() {
- self.$tabs.unbind('.tabs');
- self.$lis = self.$tabs = self.$panels = null;
- });
-
- }
- // update selected after add/remove
- else
- o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] );
-
- // set or update cookie after init and add/remove respectively
- if (o.cookie)
- $.cookie('ui-tabs-' + $.data(self.element[0]), o.selected, o.cookie);
-
- // disable tabs
- for (var i = 0, li; li = this.$lis[i]; i++)
- $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
-
- // reset cache if switching from cached to not cached
- if (o.cache === false)
- this.$tabs.removeData('cache.tabs');
-
- // set up animations
- var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
- if (o.fx && o.fx.constructor == Array)
- hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
- else
- hideFx = showFx = o.fx || baseFx;
-
- // reset some styles to maintain print style sheets etc.
- var resetCSS = { display: '', overflow: '', height: '' };
- if (!$.browser.msie) // not in IE to prevent ClearType font issue
- resetCSS.opacity = '';
-
- // Hide a tab, animation prevents browser scrolling to fragment,
- // $show is optional.
- function hideTab(clicked, $hide, $show) {
- $hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
- $hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
- if ($.browser.msie && hideFx.opacity)
- $hide[0].style.filter = '';
- if ($show)
- showTab(clicked, $show, $hide);
- });
- }
-
- // Show a tab, animation prevents browser scrolling to fragment,
- // $hide is optional.
- function showTab(clicked, $show, $hide) {
- if (showFx === baseFx)
- $show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
- $show.animate(showFx, showFx.duration || baseDuration, function() {
- $show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
- if ($.browser.msie && showFx.opacity)
- $show[0].style.filter = '';
-
- // callback
- self._trigger('show', null, self.ui(clicked, $show[0]));
- });
- }
-
- // switch a tab
- function switchTab(clicked, $li, $hide, $show) {
- /*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
- $.ajaxHistory.update(clicked.hash);
- }*/
- $li.addClass(o.selectedClass)
- .siblings().removeClass(o.selectedClass);
- hideTab(clicked, $hide, $show);
- }
-
- // attach tab event handler, unbind to avoid duplicates from former tabifying...
- this.$tabs.unbind('.tabs').bind(o.event, function() {
-
- //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
- var $li = $(this).parents('li:eq(0)'),
- $hide = self.$panels.filter(':visible'),
- $show = $(this.hash);
-
- // If tab is already selected and not unselectable or tab disabled or
- // or is already loading or click callback returns false stop here.
- // Check if click handler returns false last so that it is not executed
- // for a disabled or loading tab!
- if (($li.hasClass(o.selectedClass) && !o.unselect)
- || $li.hasClass(o.disabledClass)
- || $(this).hasClass(o.loadingClass)
- || self._trigger('select', null, self.ui(this, $show[0])) === false
- ) {
- this.blur();
- return false;
- }
-
- self.options.selected = self.$tabs.index(this);
-
- // if tab may be closed
- if (o.unselect) {
- if ($li.hasClass(o.selectedClass)) {
- self.options.selected = null;
- $li.removeClass(o.selectedClass);
- self.$panels.stop();
- hideTab(this, $hide);
- this.blur();
- return false;
- } else if (!$hide.length) {
- self.$panels.stop();
- var a = this;
- self.load(self.$tabs.index(this), function() {
- $li.addClass(o.selectedClass).addClass(o.unselectClass);
- showTab(a, $show);
- });
- this.blur();
- return false;
- }
- }
-
- if (o.cookie)
- $.cookie('ui-tabs-' + $.data(self.element[0]), self.options.selected, o.cookie);
-
- // stop possibly running animations
- self.$panels.stop();
-
- // show new tab
- if ($show.length) {
-
- // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
- /*if ($.browser.msie && o.bookmarkable) {
- var showId = this.hash.replace('#', '');
- $show.attr('id', '');
- setTimeout(function() {
- $show.attr('id', showId); // restore id
- }, 0);
- }*/
-
- var a = this;
- self.load(self.$tabs.index(this), $hide.length ?
- function() {
- switchTab(a, $li, $hide, $show);
- } :
- function() {
- $li.addClass(o.selectedClass);
- showTab(a, $show);
- }
- );
-
- // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
- /*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
- var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
- setTimeout(function() {
- scrollTo(scrollX, scrollY);
- }, 0);*/
-
- } else
- throw 'jQuery UI Tabs: Mismatching fragment identifier.';
-
- // Prevent IE from keeping other link focussed when using the back button
- // and remove dotted border from clicked link. This is controlled in modern
- // browsers via CSS, also blur removes focus from address bar in Firefox
- // which can become a usability and annoying problem with tabsRotate.
- if ($.browser.msie)
- this.blur();
-
- //return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
- return false;
-
- });
-
- // disable click if event is configured to something else
- if (!(/^click/).test(o.event))
- this.$tabs.bind('click.tabs', function() { return false; });
-
- },
- add: function(url, label, index) {
- if (index == undefined)
- index = this.$tabs.length; // append by default
-
- var o = this.options;
- var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
- $li.data('destroy.tabs', true);
-
- var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] );
-
- // try to find an existing element before creating a new one
- var $panel = $('#' + id);
- if (!$panel.length) {
- $panel = $(o.panelTemplate).attr('id', id)
- .addClass(o.hideClass)
- .data('destroy.tabs', true);
- }
- $panel.addClass(o.panelClass);
- if (index >= this.$lis.length) {
- $li.appendTo(this.element);
- $panel.appendTo(this.element[0].parentNode);
- } else {
- $li.insertBefore(this.$lis[index]);
- $panel.insertBefore(this.$panels[index]);
- }
-
- o.disabled = $.map(o.disabled,
- function(n, i) { return n >= index ? ++n : n });
-
- this._tabify();
-
- if (this.$tabs.length == 1) {
- $li.addClass(o.selectedClass);
- $panel.removeClass(o.hideClass);
- var href = $.data(this.$tabs[0], 'load.tabs');
- if (href)
- this.load(index, href);
- }
-
- // callback
- this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index]));
- },
- remove: function(index) {
- var o = this.options, $li = this.$lis.eq(index).remove(),
- $panel = this.$panels.eq(index).remove();
-
- // If selected tab was removed focus tab to the right or
- // in case the last tab was removed the tab to the left.
- if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
- this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
-
- o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
- function(n, i) { return n >= index ? --n : n });
-
- this._tabify();
-
- // callback
- this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0]));
- },
- enable: function(index) {
- var o = this.options;
- if ($.inArray(index, o.disabled) == -1)
- return;
-
- var $li = this.$lis.eq(index).removeClass(o.disabledClass);
- if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
- $li.css('display', 'inline-block');
- setTimeout(function() {
- $li.css('display', 'block');
- }, 0);
- }
-
- o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
-
- // callback
- this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index]));
- },
- disable: function(index) {
- var self = this, o = this.options;
- if (index != o.selected) { // cannot disable already selected tab
- this.$lis.eq(index).addClass(o.disabledClass);
-
- o.disabled.push(index);
- o.disabled.sort();
-
- // callback
- this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index]));
- }
- },
- select: function(index) {
- if (typeof index == 'string')
- index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
- this.$tabs.eq(index).trigger(this.options.event);
- },
- load: function(index, callback) { // callback is for internal usage only
-
- var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
- bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
-
- callback = callback || function() {};
-
- // no remote or from cache - just finish with callback
- if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
- callback();
- return;
- }
-
- // load remote from here on
-
- var inner = function(parent) {
- var $parent = $(parent), $inner = $parent.find('*:last');
- return $inner.length && $inner.is(':not(img)') && $inner || $parent;
- };
- var cleanup = function() {
- self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
- .each(function() {
- if (o.spinner)
- inner(this).parent().html(inner(this).data('label.tabs'));
- });
- self.xhr = null;
- };
-
- if (o.spinner) {
- var label = inner(a).html();
- inner(a).wrapInner('<em></em>')
- .find('em').data('label.tabs', label).html(o.spinner);
- }
-
- var ajaxOptions = $.extend({}, o.ajaxOptions, {
- url: url,
- success: function(r, s) {
- $(a.hash).html(r);
- cleanup();
-
- if (o.cache)
- $.data(a, 'cache.tabs', true); // if loaded once do not load them again
-
- // callbacks
- self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index]));
- o.ajaxOptions.success && o.ajaxOptions.success(r, s);
-
- // This callback is required because the switch has to take
- // place after loading has completed. Call last in order to
- // fire load before show callback...
- callback();
- }
- });
- if (this.xhr) {
- // terminate pending requests from other tabs and restore tab label
- this.xhr.abort();
- cleanup();
- }
- $a.addClass(o.loadingClass);
- setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
- self.xhr = $.ajax(ajaxOptions);
- }, 0);
-
- },
- url: function(index, url) {
- this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
- },
- destroy: function() {
- var o = this.options;
- this.element.unbind('.tabs')
- .removeClass(o.navClass).removeData('tabs');
- this.$tabs.each(function() {
- var href = $.data(this, 'href.tabs');
- if (href)
- this.href = href;
- var $this = $(this).unbind('.tabs');
- $.each(['href', 'load', 'cache'], function(i, prefix) {
- $this.removeData(prefix + '.tabs');
- });
- });
- this.$lis.add(this.$panels).each(function() {
- if ($.data(this, 'destroy.tabs'))
- $(this).remove();
- else
- $(this).removeClass([o.selectedClass, o.unselectClass,
- o.disabledClass, o.panelClass, o.hideClass].join(' '));
- });
- }
-});
-
-$.ui.tabs.defaults = {
- // basic setup
- unselect: false,
- event: 'click',
- disabled: [],
- cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
- // TODO history: false,
-
- // Ajax
- spinner: 'Loading…',
- cache: false,
- idPrefix: 'ui-tabs-',
- ajaxOptions: {},
-
- // animations
- fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
-
- // templates
- tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
- panelTemplate: '<div></div>',
-
- // CSS classes
- navClass: 'ui-tabs-nav',
- selectedClass: 'ui-tabs-selected',
- unselectClass: 'ui-tabs-unselect',
- disabledClass: 'ui-tabs-disabled',
- panelClass: 'ui-tabs-panel',
- hideClass: 'ui-tabs-hide',
- loadingClass: 'ui-tabs-loading'
-};
-
-$.ui.tabs.getter = "length";
-
-/*
- * Tabs Extensions
- */
-
-/*
- * Rotate
- */
-$.extend($.ui.tabs.prototype, {
- rotation: null,
- rotate: function(ms, continuing) {
-
- continuing = continuing || false;
-
- var self = this, t = this.options.selected;
-
- function start() {
- self.rotation = setInterval(function() {
- t = ++t < self.$tabs.length ? t : 0;
- self.select(t);
- }, ms);
- }
-
- function stop(e) {
- if (!e || e.clientX) { // only in case of a true click
- clearInterval(self.rotation);
- }
- }
-
- // start interval
- if (ms) {
- start();
- if (!continuing)
- this.$tabs.bind(this.options.event, stop);
- else
- this.$tabs.bind(this.options.event, function() {
- stop();
- t = self.options.selected;
- start();
- });
- }
- // stop interval
- else {
- stop();
- this.$tabs.unbind(this.options.event, stop);
- }
- }
-});
-
-})(jQuery);
--- a/web/facet.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/facet.py Wed Apr 27 09:55:17 2011 +0200
@@ -451,6 +451,11 @@
return False
+def encode(obj, encoding):
+ if isinstance(obj, unicode):
+ return obj.encode(encoding)
+ return unicode(obj).encode(encoding)
+
class RelationFacet(VocabularyFacet):
"""Base facet to filter some entities according to other entities to which
they are related. Create concret facet by inheriting from this class an then
@@ -605,7 +610,8 @@
insert_attr_select_relation(
rqlst, self.filtered_variable, self.rtype, self.role, self.target_attr,
select_target_entity=False)
- values = [str(x) for x, in self.rqlexec(rqlst.as_string())]
+ encoding = self._cw.encoding
+ values = [encode(x, encoding) for x, in self.rqlexec(rqlst.as_string())]
except:
self.exception('while computing values for %s', self)
return []
@@ -936,7 +942,7 @@
"""return the widget instance to use to display this facet"""
values = set(value for _, value in self.vocabulary() if value is not None)
# Rset with entities (the facet is selected) but without values
- if len(values) == 0:
+ if len(values) < 2:
return None
return self.wdgclass(self, min(values), max(values))
@@ -1125,8 +1131,8 @@
def _render(self):
facet = self.facet
- facet._cw.add_js('ui.slider.js')
- facet._cw.add_css('ui.all.css')
+ facet._cw.add_js('jquery.ui.js')
+ facet._cw.add_css('jquery.ui.css')
sliderid = make_uid('theslider')
facetid = xml_escape(self.facet.__regid__)
facet._cw.html_headers.add_onload(self.onload % {
--- a/web/formfields.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/formfields.py Wed Apr 27 09:55:17 2011 +0200
@@ -875,7 +875,9 @@
if self.choices:
return super(BooleanField, self).vocabulary(form)
if self.allow_none:
- return [('', ''), (form._cw._('yes'), '1'), (form._cw._('no'), '0')]
+ return [(form._cw._('indifferent'), ''),
+ (form._cw._('yes'), '1'),
+ (form._cw._('no'), '0')]
# XXX empty string for 'no' in that case for bw compat
return [(form._cw._('yes'), '1'), (form._cw._('no'), '')]
@@ -1200,14 +1202,19 @@
FIELDS = {
- 'Boolean': BooleanField,
+ 'String' : StringField,
'Bytes': FileField,
- 'Date': DateField,
- 'Datetime': DateTimeField,
+ 'Password': PasswordField,
+
+ 'Boolean': BooleanField,
'Int': IntField,
'Float': FloatField,
'Decimal': StringField,
- 'Password': PasswordField,
- 'String' : StringField,
- 'Time': TimeField,
+
+ 'Date': DateField,
+ 'Datetime': DateTimeField,
+ 'TZDatetime': DateTimeField,
+ 'Time': TimeField,
+ 'TZTime': TimeField,
+ # XXX implement 'Interval': TimeIntervalField,
}
--- a/web/formwidgets.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/formwidgets.py Wed Apr 27 09:55:17 2011 +0200
@@ -478,11 +478,12 @@
default <br/> is used.
"""
type = 'checkbox'
+ default_separator = u'<br/>\n'
vocabulary_widget = True
- def __init__(self, attrs=None, separator=u'<br/>\n', **kwargs):
+ def __init__(self, attrs=None, separator=None, **kwargs):
super(CheckBox, self).__init__(attrs, **kwargs)
- self.separator = separator
+ self.separator = separator or self.default_separator
def _render(self, form, field, renderer):
curvalues, attrs = self.values_and_attributes(form, field)
--- a/web/request.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/request.py Wed Apr 27 09:55:17 2011 +0200
@@ -734,26 +734,14 @@
return None, None
def parse_accept_header(self, header):
- """returns an ordered list of preferred languages"""
+ """returns an ordered list of accepted values"""
+ try:
+ value_parser, value_sort_key = ACCEPT_HEADER_PARSER[header.lower()]
+ except KeyError:
+ value_parser = value_sort_key = None
accepteds = self.get_header(header, '')
- values = []
- for info in accepteds.split(','):
- try:
- value, scores = info.split(';', 1)
- except ValueError:
- value = info
- score = 1.0
- else:
- for score in scores.split(';'):
- try:
- scorekey, scoreval = score.split('=')
- if scorekey == 'q': # XXX 'level'
- score = float(scoreval)
- except ValueError:
- continue
- values.append((score, value))
- values.sort(reverse=True)
- return (value for (score, value) in values)
+ values = _parse_accept_header(accepteds, value_parser, value_sort_key)
+ return (raw_value for (raw_value, parsed_value, score) in values)
def header_if_modified_since(self):
"""If the HTTP header If-modified-since is set, return the equivalent
@@ -768,8 +756,16 @@
will display '<[' at the beginning of the page
"""
self.set_content_type('text/html')
- self.main_stream.doctype = TRANSITIONAL_DOCTYPE_NOEXT
- self.main_stream.xmldecl = u''
+ self.main_stream.set_doctype(TRANSITIONAL_DOCTYPE_NOEXT)
+
+ def set_doctype(self, doctype, reset_xmldecl=True):
+ """helper method to dynamically change page doctype
+
+ :param doctype: the new doctype, e.g. '<!DOCTYPE html>'
+ :param reset_xmldecl: if True, remove the '<?xml version="1.0"?>'
+ declaration from the page
+ """
+ self.main_stream.set_doctype(doctype, reset_xmldecl)
# page data management ####################################################
@@ -858,5 +854,91 @@
self.parse_accept_header('Accept-Language')]
+
+## HTTP-accept parsers / utilies ##############################################
+def _mimetype_sort_key(accept_info):
+ """accepted mimetypes must be sorted by :
+
+ 1/ highest score first
+ 2/ most specific mimetype first, e.g. :
+ - 'text/html level=1' is more specific 'text/html'
+ - 'text/html' is more specific than 'text/*'
+ - 'text/*' itself more specific than '*/*'
+
+ """
+ raw_value, (media_type, media_subtype, media_type_params), score = accept_info
+ # FIXME: handle '+' in media_subtype ? (should xhtml+xml have a
+ # higher precedence than xml ?)
+ if media_subtype == '*':
+ score -= 0.0001
+ if media_type == '*':
+ score -= 0.0001
+ return 1./score, media_type, media_subtype, 1./(1+len(media_type_params))
+
+def _charset_sort_key(accept_info):
+ """accepted mimetypes must be sorted by :
+
+ 1/ highest score first
+ 2/ most specific charset first, e.g. :
+ - 'utf-8' is more specific than '*'
+ """
+ raw_value, value, score = accept_info
+ if value == '*':
+ score -= 0.0001
+ return 1./score, value
+
+def _parse_accept_header(raw_header, value_parser=None, value_sort_key=None):
+ """returns an ordered list accepted types
+
+ returned value is a list of 2-tuple (value, score), ordered
+ by score. Exact type of `value` will depend on what `value_parser`
+ will reutrn. if `value_parser` is None, then the raw value, as found
+ in the http header, is used.
+ """
+ if value_sort_key is None:
+ value_sort_key = lambda infos: 1./infos[-1]
+ values = []
+ for info in raw_header.split(','):
+ score = 1.0
+ other_params = {}
+ try:
+ value, infodef = info.split(';', 1)
+ except ValueError:
+ value = info
+ else:
+ for info in infodef.split(';'):
+ try:
+ infokey, infoval = info.split('=')
+ if infokey == 'q': # XXX 'level'
+ score = float(infoval)
+ continue
+ except ValueError:
+ continue
+ other_params[infokey] = infoval
+ parsed_value = value_parser(value, other_params) if value_parser else value
+ values.append( (value.strip(), parsed_value, score) )
+ values.sort(key=value_sort_key)
+ return values
+
+
+def _mimetype_parser(value, other_params):
+ """return a 3-tuple
+ (type, subtype, type_params) corresponding to the mimetype definition
+ e.g. : for 'text/*', `mimetypeinfo` will be ('text', '*', {}), for
+ 'text/html;level=1', `mimetypeinfo` will be ('text', '*', {'level': '1'})
+ """
+ try:
+ media_type, media_subtype = value.strip().split('/')
+ except ValueError: # safety belt : '/' should always be present
+ media_type = value.strip()
+ media_subtype = '*'
+ return (media_type, media_subtype, other_params)
+
+
+ACCEPT_HEADER_PARSER = {
+ 'accept': (_mimetype_parser, _mimetype_sort_key),
+ 'accept-charset': (None, _charset_sort_key),
+ }
+
from cubicweb import set_log_methods
set_log_methods(CubicWebRequestBase, LOGGER)
--- a/web/schemaviewer.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/schemaviewer.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,9 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""an helper class to display CubicWeb schema using ureports
+"""an helper class to display CubicWeb schema using ureports"""
-"""
__docformat__ = "restructuredtext en"
_ = unicode
@@ -217,7 +216,7 @@
if val is None:
val = ''
elif prop == 'constraints':
- val = ', '.join([c.restriction for c in val])
+ val = ', '.join([c.expression for c in val])
elif isinstance(val, dict):
for key, value in val.iteritems():
if isinstance(value, (list, tuple)):
--- a/web/test/unittest_application.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_application.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
--- a/web/test/unittest_form.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_form.py Wed Apr 27 09:55:17 2011 +0200
@@ -104,9 +104,9 @@
def test_reledit_composite_field(self):
rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"')
- form = self.vreg['views'].select('doreledit', self.request(),
+ form = self.vreg['views'].select('reledit', self.request(),
rset=rset, row=0, rtype='content')
- data = form.render(row=0, rtype='content', formid='base')
+ data = form.render(row=0, rtype='content', formid='base', action='edit_rtype')
self.failUnless('content_format' in data)
# form view tests #########################################################
--- a/web/test/unittest_reledit.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_reledit.py Wed Apr 27 09:55:17 2011 +0200
@@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""
-mainly regression-preventing tests for reledit/doreledit views
+mainly regression-preventing tests for reledit views
"""
from cubicweb.devtools.testlib import CubicWebTC
@@ -33,9 +33,9 @@
class ClickAndEditFormTC(ReleditMixinTC, CubicWebTC):
def test_default_config(self):
- reledit = {'title': """<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><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="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
- 'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue"><not specified></div><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="http://testing.fr/cubicweb/data/plus.png" alt="click to add a value"/></div></div></div>""",
- 'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><not specified></div><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="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+ reledit = {'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><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, '', 'edit_rtype');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>''',
+ 'long_desc': '''<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue"><not specified></div><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', 'add');" title="click to add a value"><img title="click to add a value" src="http://testing.fr/cubicweb/data/plus.png" alt="click to add a value"/></div></div></div>''',
+ 'manager': '''<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><not specified></div><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', 'edit_rtype');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>''',
'composite_card11_2ttypes': """<not specified>""",
'concerns': """<not specified>"""}
@@ -44,9 +44,11 @@
continue
rtype = rschema.type
self.assertMultiLineEqual(reledit[rtype] % {'eid': self.proj.eid},
- self.proj.view('reledit', rtype=rtype, role=role), rtype)
+ self.proj.view('reledit', rtype=rtype, role=role),
+ rtype)
def test_default_forms(self):
+ self.skip('Need to check if this test should still run post reledit/doreledit merge')
doreledit = {'title': """<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">
<fieldset>
<input name="__form_id" type="hidden" value="base" />
@@ -190,11 +192,11 @@
reledit_ctrl.tag_object_of(('Ticket', 'concerns', 'Project'),
{'edit_target': 'rtype'})
reledit = {
- '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><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', true, '');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
- 'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue"><long_desc is required></div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'long_desc', 'subject', 'long_desc-subject-%(eid)s', true, 'autolimited');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
- 'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/%(toto)s" title="">Toto</a></div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('edition', %(eid)s, 'manager', 'subject', 'manager-subject-%(eid)s', false, 'autolimited');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-%(eid)s-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm('deleteconf', %(eid)s, 'manager', 'subject', 'manager-subject-%(eid)s', false, 'autolimited');" title="click to delete this value"><img title="click to delete this value" src="http://testing.fr/cubicweb/data/cancel.png" alt="click to delete this value"/></div></div></div>""",
+ '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><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', true, '', 'edit_rtype');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+ 'long_desc': """<div id="long_desc-subject-%(eid)s-reledit" onmouseout="jQuery('#long_desc-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#long_desc-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="long_desc-subject-%(eid)s-value" class="editableFieldValue"><long_desc is required></div><div id="long_desc-subject-%(eid)s" class="editableField hidden"><div id="long_desc-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'long_desc', 'subject', 'long_desc-subject-%(eid)s', true, 'autolimited', 'edit_rtype');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>""",
+ 'manager': """<div id="manager-subject-%(eid)s-reledit" onmouseout="jQuery('#manager-subject-%(eid)s').addClass('hidden')" onmouseover="jQuery('#manager-subject-%(eid)s').removeClass('hidden')" class="releditField"><div id="manager-subject-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/personne/%(toto)s" title="">Toto</a></div><div id="manager-subject-%(eid)s" class="editableField hidden"><div id="manager-subject-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('edition', %(eid)s, 'manager', 'subject', 'manager-subject-%(eid)s', false, 'autolimited', 'edit_related');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div><div id="manager-subject-%(eid)s-delete" class="editableField" onclick="cw.reledit.loadInlineEditionForm('deleteconf', %(eid)s, 'manager', 'subject', 'manager-subject-%(eid)s', false, 'autolimited', 'delete');" title="click to delete this value"><img title="click to delete this value" src="http://testing.fr/cubicweb/data/cancel.png" alt="click to delete this value"/></div></div></div>""",
'composite_card11_2ttypes': """<not specified>""",
- 'concerns': """<div id="concerns-object-%(eid)s-reledit" onmouseout="jQuery('#concerns-object-%(eid)s').addClass('hidden')" onmouseover="jQuery('#concerns-object-%(eid)s').removeClass('hidden')" class="releditField"><div id="concerns-object-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/%(tick)s" title="">write the code</a></div><div id="concerns-object-%(eid)s" class="editableField hidden"><div id="concerns-object-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'concerns', 'object', 'concerns-object-%(eid)s', false, 'autolimited');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
+ 'concerns': """<div id="concerns-object-%(eid)s-reledit" onmouseout="jQuery('#concerns-object-%(eid)s').addClass('hidden')" onmouseover="jQuery('#concerns-object-%(eid)s').removeClass('hidden')" class="releditField"><div id="concerns-object-%(eid)s-value" class="editableFieldValue"><a href="http://testing.fr/cubicweb/ticket/%(tick)s" title="">write the code</a></div><div id="concerns-object-%(eid)s" class="editableField hidden"><div id="concerns-object-%(eid)s-update" class="editableField" onclick="cw.reledit.loadInlineEditionForm('base', %(eid)s, 'concerns', 'object', 'concerns-object-%(eid)s', false, 'autolimited', 'edit_rtype');" title="click to edit this field"><img title="click to edit this field" src="http://testing.fr/cubicweb/data/pen_icon.png" alt="click to edit this field"/></div></div></div>"""
}
for rschema, ttypes, role in self.proj.e_schema.relation_definitions(includefinal=True):
if rschema not in reledit:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_request.py Wed Apr 27 09:55:17 2011 +0200
@@ -0,0 +1,69 @@
+"""misc. unittests for utility functions
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from functools import partial
+
+from cubicweb.web.request import (_parse_accept_header,
+ _mimetype_sort_key, _mimetype_parser, _charset_sort_key)
+
+
+
+class AcceptParserTC(TestCase):
+
+ def test_parse_accept(self):
+ parse_accept_header = partial(_parse_accept_header,
+ value_parser=_mimetype_parser,
+ value_sort_key=_mimetype_sort_key)
+ # compare scores
+ self.assertEqual(parse_accept_header("audio/*;q=0.2, audio/basic"),
+ [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ),
+ ( ('audio/*', ('audio', '*', {}), 0.2 ) )])
+ self.assertEqual(parse_accept_header("text/plain;q=0.5, text/html, text/x-dvi;q=0.8, text/x-c"),
+ [( ('text/html', ('text', 'html', {}), 1.0 ) ),
+ ( ('text/x-c', ('text', 'x-c', {}), 1.0 ) ),
+ ( ('text/x-dvi', ('text', 'x-dvi', {}), 0.8 ) ),
+ ( ('text/plain', ('text', 'plain', {}), 0.5 ) )])
+ # compare mimetype precedence for a same given score
+ self.assertEqual(parse_accept_header("audio/*, audio/basic"),
+ [( ('audio/basic', ('audio', 'basic', {}), 1.0 ) ),
+ ( ('audio/*', ('audio', '*', {}), 1.0 ) )])
+ self.assertEqual(parse_accept_header("text/*, text/html, text/html;level=1, */*"),
+ [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ),
+ ( ('text/html', ('text', 'html', {}), 1.0 ) ),
+ ( ('text/*', ('text', '*', {}), 1.0 ) ),
+ ( ('*/*', ('*', '*', {}), 1.0 ) )])
+ # free party
+ self.assertEqual(parse_accept_header("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"),
+ [( ('text/html', ('text', 'html', {'level': '1'}), 1.0 ) ),
+ ( ('text/html', ('text', 'html', {}), 0.7 ) ),
+ ( ('*/*', ('*', '*', {}), 0.5 ) ),
+ ( ('text/html', ('text', 'html', {'level': '2'}), 0.4 ) ),
+ ( ('text/*', ('text', '*', {}), 0.3 ) )
+ ])
+ # chrome sample header
+ self.assertEqual(parse_accept_header("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"),
+ [( ('application/xhtml+xml', ('application', 'xhtml+xml', {}), 1.0 ) ),
+ ( ('application/xml', ('application', 'xml', {}), 1.0 ) ),
+ ( ('image/png', ('image', 'png', {}), 1.0 ) ),
+ ( ('text/html', ('text', 'html', {}), 0.9 ) ),
+ ( ('text/plain', ('text', 'plain', {}), 0.8 ) ),
+ ( ('*/*', ('*', '*', {}), 0.5 ) ),
+ ])
+
+ def test_parse_accept_language(self):
+ self.assertEqual(_parse_accept_header('fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3'),
+ [('fr', 'fr', 1.0), ('fr-fr', 'fr-fr', 0.8),
+ ('en-us', 'en-us', 0.5), ('en', 'en', 0.3)])
+
+ def test_parse_accept_charset(self):
+ parse_accept_header = partial(_parse_accept_header,
+ value_sort_key=_charset_sort_key)
+ self.assertEqual(parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7'),
+ [('ISO-8859-1', 'ISO-8859-1', 1.0),
+ ('utf-8', 'utf-8', 0.7),
+ ('*', '*', 0.7)])
+
+if __name__ == '__main__':
+ unittest_main()
--- a/web/test/unittest_urlpublisher.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_urlpublisher.py Wed Apr 27 09:55:17 2011 +0200
@@ -33,8 +33,8 @@
"""test suite for QSPreProcessor"""
def setup_database(self):
- self.create_user(u'ÿsaÿe')
req = self.request()
+ self.create_user(req, u'ÿsaÿe')
b = req.create_entity('BlogEntry', title=u'hell\'o', content=u'blabla')
c = req.create_entity('Tag', name=u'yo') # take care: Tag's name normalized to lower case
self.execute('SET C tags B WHERE C eid %(c)s, B eid %(b)s', {'c':c.eid, 'b':b.eid})
--- a/web/test/unittest_urlrewrite.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_urlrewrite.py Wed Apr 27 09:55:17 2011 +0200
@@ -103,9 +103,10 @@
class RgxActionRewriteTC(CubicWebTC):
def setup_database(self):
- self.p1 = self.create_user(u'user1')
+ req = self.request()
+ self.p1 = self.create_user(req, u'user1')
self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
- self.p2 = self.create_user(u'user2')
+ self.p2 = self.create_user(req, u'user2')
self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
def test_rgx_action_with_transforms(self):
--- a/web/test/unittest_views_basecontrollers.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_views_basecontrollers.py Wed Apr 27 09:55:17 2011 +0200
@@ -93,9 +93,9 @@
self.assertEqual([g.eid for g in e.in_group], groupeids)
def test_user_can_change_its_password(self):
- user = self.create_user('user')
+ req = self.request()
+ user = self.create_user(req, 'user')
cnx = self.login('user')
- req = self.request()
eid = u(user.eid)
req.form = {
'eid': eid, '__maineid' : eid,
@@ -160,8 +160,8 @@
self.assertEqual(email.address, 'dima@logilab.fr')
def test_edit_multiple_linked(self):
- peid = u(self.create_user('adim').eid)
req = self.request()
+ peid = u(self.create_user(req, 'adim').eid)
req.form = {'eid': [peid, 'Y'], '__maineid': peid,
'__type:'+peid: u'CWUser',
@@ -450,7 +450,8 @@
def test_nonregr_rollback_on_validation_error(self):
- p = self.create_user("doe")
+ req = self.request()
+ p = self.create_user(req, "doe")
# do not try to skip 'primary_email' for this test
old_skips = p.__class__.skip_copy_for
p.__class__.skip_copy_for = ()
@@ -497,7 +498,7 @@
class ReportBugControllerTC(CubicWebTC):
- def test_usable_by_guets(self):
+ def test_usable_by_guest(self):
self.login('anon')
self.assertRaises(NoSelectableObject,
self.vreg['controllers'].select, 'reportbug', self.request())
@@ -506,7 +507,7 @@
class SendMailControllerTC(CubicWebTC):
- def test_not_usable_by_guets(self):
+ def test_not_usable_by_guest(self):
self.assertRaises(NoSelectableObject,
self.vreg['controllers'].select, 'sendmail', self.request())
self.vreg['controllers'].select('sendmail',
@@ -529,7 +530,7 @@
req = self.request()
self.pytag = req.create_entity('Tag', name=u'python')
self.cubicwebtag = req.create_entity('Tag', name=u'cubicweb')
- self.john = self.create_user(u'John')
+ self.john = self.create_user(req, u'John')
## tests ##################################################################
--- a/web/test/unittest_views_baseviews.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_views_baseviews.py Wed Apr 27 09:55:17 2011 +0200
@@ -16,11 +16,14 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import with_statement
+
from logilab.common.testlib import unittest_main
from logilab.mtconverter import html_unescape
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.utils import json
+from cubicweb.view import StartupView, TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web.htmlwidgets import TableWidget
from cubicweb.web.views import vid_from_rset
@@ -125,5 +128,47 @@
self.assertListEqual(got, expected)
+class HTMLStreamTests(CubicWebTC):
+
+ def test_set_doctype_reset_xmldecl(self):
+ """
+ tests `cubicweb.web.request.CubicWebRequestBase.set_doctype`
+ with xmldecl reset
+ """
+ class MyView(StartupView):
+ __regid__ = 'my-view'
+ def call(self):
+ self._cw.set_doctype('<!DOCTYPE html>')
+
+ with self.temporary_appobjects(MyView):
+ html_source = self.view('my-view').source
+ source_lines = [line.strip() for line in html_source.splitlines(False)
+ if line.strip()]
+ self.assertListEqual(source_lines[:2],
+ ['<!DOCTYPE html>',
+ '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" xml:lang="en" lang="en">'])
+
+ def test_set_doctype_no_reset_xmldecl(self):
+ """
+ tests `cubicweb.web.request.CubicWebRequestBase.set_doctype`
+ with no xmldecl reset
+ """
+ html_doctype = TRANSITIONAL_DOCTYPE_NOEXT.strip()
+ class MyView(StartupView):
+ __regid__ = 'my-view'
+ def call(self):
+ self._cw.set_doctype(html_doctype, reset_xmldecl=False)
+ self._cw.main_stream.set_namespaces([('xmlns', 'http://www.w3.org/1999/xhtml')])
+ self._cw.main_stream.set_htmlattrs([('lang', 'cz')])
+
+ with self.temporary_appobjects(MyView):
+ html_source = self.view('my-view').source
+ source_lines = [line.strip() for line in html_source.splitlines(False)
+ if line.strip()]
+ self.assertListEqual(source_lines[:3],
+ ['<?xml version="1.0" encoding="UTF-8"?>',
+ html_doctype,
+ '<html xmlns="http://www.w3.org/1999/xhtml" lang="cz">'])
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_viewselector.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/test/unittest_viewselector.py Wed Apr 27 09:55:17 2011 +0200
@@ -180,7 +180,7 @@
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset2)
def test_propertiesform_jdoe(self):
- self.create_user('jdoe')
+ self.create_user(self.request(), 'jdoe')
self.login('jdoe')
req1 = self.request()
req2 = self.request()
--- a/web/views/basecontrollers.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/basecontrollers.py Wed Apr 27 09:55:17 2011 +0200
@@ -455,13 +455,13 @@
def js_reledit_form(self):
req = self._cw
args = dict((x, req.form[x])
- for x in ('formid', 'rtype', 'role', 'reload'))
+ for x in ('formid', 'rtype', 'role', 'reload', 'action'))
rset = req.eid_rset(typed_eid(self._cw.form['eid']))
try:
args['reload'] = json.loads(args['reload'])
except ValueError: # not true/false, an absolute url
assert args['reload'].startswith('http')
- view = req.vreg['views'].select('doreledit', req, rset=rset, rtype=args['rtype'])
+ view = req.vreg['views'].select('reledit', req, rset=rset, rtype=args['rtype'])
return self._call_view(view, **args)
@jsonize
--- a/web/views/owl.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/owl.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -35,15 +35,19 @@
}
OWL_TYPE_MAP = {'String': 'xsd:string',
- 'Datetime': 'xsd:dateTime',
'Bytes': 'xsd:byte',
- 'Float': 'xsd:float',
+ 'Password': 'xsd:byte',
+
'Boolean': 'xsd:boolean',
'Int': 'xsd:int',
+ 'Float': 'xsd:float',
+ 'Decimal' : 'xsd:decimal',
+
'Date':'xsd:date',
+ 'Datetime': 'xsd:dateTime',
+ 'TZDatetime': 'xsd:dateTime',
'Time': 'xsd:time',
- 'Password': 'xsd:byte',
- 'Decimal' : 'xsd:decimal',
+ 'TZTime': 'xsd:time',
'Interval': 'xsd:duration'
}
--- a/web/views/plots.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/plots.py Wed Apr 27 09:55:17 2011 +0200
@@ -47,7 +47,7 @@
@objectify_selector
def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
etypes = rset.description[0]
- if etypes[0] not in ('Date', 'Datetime'):
+ if etypes[0] not in ('Date', 'Datetime', 'TZDatetime'):
return 0
for etype in etypes[1:]:
if etype not in ('Int', 'Float'):
--- a/web/views/reledit.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/reledit.py Wed Apr 27 09:55:17 2011 +0200
@@ -15,7 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""the 'reledit' feature (eg edit attribute/relation from primary view)"""
+"""edit entity attributes/relations from any view, without going to the entity
+form
+"""
__docformat__ = "restructuredtext en"
_ = unicode
@@ -24,7 +26,8 @@
from warnings import warn
from logilab.mtconverter import xml_escape
-from logilab.common.deprecation import deprecated
+from logilab.common.deprecation import deprecated, class_renamed
+from logilab.common.decorators import cached
from cubicweb import neg_role
from cubicweb.schema import display_name
@@ -43,16 +46,18 @@
return u''
def append_field(self, *args):
pass
+ def add_hidden(self, *args):
+ pass
rctrl = uicfg.reledit_ctrl
-class ClickAndEditFormView(EntityView):
- __regid__ = 'doreledit'
+class AutoClickAndEditFormView(EntityView):
+ __regid__ = 'reledit'
__select__ = non_final_entity() & match_kwargs('rtype')
# ui side continuations
_onclick = (u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', "
- "'%(divid)s', %(reload)s, '%(vid)s');")
+ "'%(divid)s', %(reload)s, '%(vid)s', '%(action)s');")
_cancelclick = "cw.reledit.cleanupAfterCancel('%s')"
# ui side actions/buttons
@@ -75,93 +80,84 @@
# function taking the subject entity & returning a boolean or an eid
rvid=None, # vid to be applied to other side of rtype (non final relations only)
default_value=None,
- formid='base'
+ formid='base',
+ action=None
):
"""display field to edit entity's `rtype` relation on click"""
assert rtype
- assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
self._cw.add_css('cubicweb.form.css')
self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js'))
- entity = self.cw_rset.get_entity(row, col)
+ self.entity = self.cw_rset.get_entity(row, col)
rschema = self._cw.vreg.schema[rtype]
- self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')
+ self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*')
if rvid is not None or default_value is not None:
warn('[3.9] specifying rvid/default_value on select is deprecated, '
'reledit_ctrl rtag to control this' % self, DeprecationWarning)
- reload = self._compute_reload(entity, rschema, role, reload)
- divid = self._build_divid(rtype, role, entity.eid)
+ reload = self._compute_reload(rschema, role, reload)
+ divid = self._build_divid(rtype, role, self.entity.eid)
if rschema.final:
- self._handle_attribute(entity, rschema, role, divid, reload)
+ self._handle_attribute(rschema, role, divid, reload, action)
else:
if self._is_composite():
- self._handle_composite(entity, rschema, role, divid, reload, formid)
+ self._handle_composite(rschema, role, divid, reload, formid, action)
else:
- self._handle_relation(entity, rschema, role, divid, reload, formid)
+ self._handle_relation(rschema, role, divid, reload, formid, action)
- def _handle_attribute(self, entity, rschema, role, divid, reload):
- rtype = rschema.type
- value = entity.printable_value(rtype)
- if not self._should_edit_attribute(entity, rschema):
+ def _handle_attribute(self, rschema, role, divid, reload, action):
+ value = self.entity.printable_value(rschema.type)
+ if not self._should_edit_attribute(rschema):
self.w(value)
return
- display_label, related_entity = self._prepare_form(entity, rtype, role)
- form, renderer = self._build_form(entity, rtype, role, divid, 'base',
- reload, display_label, related_entity)
+ form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action)
value = value or self._compute_default_value(rschema, role)
self.view_form(divid, value, form, renderer)
- def _compute_formid_value(self, entity, rschema, role, rvid, formid):
- related_rset = entity.related(rschema.type, role)
+ def _compute_formid_value(self, rschema, role, rvid, formid):
+ related_rset = self.entity.related(rschema.type, role)
if related_rset:
value = self._cw.view(rvid, related_rset)
else:
value = self._compute_default_value(rschema, role)
- if not self._should_edit_relation(entity, rschema, role):
+ if not self._should_edit_relation(rschema, role):
return None, value
return formid, value
- def _handle_relation(self, entity, rschema, role, divid, reload, formid):
+ def _handle_relation(self, rschema, role, divid, reload, formid, action):
rvid = self._rules.get('rvid', 'autolimited')
- formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
+ formid, value = self._compute_formid_value(rschema, role, rvid, formid)
if formid is None:
return self.w(value)
- rtype = rschema.type
- display_label, related_entity = self._prepare_form(entity, rtype, role)
- form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
- display_label, related_entity, dict(vid=rvid))
+ form, renderer = self._build_form(self.entity, rschema, role, divid, formid,
+ reload, action, dict(vid=rvid))
self.view_form(divid, value, form, renderer)
- def _handle_composite(self, entity, rschema, role, divid, reload, formid):
+ def _handle_composite(self, rschema, role, divid, reload, formid, action):
# this is for attribute-like composites (1 target type, 1 related entity at most, for now)
- ttypes = self._compute_ttypes(rschema, role)
+ entity = self.entity
related_rset = entity.related(rschema.type, role)
- add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes)
- edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes)
- delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role)
+ add_related = self._may_add_related(related_rset, rschema, role)
+ edit_related = self._may_edit_related_entity(related_rset, rschema, role)
+ delete_related = edit_related and self._may_delete_related(related_rset, rschema, role)
rvid = self._rules.get('rvid', 'autolimited')
- formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid)
+ formid, value = self._compute_formid_value(rschema, role, rvid, formid)
if formid is None or not (edit_related or add_related):
# till we learn to handle cases where not (edit_related or add_related)
self.w(value)
return
- rtype = rschema.type
- ttype = ttypes[0]
- _fdata = self._prepare_composite_form(entity, rtype, role, edit_related,
- add_related and ttype)
- display_label, related_entity = _fdata
- form, renderer = self._build_form(entity, rtype, role, divid, formid, reload,
- display_label, related_entity, dict(vid=rvid))
+ form, renderer = self._build_form(entity, rschema, role, divid, formid,
+ reload, action, dict(vid=rvid))
self.view_form(divid, value, form, renderer,
edit_related, add_related, delete_related)
+ @cached
def _compute_ttypes(self, rschema, role):
dual_role = neg_role(role)
return getattr(rschema, '%ss' % dual_role)()
- def _compute_reload(self, entity, rschema, role, reload):
+ def _compute_reload(self, rschema, role, reload):
ctrl_reload = self._rules.get('reload', reload)
if callable(ctrl_reload):
- ctrl_reload = ctrl_reload(entity)
+ ctrl_reload = ctrl_reload(self.entity)
if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False
ctrl_reload = self._cw.build_url(ctrl_reload)
return ctrl_reload
@@ -179,33 +175,36 @@
def _is_composite(self):
return self._rules.get('edit_target') == 'related'
- def _may_add_related(self, related_rset, entity, rschema, role, ttypes):
+ def _may_add_related(self, related_rset, rschema, role):
""" ok for attribute-like composite entities """
+ ttypes = self._compute_ttypes(rschema, role)
if len(ttypes) > 1: # many etypes: learn how to do it
return False
- rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role)
+ rdef = rschema.role_rdef(self.entity.e_schema, ttypes[0], role)
card = rdef.role_cardinality(role)
if related_rset or card not in '?1':
return False
if role == 'subject':
- kwargs = {'fromeid': entity.eid}
+ kwargs = {'fromeid': self.entity.eid}
else:
- kwargs = {'toeid': entity.eid}
+ kwargs = {'toeid': self.entity.eid}
return rdef.has_perm(self._cw, 'add', **kwargs)
- def _may_edit_related_entity(self, related_rset, entity, rschema, role, ttypes):
+ def _may_edit_related_entity(self, related_rset, rschema, role):
""" controls the edition of the related entity """
+ ttypes = self._compute_ttypes(rschema, role)
if len(ttypes) > 1 or len(related_rset.rows) != 1:
return False
- if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
+ if self.entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1':
return False
return related_rset.get_entity(0, 0).cw_has_perm('update')
- def _may_delete_related(self, related_rset, entity, rschema, role):
+ def _may_delete_related(self, related_rset, rschema, role):
# we assume may_edit_related, only 1 related entity
if not related_rset:
return False
rentity = related_rset.get_entity(0, 0)
+ entity = self.entity
if role == 'subject':
kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid}
else:
@@ -230,33 +229,33 @@
""" builds an id for the root div of a reledit widget """
return '%s-%s-%s' % (rtype, role, entity_eid)
- def _build_args(self, entity, rtype, role, formid, reload,
+ def _build_args(self, entity, rtype, role, formid, reload, action,
extradata=None):
divid = self._build_divid(rtype, role, entity.eid)
event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid,
- 'reload' : json_dumps(reload),
+ 'reload' : json_dumps(reload), 'action': action,
'role' : role, 'vid' : u''}
if extradata:
event_args.update(extradata)
return event_args
- def _prepare_form(self, entity, _rtype, role):
- display_label = False
- related_entity = entity
- return display_label, related_entity
-
- def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related):
- display_label = True
- if edit_related and not add_related:
- related_entity = entity.related(rtype, role).get_entity(0, 0)
- elif add_related:
- _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw)
+ def _prepare_form(self, entity, rschema, role, action):
+ assert action in ('edit_rtype', 'edit_related', 'add', 'delete'), action
+ if action == 'edit_rtype':
+ return False, entity
+ label = True
+ if action in ('edit_related', 'delete'):
+ edit_entity = entity.related(rschema, role).get_entity(0, 0)
+ elif action == 'add':
+ add_etype = self._compute_ttypes(rschema, role)[0]
+ _new_entity = self._cw.vreg['etypes'].etype_class(add_etype)(self._cw)
_new_entity.eid = self._cw.varmaker.next()
- related_entity = _new_entity
+ edit_entity = _new_entity
# XXX see forms.py ~ 276 and entities.linked_to method
# is there another way ?
- self._cw.form['__linkto'] = '%s:%s:%s' % (rtype, entity.eid, neg_role(role))
- return display_label, related_entity
+ self._cw.form['__linkto'] = '%s:%s:%s' % (rschema, entity.eid, neg_role(role))
+ assert edit_entity
+ return label, edit_entity
def _build_renderer(self, related_entity, display_label):
return self._cw.vreg['formrenderers'].select(
@@ -266,13 +265,18 @@
display_help=False, button_bar_class='buttonbar',
display_progress_div=False)
- def _build_form(self, entity, rtype, role, divid, formid, reload,
- display_label, related_entity, extradata=None, **formargs):
- event_args = self._build_args(entity, rtype, role, formid,
- reload, extradata)
+ def _build_form(self, entity, rschema, role, divid, formid, reload, action,
+ extradata=None, **formargs):
+ rtype = rschema.type
+ event_args = self._build_args(entity, rtype, role, formid, reload, action, extradata)
+ if not action:
+ form = _DummyForm()
+ form.event_args = event_args
+ return form, None
+ label, edit_entity = self._prepare_form(entity, rschema, role, action)
cancelclick = self._cancelclick % divid
form = self._cw.vreg['forms'].select(
- formid, self._cw, rset=related_entity.as_rset(), entity=related_entity,
+ formid, self._cw, rset=edit_entity.as_rset(), entity=edit_entity,
domid='%s-form' % divid, formtype='inlined',
action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'),
cwtarget='eformframe', cssclass='releditForm',
@@ -298,9 +302,10 @@
if formid == 'base':
field = form.field_by_name(rtype, role, entity.e_schema)
form.append_field(field)
- return form, self._build_renderer(related_entity, display_label)
+ return form, self._build_renderer(edit_entity, label)
- def _should_edit_attribute(self, entity, rschema):
+ def _should_edit_attribute(self, rschema):
+ entity = self.entity
rdef = entity.e_schema.rdef(rschema)
# check permissions
if not entity.cw_has_perm('update'):
@@ -312,8 +317,8 @@
' use _should_edit_attribute instead',
_should_edit_attribute)
- def _should_edit_relation(self, entity, rschema, role):
- eeid = entity.eid
+ def _should_edit_relation(self, rschema, role):
+ eeid = self.entity.eid
perm_args = {'fromeid': eeid} if role == 'subject' else {'toeid': eeid}
return rschema.has_perm(self._cw, 'add', **perm_args)
@@ -335,9 +340,11 @@
w(u'<div id="%s" class="editableField hidden">' % divid)
def _edit_action(self, divid, args, edit_related, add_related, _delete_related):
+ # XXX disambiguate wrt edit_related
if not add_related: # currently, excludes edition
w = self.w
args['formid'] = 'edition' if edit_related else 'base'
+ args['action'] = 'edit_related' if edit_related else 'edit_rtype'
w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' %
(divid, xml_escape(self._onclick % args), self._cw._(self._editzonemsg)))
w(self._build_edit_zone())
@@ -346,7 +353,8 @@
def _add_action(self, divid, args, _edit_related, add_related, _delete_related):
if add_related:
w = self.w
- args['formid'] = 'edition' if add_related else 'base'
+ args['formid'] = 'edition'
+ args['action'] = 'add'
w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' %
(divid, xml_escape(self._onclick % args), self._cw._(self._addmsg)))
w(self._build_add_zone())
@@ -356,6 +364,7 @@
if delete_related:
w = self.w
args['formid'] = 'deleteconf'
+ args['action'] = 'delete'
w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">' %
(divid, xml_escape(self._onclick % args), self._cw._(self._deletemsg)))
w(self._build_delete_zone())
@@ -376,13 +385,4 @@
self._close_form_wrapper()
-class AutoClickAndEditFormView(ClickAndEditFormView):
- __regid__ = 'reledit'
-
- def _build_form(self, entity, rtype, role, divid, formid, reload,
- display_label, related_entity, extradata=None, **formargs):
- event_args = self._build_args(entity, rtype, role, 'base',
- reload, extradata)
- form = _DummyForm()
- form.event_args = event_args
- return form, None
+ClickAndEditFormView = class_renamed('ClickAndEditFormView', AutoClickAndEditFormView)
--- a/web/views/sparql.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/sparql.py Wed Apr 27 09:55:17 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -77,17 +77,22 @@
YAMS_XMLSCHEMA_MAPPING = {
'String': 'string',
+
+ 'Boolean': 'boolean',
'Int': 'integer',
'Float': 'float',
- 'Boolean': 'boolean',
+
'Datetime': 'dateTime',
+ 'TZDatetime': 'dateTime',
'Date': 'date',
'Time': 'time',
+ 'TZTime': 'time',
+
# XXX the following types don't have direct mapping
'Decimal': 'string',
'Interval': 'duration',
+ 'Bytes': 'base64Binary',
'Password': 'string',
- 'Bytes': 'base64Binary',
}
def xmlschema(yamstype):
--- a/web/views/tabs.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/tabs.py Wed Apr 27 09:55:17 2011 +0200
@@ -46,7 +46,7 @@
reloadable=False, show_spinbox=True, w=None):
"""a lazy version of wview"""
w = w or self.w
- self._cw.add_js('cubicweb.lazy.js')
+ self._cw.add_js('cubicweb.ajax.js')
urlparams = self._cw.form.copy()
urlparams.update({'vid' : vid, 'fname' : 'view'})
if rql:
@@ -127,8 +127,8 @@
if entity and len(self.cw_rset) > 1:
entity.view(default, w=self.w)
return
- self._cw.add_css('ui.tabs.css')
- self._cw.add_js(('ui.core.js', 'ui.tabs.js', 'cubicweb.ajax.js'))
+ self._cw.add_css('jquery.ui.css')
+ self._cw.add_js(('jquery.ui.js', 'cubicweb.ajax.js'))
# prune tabs : not all are to be shown
tabs, active_tab = self.prune_tabs(tabs, default)
# build the html structure
@@ -148,7 +148,6 @@
if domid == active_tab:
active_tab_idx = i
w(u'</ul>')
- w(u'</div>')
for tabid, domid, tabkwargs in tabs:
w(u'<div id="%s">' % domid)
tabkwargs.setdefault('tabid', domid)
@@ -156,11 +155,12 @@
tabkwargs.setdefault('rset', self.cw_rset)
self.lazyview(**tabkwargs)
w(u'</div>')
+ w(u'</div>')
# call the setTab() JS function *after* each tab is generated
# because the callback binding needs to be done before
# XXX make work history: true
self._cw.add_onload(u"""
- jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
+ jQuery('#entity-tabs-%(eeid)s').tabs( { selected: %(tabindex)s });
setTab('%(domid)s', '%(cookiename)s');
""" % {'tabindex' : active_tab_idx,
'domid' : active_tab,
--- a/web/views/xmlrss.py Thu Apr 21 17:25:32 2011 +0200
+++ b/web/views/xmlrss.py Wed Apr 27 09:55:17 2011 +0200
@@ -42,6 +42,8 @@
'Date': lambda x: x.strftime('%Y-%m-%d'),
'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'),
'Time': lambda x: x.strftime('%H:%M:%S'),
+ 'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
+ 'TZTime': lambda x: x.strftime('%H:%M:%S'),
'Interval': lambda x: x.days * 60*60*24 + x.seconds,
}