--- a/MANIFEST.in Mon Feb 08 13:33:09 2010 +0100
+++ b/MANIFEST.in Mon Feb 08 13:35:07 2010 +0100
@@ -17,7 +17,6 @@
recursive-include i18n *.pot *.po
recursive-include schemas *.py *.sql.*
-recursive-include common/test/data *
recursive-include entities/test/data *
recursive-include sobjects/test/data *
recursive-include server/test/data *
--- a/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,7 +7,6 @@
:license: Library General Public License version 2 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
-from cubicweb.__pkginfo__ import version as __version__
import __builtin__
# '_' is available in builtins to mark internationalized string but should
@@ -19,9 +18,7 @@
import sys, os, logging
from StringIO import StringIO
-from urllib import quote as urlquote, unquote as urlunquote
-from logilab.common.decorators import cached
from logilab.common.logging_ext import set_log_methods
@@ -30,6 +27,8 @@
else:
logging.basicConfig()
+from cubicweb.__pkginfo__ import version as __version__
+
set_log_methods(sys.modules[__name__], logging.getLogger('cubicweb'))
@@ -57,293 +56,12 @@
"Binary objects must use raw strings, not %s" % data.__class__
StringIO.write(self, data)
-
-class RequestSessionMixIn(object):
- """mixin class containing stuff shared by server session and web request
- """
- def __init__(self, vreg):
- self.vreg = vreg
- try:
- encoding = vreg.property_value('ui.encoding')
- except: # no vreg or property not registered
- encoding = 'utf-8'
- self.encoding = encoding
- # cache result of execution for (rql expr / eids),
- # should be emptied on commit/rollback of the server session / web
- # connection
- self.local_perm_cache = {}
-
- def property_value(self, key):
- if self.user:
- return self.user.property_value(key)
- return self.vreg.property_value(key)
-
- def etype_rset(self, etype, size=1):
- """return a fake result set for a particular entity type"""
- from cubicweb.rset import ResultSet
- rset = ResultSet([('A',)]*size, '%s X' % etype,
- description=[(etype,)]*size)
- def get_entity(row, col=0, etype=etype, req=self, rset=rset):
- return req.vreg.etype_class(etype)(req, rset, row, col)
- rset.get_entity = get_entity
- return self.decorate_rset(rset)
-
- def eid_rset(self, eid, etype=None):
- """return a result set for the given eid without doing actual query
- (we have the eid, we can suppose it exists and user has access to the
- entity)
- """
- from cubicweb.rset import ResultSet
- eid = typed_eid(eid)
- if etype is None:
- etype = self.describe(eid)[0]
- rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
- [(etype,)])
- return self.decorate_rset(rset)
-
- def empty_rset(self):
- """return an empty result set. This is used e.g. to substitute
- to a real result set if the user doesn't have permission to
- access the results of a query.
- """
- from cubicweb.rset import ResultSet
- return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
-
- def entity_from_eid(self, eid, etype=None):
- try:
- return self.entity_cache(eid)
- except KeyError:
- rset = self.eid_rset(eid, etype)
- entity = rset.get_entity(0, 0)
- self.set_entity_cache(entity)
- return entity
-
- def entity_cache(self, eid):
- raise KeyError
- def set_entity_cache(self, entity):
- pass
-
- def create_entity(self, etype, _cw_unsafe=False, **kwargs):
- """add a new entity of the given type
-
- Example (in a shell session):
-
- c = create_entity('Company', name=u'Logilab')
- create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe')
-
- """
- if _cw_unsafe:
- execute = self.unsafe_execute
- else:
- execute = self.execute
- rql = 'INSERT %s X' % etype
- relations = []
- restrictions = set()
- cachekey = []
- pending_relations = []
- for attr, value in kwargs.items():
- if isinstance(value, (tuple, list, set, frozenset)):
- if len(value) == 1:
- value = iter(value).next()
- else:
- del kwargs[attr]
- pending_relations.append( (attr, value) )
- continue
- if hasattr(value, 'eid'): # non final relation
- rvar = attr.upper()
- # XXX safer detection of object relation
- if attr.startswith('reverse_'):
- relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
- else:
- relations.append('X %s %s' % (attr, rvar))
- restriction = '%s eid %%(%s)s' % (rvar, attr)
- if not restriction in restrictions:
- restrictions.add(restriction)
- cachekey.append(attr)
- kwargs[attr] = value.eid
- else: # attribute
- relations.append('X %s %%(%s)s' % (attr, attr))
- if relations:
- rql = '%s: %s' % (rql, ', '.join(relations))
- if restrictions:
- rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
- created = execute(rql, kwargs, cachekey).get_entity(0, 0)
- for attr, values in pending_relations:
- if attr.startswith('reverse_'):
- restr = 'Y %s X' % attr[len('reverse_'):]
- else:
- restr = 'X %s Y' % attr
- execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
- restr, ','.join(str(r.eid) for r in values)),
- {'x': created.eid}, 'x')
- return created
-
- # url generation methods ##################################################
-
- def build_url(self, *args, **kwargs):
- """return an absolute URL using params dictionary key/values as URL
- parameters. Values are automatically URL quoted, and the
- publishing method to use may be specified or will be guessed.
- """
- # use *args since we don't want first argument to be "anonymous" to
- # avoid potential clash with kwargs
- assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
- method = args[0]
- base_url = kwargs.pop('base_url', None)
- if base_url is None:
- base_url = self.base_url()
- if '_restpath' in kwargs:
- assert method == 'view', method
- path = kwargs.pop('_restpath')
- else:
- path = method
- if not kwargs:
- return u'%s%s' % (base_url, path)
- return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
+# use this dictionary to rename entity types while keeping bw compat
+ETYPE_NAME_MAP = {}
-
- def build_url_params(self, **kwargs):
- """return encoded params to incorporate them in an URL"""
- args = []
- for param, values in kwargs.items():
- if not isinstance(values, (list, tuple)):
- values = (values,)
- for value in values:
- args.append(u'%s=%s' % (param, self.url_quote(value)))
- return '&'.join(args)
-
- def url_quote(self, value, safe=''):
- """urllib.quote is not unicode safe, use this method to do the
- necessary encoding / decoding. Also it's designed to quote each
- part of a url path and so the '/' character will be encoded as well.
- """
- if isinstance(value, unicode):
- quoted = urlquote(value.encode(self.encoding), safe=safe)
- return unicode(quoted, self.encoding)
- return urlquote(str(value), safe=safe)
-
- def url_unquote(self, quoted):
- """returns a unicode unquoted string
-
- decoding is based on `self.encoding` which is the encoding
- used in `url_quote`
- """
- if isinstance(quoted, unicode):
- quoted = quoted.encode(self.encoding)
- try:
- return unicode(urlunquote(quoted), self.encoding)
- except UnicodeDecodeError: # might occurs on manually typed URLs
- return unicode(urlunquote(quoted), 'iso-8859-1')
-
-
- # session's user related methods #####################################
-
- @cached
- def user_data(self):
- """returns a dictionnary with this user's information"""
- userinfo = {}
- if self.is_internal_session:
- userinfo['login'] = "cubicweb"
- userinfo['name'] = "cubicweb"
- userinfo['email'] = ""
- return userinfo
- user = self.actual_session().user
- userinfo['login'] = user.login
- userinfo['name'] = user.name()
- userinfo['email'] = user.get_email()
- return userinfo
-
- def is_internal_session(self):
- """overrided on the server-side"""
- return False
-
- # abstract methods to override according to the web front-end #############
-
- def base_url(self):
- """return the root url of the instance"""
- raise NotImplementedError
-
- def decorate_rset(self, rset):
- """add vreg/req (at least) attributes to the given result set """
- raise NotImplementedError
-
- def describe(self, eid):
- """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
- raise NotImplementedError
-
-
-# XXX 2.45 is allowing nicer entity type names, use this map for bw compat
-ETYPE_NAME_MAP = {# 3.2 migration
- 'ECache': 'CWCache',
- 'EUser': 'CWUser',
- 'EGroup': 'CWGroup',
- 'EProperty': 'CWProperty',
- 'EFRDef': 'CWAttribute',
- 'ENFRDef': 'CWRelation',
- 'ERType': 'CWRType',
- 'EEType': 'CWEType',
- 'EConstraintType': 'CWConstraintType',
- 'EConstraint': 'CWConstraint',
- 'EPermission': 'CWPermission',
- # 2.45 migration
- 'Eetype': 'CWEType',
- 'Ertype': 'CWRType',
- 'Efrdef': 'CWAttribute',
- 'Enfrdef': 'CWRelation',
- 'Econstraint': 'CWConstraint',
- 'Econstrainttype': 'CWConstraintType',
- 'Epermission': 'CWPermission',
- 'Egroup': 'CWGroup',
- 'Euser': 'CWUser',
- 'Eproperty': 'CWProperty',
- 'Emailaddress': 'EmailAddress',
- 'Rqlexpression': 'RQLExpression',
- 'Trinfo': 'TrInfo',
- }
-
-
-
-# XXX cubic web cube migration map
-CW_MIGRATION_MAP = {'erudi': 'cubicweb',
-
- 'eaddressbook': 'addressbook',
- 'ebasket': 'basket',
- 'eblog': 'blog',
- 'ebook': 'book',
- 'ecomment': 'comment',
- 'ecompany': 'company',
- 'econference': 'conference',
- 'eemail': 'email',
- 'eevent': 'event',
- 'eexpense': 'expense',
- 'efile': 'file',
- 'einvoice': 'invoice',
- 'elink': 'link',
- 'emailinglist': 'mailinglist',
- 'eperson': 'person',
- 'eshopcart': 'shopcart',
- 'eskillmat': 'skillmat',
- 'etask': 'task',
- 'eworkcase': 'workcase',
- 'eworkorder': 'workorder',
- 'ezone': 'zone',
- 'i18ncontent': 'i18ncontent',
- 'svnfile': 'vcsfile',
-
- 'eclassschemes': 'keyword',
- 'eclassfolders': 'folder',
- 'eclasstags': 'tag',
-
- 'jpl': 'jpl',
- 'jplintra': 'jplintra',
- 'jplextra': 'jplextra',
- 'jplorg': 'jplorg',
- 'jplrecia': 'jplrecia',
- 'crm': 'crm',
- 'agueol': 'agueol',
- 'docaster': 'docaster',
- 'asteretud': 'asteretud',
- }
+# XXX cubic web cube migration map. See if it's worth keeping this mecanism
+# to help in cube renaming
+CW_MIGRATION_MAP = {}
def neg_role(role):
if role == 'subject':
@@ -362,9 +80,6 @@
except AttributeError:
return neg_role(obj.role)
-def underline_title(title, car='-'):
- return title+'\n'+(car*len(title))
-
class CubicWebEventManager(object):
"""simple event / callback manager.
--- a/__pkginfo__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/__pkginfo__.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,7 +7,7 @@
distname = "cubicweb"
modname = "cubicweb"
-numversion = (3, 5, 12)
+numversion = (3, 6, 0)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL'
@@ -48,7 +48,6 @@
scripts = [s for s in glob.glob(join('bin', 'cubicweb-*'))
if not s.endswith('.bat')]
include_dirs = [join('test', 'data'),
- join('common', 'test', 'data'),
join('server', 'test', 'data'),
join('web', 'test', 'data'),
join('devtools', 'test', 'data'),
--- a/_exceptions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/_exceptions.py Mon Feb 08 13:35:07 2010 +0100
@@ -20,7 +20,7 @@
if self.args:
return self.msg % tuple(self.args)
return self.msg
- return ' '.join(str(arg) for arg in self.args)
+ return ' '.join(unicode(arg) for arg in self.args)
class ConfigurationError(CubicWebException):
--- a/appobject.py Mon Feb 08 13:33:09 2010 +0100
+++ b/appobject.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,28 +11,13 @@
import types
from logging import getLogger
-from datetime import datetime, timedelta, time
+from warnings import warn
from logilab.common.decorators import classproperty
from logilab.common.deprecation import deprecated
from logilab.common.logging_ext import set_log_methods
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
from cubicweb import Unauthorized, NoSelectableObject
-from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime
-
-ONESECOND = timedelta(0, 1, 0)
-CACHE_REGISTRY = {}
-
-
-class Cache(dict):
- def __init__(self):
- super(Cache, self).__init__()
- _now = datetime.now()
- self.cache_creation_date = _now
- self.latest_cache_lookup = _now
# selector base classes and operations ########################################
@@ -42,12 +27,13 @@
would be overkill::
@objectify_selector
- def yes(cls, *args, **kwargs):
+ def one(cls, *args, **kwargs):
return 1
"""
return type(selector_func.__name__, (Selector,),
- {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
+ {'__doc__': selector_func.__doc__,
+ '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
def _instantiate_selector(selector):
@@ -100,11 +86,15 @@
return AndSelector(self, other)
def __rand__(self, other):
return AndSelector(other, self)
+ def __iand__(self, other):
+ raise NotImplementedError('cant use inplace & (binary and)')
def __or__(self, other):
return OrSelector(self, other)
def __ror__(self, other):
return OrSelector(other, self)
+ def __ior__(self, other):
+ raise NotImplementedError('cant use inplace | (binary or)')
def __invert__(self):
return NotSelector(self)
@@ -195,9 +185,13 @@
class yes(Selector):
- """return arbitrary score
+ """Return the score given as parameter, with a default score of 0.5 so any
+ other selector take precedence.
- default score of 0.5 so any other selector take precedence
+ Usually used for appobjects which can be selected whatever the context, or
+ also sometimes to add arbitrary points to a score.
+
+ Take care, `yes(0)` could be named 'no'...
"""
def __init__(self, score=0.5):
self.score = score
@@ -220,7 +214,7 @@
:__registry__:
name of the registry for this object (string like 'views',
'templates'...)
- :id:
+ :__regid__:
object's identifier in the registry (string like 'main',
'primary', 'folder_box')
:__select__:
@@ -229,341 +223,201 @@
Moreover, the `__abstract__` attribute may be set to True to indicate
that a appobject is abstract and should not be registered.
- At registration time, the following attributes are set on the class:
- :vreg:
- the instance's registry
- :schema:
- the instance's schema
- :config:
- the instance's configuration
+ At selection time, the following attributes are set on the instance:
+
+ :_cw:
+ current request
+ :cw_extra_kwargs:
+ other received arguments
- At selection time, the following attributes are set on the instance:
- :req:
- current request
- :rset:
+ only if rset is found in arguments (in which case rset/row/col will be
+ removed from cwextra_kwargs):
+
+ :cw_rset:
context result set or None
- :row:
+ :cw_row:
if a result set is set and the context is about a particular cell in the
result set, and not the result set as a whole, specify the row number we
are interested in, else None
- :col:
+ :cw_col:
if a result set is set and the context is about a particular cell in the
result set, and not the result set as a whole, specify the col number we
are interested in, else None
"""
__registry__ = None
- id = None
+ __regid__ = None
__select__ = yes()
@classmethod
- def classid(cls):
- """returns a unique identifier for the appobject"""
- return '%s.%s' % (cls.__module__, cls.__name__)
-
- # XXX bw compat code
- @classmethod
- def build___select__(cls):
- for klass in cls.mro():
- if klass.__name__ == 'AppObject':
- continue # the bw compat __selector__ is there
- klassdict = klass.__dict__
- if ('__select__' in klassdict and '__selectors__' in klassdict
- and '__selgenerated__' not in klassdict):
- raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls)
- if '__selectors__' in klassdict and '__selgenerated__' not in klassdict:
- cls.__selgenerated__ = True
- # case where __selectors__ is defined locally (but __select__
- # is in a parent class)
- selectors = klassdict['__selectors__']
- if len(selectors) == 1:
- # micro optimization: don't bother with AndSelector if there's
- # only one selector
- select = _instantiate_selector(selectors[0])
- else:
- select = AndSelector(*selectors)
- cls.__select__ = select
-
- @classmethod
- def registered(cls, registry):
+ def __registered__(cls, registry):
"""called by the registry when the appobject has been registered.
It must return the object that will be actually registered (this may be
the right hook to create an instance for example). By default the
appobject is returned without any transformation.
"""
- cls.build___select__()
- cls.vreg = registry.vreg
- cls.schema = registry.schema
- cls.config = registry.config
+ try: # XXX < 3.6 bw compat
+ pdefs = cls.property_defs
+ except AttributeError:
+ pdefs = getattr(cls, 'cw_property_defs', {})
+ else:
+ warn('property_defs is deprecated, use cw_property_defs in %s'
+ % cls, DeprecationWarning)
+ for propid, pdef in pdefs.items():
+ pdef = pdef.copy() # may be shared
+ pdef['default'] = getattr(cls, propid, pdef['default'])
+ pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
+ registry.vreg.register_property(cls._cwpropkey(propid), **pdef)
+ assert callable(cls.__select__), obj
return cls
- @classmethod
- def vreg_initialization_completed(cls):
- pass
+ def __init__(self, req, **extra):
+ super(AppObject, self).__init__()
+ self._cw = req
+ try:
+ self.cw_rset = extra.pop('rset')
+ self.cw_row = extra.pop('row', None)
+ self.cw_col = extra.pop('col', None)
+ except KeyError:
+ pass
+ self.cw_extra_kwargs = extra
- # Eproperties definition:
- # key: id of the property (the actual CWProperty key is build using
- # <registry name>.<obj id>.<property id>
- # value: tuple (property type, vocabfunc, default value, property description)
- # possible types are those used by `logilab.common.configuration`
+ # persistent class properties ##############################################
+ #
+ # optional `cw_property_defs` dict on a class defines available persistent
+ # properties for this class:
+ #
+ # * key: id of the property (the actual CWProperty key is build using
+ # <registry name>.<obj id>.<property id>
+ # * value: tuple (property type, vocabfunc, default value, property description)
+ # possible types are those used by `logilab.common.configuration`
#
# notice that when it exists multiple objects with the same id (adaptation,
# overriding) only the first encountered definition is considered, so those
# objects can't try to have different default values for instance.
-
- property_defs = {}
-
- @classmethod
- def register_properties(cls):
- for propid, pdef in cls.property_defs.items():
- pdef = pdef.copy() # may be shared
- pdef['default'] = getattr(cls, propid, pdef['default'])
- pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide'))
- cls.vreg.register_property(cls.propkey(propid), **pdef)
+ #
+ # you can then access to a property value using self.cw_propval, where self
+ # is an instance of class
@classmethod
- def propkey(cls, propid):
- return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
-
- @classproperty
- @deprecated('use __select__ and & or | operators')
- def __selectors__(cls):
- selector = cls.__select__
- if isinstance(selector, AndSelector):
- return tuple(selector.selectors)
- if not isinstance(selector, tuple):
- selector = (selector,)
- return selector
-
- def __init__(self, req=None, rset=None, row=None, col=None, **extra):
- super(AppObject, self).__init__()
- self.req = req
- self.rset = rset
- self.row = row
- self.col = col
- self.extra_kwargs = extra
-
- def get_cache(self, cachename):
- """
- NOTE: cachename should be dotted names as in :
- - cubicweb.mycache
- - cubes.blog.mycache
- - etc.
- """
- if cachename in CACHE_REGISTRY:
- cache = CACHE_REGISTRY[cachename]
- else:
- cache = CACHE_REGISTRY[cachename] = Cache()
- _now = datetime.now()
- if _now > cache.latest_cache_lookup + ONESECOND:
- ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
- {'name':cachename}).get_entity(0,0)
- cache.latest_cache_lookup = _now
- if not ecache.valid(cache.cache_creation_date):
- cache.clear()
- cache.cache_creation_date = _now
- return cache
-
- def propval(self, propid):
- assert self.req
- return self.req.property_value(self.propkey(propid))
-
- def limited_rql(self):
- """return a printable rql for the result set associated to the object,
- with limit/offset correctly set according to maximum page size and
- currently displayed page when necessary
+ def _cwpropkey(cls, propid):
+ """return cw property key for the property of the given id for this
+ class
"""
- # try to get page boundaries from the navigation component
- # XXX we should probably not have a ref to this component here (eg in
- # cubicweb.common)
- nav = self.vreg['components'].select_object('navigation', self.req,
- rset=self.rset)
- if nav:
- start, stop = nav.page_boundaries()
- rql = self._limit_offset_rql(stop - start, start)
- # result set may have be limited manually in which case navigation won't
- # apply
- elif self.rset.limited:
- rql = self._limit_offset_rql(*self.rset.limited)
- # navigation component doesn't apply and rset has not been limited, no
- # need to limit query
- else:
- rql = self.rset.printable_rql()
- return rql
+ return '%s.%s.%s' % (cls.__registry__, cls.__regid__, propid)
- def _limit_offset_rql(self, limit, offset):
- rqlst = self.rset.syntax_tree()
- if len(rqlst.children) == 1:
- select = rqlst.children[0]
- olimit, ooffset = select.limit, select.offset
- select.limit, select.offset = limit, offset
- rql = rqlst.as_string(kwargs=self.rset.args)
- # restore original limit/offset
- select.limit, select.offset = olimit, ooffset
- else:
- newselect = Select()
- newselect.limit = limit
- newselect.offset = offset
- aliases = [VariableRef(newselect.get_variable(vref.name, i))
- for i, vref in enumerate(rqlst.selection)]
- newselect.set_with([SubQuery(aliases, rqlst)], check=False)
- newunion = Union()
- newunion.append(newselect)
- rql = rqlst.as_string(kwargs=self.rset.args)
- rqlst.parent = None
- return rql
+ def cw_propval(self, propid):
+ """return cw property value associated to key
- def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
- **kwargs):
- """shortcut to self.vreg.view method avoiding to pass self.req"""
- return self.vreg[__registry].render(__vid, self.req, __fallback_oid,
- rset=rset, **kwargs)
-
- def initialize_varmaker(self):
- varmaker = self.req.get_page_data('rql_varmaker')
- if varmaker is None:
- varmaker = self.req.varmaker
- self.req.set_page_data('rql_varmaker', varmaker)
- self.varmaker = varmaker
-
- # url generation methods ##################################################
-
- controller = 'view'
-
- def build_url(self, *args, **kwargs):
- """return an absolute URL using params dictionary key/values as URL
- parameters. Values are automatically URL quoted, and the
- publishing method to use may be specified or will be guessed.
+ <cls.__registry__>.<cls.id>.<propid>
"""
- # use *args since we don't want first argument to be "anonymous" to
- # avoid potential clash with kwargs
- if args:
- assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
- method = args[0]
- else:
- method = None
- # XXX I (adim) think that if method is passed explicitly, we should
- # not try to process it and directly call req.build_url()
- if method is None:
- method = self.controller
- if method == 'view' and self.req.from_controller() == 'view' and \
- not '_restpath' in kwargs:
- method = self.req.relative_path(includeparams=False) or 'view'
- return self.req.build_url(method, **kwargs)
+ return self._cw.property_value(self._cwpropkey(propid))
+
+ # deprecated ###############################################################
+
+ @property
+ @deprecated('[3.6] use self.__regid__')
+ def id(self):
+ return self.__regid__
- # various resources accessors #############################################
+ @property
+ @deprecated('[3.6] use self._cw.vreg')
+ def vreg(self):
+ return self._cw.vreg
- def entity(self, row, col=0):
- """short cut to get an entity instance for a particular row/column
- (col default to 0)
- """
- return self.rset.get_entity(row, col)
+ @property
+ @deprecated('[3.6] use self._cw.vreg.schema')
+ def schema(self):
+ return self._cw.vreg.schema
+
+ @property
+ @deprecated('[3.6] use self._cw.vreg.config')
+ def config(self):
+ return self._cw.vreg.config
- def complete_entity(self, row, col=0, skip_bytes=True):
- """short cut to get an completed entity instance for a particular
- row (all instance's attributes have been fetched)
- """
- entity = self.entity(row, col)
- entity.complete(skip_bytes=skip_bytes)
- return entity
+ @property
+ @deprecated('[3.6] use self._cw')
+ def req(self):
+ return self._cw
+
+ @deprecated('[3.6] use self.cw_rset')
+ def get_rset(self):
+ return self.cw_rset
+ @deprecated('[3.6] use self.cw_rset')
+ def set_rset(self, rset):
+ self.cw_rset = rset
+ rset = property(get_rset, set_rset)
- def user_rql_callback(self, args, msg=None):
- """register a user callback to execute some rql query and return an url
- to call it ready to be inserted in html
- """
- def rqlexec(req, rql, args=None, key=None):
- req.execute(rql, args, key)
- return self.user_callback(rqlexec, args, msg)
+ @property
+ @deprecated('[3.6] use self.cw_row')
+ def row(self):
+ return self.cw_row
- def user_callback(self, cb, args, msg=None, nonify=False):
- """register the given user callback and return an url to call it ready to be
- inserted in html
- """
- from simplejson import dumps
- self.req.add_js('cubicweb.ajax.js')
- cbname = self.req.register_onetime_callback(cb, *args)
- msg = dumps(msg or '')
- return "javascript:userCallbackThenReloadPage('%s', %s)" % (
- cbname, msg)
+ @property
+ @deprecated('[3.6] use self.cw_col')
+ def col(self):
+ return self.cw_col
- # formating methods #######################################################
+ @property
+ @deprecated('[3.6] use self.cw_extra_kwargs')
+ def extra_kwargs(self):
+ return self.cw_extra_kwargs
- def tal_render(self, template, variables):
- """render a precompiled page template with variables in the given
- dictionary as context
- """
- from cubicweb.ext.tal import CubicWebContext
- context = CubicWebContext()
- context.update({'self': self, 'rset': self.rset, '_' : self.req._,
- 'req': self.req, 'user': self.req.user})
- context.update(variables)
- output = UStringIO()
- template.expand(context, output)
- return output.getvalue()
+ @deprecated('[3.6] use self._cw.view')
+ def view(self, *args, **kwargs):
+ return self._cw.view(*args, **kwargs)
+
+ @property
+ @deprecated('[3.6] use self._cw.varmaker')
+ def varmaker(self):
+ return self._cw.varmaker
+
+ @deprecated('[3.6] use self._cw.get_cache')
+ def get_cache(self, cachename):
+ return self._cw.get_cache(cachename)
+
+ @deprecated('[3.6] use self._cw.build_url')
+ def build_url(self, *args, **kwargs):
+ return self._cw.build_url(*args, **kwargs)
+ @deprecated('[3.6] use self.cw_rset.limited_rql')
+ def limited_rql(self):
+ return self.cw_rset.limited_rql()
+
+ @deprecated('[3.6] use self.cw_rset.complete_entity(row,col) instead')
+ def complete_entity(self, row, col=0, skip_bytes=True):
+ return self.cw_rset.complete_entity(row, col, skip_bytes)
+
+ @deprecated('[3.6] use self.cw_rset.get_entity(row,col) instead')
+ def entity(self, row, col=0):
+ return self.cw_rset.get_entity(row, col)
+
+ @deprecated('[3.6] use self._cw.user_rql_callback')
+ def user_rql_callback(self, args, msg=None):
+ return self._cw.user_rql_callback(args, msg)
+
+ @deprecated('[3.6] use self._cw.user_callback')
+ def user_callback(self, cb, args, msg=None, nonify=False):
+ return self._cw.user_callback(cb, args, msg, nonify)
+
+ @deprecated('[3.6] use self._cw.format_date')
def format_date(self, date, date_format=None, time=False):
- """return a string for a date time according to instance's
- configuration
- """
- if date:
- if date_format is None:
- if time:
- date_format = self.req.property_value('ui.datetime-format')
- else:
- date_format = self.req.property_value('ui.date-format')
- return ustrftime(date, date_format)
- return u''
+ return self._cw.format_date(date, date_format, time)
+ @deprecated('[3.6] use self._cw.format_timoe')
def format_time(self, time):
- """return a string for a time according to instance's
- configuration
- """
- if time:
- return ustrftime(time, self.req.property_value('ui.time-format'))
- return u''
+ return self._cw.format_time(time)
+ @deprecated('[3.6] use self._cw.format_float')
def format_float(self, num):
- """return a string for floating point number according to instance's
- configuration """
- if num is not None:
- return self.req.property_value('ui.float-format') % num
- return u''
+ return self._cw.format_float(num)
+ @deprecated('[3.6] use self._cw.parse_datetime')
def parse_datetime(self, value, etype='Datetime'):
- """get a datetime or time from a string (according to etype)
- Datetime formatted as Date are accepted
- """
- assert etype in ('Datetime', 'Date', 'Time'), etype
- # XXX raise proper validation error
- if etype == 'Datetime':
- format = self.req.property_value('ui.datetime-format')
- try:
- return todatetime(strptime(value, format))
- except ValueError:
- pass
- elif etype == 'Time':
- format = self.req.property_value('ui.time-format')
- try:
- # (adim) I can't find a way to parse a Time with a custom format
- date = strptime(value, format) # this returns a DateTime
- return time(date.hour, date.minute, date.second)
- except ValueError:
- raise ValueError('can\'t parse %r (expected %s)' % (value, format))
- try:
- format = self.req.property_value('ui.date-format')
- dt = strptime(value, format)
- if etype == 'Datetime':
- return todatetime(dt)
- return todate(dt)
- except ValueError:
- raise ValueError('can\'t parse %r (expected %s)' % (value, format))
+ return self._cw.parse_datetime(value, etype)
- # security related methods ################################################
-
- def ensure_ro_rql(self, rql):
- """raise an exception if the given rql is not a select query"""
- first = rql.split(' ', 1)[0].lower()
- if first in ('insert', 'set', 'delete'):
- raise Unauthorized(self.req._('only select queries are authorized'))
+ @deprecated('[3.6] use self.cw_propval')
+ def propval(self, propid):
+ return self._cw.property_value(self._cwpropkey(propid))
set_log_methods(AppObject, getLogger('cubicweb.appobject'))
--- a/common/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,47 +7,3 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from logilab.common.adbh import FunctionDescr
-
-from cubicweb._exceptions import * # bw compat
-
-from rql.utils import register_function, iter_funcnode_variables
-
-class COMMA_JOIN(FunctionDescr):
- supported_backends = ('postgres', 'sqlite',)
- rtype = 'String'
-
- @classmethod
- def st_description(cls, funcnode, mainindex, tr):
- return ', '.join(sorted(term.get_description(mainindex, tr)
- for term in iter_funcnode_variables(funcnode)))
-
-register_function(COMMA_JOIN) # XXX do not expose?
-
-
-class CONCAT_STRINGS(COMMA_JOIN):
- aggregat = True
-
-register_function(CONCAT_STRINGS) # XXX bw compat
-
-class GROUP_CONCAT(CONCAT_STRINGS):
- supported_backends = ('mysql', 'postgres', 'sqlite',)
-
-register_function(GROUP_CONCAT)
-
-
-class LIMIT_SIZE(FunctionDescr):
- supported_backends = ('postgres', 'sqlite',)
- rtype = 'String'
-
- @classmethod
- def st_description(cls, funcnode, mainindex, tr):
- return funcnode.children[0].get_description(mainindex, tr)
-
-register_function(LIMIT_SIZE)
-
-
-class TEXT_LIMIT_SIZE(LIMIT_SIZE):
- supported_backends = ('mysql', 'postgres', 'sqlite',)
-
-register_function(TEXT_LIMIT_SIZE)
--- a/common/appobject.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""pre 3.2 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.appobject', DeprecationWarning, stacklevel=2)
-from cubicweb.appobject import *
--- a/common/entity.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-"""pre 3.2 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.entity', DeprecationWarning, stacklevel=2)
-from cubicweb.entity import *
-from cubicweb.entity import _marker
--- a/common/i18n.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-"""Some i18n/gettext utilities.
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import re
-import os
-import sys
-from os.path import join, basename, splitext, exists
-from glob import glob
-
-from cubicweb.toolsutils import create_dir
-
-def extract_from_tal(files, output_file):
- """extract i18n strings from tal and write them into the given output file
- using standard python gettext marker (_)
- """
- output = open(output_file, 'w')
- for filepath in files:
- for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()):
- print >> output, '_("%s")' % match.group(2)
- output.close()
-
-
-def add_msg(w, msgid, msgctx=None):
- """write an empty pot msgid definition"""
- if isinstance(msgid, unicode):
- msgid = msgid.encode('utf-8')
- if msgctx:
- if isinstance(msgctx, unicode):
- msgctx = msgctx.encode('utf-8')
- w('msgctxt "%s"\n' % msgctx)
- msgid = msgid.replace('"', r'\"').splitlines()
- if len(msgid) > 1:
- w('msgid ""\n')
- for line in msgid:
- w('"%s"' % line.replace('"', r'\"'))
- else:
- w('msgid "%s"\n' % msgid[0])
- w('msgstr ""\n\n')
-
-
-def execute(cmd):
- """display the command, execute it and raise an Exception if returned
- status != 0
- """
- print cmd.replace(os.getcwd() + os.sep, '')
- from subprocess import call
- status = call(cmd, shell=True)
- if status != 0:
- raise Exception('status = %s' % status)
-
-
-def available_catalogs(i18ndir=None):
- if i18ndir is None:
- wildcard = '*.po'
- else:
- wildcard = join(i18ndir, '*.po')
- for popath in glob(wildcard):
- lang = splitext(basename(popath))[0]
- yield lang, popath
-
-
-def compile_i18n_catalogs(sourcedirs, destdir, langs):
- """generate .mo files for a set of languages into the `destdir` i18n directory
- """
- from logilab.common.fileutils import ensure_fs_mode
- print '-> compiling %s catalogs...' % destdir
- errors = []
- for lang in langs:
- langdir = join(destdir, lang, 'LC_MESSAGES')
- if not exists(langdir):
- create_dir(langdir)
- pofiles = [join(path, '%s.po' % lang) for path in sourcedirs]
- pofiles = [pof for pof in pofiles if exists(pof)]
- mergedpo = join(destdir, '%s_merged.po' % lang)
- try:
- # merge instance/cubes messages catalogs with the stdlib's one
- execute('msgcat --use-first --sort-output --strict -o "%s" %s'
- % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
- # make sure the .mo file is writeable and compiles with *msgfmt*
- applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
- try:
- ensure_fs_mode(applmo)
- except OSError:
- pass # suppose not exists
- execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
- except Exception, ex:
- errors.append('while handling language %s: %s' % (lang, ex))
- try:
- # clean everything
- os.unlink(mergedpo)
- except Exception:
- continue
- return errors
--- a/common/mail.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/mail.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,272 +1,5 @@
-"""Common utilies to format / semd emails.
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from base64 import b64encode, b64decode
-from itertools import repeat
-from time import time
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
-from email.MIMEImage import MIMEImage
-from email.Header import Header
-try:
- from socket import gethostname
-except ImportError:
- def gethostname(): # gae
- return 'XXX'
-
-from cubicweb.view import EntityView
-from cubicweb.entity import Entity
-
-def header(ustring):
- return Header(ustring.encode('UTF-8'), 'UTF-8')
-
-def addrheader(uaddr, uname=None):
- # even if an email address should be ascii, encode it using utf8 since
- # automatic tests may generate non ascii email address
- addr = uaddr.encode('UTF-8')
- if uname:
- return '%s <%s>' % (header(uname).encode(), addr)
- return addr
-
-
-def construct_message_id(appid, eid, withtimestamp=True):
- if withtimestamp:
- addrpart = 'eid=%s×tamp=%.10f' % (eid, time())
- else:
- addrpart = 'eid=%s' % eid
- # we don't want any equal sign nor trailing newlines
- leftpart = b64encode(addrpart, '.-').rstrip().rstrip('=')
- return '<%s@%s.%s>' % (leftpart, appid, gethostname())
-
-
-def parse_message_id(msgid, appid):
- if msgid[0] == '<':
- msgid = msgid[1:]
- if msgid[-1] == '>':
- msgid = msgid[:-1]
- try:
- values, qualif = msgid.split('@')
- padding = len(values) % 4
- values = b64decode(str(values + '='*padding), '.-')
- values = dict(v.split('=') for v in values.split('&'))
- fromappid, host = qualif.split('.', 1)
- except:
- return None
- if appid != fromappid or host != gethostname():
- return None
- return values
-
-
-def format_mail(uinfo, to_addrs, content, subject="",
- cc_addrs=(), msgid=None, references=(), config=None):
- """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
-
- to_addrs and cc_addrs are expected to be a list of email address without
- name
- """
- assert type(content) is unicode, repr(content)
- msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
- # safety: keep only the first newline
- subject = subject.splitlines()[0]
- msg['Subject'] = header(subject)
- if uinfo.get('email'):
- email = uinfo['email']
- elif config and config['sender-addr']:
- email = unicode(config['sender-addr'])
- else:
- email = u''
- if uinfo.get('name'):
- name = uinfo['name']
- elif config and config['sender-addr']:
- name = unicode(config['sender-name'])
- else:
- name = u''
- msg['From'] = addrheader(email, name)
- if config and config['sender-addr'] and config['sender-addr'] != email:
- appaddr = addrheader(config['sender-addr'], config['sender-name'])
- msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
- elif email:
- msg['Reply-to'] = msg['From']
- if config is not None:
- msg['X-CW'] = config.appid
- unique_addrs = lambda addrs: sorted(set(addr for addr in addrs if addr is not None))
- msg['To'] = ', '.join(addrheader(addr) for addr in unique_addrs(to_addrs))
- if cc_addrs:
- msg['Cc'] = ', '.join(addrheader(addr) for addr in unique_addrs(cc_addrs))
- if msgid:
- msg['Message-id'] = msgid
- if references:
- msg['References'] = ', '.join(references)
- return msg
-
-
-class HtmlEmail(MIMEMultipart):
-
- def __init__(self, subject, textcontent, htmlcontent,
- sendermail=None, sendername=None, recipients=None, ccrecipients=None):
- MIMEMultipart.__init__(self, 'related')
- self['Subject'] = header(subject)
- self.preamble = 'This is a multi-part message in MIME format.'
- # Attach alternative text message
- alternative = MIMEMultipart('alternative')
- self.attach(alternative)
- msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
- alternative.attach(msgtext)
- # Attach html message
- msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
- alternative.attach(msghtml)
- if sendermail or sendername:
- self['From'] = addrheader(sendermail, sendername)
- if recipients:
- self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
- if ccrecipients:
- self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
-
- def attach_image(self, data, htmlId):
- image = MIMEImage(data)
- image.add_header('Content-ID', '<%s>' % htmlId)
- self.attach(image)
-
-
-class NotificationView(EntityView):
- """abstract view implementing the "email" API (eg to simplify sending
- notification)
- """
- # XXX refactor this class to work with len(rset) > 1
-
- msgid_timestamp = True
-
- # this is usually the method to call
- def render_and_send(self, **kwargs):
- """generate and send an email message for this view"""
- delayed = kwargs.pop('delay_to_commit', None)
- for recipients, msg in self.render_emails(**kwargs):
- if delayed is None:
- self.send(recipients, msg)
- elif delayed:
- self.send_on_commit(recipients, msg)
- else:
- self.send_now(recipients, msg)
-
- def cell_call(self, row, col=0, **kwargs):
- self.w(self.req._(self.content) % self.context(**kwargs))
-
- def render_emails(self, **kwargs):
- """generate and send emails for this view (one per recipient)"""
- self._kwargs = kwargs
- recipients = self.recipients()
- if not recipients:
- self.info('skipping %s notification, no recipients', self.id)
- return
- if self.rset is not None:
- entity = self.entity(self.row or 0, self.col or 0)
- # if the view is using timestamp in message ids, no way to reference
- # previous email
- if not self.msgid_timestamp:
- refs = [self.construct_message_id(eid)
- for eid in entity.notification_references(self)]
- else:
- refs = ()
- msgid = self.construct_message_id(entity.eid)
- else:
- refs = ()
- msgid = None
- req = self.req
- self.user_data = req.user_data()
- origlang = req.lang
- for something in recipients:
- if isinstance(something, Entity):
- # hi-jack self.req to get a session for the returned user
- self.req = self.req.hijack_user(something)
- emailaddr = something.get_email()
- else:
- emailaddr, lang = something
- self.req.set_language(lang)
- # since the same view (eg self) may be called multiple time and we
- # need a fresh stream at each iteration, reset it explicitly
- self.w = None
- # XXX call render before subject to set .row/.col attributes on the
- # view
- try:
- content = self.render(row=0, col=0, **kwargs)
- subject = self.subject()
- except SkipEmail:
- continue
- except Exception, ex:
- # shouldn't make the whole transaction fail because of rendering
- # error (unauthorized or such)
- self.exception(str(ex))
- continue
- msg = format_mail(self.user_data, [emailaddr], content, subject,
- config=self.config, msgid=msgid, references=refs)
- yield [emailaddr], msg
- # restore language
- req.set_language(origlang)
-
- # recipients / email sending ###############################################
-
- def recipients(self):
- """return a list of either 2-uple (email, language) or user entity to
- who this email should be sent
- """
- # use super_session when available, we don't want to consider security
- # when selecting recipients_finder
- try:
- req = self.req.super_session
- except AttributeError:
- req = self.req
- finder = self.vreg['components'].select('recipients_finder', req,
- rset=self.rset,
- row=self.row or 0,
- col=self.col or 0)
- return finder.recipients()
-
- def send_now(self, recipients, msg):
- self.config.sendmails([(msg, recipients)])
-
- def send_on_commit(self, recipients, msg):
- raise NotImplementedError
-
- send = send_now
-
- # email generation helpers #################################################
-
- def construct_message_id(self, eid):
- return construct_message_id(self.config.appid, eid, self.msgid_timestamp)
-
- def format_field(self, attr, value):
- return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
-
- def format_section(self, attr, value):
- return '%(attr)s\n%(ul)s\n%(value)s\n' % {
- 'attr': attr, 'ul': '-'*len(attr), 'value': value}
-
- def subject(self):
- entity = self.entity(self.row or 0, self.col or 0)
- subject = self.req._(self.message)
- etype = entity.dc_type()
- eid = entity.eid
- login = self.user_data['login']
- return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
-
- def context(self, **kwargs):
- entity = self.entity(self.row or 0, self.col or 0)
- for key, val in kwargs.iteritems():
- if val and isinstance(val, unicode) and val.strip():
- kwargs[key] = self.req._(val)
- kwargs.update({'user': self.user_data['login'],
- 'eid': entity.eid,
- 'etype': entity.dc_type(),
- 'url': entity.absolute_url(),
- 'title': entity.dc_long_title(),})
- return kwargs
-
-
-class SkipEmail(Exception):
- """raise this if you decide to skip an email during its generation"""
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mail', DeprecationWarning, stacklevel=2)
+from cubicweb.mail import *
--- a/common/migration.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,373 +0,0 @@
-"""utilities for instances migration
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import sys
-import os
-import logging
-import tempfile
-from os.path import exists, join, basename, splitext
-
-from logilab.common.decorators import cached
-from logilab.common.configuration import REQUIRED, read_old_config
-from logilab.common.shellutils import ASK
-
-from cubicweb import ConfigurationError
-
-
-def filter_scripts(config, directory, fromversion, toversion, quiet=True):
- """return a list of paths of migration files to consider to upgrade
- from a version to a greater one
- """
- from logilab.common.changelog import Version # doesn't work with appengine
- assert fromversion
- assert toversion
- assert isinstance(fromversion, tuple), fromversion.__class__
- assert isinstance(toversion, tuple), toversion.__class__
- assert fromversion <= toversion, (fromversion, toversion)
- if not exists(directory):
- if not quiet:
- print directory, "doesn't exists, no migration path"
- return []
- if fromversion == toversion:
- return []
- result = []
- for fname in os.listdir(directory):
- if fname.endswith('.pyc') or fname.endswith('.pyo') \
- or fname.endswith('~'):
- continue
- fpath = join(directory, fname)
- try:
- tver, mode = fname.split('_', 1)
- except ValueError:
- continue
- mode = mode.split('.', 1)[0]
- if not config.accept_mode(mode):
- continue
- try:
- tver = Version(tver)
- except ValueError:
- continue
- if tver <= fromversion:
- continue
- if tver > toversion:
- continue
- result.append((tver, fpath))
- # be sure scripts are executed in order
- return sorted(result)
-
-
-IGNORED_EXTENSIONS = ('.swp', '~')
-
-
-def execscript_confirm(scriptpath):
- """asks for confirmation before executing a script and provides the
- ability to show the script's content
- """
- while True:
- answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
- if answer == 'n':
- return False
- elif answer == 'show':
- stream = open(scriptpath)
- scriptcontent = stream.read()
- stream.close()
- print
- print scriptcontent
- print
- else:
- return True
-
-def yes(*args, **kwargs):
- return True
-
-
-class MigrationHelper(object):
- """class holding CubicWeb Migration Actions used by migration scripts"""
-
- def __init__(self, config, interactive=True, verbosity=1):
- self.config = config
- if config:
- # no config on shell to a remote instance
- self.config.init_log(logthreshold=logging.ERROR, debug=True)
- # 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
- self.verbosity = verbosity
- self.need_wrap = True
- if not interactive or not verbosity:
- self.confirm = yes
- self.execscript_confirm = yes
- else:
- self.execscript_confirm = execscript_confirm
- self._option_changes = []
- self.__context = {'confirm': self.confirm,
- 'config': self.config,
- 'interactive_mode': interactive,
- }
-
- def __getattribute__(self, name):
- try:
- return object.__getattribute__(self, name)
- except AttributeError:
- cmd = 'cmd_%s' % name
- if hasattr(self, cmd):
- meth = getattr(self, cmd)
- return lambda *args, **kwargs: self.interact(args, kwargs,
- meth=meth)
- raise
- raise AttributeError(name)
-
- def repo_connect(self):
- return self.config.repository()
-
- def migrate(self, vcconf, toupgrade, options):
- """upgrade the given set of cubes
-
- `cubes` is an ordered list of 3-uple:
- (cube, fromversion, toversion)
- """
- if options.fs_only:
- # monkey path configuration.accept_mode so database mode (e.g. Any)
- # won't be accepted
- orig_accept_mode = self.config.accept_mode
- def accept_mode(mode):
- if mode == 'Any':
- return False
- return orig_accept_mode(mode)
- self.config.accept_mode = accept_mode
- # may be an iterator
- toupgrade = tuple(toupgrade)
- vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
- ctx = self.__context
- ctx['versions_map'] = vmap
- if self.config.accept_mode('Any') and 'cubicweb' in vmap:
- migrdir = self.config.migration_scripts_dir()
- self.cmd_process_script(join(migrdir, 'bootstrapmigration_repository.py'))
- for cube, fromversion, toversion in toupgrade:
- if cube == 'cubicweb':
- migrdir = self.config.migration_scripts_dir()
- else:
- migrdir = self.config.cube_migration_scripts_dir(cube)
- scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
- if scripts:
- prevversion = None
- for version, script in scripts:
- # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call
- # cube_upgraded once all script of X.Y.Z have been executed
- if prevversion is not None and version != prevversion:
- self.cube_upgraded(cube, prevversion)
- prevversion = version
- self.cmd_process_script(script)
- self.cube_upgraded(cube, toversion)
- else:
- self.cube_upgraded(cube, toversion)
-
- def cube_upgraded(self, cube, version):
- pass
-
- def shutdown(self):
- pass
-
- def interact(self, args, kwargs, meth):
- """execute the given method according to user's confirmation"""
- msg = 'Execute command: %s(%s) ?' % (
- meth.__name__[4:],
- ', '.join([repr(arg) for arg in args] +
- ['%s=%r' % (n,v) for n,v in kwargs.items()]))
- if 'ask_confirm' in kwargs:
- ask_confirm = kwargs.pop('ask_confirm')
- else:
- ask_confirm = True
- if not ask_confirm or self.confirm(msg):
- return meth(*args, **kwargs)
-
- def confirm(self, question, shell=True, abort=True, retry=False, default='y'):
- """ask for confirmation and return true on positive answer
-
- if `retry` is true the r[etry] answer may return 2
- """
- possibleanswers = ['y','n']
- if abort:
- possibleanswers.append('abort')
- if shell:
- possibleanswers.append('shell')
- if retry:
- possibleanswers.append('retry')
- try:
- answer = ASK.ask(question, possibleanswers, default)
- except (EOFError, KeyboardInterrupt):
- answer = 'abort'
- if answer == 'n':
- return False
- if answer == 'retry':
- return 2
- if answer == 'abort':
- raise SystemExit(1)
- if shell and answer == 'shell':
- self.interactive_shell()
- return self.confirm(question)
- return True
-
- def interactive_shell(self):
- self.confirm = yes
- self.need_wrap = False
- # avoid '_' to be added to builtins by sys.display_hook
- def do_not_add___to_builtins(obj):
- if obj is not None:
- print repr(obj)
- sys.displayhook = do_not_add___to_builtins
- local_ctx = self._create_context()
- try:
- import readline
- from rlcompleter import Completer
- except ImportError:
- # readline not available
- pass
- else:
- readline.set_completer(Completer(local_ctx).complete)
- readline.parse_and_bind('tab: complete')
- home_key = 'HOME'
- if sys.platform == 'win32':
- home_key = 'USERPROFILE'
- histfile = os.path.join(os.environ[home_key], ".eshellhist")
- try:
- readline.read_history_file(histfile)
- except IOError:
- pass
- from code import interact
- banner = """entering the migration python shell
-just type migration commands or arbitrary python code and type ENTER to execute it
-type "exit" or Ctrl-D to quit the shell and resume operation"""
- # give custom readfunc to avoid http://bugs.python.org/issue1288615
- def unicode_raw_input(prompt):
- return unicode(raw_input(prompt), sys.stdin.encoding)
- interact(banner, readfunc=unicode_raw_input, local=local_ctx)
- readline.write_history_file(histfile)
- # delete instance's confirm attribute to avoid questions
- del self.confirm
- self.need_wrap = True
-
- @cached
- def _create_context(self):
- """return a dictionary to use as migration script execution context"""
- context = self.__context
- for attr in dir(self):
- if attr.startswith('cmd_'):
- if self.need_wrap:
- context[attr[4:]] = getattr(self, attr[4:])
- else:
- context[attr[4:]] = getattr(self, attr)
- return context
-
- def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
- """execute a migration script
- in interactive mode, display the migration script path, ask for
- confirmation and execute it if confirmed
- """
- migrscript = os.path.normpath(migrscript)
- if migrscript.endswith('.py'):
- script_mode = 'python'
- elif migrscript.endswith('.txt') or migrscript.endswith('.rst'):
- script_mode = 'doctest'
- else:
- raise Exception('This is not a valid cubicweb shell input')
- if not self.execscript_confirm(migrscript):
- return
- scriptlocals = self._create_context().copy()
- if script_mode == 'python':
- if funcname is None:
- pyname = '__main__'
- else:
- pyname = splitext(basename(migrscript))[0]
- scriptlocals.update({'__file__': migrscript, '__name__': pyname})
- execfile(migrscript, scriptlocals)
- if funcname is not None:
- try:
- func = scriptlocals[funcname]
- self.info('found %s in locals', funcname)
- assert callable(func), '%s (%s) is not callable' % (func, funcname)
- except KeyError:
- self.critical('no %s in script %s', funcname, migrscript)
- return None
- return func(*args, **kwargs)
- else: # script_mode == 'doctest'
- import doctest
- doctest.testfile(migrscript, module_relative=False,
- optionflags=doctest.ELLIPSIS, globs=scriptlocals)
-
- def cmd_option_renamed(self, oldname, newname):
- """a configuration option has been renamed"""
- self._option_changes.append(('renamed', oldname, newname))
-
- def cmd_option_group_change(self, option, oldgroup, newgroup):
- """a configuration option has been moved in another group"""
- self._option_changes.append(('moved', option, oldgroup, newgroup))
-
- def cmd_option_added(self, optname):
- """a configuration option has been added"""
- self._option_changes.append(('added', optname))
-
- def cmd_option_removed(self, optname):
- """a configuration option has been removed"""
- # can safely be ignored
- #self._option_changes.append(('removed', optname))
-
- def cmd_option_type_changed(self, optname, oldtype, newvalue):
- """a configuration option's type has changed"""
- self._option_changes.append(('typechanged', optname, oldtype, newvalue))
-
- def cmd_add_cubes(self, cubes):
- """modify the list of used cubes in the in-memory config
- returns newly inserted cubes, including dependencies
- """
- if isinstance(cubes, basestring):
- cubes = (cubes,)
- origcubes = self.config.cubes()
- newcubes = [p for p in self.config.expand_cubes(cubes)
- if not p in origcubes]
- if newcubes:
- for cube in cubes:
- assert cube in newcubes
- self.config.add_cubes(newcubes)
- return newcubes
-
- def cmd_remove_cube(self, cube, removedeps=False):
- if removedeps:
- toremove = self.config.expand_cubes([cube])
- else:
- toremove = (cube,)
- origcubes = self.config._cubes
- basecubes = [c for c in origcubes if not c in toremove]
- self.config._cubes = tuple(self.config.expand_cubes(basecubes))
- removed = [p for p in origcubes if not p in self.config._cubes]
- if not cube in removed:
- raise ConfigurationError("can't remove cube %s, "
- "used as a dependency" % cube)
- return removed
-
- def rewrite_configuration(self):
- # import locally, show_diffs unavailable in gae environment
- from cubicweb.toolsutils import show_diffs
- configfile = self.config.main_config_file()
- if self._option_changes:
- read_old_config(self.config, self._option_changes, configfile)
- fd, newconfig = tempfile.mkstemp()
- for optdescr in self._option_changes:
- if optdescr[0] == 'added':
- optdict = self.config.get_option_def(optdescr[1])
- if optdict.get('default') is REQUIRED:
- self.config.input_option(optdescr[1], optdict)
- self.config.generate_config(open(newconfig, 'w'))
- show_diffs(configfile, newconfig)
- os.close(fd)
- if exists(newconfig):
- os.unlink(newconfig)
-
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(MigrationHelper, getLogger('cubicweb.migration'))
--- a/common/mixins.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/mixins.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,315 +1,5 @@
-"""mixins of entity/views organized somewhat in a graph or tree structure
-
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common.deprecation import deprecated
-from logilab.common.decorators import cached
-
-from cubicweb import typed_eid
-from cubicweb.selectors import implements
-from cubicweb.interfaces import IEmailable, ITree
-
-
-class TreeMixIn(object):
- """base tree-mixin providing the tree interface
-
- This mixin has to be inherited explicitly and configured using the
- tree_attribute, parent_target and children_target class attribute to
- benefit from this default implementation
- """
- tree_attribute = None
- # XXX misnamed
- parent_target = 'subject'
- children_target = 'object'
-
- def different_type_children(self, entities=True):
- """return children entities of different type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema != self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.col)
-
- def same_type_children(self, entities=True):
- """return children entities of the same type as this entity.
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- res = self.related(self.tree_attribute, self.children_target,
- entities=entities)
- if entities:
- return [e for e in res if e.e_schema == self.e_schema]
- return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col)
-
- def iterchildren(self, _done=None):
- if _done is None:
- _done = set()
- for child in self.children():
- if child.eid in _done:
- self.error('loop in %s tree', self.id.lower())
- continue
- yield child
- _done.add(child.eid)
-
- def prefixiter(self, _done=None):
- if _done is None:
- _done = set()
- if self.eid in _done:
- return
- yield self
- _done.add(self.eid)
- for child in self.iterchildren(_done):
- try:
- for entity in child.prefixiter(_done):
- yield entity
- except AttributeError:
- pass
-
- @cached
- def path(self):
- """returns the list of eids from the root object to this object"""
- path = []
- parent = self
- while parent:
- if parent.eid in path:
- self.error('loop in %s tree', self.id.lower())
- break
- path.append(parent.eid)
- try:
- # check we are not leaving the tree
- if (parent.tree_attribute != self.tree_attribute or
- parent.parent_target != self.parent_target):
- break
- parent = parent.parent()
- except AttributeError:
- break
-
- path.reverse()
- return path
-
- def iterparents(self):
- def _uptoroot(self):
- curr = self
- while True:
- curr = curr.parent()
- if curr is None:
- break
- yield curr
- return _uptoroot(self)
-
- def notification_references(self, view):
- """used to control References field of email send on notification
- for this entity. `view` is the notification view.
-
- Should return a list of eids which can be used to generate message ids
- of previously sent email
- """
- return self.path()[:-1]
-
-
- ## ITree interface ########################################################
- def parent(self):
- """return the parent entity if any, else None (e.g. if we are on the
- root
- """
- try:
- return self.related(self.tree_attribute, self.parent_target,
- entities=True)[0]
- except (KeyError, IndexError):
- return None
-
- def children(self, entities=True, sametype=False):
- """return children entities
-
- according to the `entities` parameter, return entity objects or the
- equivalent result set
- """
- if sametype:
- return self.same_type_children(entities)
- else:
- return self.related(self.tree_attribute, self.children_target,
- entities=entities)
-
- def children_rql(self):
- return self.related_rql(self.tree_attribute, self.children_target)
-
- def is_leaf(self):
- return len(self.children()) == 0
-
- def is_root(self):
- return self.parent() is None
-
- def root(self):
- """return the root object"""
- return self.req.entity_from_eid(self.path()[0])
-
-
-class EmailableMixIn(object):
- """base mixin providing the default get_email() method used by
- the massmailing view
-
- NOTE: The default implementation is based on the
- primary_email / use_email scheme
- """
- __implements__ = (IEmailable,)
-
- def get_email(self):
- if getattr(self, 'primary_email', None):
- return self.primary_email[0].address
- if getattr(self, 'use_email', None):
- return self.use_email[0].address
- return None
-
- @classmethod
- def allowed_massmail_keys(cls):
- """returns a set of allowed email substitution keys
-
- The default is to return the entity's attribute list but an
- entity class might override this method to allow extra keys.
- For instance, the Person class might want to return a `companyname`
- key.
- """
- return set(rschema.type
- for rschema, attrtype in cls.e_schema.attribute_definitions()
- if attrtype.type not in ('Password', 'Bytes'))
-
- def as_email_context(self):
- """returns the dictionary as used by the sendmail controller to
- build email bodies.
-
- NOTE: the dictionary keys should match the list returned by the
- `allowed_massmail_keys` method.
- """
- return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
-
-
-"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
-classes which have the relation described by the dict's key.
-
-NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
-(eg without plugged classes). This includes bases Entity and AnyEntity classes.
-"""
-MI_REL_TRIGGERS = {
- ('primary_email', 'subject'): EmailableMixIn,
- ('use_email', 'subject'): EmailableMixIn,
- }
-
-
-
-def _done_init(done, view, row, col):
- """handle an infinite recursion safety belt"""
- if done is None:
- done = set()
- entity = view.entity(row, col)
- if entity.eid in done:
- msg = entity.req._('loop in %(rel)s relation (%(eid)s)') % {
- 'rel': entity.tree_attribute,
- 'eid': entity.eid
- }
- return None, msg
- done.add(entity.eid)
- return done, entity
-
-
-class TreeViewMixIn(object):
- """a recursive tree view"""
- id = 'tree'
- item_vid = 'treeitem'
- __select__ = implements(ITree)
-
- def call(self, done=None, **kwargs):
- if done is None:
- done = set()
- super(TreeViewMixIn, self).call(done=done, **kwargs)
-
- def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
- done, entity = _done_init(done, self, row, col)
- if done is None:
- # entity is actually an error message
- self.w(u'<li class="badcontent">%s</li>' % entity)
- return
- self.open_item(entity)
- entity.view(vid or self.item_vid, w=self.w, **kwargs)
- relatedrset = entity.children(entities=False)
- self.wview(self.id, relatedrset, 'null', done=done, **kwargs)
- self.close_item(entity)
-
- def open_item(self, entity):
- self.w(u'<li class="%s">\n' % entity.id.lower())
- def close_item(self, entity):
- self.w(u'</li>\n')
-
-
-class TreePathMixIn(object):
- """a recursive path view"""
- id = 'path'
- item_vid = 'oneline'
- separator = u' > '
-
- def call(self, **kwargs):
- self.w(u'<div class="pathbar">')
- super(TreePathMixIn, self).call(**kwargs)
- self.w(u'</div>')
-
- def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
- done, entity = _done_init(done, self, row, col)
- if done is None:
- # entity is actually an error message
- self.w(u'<span class="badcontent">%s</span>' % entity)
- return
- parent = entity.parent()
- if parent:
- parent.view(self.id, w=self.w, done=done)
- self.w(self.separator)
- entity.view(vid or self.item_vid, w=self.w)
-
-
-class ProgressMixIn(object):
- """provide default implementations for IProgress interface methods"""
- # This is an adapter isn't it ?
-
- @property
- def cost(self):
- return self.progress_info()['estimated']
-
- @property
- def revised_cost(self):
- return self.progress_info().get('estimatedcorrected', self.cost)
-
- @property
- def done(self):
- return self.progress_info()['done']
-
- @property
- def todo(self):
- return self.progress_info()['todo']
-
- @cached
- def progress_info(self):
- raise NotImplementedError()
-
- def finished(self):
- return not self.in_progress()
-
- def in_progress(self):
- raise NotImplementedError()
-
- def progress(self):
- try:
- return 100. * self.done / self.revised_cost
- except ZeroDivisionError:
- # total cost is 0 : if everything was estimated, task is completed
- if self.progress_info().get('notestimated'):
- return 0.
- return 100
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mixins', DeprecationWarning, stacklevel=2)
+from cubicweb.mixins import *
--- a/common/mttransforms.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/mttransforms.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,93 +1,5 @@
-"""mime type transformation engine for cubicweb, based on mtconverter
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab import mtconverter
-
-from logilab.mtconverter.engine import TransformEngine
-from logilab.mtconverter.transform import Transform
-from logilab.mtconverter import (register_base_transforms,
- register_pil_transforms,
- register_pygments_transforms)
-
-from cubicweb.common.uilib import rest_publish, html_publish
-
-HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
-
-# CubicWeb specific transformations
-
-class rest_to_html(Transform):
- inputs = ('text/rest', 'text/x-rst')
- output = 'text/html'
- def _convert(self, trdata):
- return rest_publish(trdata.appobject, trdata.decode())
-
-class html_to_html(Transform):
- inputs = HTML_MIMETYPES
- output = 'text/html'
- def _convert(self, trdata):
- return html_publish(trdata.appobject, trdata.data)
-
-
-# Instantiate and configure the transformation engine
-
-mtconverter.UNICODE_POLICY = 'replace'
-
-ENGINE = TransformEngine()
-ENGINE.add_transform(rest_to_html())
-ENGINE.add_transform(html_to_html())
-
-try:
- from cubicweb.ext.tal import compile_template
-except ImportError:
- HAS_TAL = False
- from cubicweb import schema
- schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template')
-
-else:
- HAS_TAL = True
-
- class ept_to_html(Transform):
- inputs = ('text/cubicweb-page-template',)
- output = 'text/html'
- output_encoding = 'utf-8'
- def _convert(self, trdata):
- value = trdata.encode(self.output_encoding)
- return trdata.appobject.tal_render(compile_template(value), {})
-
- ENGINE.add_transform(ept_to_html())
-
-if register_pil_transforms(ENGINE, verb=False):
- HAS_PIL_TRANSFORMS = True
-else:
- HAS_PIL_TRANSFORMS = False
-
-try:
- from logilab.mtconverter.transforms import pygmentstransforms
- for mt in ('text/plain',) + HTML_MIMETYPES:
- try:
- pygmentstransforms.mimetypes.remove(mt)
- except ValueError:
- continue
- register_pygments_transforms(ENGINE, verb=False)
-
- def patch_convert(cls):
- def _convert(self, trdata, origconvert=cls._convert):
- try:
- trdata.appobject.req.add_css('pygments.css')
- except AttributeError: # session has no add_css, only http request
- pass
- return origconvert(self, trdata)
- cls._convert = _convert
- patch_convert(pygmentstransforms.PygmentsHTMLTransform)
-
- HAS_PYGMENTS_TRANSFORMS = True
-except ImportError:
- HAS_PYGMENTS_TRANSFORMS = False
-
-register_base_transforms(ENGINE, verb=False)
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.mttransforms', DeprecationWarning, stacklevel=2)
+from cubicweb.mttransforms import *
--- a/common/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""pre 3.0 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.schema', DeprecationWarning, stacklevel=2)
-from cubicweb.schema import *
--- a/common/selectors.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-"""pre 3.2 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2)
-from cubicweb.selectors import *
-from cubicweb.selectors import _rql_condition
--- a/common/tags.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/tags.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,49 +1,5 @@
-"""helper classes to generate simple (X)HTML tags
-
-:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.common.uilib import simple_sgml_tag, sgml_attributes
-
-class tag(object):
- def __init__(self, name, escapecontent=True):
- self.name = name
- self.escapecontent = escapecontent
-
- def __call__(self, __content=None, **attrs):
- attrs.setdefault('escapecontent', self.escapecontent)
- return simple_sgml_tag(self.name, __content, **attrs)
-
-button = tag('button')
-input = tag('input')
-textarea = tag('textarea')
-a = tag('a')
-span = tag('span')
-div = tag('div', False)
-img = tag('img')
-label = tag('label')
-option = tag('option')
-h1 = tag('h1')
-h2 = tag('h2')
-h3 = tag('h3')
-h4 = tag('h4')
-h5 = tag('h5')
-tr = tag('tr')
-th = tag('th')
-td = tag('td')
-
-def select(name, id=None, multiple=False, options=[], **attrs):
- if multiple:
- attrs['multiple'] = 'multiple'
- if id:
- attrs['id'] = id
- attrs['name'] = name
- html = [u'<select %s>' % sgml_attributes(attrs)]
- html += options
- html.append(u'</select>')
- return u'\n'.join(html)
-
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.tags', DeprecationWarning, stacklevel=2)
+from cubicweb.tags import *
--- a/common/test/data/bootstrap_cubes Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- a/common/test/data/migration/0.0.3_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.0.4_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.1.0_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/0.1.0_common.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""common to all configuration
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.0_repository.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""repository specific
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.0_web.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""web only
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/data/migration/0.1.2_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-coucou
--- a/common/test/data/migration/depends.map Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-0.0.2: 2.3.0
-0.0.3: 2.4.0
-# missing 0.0.4 entry, that's alright
-0.1.0: 2.6.0
-0.1.2: 2.10.0
--- a/common/test/data/server_migration/bootstrapmigration_repository.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-"""allways executed before all others in server migration
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
--- a/common/test/unittest_mail.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit tests for module cubicweb.common.mail
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-import os
-import sys
-
-from logilab.common.testlib import unittest_main
-from logilab.common.umessage import message_from_string
-
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.common.mail import format_mail
-
-
-def getlogin():
- """avoid usinng os.getlogin() because of strange tty / stdin problems
- (man 3 getlogin)
- Another solution would be to use $LOGNAME, $USER or $USERNAME
- """
- if sys.platform != 'win32':
- import pwd
- return pwd.getpwuid(os.getuid())[0]
- else:
- return os.environ.get('USERNAME')
-
-
-class EmailTC(EnvBasedTC):
-
- def test_format_mail(self):
- self.set_option('sender-addr', 'bim@boum.fr')
- self.set_option('sender-name', 'BimBam')
-
- mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
- config=self.config)
- self.assertLinesEquals(mail.as_string(), """\
-MIME-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: base64
-Subject: =?utf-8?q?b=C3=AFjour?=
-From: =?utf-8?q?oim?= <oim@logilab.fr>
-Reply-to: =?utf-8?q?oim?= <oim@logilab.fr>, =?utf-8?q?BimBam?= <bim@boum.fr>
-X-CW: data
-To: test@logilab.fr
-
-dW4gcGV0aXQgY8O2dWNvdQ==
-""")
- msg = message_from_string(mail.as_string())
- self.assertEquals(msg.get('subject'), u'bïjour')
- self.assertEquals(msg.get('from'), u'oim <oim@logilab.fr>')
- self.assertEquals(msg.get('to'), u'test@logilab.fr')
- self.assertEquals(msg.get('reply-to'), u'oim <oim@logilab.fr>, BimBam <bim@boum.fr>')
- self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou')
-
-
- def test_format_mail_euro(self):
- mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
- self.assertLinesEquals(mail.as_string(), """\
-MIME-Version: 1.0
-Content-Type: text/plain; charset="utf-8"
-Content-Transfer-Encoding: base64
-Subject: =?utf-8?b?YsOvam91ciDigqw=?=
-From: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
-Reply-to: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
-To: test@logilab.fr
-
-dW4gcGV0aXQgY8O2dWNvdSDigqw=
-""")
- msg = message_from_string(mail.as_string())
- self.assertEquals(msg.get('subject'), u'bïjour €')
- self.assertEquals(msg.get('from'), u'oîm <oim@logilab.fr>')
- self.assertEquals(msg.get('to'), u'test@logilab.fr')
- self.assertEquals(msg.get('reply-to'), u'oîm <oim@logilab.fr>')
- self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou €')
-
-
- def test_format_mail_from_reply_to(self):
- # no sender-name, sender-addr in the configuration
- self.set_option('sender-name', '')
- self.set_option('sender-addr', '')
- msg = format_mail({'name': u'', 'email': u''},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- self.assertEquals(msg.get('from'), u'')
- self.assertEquals(msg.get('reply-to'), None)
- msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
- msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
- # set sender name and address as expected
- self.set_option('sender-name', 'cubicweb-test')
- self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
- # anonymous notification: no name and no email specified
- msg = format_mail({'name': u'', 'email': u''},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'cubicweb-test <cubicweb-test@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'cubicweb-test <cubicweb-test@logilab.fr>')
- # anonymous notification: only email specified
- msg = format_mail({'email': u'tutu@logilab.fr'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'cubicweb-test <tutu@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'cubicweb-test <tutu@logilab.fr>, cubicweb-test <cubicweb-test@logilab.fr>')
- # anonymous notification: only name specified
- msg = format_mail({'name': u'tutu'},
- ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
- config=self.config)
- msg = message_from_string(msg.as_string())
- self.assertEquals(msg.get('from'), u'tutu <cubicweb-test@logilab.fr>')
- self.assertEquals(msg.get('reply-to'), u'tutu <cubicweb-test@logilab.fr>')
-
-
-
-if __name__ == '__main__':
- unittest_main()
-
--- a/common/test/unittest_migration.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-"""cubicweb.common.migration unit tests
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from os.path import abspath
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.apptest import TestEnvironment
-
-from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.common.migration import MigrationHelper, filter_scripts
-from cubicweb.server.migractions import ServerMigrationHelper
-
-
-class Schema(dict):
- def has_entity(self, e_type):
- return self.has_key(e_type)
-
-SMIGRDIR = abspath('data/server_migration') + '/'
-TMIGRDIR = abspath('data/migration') + '/'
-
-class MigrTestConfig(TestServerConfiguration):
- verbosity = 0
- def migration_scripts_dir(cls):
- return SMIGRDIR
-
- def cube_migration_scripts_dir(cls, cube):
- return TMIGRDIR
-
-class MigrationToolsTC(TestCase):
- def setUp(self):
- self.config = MigrTestConfig('data')
- from yams.schema import Schema
- self.config.load_schema = lambda expand_cubes=False: Schema('test')
- self.config.__class__.cubicweb_appobject_path = frozenset()
- self.config.__class__.cube_appobject_path = frozenset()
-
- def test_filter_scripts_base(self):
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
- [])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
- ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
- [])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
- ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-
- self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
- [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
- self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
- [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
- ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
-
- def test_filter_scripts_for_mode(self):
- self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
- config = CubicWebConfiguration('data')
- config.verbosity = 0
- self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
- self.assertIsInstance(config.migration_handler(), MigrationHelper)
- config = self.config
- config.__class__.name = 'twisted'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
- config.__class__.name = 'repository'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
- config.__class__.name = 'all-in-one'
- self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
- [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
- ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
- config.__class__.name = 'repository'
-
-
-from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
-
-class BaseCreationTC(TestCase):
-
- def test_db_creation(self):
- """make sure database can be created"""
- config = ApptestConfiguration('data')
- source = config.sources()['system']
- self.assertEquals(source['db-driver'], 'sqlite')
- cleanup_sqlite(source['db-name'], removecube=True)
- init_test_database(driver=source['db-driver'], config=config)
-
-
-if __name__ == '__main__':
- unittest_main()
--- a/common/test/unittest_uilib.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unittests for cubicweb.common.uilib
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-__docformat__ = "restructuredtext en"
-
-from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.tree import Node
-
-from cubicweb.common import uilib
-
-class UILIBTC(TestCase):
-
- def test_remove_tags(self):
- """make sure remove_tags remove all tags"""
- data = [
- ('<h1>Hello</h1>', 'Hello'),
- ('<h1>Hello <a href="foo/bar"><b>s</b>pam</a></h1>', 'Hello spam'),
- ('<br>Hello<img src="doh.png"/>', 'Hello'),
- ('<p></p>', ''),
- ]
- for text, expected in data:
- got = uilib.remove_html_tags(text)
- self.assertEquals(got, expected)
-
- def test_fallback_safe_cut(self):
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 4), u'ab c...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 5), u'ab <a href="hello">cd</a>')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a>', 4), u'ab &...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a> ef', 5), u'ab &d...')
- self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">ìd</a>', 4), u'ab ì...')
- self.assertEquals(uilib.fallback_safe_cut(u'& <a href="hello">&d</a> ef', 4), u'& &d...')
-
- def test_lxml_safe_cut(self):
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 4), u'<p>aaa</p><div>a...</div>')
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 7), u'<p>aaa</p><div>aaad</div>...')
- self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div>', 7), u'<p>aaa</p><div>aaad</div>')
- # Missing ellipsis due to space management but we don't care
- self.assertEquals(uilib.safe_cut(u'ab <a href="hello">&d</a>', 4), u'<p>ab <a href="hello">&...</a></p>')
-
- def test_cut(self):
- """tests uilib.cut() behaviour"""
- data = [
- ('hello', 'hello'),
- ('hello world', 'hello wo...'),
- ("hell<b>O'</b> world", "hell<b>O..."),
- ]
- for text, expected in data:
- got = uilib.cut(text, 8)
- self.assertEquals(got, expected)
-
- def test_text_cut(self):
- """tests uilib.text_cut() behaviour with no text"""
- data = [('',''),
- ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
-consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
-cillum dolore eu fugiat nulla pariatur.""",
- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
-tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo \
-consequat."),
- ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam,
-quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
-consequat Duis aute irure dolor in reprehenderit in voluptate velit esse
-cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non
-proident, sunt in culpa qui officia deserunt mollit anim id est laborum
-""",
- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
-tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam, \
-quis nostrud exercitation ullamco laboris nisi"),
- ]
- for text, expected in data:
- got = uilib.text_cut(text, 30)
- self.assertEquals(got, expected)
-
-if __name__ == '__main__':
- unittest_main()
-
--- a/common/uilib.py Mon Feb 08 13:33:09 2010 +0100
+++ b/common/uilib.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,383 +1,5 @@
-# -*- coding: utf-8 -*-
-"""user interface libraries
-
-contains some functions designed to help implementation of cubicweb user interface
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import csv
-import re
-from StringIO import StringIO
-
-from logilab.mtconverter import xml_escape, html_unescape
-
-from cubicweb.utils import ustrftime
-
-def rql_for_eid(eid):
- """return the rql query necessary to fetch entity with the given eid. This
- function should only be used to generate link with rql inside, not to give
- to cursor.execute (in which case you won't benefit from rql cache).
-
- :Parameters:
- - `eid`: the eid of the entity we should search
- :rtype: str
- :return: the rql query
- """
- return 'Any X WHERE X eid %s' % eid
-
-
-def printable_value(req, attrtype, value, props=None, displaytime=True):
- """return a displayable value (i.e. unicode string)"""
- if value is None or attrtype == 'Bytes':
- return u''
- if attrtype == 'String':
- # don't translate empty value if you don't want strange results
- if props is not None and value and props.get('internationalizable'):
- return req._(value)
- return value
- if attrtype == 'Date':
- return ustrftime(value, req.property_value('ui.date-format'))
- if attrtype == 'Time':
- return ustrftime(value, req.property_value('ui.time-format'))
- if attrtype == 'Datetime':
- if displaytime:
- return ustrftime(value, req.property_value('ui.datetime-format'))
- return ustrftime(value, req.property_value('ui.date-format'))
- if attrtype == 'Boolean':
- if value:
- return req._('yes')
- return req._('no')
- if attrtype == 'Float':
- value = req.property_value('ui.float-format') % value
- return unicode(value)
-
-
-# text publishing #############################################################
-
-try:
- from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
-except ImportError:
- def rest_publish(entity, data):
- """default behaviour if docutils was not found"""
- return xml_escape(data)
-
-TAG_PROG = re.compile(r'</?.*?>', re.U)
-def remove_html_tags(text):
- """Removes HTML tags from text
-
- >>> remove_html_tags('<td>hi <a href="http://www.google.fr">world</a></td>')
- 'hi world'
- >>>
- """
- return TAG_PROG.sub('', text)
-
-
-REF_PROG = re.compile(r"<ref\s+rql=([\'\"])([^\1]*?)\1\s*>([^<]*)</ref>", re.U)
-def _subst_rql(view, obj):
- delim, rql, descr = obj.groups()
- return u'<a href="%s">%s</a>' % (view.build_url(rql=rql), descr)
-
-def html_publish(view, text):
- """replace <ref rql=''> links by <a href="...">"""
- if not text:
- return u''
- return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text)
-
-# fallback implementation, nicer one defined below if lxml is available
-def soup2xhtml(data, encoding):
- # normalize line break
- # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
- return u'\n'.join(data.splitlines())
-
-# fallback implementation, nicer one defined below if lxml> 2.0 is available
-def safe_cut(text, length):
- """returns a string of length <length> based on <text>, removing any html
- tags from given text if cut is necessary."""
- if text is None:
- return u''
- noenttext = html_unescape(text)
- text_nohtml = remove_html_tags(noenttext)
- # try to keep html tags if text is short enough
- if len(text_nohtml) <= length:
- return text
- # else if un-tagged text is too long, cut it
- return xml_escape(text_nohtml[:length] + u'...')
-
-fallback_safe_cut = safe_cut
-
-
-try:
- from lxml import etree
-except (ImportError, AttributeError):
- # gae environment: lxml not available
- pass
-else:
-
- def soup2xhtml(data, encoding):
- """tidy (at least try) html soup and return the result
- Note: the function considers a string with no surrounding tag as valid
- if <div>`data`</div> can be parsed by an XML parser
- """
- # normalize line break
- # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
- data = u'\n'.join(data.splitlines())
- # XXX lxml 1.1 support still needed ?
- xmltree = etree.HTML('<div>%s</div>' % data)
- # NOTE: lxml 1.1 (etch platforms) doesn't recognize
- # the encoding=unicode parameter (lxml 2.0 does), this is
- # why we specify an encoding and re-decode to unicode later
- body = etree.tostring(xmltree[0], encoding=encoding)
- # remove <body> and </body> and decode to unicode
- return body[11:-13].decode(encoding)
-
- if hasattr(etree.HTML('<div>test</div>'), 'iter'):
-
- def safe_cut(text, length):
- """returns an html document of length <length> based on <text>,
- and cut is necessary.
- """
- if text is None:
- return u''
- dom = etree.HTML(text)
- curlength = 0
- add_ellipsis = False
- for element in dom.iter():
- if curlength >= length:
- parent = element.getparent()
- parent.remove(element)
- if curlength == length and (element.text or element.tail):
- add_ellipsis = True
- else:
- if element.text is not None:
- element.text = cut(element.text, length - curlength)
- curlength += len(element.text)
- if element.tail is not None:
- if curlength < length:
- element.tail = cut(element.tail, length - curlength)
- curlength += len(element.tail)
- elif curlength == length:
- element.tail = '...'
- else:
- element.tail = ''
- text = etree.tounicode(dom[0])[6:-7] # remove wrapping <body></body>
- if add_ellipsis:
- return text + u'...'
- return text
-
-def text_cut(text, nbwords=30, gotoperiod=True):
- """from the given plain text, return a text with at least <nbwords> words,
- trying to go to the end of the current sentence.
-
- :param nbwords: the minimum number of words required
- :param gotoperiod: specifies if the function should try to go to
- the first period after the cut (i.e. finish
- the sentence if possible)
-
- Note that spaces are normalized.
- """
- if text is None:
- return u''
- words = text.split()
- text = u' '.join(words) # normalize spaces
- textlength = minlength = len(' '.join(words[:nbwords]))
- if gotoperiod:
- textlength = text.find('.', minlength) + 1
- if textlength == 0: # no period found
- textlength = minlength
- return text[:textlength]
-
-def cut(text, length):
- """returns a string of a maximum length <length> based on <text>
- (approximatively, since if text has been cut, '...' is added to the end of the string,
- resulting in a string of len <length> + 3)
- """
- if text is None:
- return u''
- if len(text) <= length:
- return text
- # else if un-tagged text is too long, cut it
- return text[:length] + u'...'
-
-
-
-# HTML generation helper functions ############################################
-
-HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
- 'img', 'area', 'input', 'col'))
-
-def sgml_attributes(attrs):
- return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
- for attr, value in sorted(attrs.items())
- if value is not None)
-
-def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
- """generation of a simple sgml tag (eg without children tags) easier
-
- content and attri butes will be escaped
- """
- value = u'<%s' % tag
- if attrs:
- try:
- attrs['class'] = attrs.pop('klass')
- except KeyError:
- pass
- value += u' ' + sgml_attributes(attrs)
- if content:
- if escapecontent:
- content = xml_escape(unicode(content))
- value += u'>%s</%s>' % (content, tag)
- else:
- if tag in HTML4_EMPTY_TAGS:
- value += u' />'
- else:
- value += u'></%s>' % tag
- return value
-
-def tooltipize(text, tooltip, url=None):
- """make an HTML tooltip"""
- url = url or '#'
- return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text)
-
-def toggle_action(nodeid):
- """builds a HTML link that uses the js toggleVisibility function"""
- return u"javascript: toggleVisibility('%s')" % nodeid
-
-def toggle_link(nodeid, label):
- """builds a HTML link that uses the js toggleVisibility function"""
- return u'<a href="%s">%s</a>' % (toggle_action(nodeid), label)
-
-
-def ureport_as_html(layout):
- from logilab.common.ureports import HTMLWriter
- formater = HTMLWriter(True)
- stream = StringIO() #UStringIO() don't want unicode assertion
- formater.format(layout, stream)
- res = stream.getvalue()
- if isinstance(res, str):
- res = unicode(res, 'UTF8')
- return res
-
-# traceback formatting ########################################################
-
-import traceback
-
-def rest_traceback(info, exception):
- """return a ReST formated traceback"""
- res = [u'Traceback\n---------\n::\n']
- for stackentry in traceback.extract_tb(info[2]):
- res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
- if stackentry[3]:
- res.append(u'\t %s' % stackentry[3].decode('utf-8', 'replace'))
- res.append(u'\n')
- try:
- res.append(u'\t Error: %s\n' % exception)
- except:
- pass
- return u'\n'.join(res)
-
-
-def html_traceback(info, exception, title='',
- encoding='ISO-8859-1', body=''):
- """ return an html formatted traceback from python exception infos.
- """
- tcbk = info[2]
- stacktb = traceback.extract_tb(tcbk)
- strings = []
- if body:
- strings.append(u'<div class="error_body">')
- # FIXME
- strings.append(body)
- strings.append(u'</div>')
- if title:
- strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
- try:
- strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
- except UnicodeError:
- pass
- strings.append(u'<div class="error_traceback">')
- for index, stackentry in enumerate(stacktb):
- strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
- u'<b class="line">%s</b>, <b>function</b> '
- u'<b class="function">%s</b>:<br/>'%(
- xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
- if stackentry[3]:
- string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
- strings.append(u'  %s<br/>\n' % (string))
- # add locals info for each entry
- try:
- local_context = tcbk.tb_frame.f_locals
- html_info = []
- chars = 0
- for name, value in local_context.iteritems():
- value = xml_escape(repr(value))
- info = u'<span class="name">%s</span>=%s, ' % (name, value)
- line_length = len(name) + len(value)
- chars += line_length
- # 150 is the result of *years* of research ;-) (CSS might be helpful here)
- if chars > 150:
- info = u'<br/>' + info
- chars = line_length
- html_info.append(info)
- boxid = 'ctxlevel%d' % index
- strings.append(u'[%s]' % toggle_link(boxid, '+'))
- strings.append(u'<div id="%s" class="pycontext hidden">%s</div>' %
- (boxid, ''.join(html_info)))
- tcbk = tcbk.tb_next
- except Exception:
- pass # doesn't really matter if we have no context info
- strings.append(u'</div>')
- return '\n'.join(strings)
-
-# csv files / unicode support #################################################
-
-class UnicodeCSVWriter:
- """proxies calls to csv.writer.writerow to be able to deal with unicode"""
-
- def __init__(self, wfunc, encoding, **kwargs):
- self.writer = csv.writer(self, **kwargs)
- self.wfunc = wfunc
- self.encoding = encoding
-
- def write(self, data):
- self.wfunc(data)
-
- def writerow(self, row):
- csvrow = []
- for elt in row:
- if isinstance(elt, unicode):
- csvrow.append(elt.encode(self.encoding))
- else:
- csvrow.append(str(elt))
- self.writer.writerow(csvrow)
-
- def writerows(self, rows):
- for row in rows:
- self.writerow(row)
-
-
-# some decorators #############################################################
-
-class limitsize(object):
- def __init__(self, maxsize):
- self.maxsize = maxsize
-
- def __call__(self, function):
- def newfunc(*args, **kwargs):
- ret = function(*args, **kwargs)
- if isinstance(ret, basestring):
- return ret[:self.maxsize]
- return ret
- return newfunc
-
-
-def htmlescape(function):
- def newfunc(*args, **kwargs):
- ret = function(*args, **kwargs)
- assert isinstance(ret, basestring)
- return xml_escape(ret)
- return newfunc
+"""pre 3.6 bw compat"""
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.uilib', DeprecationWarning, stacklevel=2)
+from cubicweb.uilib import *
--- a/common/utils.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""pre 3.2 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.utils', DeprecationWarning, stacklevel=2)
-from cubicweb.utils import *
--- a/common/view.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""pre 3.2 bw compat
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-# pylint: disable-msg=W0614,W0401
-from warnings import warn
-warn('moved to cubicweb.view', DeprecationWarning, stacklevel=2)
-from cubicweb.view import *
--- a/cwconfig.py Mon Feb 08 13:33:09 2010 +0100
+++ b/cwconfig.py Mon Feb 08 13:35:07 2010 +0100
@@ -78,12 +78,13 @@
import sys
import os
import logging
+import tempfile
from smtplib import SMTP
from threading import Lock
from os.path import exists, join, expanduser, abspath, normpath, basename, isdir
-import tempfile
+from warnings import warn
-from logilab.common.decorators import cached
+from logilab.common.decorators import cached, classproperty
from logilab.common.deprecation import deprecated
from logilab.common.logging_ext import set_log_methods, init_log
from logilab.common.configuration import (Configuration, Method,
@@ -290,11 +291,6 @@
this option is set to yes",
'group': 'email', 'inputlevel': 2,
}),
- ('disable-appobjects',
- {'type' : 'csv', 'default': (),
- 'help': 'comma separated list of identifiers of application objects (<registry>.<oid>) to disable',
- 'group': 'appobjects', 'inputlevel': 2,
- }),
)
# static and class methods used to get instance independant resources ##
@@ -355,6 +351,14 @@
path.append(cls.CUBES_DIR)
return path
+ @classproperty
+ def extrapath(cls):
+ extrapath = {}
+ for cubesdir in cls.cubes_search_path():
+ if cubesdir != cls.CUBES_DIR:
+ extrapath[cubesdir] = 'cubes'
+ return extrapath
+
@classmethod
def cube_dir(cls, cube):
"""return the cube directory for the given cube id,
@@ -470,7 +474,7 @@
from logilab.common.modutils import load_module_from_file
cls.cls_adjust_sys_path()
for ctlfile in ('web/webctl.py', 'etwist/twctl.py',
- 'server/serverctl.py', 'hercule.py',
+ 'server/serverctl.py',
'devtools/devctl.py', 'goa/goactl.py'):
if exists(join(CW_SOFTWARE_ROOT, ctlfile)):
try:
@@ -480,14 +484,23 @@
(ctlfile, err))
cls.info('loaded cubicweb-ctl plugin %s', ctlfile)
for cube in cls.available_cubes():
- pluginfile = join(cls.cube_dir(cube), 'ecplugin.py')
+ oldpluginfile = join(cls.cube_dir(cube), 'ecplugin.py')
+ pluginfile = join(cls.cube_dir(cube), 'ccplugin.py')
initfile = join(cls.cube_dir(cube), '__init__.py')
if exists(pluginfile):
try:
+ __import__('cubes.%s.ccplugin' % cube)
+ cls.info('loaded cubicweb-ctl plugin from %s', cube)
+ except:
+ cls.exception('while loading plugin %s', pluginfile)
+ elif exists(oldpluginfile):
+ warn('[3.6] %s: ecplugin module should be renamed to ccplugin' % cube,
+ DeprecationWarning)
+ try:
__import__('cubes.%s.ecplugin' % cube)
cls.info('loaded cubicweb-ctl plugin from %s', cube)
except:
- cls.exception('while loading plugin %s', pluginfile)
+ cls.exception('while loading plugin %s', oldpluginfile)
elif exists(initfile):
try:
__import__('cubes.%s' % cube)
@@ -556,6 +569,7 @@
return vregpath
def __init__(self):
+ register_stored_procedures()
ConfigurationMixIn.__init__(self)
self.adjust_sys_path()
self.load_defaults()
@@ -862,11 +876,15 @@
if exists(sitefile) and not sitefile in self._site_loaded:
self._load_site_cubicweb(sitefile)
self._site_loaded.add(sitefile)
- self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
+ self.warning('[3.5] site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
def _load_site_cubicweb(self, sitefile):
- from logilab.common.modutils import load_module_from_file
- module = load_module_from_file(sitefile)
+ # XXX extrapath argument to load_module_from_file only in lgc > 0.46
+ from logilab.common.modutils import load_module_from_modpath, modpath_from_file
+ def load_module_from_file(filepath, path=None, use_sys=1, extrapath=None):
+ return load_module_from_modpath(modpath_from_file(filepath, extrapath),
+ path, use_sys)
+ module = load_module_from_file(sitefile, extrapath=self.extrapath)
self.info('%s loaded', sitefile)
# cube specific options
if getattr(module, 'options', None):
@@ -935,11 +953,11 @@
def migration_handler(self):
"""return a migration handler instance"""
- from cubicweb.common.migration import MigrationHelper
+ from cubicweb.migration import MigrationHelper
return MigrationHelper(self, verbosity=self.verbosity)
def i18ncompile(self, langs=None):
- from cubicweb.common import i18n
+ from cubicweb import i18n
if langs is None:
langs = self.available_languages()
i18ndir = join(self.apphome, 'i18n')
@@ -976,3 +994,61 @@
# alias to get a configuration instance from an instance id
instance_configuration = CubicWebConfiguration.config_for
application_configuration = deprecated('use instance_configuration')(instance_configuration)
+
+
+_EXT_REGISTERED = False
+def register_stored_procedures():
+ from logilab.common.adbh import FunctionDescr
+ from rql.utils import register_function, iter_funcnode_variables
+
+ global _EXT_REGISTERED
+ if _EXT_REGISTERED:
+ return
+ _EXT_REGISTERED = True
+
+ class COMMA_JOIN(FunctionDescr):
+ supported_backends = ('postgres', 'sqlite',)
+ rtype = 'String'
+
+ @classmethod
+ def st_description(cls, funcnode, mainindex, tr):
+ return ', '.join(sorted(term.get_description(mainindex, tr)
+ for term in iter_funcnode_variables(funcnode)))
+
+ register_function(COMMA_JOIN) # XXX do not expose?
+
+
+ class CONCAT_STRINGS(COMMA_JOIN):
+ aggregat = True
+
+ register_function(CONCAT_STRINGS) # XXX bw compat
+
+ class GROUP_CONCAT(CONCAT_STRINGS):
+ supported_backends = ('mysql', 'postgres', 'sqlite',)
+
+ register_function(GROUP_CONCAT)
+
+
+ class LIMIT_SIZE(FunctionDescr):
+ supported_backends = ('postgres', 'sqlite',)
+ rtype = 'String'
+
+ @classmethod
+ def st_description(cls, funcnode, mainindex, tr):
+ return funcnode.children[0].get_description(mainindex, tr)
+
+ register_function(LIMIT_SIZE)
+
+
+ class TEXT_LIMIT_SIZE(LIMIT_SIZE):
+ supported_backends = ('mysql', 'postgres', 'sqlite',)
+
+ register_function(TEXT_LIMIT_SIZE)
+
+
+
+ class FSPATH(FunctionDescr):
+ supported_backends = ('postgres', 'sqlite',)
+ rtype = 'Bytes'
+
+ register_function(FSPATH)
--- a/cwctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/cwctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,7 +1,7 @@
"""%%prog %s [options] %s
-CubicWeb main instances controller.
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+The CubicWeb swiss-knife.
+
%s"""
import sys
@@ -17,9 +17,9 @@
from logilab.common.clcommands import register_commands, pop_arg
from logilab.common.shellutils import ASK
-from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title
+from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
-from cubicweb.toolsutils import Command, main_run, rm, create_dir
+from cubicweb.toolsutils import Command, main_run, rm, create_dir, underline_title
def wait_process_end(pid, maxtry=10, waittime=1):
"""wait for a process to actually die"""
@@ -312,7 +312,7 @@
# handle i18n files structure
# in the first cube given
print '-> preparing i18n catalogs'
- from cubicweb.common import i18n
+ from cubicweb import i18n
langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
errors = config.i18ncompile(langs)
if errors:
@@ -690,7 +690,7 @@
# * install new languages
# * recompile catalogs
# in the first componant given
- from cubicweb.common import i18n
+ from cubicweb import i18n
templdir = cwcfg.cube_dir(config.cubes()[0])
langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
errors = config.i18ncompile(langs)
--- a/cwvreg.py Mon Feb 08 13:33:09 2010 +0100
+++ b/cwvreg.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,7 +18,7 @@
ObjectNotFound, NoSelectableObject, RegistryNotFound,
RegistryOutOfDate, CW_EVENT_MANAGER, onevent)
from cubicweb.utils import dump_class
-from cubicweb.vregistry import VRegistry, Registry
+from cubicweb.vregistry import VRegistry, Registry, class_regid
from cubicweb.rtags import RTAGS
@@ -58,14 +58,7 @@
def schema(self):
return self.vreg.schema
- def initialization_completed(self):
- # call vreg_initialization_completed on appobjects and print
- # registry content
- for appobjects in self.itervalues():
- for appobject in appobjects:
- # XXX kill vreg_initialization_completed
- appobject.vreg_initialization_completed()
-
+ @deprecated('[3.6] select object, then use obj.render()')
def render(self, __oid, req, __fallback_oid=None, rset=None, initargs=None,
**kwargs):
"""Select object with the given id (`__oid`) then render it. If the
@@ -90,20 +83,22 @@
obj = self.select(__fallback_oid, req, rset=rset, **initargs)
return obj.render(**kwargs)
+ @deprecated('[3.6] use select_or_none and test for obj.cw_propval("visible")')
def select_vobject(self, oid, *args, **kwargs):
- selected = self.select_object(oid, *args, **kwargs)
- if selected and selected.propval('visible'):
+ selected = self.select_or_none(oid, *args, **kwargs)
+ if selected and selected.cw_propval('visible'):
return selected
return None
- def possible_vobjects(self, *args, **kwargs):
+ def poss_visible_objects(self, *args, **kwargs):
"""return an ordered list of possible app objects in a given registry,
supposing they support the 'visible' and 'order' properties (as most
visualizable objects)
"""
return sorted([x for x in self.possible_objects(*args, **kwargs)
- if x.propval('visible')],
- key=lambda x: x.propval('order'))
+ if x.cw_propval('visible')],
+ key=lambda x: x.cw_propval('order'))
+ possible_vobjects = deprecated('[3.6] use poss_visible_objects()')(poss_visible_objects)
VRegistry.REGISTRY_FACTORY[None] = CWRegistry
@@ -117,17 +112,27 @@
super(ETypeRegistry, self).initialization_completed()
# clear etype cache if you don't want to run into deep weirdness
clear_cache(self, 'etype_class')
+ clear_cache(self, 'parent_classes')
def register(self, obj, **kwargs):
- oid = kwargs.get('oid') or obj.id
+ oid = kwargs.get('oid') or class_regid(obj)
if oid != 'Any' and not oid in self.schema:
self.error('don\'t register %s, %s type not defined in the '
- 'schema', obj, obj.id)
+ 'schema', obj, oid)
return
kwargs['clear'] = True
super(ETypeRegistry, self).register(obj, **kwargs)
@cached
+ def parent_classes(self, etype):
+ if etype == 'Any':
+ return [self.etype_class('Any')]
+ eschema = self.schema.eschema(etype)
+ parents = [self.etype_class(e.type) for e in eschema.ancestors()]
+ parents.append(self.etype_class('Any'))
+ return parents
+
+ @cached
def etype_class(self, etype):
"""return an entity class for the given entity type.
@@ -138,42 +143,42 @@
"""
etype = str(etype)
if etype == 'Any':
- return self.select('Any', 'Any')
+ objects = self['Any']
+ assert len(objects) == 1, objects
+ return objects[0]
eschema = self.schema.eschema(etype)
baseschemas = [eschema] + eschema.ancestors()
# browse ancestors from most specific to most generic and try to find an
# associated custom entity class
- cls = None
for baseschema in baseschemas:
try:
btype = ETYPE_NAME_MAP[baseschema]
except KeyError:
btype = str(baseschema)
- if cls is None:
- try:
- objects = self[btype]
- assert len(objects) == 1, objects
- if btype == etype:
- cls = objects[0]
- else:
- cls = self.etype_class(btype)
- except ObjectNotFound:
- continue
- else:
- # ensure parent classes are built first
- self.etype_class(btype)
- if cls is None:
+ try:
+ objects = self[btype]
+ assert len(objects) == 1, objects
+ if btype == etype:
+ cls = objects[0]
+ else:
+ # recurse to ensure issubclass(etype_class('Child'),
+ # etype_class('Parent'))
+ cls = self.etype_class(btype)
+ break
+ except ObjectNotFound:
+ pass
+ else:
# no entity class for any of the ancestors, fallback to the default
# one
objects = self['Any']
assert len(objects) == 1, objects
cls = objects[0]
- # make a copy event if cls.id == etype, else we may have pb for client
- # application using multiple connections to different repositories (eg
- # shingouz)
+ # make a copy event if cls.__regid__ == etype, else we may have pb for
+ # client application using multiple connections to different
+ # repositories (eg shingouz)
cls = dump_class(cls, etype)
- cls.id = etype
- cls.__initialize__()
+ cls.__regid__ = etype
+ cls.__initialize__(self.schema)
return cls
VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry
@@ -181,12 +186,13 @@
class ViewsRegistry(CWRegistry):
- def main_template(self, req, oid='main-template', **kwargs):
+ def main_template(self, req, oid='main-template', rset=None, **kwargs):
"""display query by calling the given template (default to main),
and returning the output as a string instead of requiring the [w]rite
method as argument
"""
- res = self.render(oid, req, **kwargs)
+ obj = self.select(oid, req, rset=rset, **kwargs)
+ res = obj.render(**kwargs)
if isinstance(res, unicode):
return res.encode(req.encoding)
assert isinstance(res, str)
@@ -201,7 +207,7 @@
if vid[0] == '_':
continue
try:
- view = self.select_best(views, req, rset=rset, **kwargs)
+ view = self._select_best(views, req, rset=rset, **kwargs)
if view.linkable():
yield view
except NoSelectableObject:
@@ -217,7 +223,7 @@
def possible_actions(self, req, rset=None, **kwargs):
if rset is None:
- actions = self.possible_vobjects(req, rset=rset, **kwargs)
+ actions = self.poss_visible_objects(req, rset=rset, **kwargs)
else:
actions = rset.possible_actions(**kwargs) # cached implementation
result = {}
@@ -334,7 +340,7 @@
def register_if_interface_found(self, obj, ifaces, **kwargs):
"""register an object but remove it if no entity class implements one of
- the given interfaces
+ the given interfaces at the end of the registration process
"""
self.register(obj, **kwargs)
if not isinstance(ifaces, (tuple, list)):
@@ -354,35 +360,23 @@
if force_reload is None:
force_reload = self.config.debugmode
try:
- self._register_objects(path, force_reload)
+ super(CubicWebVRegistry, self).register_objects(
+ path, force_reload, self.config.extrapath)
except RegistryOutOfDate:
CW_EVENT_MANAGER.emit('before-registry-reload')
# modification detected, reset and reload
self.reset(path, force_reload)
- self._register_objects(path, force_reload)
+ super(CubicWebVRegistry, self).register_objects(
+ path, force_reload, self.config.extrapath)
CW_EVENT_MANAGER.emit('after-registry-reload')
- def _register_objects(self, path, force_reload=None):
- """overriden to remove objects requiring a missing interface"""
- extrapath = {}
- for cubesdir in self.config.cubes_search_path():
- if cubesdir != self.config.CUBES_DIR:
- extrapath[cubesdir] = 'cubes'
- if super(CubicWebVRegistry, self).register_objects(path, force_reload,
- extrapath):
- self.initialization_completed()
- # don't check rtags if we don't want to cleanup_interface_sobjects
- for rtag in RTAGS:
- rtag.init(self.schema,
- check=self.config.cleanup_interface_sobjects)
+ def initialization_completed(self):
+ """cw specific code once vreg initialization is completed:
- def initialization_completed(self):
- for regname, reg in self.iteritems():
- self.debug('available in registry %s: %s', regname, sorted(reg))
- reg.initialization_completed()
- for appobjects in reg.itervalues():
- for appobjectcls in appobjects:
- appobjectcls.register_properties()
+ * remove objects requiring a missing interface, unless
+ config.cleanup_interface_sobjects is false
+ * init rtags
+ """
# we may want to keep interface dependent objects (e.g.for i18n
# catalog generation)
if self.config.cleanup_interface_sobjects:
@@ -409,6 +403,11 @@
# clear needs_iface so we don't try to remove some not-anymore-in
# objects on automatic reloading
self._needs_iface.clear()
+ super(CubicWebVRegistry, self).initialization_completed()
+ for rtag in RTAGS:
+ # don't check rtags if we don't want to cleanup_interface_sobjects
+ rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
+
# rql parsing utilities ####################################################
@@ -469,7 +468,7 @@
try:
return self['propertyvalues'][key]
except KeyError:
- return self['propertydefs'][key]['default']
+ return self.property_info(key)['default']
def typed_value(self, key, value):
"""value is an unicode string, return it correctly typed. Let potential
--- a/dbapi.py Mon Feb 08 13:33:09 2010 +0100
+++ b/dbapi.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,8 +19,9 @@
from logilab.common.decorators import monkeypatch
from logilab.common.deprecation import deprecated
-from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
-from cubicweb import cwvreg, cwconfig
+from cubicweb import ETYPE_NAME_MAP, ConnectionError, cwvreg, cwconfig
+from cubicweb.req import RequestSessionBase
+
_MARKER = object()
@@ -42,9 +43,9 @@
registries.
"""
defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None]
- orig_select_best = defaultcls.orig_select_best = defaultcls.select_best
+ orig_select_best = defaultcls.orig_select_best = defaultcls._select_best
@monkeypatch(defaultcls)
- def select_best(self, appobjects, *args, **kwargs):
+ def _select_best(self, appobjects, *args, **kwargs):
"""return an instance of the most specific object according
to parameters
@@ -110,20 +111,21 @@
except Exception, ex:
raise ConnectionError(str(ex))
-def repo_connect(repo, login, password, cnxprops=None):
+def repo_connect(repo, login, **kwargs):
"""Constructor to create a new connection to the CubicWeb repository.
Returns a Connection instance.
"""
- cnxprops = cnxprops or ConnectionProperties('inmemory')
- cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops)
- cnx = Connection(repo, cnxid, cnxprops)
- if cnxprops.cnxtype == 'inmemory':
+ if not 'cnxprops' in kwargs:
+ kwargs['cnxprops'] = ConnectionProperties('inmemory')
+ cnxid = repo.connect(unicode(login), **kwargs)
+ cnx = Connection(repo, cnxid, kwargs['cnxprops'])
+ if kwargs['cnxprops'].cnxtype == 'inmemory':
cnx.vreg = repo.vreg
return cnx
-def connect(database=None, login=None, password=None, host=None, group=None,
- cnxprops=None, setvreg=True, mulcnx=True, initlog=True):
+def connect(database=None, login=None, host=None, group=None,
+ cnxprops=None, setvreg=True, mulcnx=True, initlog=True, **kwargs):
"""Constructor for creating a connection to the CubicWeb repository.
Returns a Connection object.
@@ -153,11 +155,11 @@
vreg.set_schema(schema)
else:
vreg = None
- cnx = repo_connect(repo, login, password, cnxprops)
+ cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
cnx.vreg = vreg
return cnx
-def in_memory_cnx(config, login, password):
+def in_memory_cnx(config, login, **kwargs):
"""usefull method for testing and scripting to get a dbapi.Connection
object connected to an in-memory repository instance
"""
@@ -170,11 +172,11 @@
repo = get_repository('inmemory', config=config, vreg=vreg)
# connection to the CubicWeb repository
cnxprops = ConnectionProperties('inmemory')
- cnx = repo_connect(repo, login, password, cnxprops=cnxprops)
+ cnx = repo_connect(repo, login, cnxprops=cnxprops, **kwargs)
return repo, cnx
-class DBAPIRequest(RequestSessionMixIn):
+class DBAPIRequest(RequestSessionBase):
def __init__(self, vreg, cnx=None):
super(DBAPIRequest, self).__init__(vreg)
--- a/debian.hardy/rules Mon Feb 08 13:33:09 2010 +0100
+++ b/debian.hardy/rules Mon Feb 08 13:35:07 2010 +0100
@@ -33,7 +33,6 @@
dh_clean
dh_installdirs
- #python setup.py install_lib --no-compile --install-dir=debian/cubicweb-common/usr/lib/python2.4/site-packages/
python setup.py -q install --no-compile --prefix=debian/tmp/usr
# Put all the python library and data in cubicweb-common
@@ -51,7 +50,6 @@
rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
rm -rf debian/cubicweb-twisted/usr/lib/${PY_VERSION}/site-packages/cubicweb/etwist/test
rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/ext/test
- rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/entities/test
# cubes directory must be managed as a valid python module
--- a/debian/control Mon Feb 08 13:33:09 2010 +0100
+++ b/debian/control Mon Feb 08 13:35:07 2010 +0100
@@ -77,7 +77,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.46.0), python-yams (>= 0.25.0), python-rql (>= 0.22.3), python-lxml
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.47.0), python-yams (>= 0.27.0), python-rql (>= 0.24.0), python-lxml
Recommends: python-simpletal (>= 4.0)
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/debian/cubicweb-dev.install.in Mon Feb 08 13:33:09 2010 +0100
+++ b/debian/cubicweb-dev.install.in Mon Feb 08 13:35:07 2010 +0100
@@ -6,6 +6,7 @@
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/test usr/lib/PY_VERSION/site-packages/cubicweb/ext/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/test usr/lib/PY_VERSION/site-packages/cubicweb/server/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/hooks/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/web/test usr/lib/PY_VERSION/site-packages/cubicweb/web/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/etwist/test usr/lib/PY_VERSION/site-packages/cubicweb/etwist/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/goa/test usr/lib/PY_VERSION/site-packages/cubicweb/goa/
--- a/debian/cubicweb-server.install.in Mon Feb 08 13:33:09 2010 +0100
+++ b/debian/cubicweb-server.install.in Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,5 @@
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/ usr/lib/PY_VERSION/site-packages/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/hooks/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/schemas/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/share/cubicweb/migration/ usr/share/cubicweb/
--- a/debian/rules Mon Feb 08 13:33:09 2010 +0100
+++ b/debian/rules Mon Feb 08 13:35:07 2010 +0100
@@ -48,6 +48,7 @@
# Remove unittests directory (should be available in cubicweb-dev only)
rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
+ rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/hooks/test
rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
rm -rf debian/cubicweb-twisted/usr/lib/${PY_VERSION}/site-packages/cubicweb/etwist/test
--- a/devtools/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,20 +13,55 @@
from os.path import (abspath, join, exists, basename, dirname, normpath, split,
isfile, isabs)
-from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError
-from cubicweb.utils import strptime
-from cubicweb.toolsutils import read_config
-from cubicweb.cwconfig import CubicWebConfiguration, merge_options
+from logilab.common.date import strptime
+from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError, schema, cwconfig
from cubicweb.server.serverconfig import ServerConfiguration
from cubicweb.etwist.twconfig import TwistedConfiguration
+cwconfig.CubicWebConfiguration.cls_adjust_sys_path()
+
+# db auto-population configuration #############################################
+
+SYSTEM_ENTITIES = schema.SCHEMA_TYPES | set((
+ 'CWGroup', 'CWUser', 'CWProperty',
+ 'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition',
+ 'TrInfo', 'SubWorkflowExitPoint',
+ ))
+
+SYSTEM_RELATIONS = schema.META_RTYPES | set((
+ # workflow related
+ 'workflow_of', 'state_of', 'transition_of', 'initial_state', 'allowed_transition',
+ 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
+ 'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
+ # cwproperty
+ 'for_user',
+ # schema definition
+ 'specializes',
+ 'relation_type', 'from_entity', 'to_entity',
+ 'constrained_by', 'cstrtype', 'widget',
+ 'read_permission', 'update_permission', 'delete_permission', 'add_permission',
+ # permission
+ 'in_group', 'require_group', 'require_permission',
+ # deducted from other relations
+ 'primary_email',
+ ))
+
+# content validation configuration #############################################
+
# validators are used to validate (XML, DTD, whatever) view's content
# validators availables are :
# 'dtd' : validates XML + declared DTD
# 'xml' : guarantees XML is well formed
# None : do not try to validate anything
+
+# {'vid': validator}
VIEW_VALIDATORS = {}
+
+
+# cubicweb test configuration ##################################################
+
BASE_URL = 'http://testing.fr/cubicweb/'
+
DEFAULT_SOURCES = {'system': {'adapter' : 'native',
'db-encoding' : 'UTF-8', #'ISO-8859-1',
'db-user' : u'admin',
@@ -40,13 +75,14 @@
},
}
+
class TestServerConfiguration(ServerConfiguration):
mode = 'test'
set_language = False
read_instance_schema = False
bootstrap_schema = False
init_repository = True
- options = merge_options(ServerConfiguration.options + (
+ options = cwconfig.merge_options(ServerConfiguration.options + (
('anonymous-user',
{'type' : 'string',
'default': None,
@@ -66,10 +102,11 @@
def __init__(self, appid, log_threshold=logging.CRITICAL+10):
ServerConfiguration.__init__(self, appid)
- self.global_set_option('log-file', None)
self.init_log(log_threshold, force=True)
# need this, usually triggered by cubicweb-ctl
self.load_cwctl_plugins()
+ self.global_set_option('anonymous-user', 'anon')
+ self.global_set_option('anonymous-password', 'anon')
anonymous_user = TwistedConfiguration.anonymous_user.im_func
@@ -81,6 +118,11 @@
return abspath('..')
appdatahome = apphome
+ def load_configuration(self):
+ super(TestServerConfiguration, self).load_configuration()
+ self.global_set_option('anonymous-user', 'anon')
+ self.global_set_option('anonymous-password', 'anon')
+
def main_config_file(self):
"""return instance's control configuration file"""
return join(self.apphome, '%s.conf' % self.name)
@@ -118,30 +160,11 @@
sources = DEFAULT_SOURCES
return sources
- def load_defaults(self):
- super(TestServerConfiguration, self).load_defaults()
- # note: don't call global set option here, OptionManager may not yet be initialized
- # add anonymous user
- self.set_option('anonymous-user', 'anon')
- self.set_option('anonymous-password', 'anon')
- # uncomment the line below if you want rql queries to be logged
- #self.set_option('query-log-file', '/tmp/test_rql_log.' + `os.getpid()`)
- self.set_option('sender-name', 'cubicweb-test')
- self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
- try:
- send_to = '%s@logilab.fr' % os.getlogin()
- except (OSError, AttributeError):
- send_to = '%s@logilab.fr' % (os.environ.get('USER')
- or os.environ.get('USERNAME')
- or os.environ.get('LOGNAME'))
- self.set_option('sender-addr', send_to)
- self.set_option('default-dest-addrs', send_to)
- self.set_option('base-url', BASE_URL)
-
class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
repo_method = 'inmemory'
- options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options)
+ options = cwconfig.merge_options(TestServerConfiguration.options
+ + TwistedConfiguration.options)
cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path
cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path
@@ -163,98 +186,84 @@
BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
self.init_repository = sourcefile is None
self.sourcefile = sourcefile
- import re
- self.global_set_option('embed-allowed', re.compile('.*'))
-class RealDatabaseConfiguration(ApptestConfiguration):
- init_repository = False
- sourcesdef = {'system': {'adapter' : 'native',
- 'db-encoding' : 'UTF-8', #'ISO-8859-1',
- 'db-user' : u'admin',
- 'db-password' : 'gingkow',
- 'db-name' : 'seotest',
- 'db-driver' : 'postgres',
- 'db-host' : None,
- },
- 'admin' : {'login': u'admin',
- 'password': u'gingkow',
- },
- }
+# test database handling #######################################################
- def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
- ApptestConfiguration.__init__(self, appid)
- self.init_repository = False
+def init_test_database(config=None, configdir='data'):
+ """init a test database for a specific driver"""
+ from cubicweb.dbapi import in_memory_cnx
+ config = config or TestServerConfiguration(configdir)
+ sources = config.sources()
+ driver = sources['system']['db-driver']
+ if driver == 'sqlite':
+ init_test_database_sqlite(config)
+ elif driver == 'postgres':
+ init_test_database_postgres(config)
+ else:
+ raise ValueError('no initialization function for driver %r' % driver)
+ config._cubes = None # avoid assertion error
+ repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
+ password=sources['admin']['password'] or 'xxx')
+ if driver == 'sqlite':
+ install_sqlite_patch(repo.querier)
+ return repo, cnx
- def sources(self):
- """
- By default, we run tests with the sqlite DB backend.
- One may use its own configuration by just creating a
- 'sources' file in the test directory from wich tests are
- launched.
- """
- self._sources = self.sourcesdef
- return self._sources
+def reset_test_database(config):
+ """init a test database for a specific driver"""
+ driver = config.sources()['system']['db-driver']
+ if driver == 'sqlite':
+ reset_test_database_sqlite(config)
+ else:
+ raise ValueError('no reset function for driver %r' % driver)
-def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None):
- """convenience function that builds a real-db configuration class"""
- sourcesdef = {'system': {'adapter' : 'native',
- 'db-encoding' : 'UTF-8', #'ISO-8859-1',
- 'db-user' : dbuser,
- 'db-password' : dbpassword,
- 'db-name' : dbname,
- 'db-driver' : 'postgres',
- 'db-host' : dbhost,
- },
- 'admin' : {'login': adminuser,
- 'password': adminpassword,
- },
- }
- return type('MyRealDBConfig', (RealDatabaseConfiguration,),
- {'sourcesdef': sourcesdef})
+### postgres test database handling ############################################
-def loadconfig(filename):
- """convenience function that builds a real-db configuration class
- from a file
- """
- return type('MyRealDBConfig', (RealDatabaseConfiguration,),
- {'sourcesdef': read_config(filename)})
+def init_test_database_postgres(config):
+ """initialize a fresh sqlite databse used for testing purpose"""
+ if config.init_repository:
+ from cubicweb.server import init_repository
+ init_repository(config, interactive=False, drop=True)
-class LivetestConfiguration(BaseApptestConfiguration):
- init_repository = False
+### sqlite test database handling ##############################################
+
+def cleanup_sqlite(dbfile, removetemplate=False):
+ try:
+ os.remove(dbfile)
+ os.remove('%s-journal' % dbfile)
+ except OSError:
+ pass
+ if removetemplate:
+ try:
+ os.remove('%s-template' % dbfile)
+ except OSError:
+ pass
- def __init__(self, cube=None, sourcefile=None, pyro_name=None,
- log_threshold=logging.CRITICAL):
- TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold)
- self.appid = pyro_name or cube
- # don't change this, else some symlink problems may arise in some
- # environment (e.g. mine (syt) ;o)
- # XXX I'm afraid this test will prevent to run test from a production
- # environment
- self._sources = None
- # instance cube test
- if cube is not None:
- self.apphome = self.cube_dir(cube)
- elif 'web' in os.getcwd().split(os.sep):
- # web test
- self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
- else:
- # cube test
- self.apphome = abspath('..')
- self.sourcefile = sourcefile
- self.global_set_option('realm', '')
- self.use_pyro = pyro_name is not None
+def reset_test_database_sqlite(config):
+ import shutil
+ dbfile = config.sources()['system']['db-name']
+ cleanup_sqlite(dbfile)
+ template = '%s-template' % dbfile
+ if exists(template):
+ shutil.copy(template, dbfile)
+ return True
+ return False
- def pyro_enabled(self):
- if self.use_pyro:
- return True
- else:
- return False
+def init_test_database_sqlite(config):
+ """initialize a fresh sqlite databse used for testing purpose"""
+ # remove database file if it exists
+ dbfile = config.sources()['system']['db-name']
+ if not reset_test_database_sqlite(config):
+ # initialize the database
+ import shutil
+ from cubicweb.server import init_repository
+ init_repository(config, interactive=False)
+ dbfile = config.sources()['system']['db-name']
+ shutil.copy(dbfile, '%s-template' % dbfile)
-CubicWebConfiguration.cls_adjust_sys_path()
def install_sqlite_patch(querier):
"""This patch hotfixes the following sqlite bug :
@@ -293,58 +302,3 @@
return new_execute
querier.__class__.execute = wrap_execute(querier.__class__.execute)
querier.__class__._devtools_sqlite_patched = True
-
-def init_test_database(driver='sqlite', configdir='data', config=None,
- vreg=None):
- """init a test database for a specific driver"""
- from cubicweb.dbapi import in_memory_cnx
- if vreg and not config:
- config = vreg.config
- config = config or TestServerConfiguration(configdir)
- source = config.sources()
- if driver == 'sqlite':
- init_test_database_sqlite(config, source)
- elif driver == 'postgres':
- init_test_database_postgres(config, source)
- else:
- raise ValueError('no initialization function for driver %r' % driver)
- config._cubes = None # avoid assertion error
- repo, cnx = in_memory_cnx(vreg or config, unicode(source['admin']['login']),
- source['admin']['password'] or 'xxx')
- if driver == 'sqlite':
- install_sqlite_patch(repo.querier)
- return repo, cnx
-
-def init_test_database_postgres(config, source, vreg=None):
- """initialize a fresh sqlite databse used for testing purpose"""
- if config.init_repository:
- from cubicweb.server import init_repository
- init_repository(config, interactive=False, drop=True, vreg=vreg)
-
-def cleanup_sqlite(dbfile, removetemplate=False):
- try:
- os.remove(dbfile)
- os.remove('%s-journal' % dbfile)
- except OSError:
- pass
- if removetemplate:
- try:
- os.remove('%s-template' % dbfile)
- except OSError:
- pass
-
-def init_test_database_sqlite(config, source, vreg=None):
- """initialize a fresh sqlite databse used for testing purpose"""
- import shutil
- # remove database file if it exists (actually I know driver == 'sqlite' :)
- dbfile = source['system']['db-name']
- source['system']['db-name'] = os.path.abspath(dbfile)
- cleanup_sqlite(dbfile)
- template = '%s-template' % dbfile
- if exists(template):
- shutil.copy(template, dbfile)
- else:
- # initialize the database
- from cubicweb.server import init_repository
- init_repository(config, interactive=False, vreg=vreg)
- shutil.copy(dbfile, template)
--- a/devtools/_apptest.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-"""Hidden internals for the devtools.apptest module
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import sys, traceback
-
-from logilab.common.pytest import pause_tracing, resume_tracing
-
-import yams.schema
-
-from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
-from cubicweb.cwvreg import CubicWebVRegistry
-
-from cubicweb.web.application import CubicWebPublisher
-from cubicweb.web import Redirect
-
-from cubicweb.devtools import ApptestConfiguration, init_test_database
-from cubicweb.devtools.fake import FakeRequest
-
-SYSTEM_ENTITIES = ('CWGroup', 'CWUser',
- 'CWAttribute', 'CWRelation',
- 'CWConstraint', 'CWConstraintType', 'CWProperty',
- 'CWEType', 'CWRType',
- 'Workflow', 'State', 'BaseTransition', 'Transition', 'WorkflowTransition', 'TrInfo', 'SubWorkflowExitPoint',
- 'RQLExpression',
- )
-SYSTEM_RELATIONS = (
- # virtual relation
- 'identity',
- # metadata
- 'is', 'is_instance_of', 'owned_by', 'created_by', 'specializes',
- # workflow related
- 'workflow_of', 'state_of', 'transition_of', 'initial_state', 'allowed_transition',
- 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
- 'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
- # permission
- 'in_group', 'require_group', 'require_permission',
- 'read_permission', 'update_permission', 'delete_permission', 'add_permission',
- # eproperty
- 'for_user',
- # schema definition
- 'relation_type', 'from_entity', 'to_entity',
- 'constrained_by', 'cstrtype', 'widget',
- # deducted from other relations
- 'primary_email',
- )
-
-def unprotected_entities(app_schema, strict=False):
- """returned a Set of each non final entity type, excluding CWGroup, and CWUser...
- """
- if strict:
- protected_entities = yams.schema.BASE_TYPES
- else:
- protected_entities = yams.schema.BASE_TYPES.union(set(SYSTEM_ENTITIES))
- entities = set(app_schema.entities())
- return entities - protected_entities
-
-
-def ignore_relations(*relations):
- global SYSTEM_RELATIONS
- SYSTEM_RELATIONS += relations
-
-
-class TestEnvironment(object):
- """TestEnvironment defines a context (e.g. a config + a given connection) in
- which the tests are executed
- """
-
- def __init__(self, appid, reporter=None, verbose=False,
- configcls=ApptestConfiguration, requestcls=FakeRequest):
- config = configcls(appid)
- self.requestcls = requestcls
- self.cnx = None
- config.db_perms = False
- source = config.sources()['system']
- if verbose:
- print "init test database ..."
- self.vreg = vreg = CubicWebVRegistry(config)
- self.admlogin = source['db-user']
- # restore database <=> init database
- self.restore_database()
- if verbose:
- print "init done"
- config.repository = lambda x=None: self.repo
- self.app = CubicWebPublisher(config, vreg=vreg)
- self.verbose = verbose
- schema = self.vreg.schema
- # else we may run into problems since email address are ususally share in app tests
- # XXX should not be necessary anymore
- schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False)
- self.deletable_entities = unprotected_entities(schema)
-
- def restore_database(self):
- """called by unittests' tearDown to restore the original database
- """
- try:
- pause_tracing()
- if self.cnx:
- self.cnx.close()
- source = self.vreg.config.sources()['system']
- self.repo, self.cnx = init_test_database(driver=source['db-driver'],
- vreg=self.vreg)
- self._orig_cnx = self.cnx
- resume_tracing()
- except:
- resume_tracing()
- traceback.print_exc()
- sys.exit(1)
- # XXX cnx decoration is usually done by the repository authentication manager,
- # necessary in authentication tests
- self.cnx.vreg = self.vreg
- self.cnx.login = source['db-user']
- self.cnx.password = source['db-password']
-
-
- def create_user(self, login, groups=('users',), req=None):
- req = req or self.create_request()
- cursor = self._orig_cnx.cursor(req)
- rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
- {'login': unicode(login), 'passwd': login.encode('utf8')})
- user = rset.get_entity(0, 0)
- cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
- % ','.join(repr(g) for g in groups),
- {'x': user.eid}, 'x')
- user.clear_related_cache('in_group', 'subject')
- self._orig_cnx.commit()
- return user
-
- def login(self, login, password=None):
- if login == self.admlogin:
- self.restore_connection()
- else:
- self.cnx = repo_connect(self.repo, unicode(login),
- password or str(login),
- ConnectionProperties('inmemory'))
- if login == self.vreg.config.anonymous_user()[0]:
- self.cnx.anonymous_connection = True
- return self.cnx
-
- def restore_connection(self):
- if not self.cnx is self._orig_cnx:
- try:
- self.cnx.close()
- except ProgrammingError:
- pass # already closed
- self.cnx = self._orig_cnx
-
- ############################################################################
-
- def execute(self, rql, args=None, eidkey=None, req=None):
- """executes <rql>, builds a resultset, and returns a couple (rset, req)
- where req is a FakeRequest
- """
- req = req or self.create_request(rql=rql)
- return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
-
- def create_request(self, rql=None, **kwargs):
- """executes <rql>, builds a resultset, and returns a
- couple (rset, req) where req is a FakeRequest
- """
- if rql:
- kwargs['rql'] = rql
- req = self.requestcls(self.vreg, form=kwargs)
- req.set_connection(self.cnx)
- return req
-
- def get_rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
- """executes <rql>, builds a resultset, and returns a
- couple (rset, req) where req is a FakeRequest
- """
- return (self.execute(rql, args, eidkey),
- self.create_request(rql=rql, **optional_args or {}))
-
-
-class ExistingTestEnvironment(TestEnvironment):
-
- def __init__(self, appid, sourcefile, verbose=False):
- config = ApptestConfiguration(appid, sourcefile=sourcefile)
- if verbose:
- print "init test database ..."
- source = config.sources()['system']
- self.vreg = CubicWebVRegistry(config)
- self.cnx = init_test_database(driver=source['db-driver'],
- vreg=self.vreg)[1]
- if verbose:
- print "init done"
- self.app = CubicWebPublisher(config, vreg=self.vreg)
- self.verbose = verbose
- # this is done when the publisher is opening a connection
- self.cnx.vreg = self.vreg
-
- def setup(self, config=None):
- """config is passed by TestSuite but is ignored in this environment"""
- cursor = self.cnx.cursor()
- self.last_eid = cursor.execute('Any X WHERE X creation_date D ORDERBY D DESC LIMIT 1').rows[0][0]
-
- def cleanup(self):
- """cancel inserted elements during tests"""
- cursor = self.cnx.cursor()
- cursor.execute('DELETE Any X WHERE X eid > %(x)s', {'x' : self.last_eid}, eid_key='x')
- print "cleaning done"
- self.cnx.commit()
--- a/devtools/apptest.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,522 +0,0 @@
-"""This module provides misc utilities to test instances
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from copy import deepcopy
-
-import simplejson
-
-from logilab.common.testlib import TestCase
-from logilab.common.pytest import nocoverage
-from logilab.common.umessage import message_from_string
-
-from logilab.common.deprecation import deprecated
-
-from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
-from cubicweb.devtools._apptest import TestEnvironment
-from cubicweb.devtools.fake import FakeRequest
-
-from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
-
-
-MAILBOX = []
-class Email:
- def __init__(self, recipients, msg):
- self.recipients = recipients
- self.msg = msg
-
- @property
- def message(self):
- return message_from_string(self.msg)
-
- @property
- def subject(self):
- return self.message.get('Subject')
-
- @property
- def content(self):
- return self.message.get_payload(decode=True)
-
- def __repr__(self):
- return '<Email to %s with subject %s>' % (','.join(self.recipients),
- self.message.get('Subject'))
-
-class MockSMTP:
- def __init__(self, server, port):
- pass
- def close(self):
- pass
- def sendmail(self, helo_addr, recipients, msg):
- MAILBOX.append(Email(recipients, msg))
-
-from cubicweb import cwconfig
-cwconfig.SMTP = MockSMTP
-
-
-def get_versions(self, checkversions=False):
- """return the a dictionary containing cubes used by this instance
- as key with their version as value, including cubicweb version. This is a
- public method, not requiring a session id.
-
- replace Repository.get_versions by this method if you don't want versions
- checking
- """
- vcconf = {'cubicweb': self.config.cubicweb_version()}
- self.config.bootstrap_cubes()
- for pk in self.config.cubes():
- version = self.config.cube_version(pk)
- vcconf[pk] = version
- self.config._cubes = None
- return vcconf
-
-
-@property
-def late_binding_env(self):
- """builds TestEnvironment as late as possible"""
- if not hasattr(self, '_env'):
- self.__class__._env = TestEnvironment('data', configcls=self.configcls,
- requestcls=self.requestcls)
- return self._env
-
-
-class autoenv(type):
- """automatically set environment on EnvBasedTC subclasses if necessary
- """
- def __new__(mcs, name, bases, classdict):
- env = classdict.get('env')
- # try to find env in one of the base classes
- if env is None:
- for base in bases:
- env = getattr(base, 'env', None)
- if env is not None:
- classdict['env'] = env
- break
- if not classdict.get('__abstract__') and not classdict.get('env'):
- classdict['env'] = late_binding_env
- return super(autoenv, mcs).__new__(mcs, name, bases, classdict)
-
-
-class EnvBasedTC(TestCase):
- """abstract class for test using an apptest environment
- """
- __metaclass__ = autoenv
- __abstract__ = True
- env = None
- configcls = ApptestConfiguration
- requestcls = FakeRequest
-
- # user / session management ###############################################
-
- def user(self, req=None):
- if req is None:
- req = self.env.create_request()
- return self.env.cnx.user(req)
- else:
- return req.user
-
- def create_user(self, *args, **kwargs):
- return self.env.create_user(*args, **kwargs)
-
- def login(self, login, password=None):
- return self.env.login(login, password)
-
- def restore_connection(self):
- self.env.restore_connection()
-
- # db api ##################################################################
-
- @nocoverage
- def cursor(self, req=None):
- return self.env.cnx.cursor(req or self.request())
-
- @nocoverage
- def execute(self, *args, **kwargs):
- return self.env.execute(*args, **kwargs)
-
- @nocoverage
- def commit(self):
- self.env.cnx.commit()
-
- @nocoverage
- def rollback(self):
- try:
- self.env.cnx.rollback()
- except ProgrammingError:
- pass
-
- # other utilities #########################################################
- def set_debug(self, debugmode):
- from cubicweb.server import set_debug
- set_debug(debugmode)
-
- @property
- def config(self):
- return self.vreg.config
-
- def session(self):
- """return current server side session (using default manager account)"""
- return self.env.repo._sessions[self.env.cnx.sessionid]
-
- def request(self, *args, **kwargs):
- """return a web interface request"""
- return self.env.create_request(*args, **kwargs)
-
- @nocoverage
- def rset_and_req(self, *args, **kwargs):
- return self.env.get_rset_and_req(*args, **kwargs)
-
- def entity(self, rql, args=None, eidkey=None, req=None):
- return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
-
- def etype_instance(self, etype, req=None):
- req = req or self.request()
- e = self.env.vreg['etypes'].etype_class(etype)(req)
- e.eid = None
- return e
-
- def add_entity(self, etype, **kwargs):
- rql = ['INSERT %s X' % etype]
-
- # dict for replacement in RQL Request
- rql_args = {}
-
- if kwargs: #
- rql.append(':')
- # dict to define new entities variables
- entities = {}
-
- # assignement part of the request
- sub_rql = []
- for key, value in kwargs.iteritems():
- # entities
- if hasattr(value, 'eid'):
- new_value = "%s__" % key.upper()
-
- entities[new_value] = value.eid
- rql_args[new_value] = value.eid
-
- sub_rql.append("X %s %s" % (key, new_value))
- # final attributes
- else:
- sub_rql.append('X %s %%(%s)s' % (key, key))
- rql_args[key] = value
- rql.append(', '.join(sub_rql))
-
-
- if entities:
- rql.append('WHERE')
- # WHERE part of the request (to link entity to they eid)
- sub_rql = []
- for key, value in entities.iteritems():
- sub_rql.append("%s eid %%(%s)s" % (key, key))
- rql.append(', '.join(sub_rql))
-
- rql = ' '.join(rql)
- rset = self.execute(rql, rql_args)
- return rset.get_entity(0, 0)
-
- def set_option(self, optname, value):
- self.vreg.config.global_set_option(optname, value)
-
- def pviews(self, req, rset):
- return sorted((a.id, a.__class__) for a in self.vreg['views'].possible_views(req, rset=rset))
-
- def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions', 'footer')):
- return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset)
- if a.category not in skipcategories]
-
- def action_submenu(self, req, rset, id):
- return self._test_action(self.vreg['actions'].select(id, req, rset=rset))
-
- def _test_action(self, action):
- class fake_menu(list):
- @property
- def items(self):
- return self
- class fake_box(object):
- def mk_action(self, label, url, **kwargs):
- return (label, url)
- def box_action(self, action, **kwargs):
- return (action.title, action.url())
- submenu = fake_menu()
- action.fill_menu(fake_box(), submenu)
- return submenu
-
- def pactions_by_cats(self, req, rset, categories=('addrelated',)):
- return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset)
- if a.category in categories]
-
- paddrelactions = deprecated()(pactions_by_cats)
-
- def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
- res = {}
- for a in self.vreg['actions'].possible_vobjects(req, rset=rset):
- if a.category not in skipcategories:
- res.setdefault(a.category, []).append(a.__class__)
- return res
-
-
- def remote_call(self, fname, *args):
- """remote call simulation"""
- dump = simplejson.dumps
- args = [dump(arg) for arg in args]
- req = self.request(fname=fname, pageid='123', arg=args)
- ctrl = self.vreg['controllers'].select('json', req)
- return ctrl.publish(), req
-
- # default test setup and teardown #########################################
-
- def setup_database(self):
- pass
-
- def setUp(self):
- self.restore_connection()
- session = self.session()
- #self.maxeid = self.execute('Any MAX(X)')
- session.set_pool()
- self.maxeid = session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
- self.app = self.env.app
- self.vreg = self.env.app.vreg
- self.schema = self.vreg.schema
- self.vreg.config.mode = 'test'
- # set default-dest-addrs to a dumb email address to avoid mailbox or
- # mail queue pollution
- self.set_option('default-dest-addrs', ['whatever'])
- self.setup_database()
- self.commit()
- MAILBOX[:] = [] # reset mailbox
-
- @nocoverage
- def tearDown(self):
- self.rollback()
- # self.env.restore_database()
- self.env.restore_connection()
- self.session().unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
- self.commit()
-
- # global resources accessors ###############################################
-
-# XXX
-try:
- from cubicweb.web import Redirect
- from urllib import unquote
-except ImportError:
- pass # cubicweb-web not installed
-else:
- class ControllerTC(EnvBasedTC):
- def setUp(self):
- super(ControllerTC, self).setUp()
- self.req = self.request()
- self.ctrl = self.vreg['controllers'].select('edit', self.req)
-
- def publish(self, req):
- assert req is self.ctrl.req
- try:
- result = self.ctrl.publish()
- req.cnx.commit()
- except Redirect:
- req.cnx.commit()
- raise
- return result
-
- def expect_redirect_publish(self, req=None):
- if req is not None:
- self.ctrl = self.vreg['controllers'].select('edit', req)
- else:
- req = self.req
- try:
- res = self.publish(req)
- except Redirect, ex:
- try:
- path, params = ex.location.split('?', 1)
- except:
- path, params = ex.location, ""
- req._url = path
- cleanup = lambda p: (p[0], unquote(p[1]))
- params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
- return req.relative_path(False), params # path.rsplit('/', 1)[-1], params
- else:
- self.fail('expected a Redirect exception')
-
-
-def make_late_binding_repo_property(attrname):
- @property
- def late_binding(self):
- """builds cnx as late as possible"""
- if not hasattr(self, attrname):
- # sets explicit test mode here to avoid autoreload
- from cubicweb.cwconfig import CubicWebConfiguration
- CubicWebConfiguration.mode = 'test'
- cls = self.__class__
- config = self.repo_config or TestServerConfiguration('data')
- cls._repo, cls._cnx = init_test_database('sqlite', config=config)
- return getattr(self, attrname)
- return late_binding
-
-
-class autorepo(type):
- """automatically set repository on RepositoryBasedTC subclasses if necessary
- """
- def __new__(mcs, name, bases, classdict):
- repo = classdict.get('repo')
- # try to find repo in one of the base classes
- if repo is None:
- for base in bases:
- repo = getattr(base, 'repo', None)
- if repo is not None:
- classdict['repo'] = repo
- break
- if name != 'RepositoryBasedTC' and not classdict.get('repo'):
- classdict['repo'] = make_late_binding_repo_property('_repo')
- classdict['cnx'] = make_late_binding_repo_property('_cnx')
- return super(autorepo, mcs).__new__(mcs, name, bases, classdict)
-
-
-class RepositoryBasedTC(TestCase):
- """abstract class for test using direct repository connections
- """
- __metaclass__ = autorepo
- repo_config = None # set a particular config instance if necessary
-
- # user / session management ###############################################
-
- def create_user(self, user, groups=('users',), password=None, commit=True):
- if password is None:
- password = user
- eid = self.execute('INSERT CWUser X: X login %(x)s, X upassword %(p)s',
- {'x': unicode(user), 'p': password})[0][0]
- groups = ','.join(repr(group) for group in groups)
- self.execute('SET X in_group Y WHERE X eid %%(x)s, Y name IN (%s)' % groups,
- {'x': eid})
- if commit:
- self.commit()
- self.session.reset_pool()
- return eid
-
- def login(self, login, password=None):
- cnx = repo_connect(self.repo, unicode(login), password or login,
- ConnectionProperties('inmemory'))
- self.cnxs.append(cnx)
- return cnx
-
- def current_session(self):
- return self.repo._sessions[self.cnxs[-1].sessionid]
-
- def restore_connection(self):
- assert len(self.cnxs) == 1, self.cnxs
- cnx = self.cnxs.pop()
- try:
- cnx.close()
- except Exception, ex:
- print "exception occured while closing connection", ex
-
- # db api ##################################################################
-
- def execute(self, rql, args=None, eid_key=None):
- assert self.session.id == self.cnxid
- rset = self.__execute(self.cnxid, rql, args, eid_key)
- rset.vreg = self.vreg
- rset.req = self.session
- # call to set_pool is necessary to avoid pb when using
- # instance entities for convenience
- self.session.set_pool()
- return rset
-
- def commit(self):
- self.__commit(self.cnxid)
- self.session.set_pool()
-
- def rollback(self):
- self.__rollback(self.cnxid)
- self.session.set_pool()
-
- def close(self):
- self.__close(self.cnxid)
-
- # other utilities #########################################################
-
- def set_debug(self, debugmode):
- from cubicweb.server import set_debug
- set_debug(debugmode)
-
- def set_option(self, optname, value):
- self.vreg.config.global_set_option(optname, value)
-
- def add_entity(self, etype, **kwargs):
- restrictions = ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
- rql = 'INSERT %s X' % etype
- if kwargs:
- rql += ': %s' % ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
- rset = self.execute(rql, kwargs)
- return rset.get_entity(0, 0)
-
- def default_user_password(self):
- config = self.repo.config #TestConfiguration('data')
- user = unicode(config.sources()['system']['db-user'])
- passwd = config.sources()['system']['db-password']
- return user, passwd
-
- def close_connections(self):
- for cnx in self.cnxs:
- try:
- cnx.rollback()
- cnx.close()
- except:
- continue
- self.cnxs = []
-
- pactions = EnvBasedTC.pactions.im_func
- pactionsdict = EnvBasedTC.pactionsdict.im_func
-
- # default test setup and teardown #########################################
-
- def _prepare(self):
- MAILBOX[:] = [] # reset mailbox
- if hasattr(self, 'cnxid'):
- return
- repo = self.repo
- self.__execute = repo.execute
- self.__commit = repo.commit
- self.__rollback = repo.rollback
- self.__close = repo.close
- self.cnxid = self.cnx.sessionid
- self.session = repo._sessions[self.cnxid]
- self.cnxs = []
- # reset caches, they may introduce bugs among tests
- repo._type_source_cache = {}
- repo._extid_cache = {}
- repo.querier._rql_cache = {}
- for source in repo.sources:
- source.reset_caches()
- for s in repo.sources:
- if hasattr(s, '_cache'):
- s._cache = {}
-
- @property
- def config(self):
- return self.repo.config
-
- @property
- def vreg(self):
- return self.repo.vreg
-
- @property
- def schema(self):
- return self.repo.schema
-
- def setUp(self):
- self._prepare()
- self.session.set_pool()
- self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
-
- def tearDown(self):
- self.close_connections()
- self.rollback()
- self.session.unsafe_execute('DELETE Any X WHERE X eid > %(x)s', {'x': self.maxeid})
- self.commit()
-
--- a/devtools/dataimport.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/dataimport.py Mon Feb 08 13:35:07 2010 +0100
@@ -51,16 +51,52 @@
"""
__docformat__ = "restructuredtext en"
-import sys, csv, traceback
+import sys
+import csv
+import traceback
+import os.path as osp
+from StringIO import StringIO
from logilab.common import shellutils
+from logilab.common.deprecation import deprecated
-def utf8csvreader(file, encoding='utf-8', separator=',', quote='"'):
- """A csv reader that accepts files with any encoding and outputs
- unicode strings."""
- for row in csv.reader(file, delimiter=separator, quotechar=quote):
+def ucsvreader_pb(filepath, encoding='utf-8', separator=',', quote='"',
+ skipfirst=False, withpb=True):
+ """same as ucsvreader but a progress bar is displayed as we iter on rows"""
+ if not osp.exists(filepath):
+ raise Exception("file doesn't exists: %s" % filepath)
+ rowcount = int(shellutils.Execute('wc -l "%s"' % filepath).out.strip().split()[0])
+ if skipfirst:
+ rowcount -= 1
+ if withpb:
+ pb = shellutils.ProgressBar(rowcount, 50)
+ for urow in ucsvreader(file(filepath), encoding, separator, quote, skipfirst):
+ yield urow
+ if withpb:
+ pb.update()
+ print ' %s rows imported' % rowcount
+
+def ucsvreader(stream, encoding='utf-8', separator=',', quote='"',
+ skipfirst=False):
+ """A csv reader that accepts files with any encoding and outputs unicode
+ strings
+ """
+ it = iter(csv.reader(stream, delimiter=separator, quotechar=quote))
+ if skipfirst:
+ it.next()
+ for row in it:
yield [item.decode(encoding) for item in row]
+utf8csvreader = deprecated('use ucsvreader instead')(ucsvreader)
+
+def commit_every(nbit, store, it):
+ for i, x in enumerate(it):
+ yield x
+ if nbit is not None and i % nbit:
+ store.checkpoint()
+ if nbit is not None:
+ store.checkpoint()
+
def lazytable(reader):
"""The first row is taken to be the header of the table and
used to output a dict for each row of data.
@@ -71,10 +107,56 @@
for row in reader:
yield dict(zip(header, row))
+def mk_entity(row, map):
+ """Return a dict made from sanitized mapped values.
+
+ >>> row = {'myname': u'dupont'}
+ >>> map = [('myname', u'name', (capitalize_if_unicase,))]
+ >>> mk_entity(row, map)
+ {'name': u'Dupont'}
+ """
+ res = {}
+ for src, dest, funcs in map:
+ res[dest] = row[src]
+ for func in funcs:
+ res[dest] = func(res[dest])
+ return res
+
+
+# user interactions ############################################################
+
def tell(msg):
print msg
-# base sanitizing functions #####
+def confirm(question):
+ """A confirm function that asks for yes/no/abort and exits on abort."""
+ answer = shellutils.ASK.ask(question, ('Y','n','abort'), 'Y')
+ if answer == 'abort':
+ sys.exit(1)
+ return answer == 'Y'
+
+
+class catch_error(object):
+ """Helper for @contextmanager decorator."""
+
+ def __init__(self, ctl, key='unexpected error', msg=None):
+ self.ctl = ctl
+ self.key = key
+ self.msg = msg
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ if type is not None:
+ if issubclass(type, (KeyboardInterrupt, SystemExit)):
+ return # re-raise
+ if self.ctl.catcherrors:
+ self.ctl.record_error(self.key, None, type, value, traceback)
+ return True # silent
+
+
+# base sanitizing functions ####################################################
def capitalize_if_unicase(txt):
if txt.isupper() or txt.islower():
@@ -99,30 +181,19 @@
def strip(txt):
return txt.strip()
-# base checks #####
+
+# base integrity checking functions ############################################
def check_doubles(buckets):
"""Extract the keys that have more than one item in their bucket."""
return [(key, len(value)) for key,value in buckets.items() if len(value) > 1]
-# make entity helper #####
-
-def mk_entity(row, map):
- """Return a dict made from sanitized mapped values.
+def check_doubles_not_none(buckets):
+ """Extract the keys that have more than one item in their bucket."""
+ return [(key, len(value)) for key,value in buckets.items() if key is not None and len(value) > 1]
- >>> row = {'myname': u'dupont'}
- >>> map = [('myname', u'name', (capitalize_if_unicase,))]
- >>> mk_entity(row, map)
- {'name': u'Dupont'}
- """
- res = {}
- for src, dest, funcs in map:
- res[dest] = row[src]
- for func in funcs:
- res[dest] = func(res[dest])
- return res
-# object stores
+# object stores #################################################################
class ObjectStore(object):
"""Store objects in memory for faster testing. Will not
@@ -181,27 +252,52 @@
if item[key] == value:
yield item
- def rql(self, query, args):
- if self._rql:
- return self._rql(query, args)
+ def checkpoint(self):
+ pass
- def checkpoint(self):
- if self._checkpoint:
- self._checkpoint()
class RQLObjectStore(ObjectStore):
"""ObjectStore that works with an actual RQL repository."""
+ _rql = None # bw compat
+
+ def __init__(self, session=None, checkpoint=None):
+ ObjectStore.__init__(self)
+ if session is not None:
+ if not hasattr(session, 'set_pool'):
+ # connection
+ cnx = session
+ session = session.request()
+ session.set_pool = lambda : None
+ checkpoint = checkpoint or cnx.commit
+ self.session = session
+ self.checkpoint = checkpoint or session.commit
+ elif checkpoint is not None:
+ self.checkpoint = checkpoint
+
+ def rql(self, *args):
+ if self._rql is not None:
+ return self._rql(*args)
+ self.session.set_pool()
+ return self.session.execute(*args)
+
+ def create_entity(self, *args, **kwargs):
+ self.session.set_pool()
+ entity = self.session.create_entity(*args, **kwargs)
+ self.eids[entity.eid] = entity
+ self.types.setdefault(args[0], []).append(entity.eid)
+ return entity
def _put(self, type, item):
query = ('INSERT %s X: ' % type) + ', '.join(['X %s %%(%s)s' % (key,key) for key in item])
return self.rql(query, item)[0][0]
def relate(self, eid_from, rtype, eid_to):
- query = 'SET X %s Y WHERE X eid %%(from)s, Y eid %%(to)s' % rtype
- self.rql(query, {'from': int(eid_from), 'to': int(eid_to)})
+ self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
+ {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
self.relations.add( (eid_from, rtype, eid_to) )
-# import controller #####
+
+# the import controller ########################################################
class CWImportController(object):
"""Controller of the data import process.
@@ -212,12 +308,17 @@
>>> ctl.run()
"""
- def __init__(self, store):
+ def __init__(self, store, askerror=False, catcherrors=None, tell=tell,
+ commitevery=50):
self.store = store
self.generators = None
self.data = {}
self.errors = None
- self.askerror = False
+ self.askerror = askerror
+ if catcherrors is None:
+ catcherrors = askerror
+ self.catcherrors = catcherrors
+ self.commitevery = commitevery # set to None to do a single commit
self._tell = tell
def check(self, type, key, value):
@@ -230,34 +331,47 @@
self.check(key, entity[key], None)
entity[key] = default
+ def record_error(self, key, msg=None, type=None, value=None, tb=None):
+ tmp = StringIO()
+ if type is None:
+ traceback.print_exc(file=tmp)
+ else:
+ traceback.print_exception(type, value, tb, file=tmp)
+ print tmp.getvalue()
+ # use a list to avoid counting a <nb lines> errors instead of one
+ errorlog = self.errors.setdefault(key, [])
+ if msg is None:
+ errorlog.append(tmp.getvalue().splitlines())
+ else:
+ errorlog.append( (msg, tmp.getvalue().splitlines()) )
+
def run(self):
self.errors = {}
for func, checks in self.generators:
self._checks = {}
- func_name = func.__name__[4:]
- question = 'Importation de %s' % func_name
- self.tell(question)
+ func_name = func.__name__[4:] # XXX
+ self.tell('Importing %s' % func_name)
try:
func(self)
except:
- import StringIO
- tmp = StringIO.StringIO()
- traceback.print_exc(file=tmp)
- print tmp.getvalue()
- self.errors[func_name] = ('Erreur lors de la transformation',
- tmp.getvalue().splitlines())
+ if self.catcherrors:
+ self.record_error(func_name, 'While calling %s' % func.__name__)
+ else:
+ raise
for key, func, title, help in checks:
buckets = self._checks.get(key)
if buckets:
err = func(buckets)
if err:
self.errors[title] = (help, err)
- self.store.checkpoint()
- errors = sum(len(err[1]) for err in self.errors.values())
- self.tell('Importation terminée. (%i objets, %i types, %i relations et %i erreurs).'
+ self.store.checkpoint()
+ self.tell('\nImport completed: %i entities (%i types), %i relations'
% (len(self.store.eids), len(self.store.types),
- len(self.store.relations), errors))
- if self.errors and self.askerror and confirm('Afficher les erreurs ?'):
+ len(self.store.relations)))
+ nberrors = sum(len(err[1]) for err in self.errors.values())
+ if nberrors:
+ print '%s errors' % nberrors
+ if self.errors and self.askerror and confirm('Display errors?'):
import pprint
pprint.pprint(self.errors)
@@ -270,9 +384,6 @@
def tell(self, msg):
self._tell(msg)
-def confirm(question):
- """A confirm function that asks for yes/no/abort and exits on abort."""
- answer = shellutils.ASK.ask(question, ('Y','n','abort'), 'Y')
- if answer == 'abort':
- sys.exit(1)
- return answer == 'Y'
+ def iter_and_commit(self, datakey):
+ """iter rows, triggering commit every self.commitevery iterations"""
+ return commit_every(self.commitevery, self.store, self.get_data(datakey))
--- a/devtools/devctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/devctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,36 +14,47 @@
from os.path import join, exists, abspath, basename, normpath, split, isdir
from copy import deepcopy
from warnings import warn
+from tempfile import NamedTemporaryFile
+from subprocess import Popen
from logilab.common import STD_BLACKLIST
from logilab.common.modutils import get_module_files
from logilab.common.textutils import splitstrip
from logilab.common.shellutils import ASK
-from logilab.common.clcommands import register_commands
+from logilab.common.clcommands import register_commands, pop_arg
+
+from yams import schema2dot
from cubicweb.__pkginfo__ import version as cubicwebversion
-from cubicweb import (CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage,
- underline_title)
+from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
+from cubicweb.toolsutils import Command, copy_skeleton, underline_title
from cubicweb.schema import CONSTRAINTS
-from cubicweb.toolsutils import Command, copy_skeleton
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration
+from yams import BASE_TYPES
+from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
+ WORKFLOW_TYPES, INTERNAL_TYPES)
-class DevCubeConfiguration(ServerConfiguration, WebConfiguration):
- """dummy config to get full library schema and entities"""
+
+class DevConfiguration(ServerConfiguration, WebConfiguration):
+ """dummy config to get full library schema and appobjects for
+ a cube or for cubicweb (without a home)
+ """
creating = True
- cubicweb_appobject_path = ServerConfiguration.cubicweb_appobject_path | WebConfiguration.cubicweb_appobject_path
- cube_appobject_path = ServerConfiguration.cube_appobject_path | WebConfiguration.cube_appobject_path
+ cleanup_interface_sobjects = False
- def __init__(self, cube):
- super(DevCubeConfiguration, self).__init__(cube)
- if cube is None:
- self._cubes = ()
+ cubicweb_appobject_path = (ServerConfiguration.cubicweb_appobject_path
+ | WebConfiguration.cubicweb_appobject_path)
+ cube_appobject_path = (ServerConfiguration.cube_appobject_path
+ | WebConfiguration.cube_appobject_path)
+
+ def __init__(self, *cubes):
+ super(DevConfiguration, self).__init__(cubes and cubes[0] or None)
+ if cubes:
+ self._cubes = self.reorder_cubes(
+ self.expand_cubes(cubes, with_recommends=True))
else:
- self._cubes = self.reorder_cubes(self.expand_cubes(self.my_cubes(cube)))
-
- def my_cubes(self, cube):
- return (cube,) + self.cube_dependencies(cube) + self.cube_recommends(cube)
+ self._cubes = ()
@property
def apphome(self):
@@ -54,16 +65,6 @@
pass
def load_configuration(self):
pass
-
-
-class DevDepConfiguration(DevCubeConfiguration):
- """configuration to use to generate cubicweb po files or to use as "library" configuration
- to filter out message ids from cubicweb and dependencies of a cube
- """
-
- def my_cubes(self, cube):
- return self.cube_dependencies(cube) + self.cube_recommends(cube)
-
def default_log_file(self):
return None
@@ -94,16 +95,16 @@
should be marked using '_' and extracted using xgettext
"""
from cubicweb.cwvreg import CubicWebVRegistry
- cube = cubedir and split(cubedir)[-1]
- libconfig = DevDepConfiguration(cube)
- libconfig.cleanup_interface_sobjects = False
- cleanup_sys_modules(libconfig)
if cubedir:
- config = DevCubeConfiguration(cube)
- config.cleanup_interface_sobjects = False
+ cube = split(cubedir)[-1]
+ config = DevConfiguration(cube)
+ depcubes = list(config._cubes)
+ depcubes.remove(cube)
+ libconfig = DevConfiguration(*depcubes)
else:
- config = libconfig
- libconfig = None
+ config = DevConfiguration()
+ cube = libconfig = None
+ cleanup_sys_modules(config)
schema = config.load_schema(remove_unused_rtypes=False)
vreg = CubicWebVRegistry(config)
# set_schema triggers objects registrations
@@ -113,7 +114,7 @@
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
- from cubicweb.common.i18n import add_msg
+ from cubicweb.i18n import add_msg
from cubicweb.web import uicfg
from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES
no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
@@ -125,19 +126,19 @@
if libconfig is not None:
from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects
libschema = libconfig.load_schema(remove_unused_rtypes=False)
- rinlined = deepcopy(uicfg.autoform_is_inlined)
+ afs = deepcopy(uicfg.autoform_section)
appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
clear_rtag_objects()
cleanup_sys_modules(libconfig)
libvreg = CubicWebVRegistry(libconfig)
libvreg.set_schema(libschema) # trigger objects registration
- librinlined = uicfg.autoform_is_inlined
+ libafs = uicfg.autoform_section
libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
# prefill vregdone set
list(_iter_vreg_objids(libvreg, vregdone))
else:
libschema = {}
- rinlined = uicfg.autoform_is_inlined
+ afs = uicfg.autoform_section
appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
for cstrtype in CONSTRAINTS:
add_msg(w, cstrtype)
@@ -156,15 +157,17 @@
if eschema.final:
continue
for rschema, targetschemas, role in eschema.relation_definitions(True):
+ if rschema.final:
+ continue
for tschema in targetschemas:
- if rinlined.etype_get(eschema, rschema, role, tschema) and \
+ fsections = afs.etype_get(eschema, rschema, role, tschema)
+ if 'main_inlined' in fsections and \
(libconfig is None or not
- librinlined.etype_get(eschema, rschema, role, tschema)):
+ 'main_inlined' in libafs.etype_get(
+ eschema, rschema, role, tschema)):
add_msg(w, 'add a %s' % tschema,
'inlined:%s.%s.%s' % (etype, rschema, role))
- add_msg(w, 'remove this %s' % tschema,
- 'inlined:%s.%s.%s' % (etype, rschema, role))
- add_msg(w, 'This %s' % tschema,
+ add_msg(w, str(tschema),
'inlined:%s.%s.%s' % (etype, rschema, role))
if appearsin_addmenu.etype_get(eschema, rschema, role, tschema) and \
(libconfig is None or not
@@ -184,7 +187,7 @@
# XXX also generate "creating ...' messages for actions in the
# addrelated submenu
w('# subject and object forms for each relation type\n')
- w('# (no object form for final or symetric relation types)\n')
+ w('# (no object form for final or symmetric relation types)\n')
w('\n')
for rschema in sorted(schema.relations()):
rtype = rschema.type
@@ -204,7 +207,7 @@
for subjschema in rschema.subjects():
if not subjschema in libsubjects:
add_msg(w, rtype, subjschema.type)
- if not (schema.rschema(rtype).final or rschema.symetric):
+ if not (schema.rschema(rtype).final or rschema.symmetric):
if rschema not in no_context_rtypes:
libobjects = librschema and librschema.objects() or ()
for objschema in rschema.objects():
@@ -217,14 +220,19 @@
add_msg(w, '%s_description' % objid)
add_msg(w, objid)
+
def _iter_vreg_objids(vreg, done, prefix=None):
for reg, objdict in vreg.items():
for objects in objdict.values():
for obj in objects:
- objid = '%s_%s' % (reg, obj.id)
+ objid = '%s_%s' % (reg, obj.__regid__)
if objid in done:
break
- if obj.property_defs:
+ try: # XXX < 3.6 bw compat
+ pdefs = obj.property_defs
+ except AttributeError:
+ pdefs = getattr(obj, 'cw_property_defs', {})
+ if pdefs:
yield objid
done.add(objid)
break
@@ -279,7 +287,7 @@
import yams
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import globfind, find, rm
- from cubicweb.common.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute
tempdir = tempfile.mkdtemp()
potfiles = [join(I18NDIR, 'static-messages.pot')]
print '-> extract schema messages.'
@@ -340,9 +348,10 @@
def run(self, args):
"""run the command with its specific arguments"""
if args:
- cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
+ cubes = [DevConfiguration.cube_dir(cube) for cube in args]
else:
- cubes = [DevCubeConfiguration.cube_dir(cube) for cube in DevCubeConfiguration.available_cubes()]
+ cubes = [DevConfiguration.cube_dir(cube)
+ for cube in DevConfiguration.available_cubes()]
cubes = [cubepath for cubepath in cubes if exists(join(cubepath, 'i18n'))]
update_cubes_catalogs(cubes)
@@ -372,7 +381,7 @@
import tempfile
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
- from cubicweb.common.i18n import extract_from_tal, execute
+ from cubicweb.i18n import extract_from_tal, execute
toedit = []
cube = basename(normpath(cubedir))
tempdir = tempfile.mkdtemp()
@@ -435,17 +444,18 @@
return toedit
-class LiveServerCommand(Command):
- """Run a server from within a cube directory.
- """
- name = 'live-server'
- arguments = ''
- options = ()
+# XXX totally broken, fix it
+# class LiveServerCommand(Command):
+# """Run a server from within a cube directory.
+# """
+# name = 'live-server'
+# arguments = ''
+# options = ()
- def run(self, args):
- """run the command with its specific arguments"""
- from cubicweb.devtools.livetest import runserver
- runserver()
+# def run(self, args):
+# """run the command with its specific arguments"""
+# from cubicweb.devtools.livetest import runserver
+# runserver()
class NewCubeCommand(Command):
@@ -617,9 +627,67 @@
for clocktime, cputime, occ, rql in stat:
print '%.2f;%.2f;%.2f;%s;%s' % (clocktime/total_time, clocktime, cputime, occ, rql)
+
+class GenerateSchema(Command):
+ """Generate schema image for the given cube"""
+ name = "schema"
+ arguments = '<cube>'
+ options = [('output-file', {'type':'file', 'default': None,
+ 'metavar': '<file>', 'short':'o', 'help':'output image file',
+ 'input':False}),
+ ('viewer', {'type': 'string', 'default':None,
+ 'short': "d", 'metavar':'<cmd>',
+ 'help':'command use to view the generated file (empty for none)'}
+ ),
+ ('show-meta', {'action': 'store_true', 'default':False,
+ 'short': "m", 'metavar': "<yN>",
+ 'help':'include meta and internal entities in schema'}
+ ),
+ ('show-workflow', {'action': 'store_true', 'default':False,
+ 'short': "w", 'metavar': "<yN>",
+ 'help':'include workflow entities in schema'}
+ ),
+ ('show-cw-user', {'action': 'store_true', 'default':False,
+ 'metavar': "<yN>",
+ 'help':'include cubicweb user entities in schema'}
+ ),
+ ('exclude-type', {'type':'string', 'default':'',
+ 'short': "x", 'metavar': "<types>",
+ 'help':'coma separated list of entity types to remove from view'}
+ ),
+ ('include-type', {'type':'string', 'default':'',
+ 'short': "i", 'metavar': "<types>",
+ 'help':'coma separated list of entity types to include in view'}
+ ),
+ ]
+
+ def run(self, args):
+ from logilab.common.textutils import splitstrip
+ cubes = splitstrip(pop_arg(args, 1))
+ dev_conf = DevConfiguration(*cubes)
+ schema = dev_conf.load_schema()
+ out, viewer = self['output-file'], self['viewer']
+ if out is None:
+ tmp_file = NamedTemporaryFile(suffix=".svg")
+ out = tmp_file.name
+ skiptypes = BASE_TYPES | SCHEMA_TYPES
+ if not self['show-meta']:
+ skiptypes |= META_RTYPES | SYSTEM_RTYPES | INTERNAL_TYPES
+ if not self['show-workflow']:
+ skiptypes |= WORKFLOW_TYPES
+ if not self['show-cw-user']:
+ skiptypes |= set(('CWUser', 'CWGroup', 'EmailAddress'))
+ skiptypes |= set(self['exclude-type'].split(','))
+ skiptypes -= set(self['include-type'].split(','))
+ schema2dot.schema2dot(schema, out, skiptypes=skiptypes)
+ if viewer:
+ p = Popen((viewer, out))
+ p.wait()
+
register_commands((UpdateCubicWebCatalogCommand,
UpdateTemplateCatalogCommand,
- LiveServerCommand,
+ #LiveServerCommand,
NewCubeCommand,
ExamineLogCommand,
+ GenerateSchema,
))
--- a/devtools/fake.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/fake.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,12 +7,11 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.common.testlib import mock_object as Mock
from logilab.common.adbh import get_adv_func_helper
from indexer import get_indexer
-from cubicweb import RequestSessionMixIn
+from cubicweb.req import RequestSessionBase
from cubicweb.cwvreg import CubicWebVRegistry
from cubicweb.web.request import CubicWebRequestBase
from cubicweb.devtools import BASE_URL, BaseApptestConfiguration
@@ -81,15 +80,15 @@
def set_header(self, header, value, raw=True):
"""set an output HTTP header"""
- pass
+ self._headers[header] = value
def add_header(self, header, value):
"""set an output HTTP header"""
- pass
+ self._headers[header] = value # XXX
def remove_header(self, header):
"""remove an output HTTP header"""
- pass
+ self._headers.pop(header, None)
def get_header(self, header, default=None):
"""return the value associated with the given input header,
@@ -97,16 +96,24 @@
"""
return self._headers.get(header, default)
- def set_cookie(self, cookie, key, maxage=300):
+ def set_cookie(self, cookie, key, maxage=300, expires=None):
"""set / update a cookie key
by default, cookie will be available for the next 5 minutes
"""
- pass
+ morsel = cookie[key]
+ if maxage is not None:
+ morsel['Max-Age'] = maxage
+ if expires:
+ morsel['expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S %z')
+ # make sure cookie is set on the correct path
+ morsel['path'] = self.base_url_path()
+ self.add_header('Set-Cookie', morsel.OutputString())
+ self.add_header('Cookie', morsel.OutputString())
def remove_cookie(self, cookie, key):
- """remove a cookie by expiring it"""
- pass
+ self.remove_header('Set-Cookie')
+ self.remove_header('Cookie')
def validate_cache(self):
pass
@@ -130,7 +137,7 @@
return True
-class FakeSession(RequestSessionMixIn):
+class FakeSession(RequestSessionBase):
def __init__(self, repo=None, user=None):
self.repo = repo
self.vreg = getattr(self.repo, 'vreg', CubicWebVRegistry(FakeConfig(), initlog=False))
--- a/devtools/fill.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/fill.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,47 +10,37 @@
from random import randint, choice
from copy import deepcopy
-from datetime import datetime, date, time#timedelta
+from datetime import datetime, date, time, timedelta
from decimal import Decimal
+from logilab.common import attrdict
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
- IntervalBoundConstraint)
+ IntervalBoundConstraint, BoundConstraint,
+ Attribute, actual_value)
from rql.utils import decompose_b26 as base_decompose_b26
from cubicweb import Binary
from cubicweb.schema import RQLConstraint
+def custom_range(start, stop, step):
+ while start < stop:
+ yield start
+ start += step
+
def decompose_b26(index, ascii=False):
"""return a letter (base-26) decomposition of index"""
if ascii:
return base_decompose_b26(index)
return base_decompose_b26(index, u'éabcdefghijklmnopqrstuvwxyz')
-def get_choices(eschema, attrname):
- """returns possible choices for 'attrname'
- if attrname doesn't have ChoiceConstraint, return None
- """
- for cst in eschema.constraints(attrname):
- if isinstance(cst, StaticVocabularyConstraint):
- return cst.vocabulary()
- return None
-
-
def get_max_length(eschema, attrname):
"""returns the maximum length allowed for 'attrname'"""
- for cst in eschema.constraints(attrname):
+ for cst in eschema.rdef(attrname).constraints:
if isinstance(cst, SizeConstraint) and cst.max:
return cst.max
return 300
#raise AttributeError('No Size constraint on attribute "%s"' % attrname)
-def get_bounds(eschema, attrname):
- for cst in eschema.constraints(attrname):
- if isinstance(cst, IntervalBoundConstraint):
- return cst.minvalue, cst.maxvalue
- return None, None
-
-
_GENERATED_VALUES = {}
class _ValueGenerator(object):
@@ -64,55 +54,52 @@
# some stuff ...
return alist_of_acceptable_values # or None
"""
- self.e_schema = eschema
self.choice_func = choice_func
+ self.eschema = eschema
- def _generate_value(self, attrname, index, **kwargs):
- if not self.e_schema.has_unique_values(attrname):
- return self.__generate_value(attrname, index, **kwargs)
- value = self.__generate_value(attrname, index, **kwargs)
- while value in _GENERATED_VALUES.get((self.e_schema.type, attrname), ()):
- index += 1
- value = self.__generate_value(attrname, index, **kwargs)
- _GENERATED_VALUES.setdefault((self.e_schema.type, attrname), set()).add(value)
+ def generate_attribute_value(self, entity, attrname, index=1, **kwargs):
+ if attrname in entity:
+ return entity[attrname]
+ eschema = self.eschema
+ if not eschema.has_unique_values(attrname):
+ value = self.__generate_value(entity, attrname, index, **kwargs)
+ else:
+ value = self.__generate_value(entity, attrname, index, **kwargs)
+ while value in _GENERATED_VALUES.get((eschema, attrname), ()):
+ index += 1
+ value = self.__generate_value(entity, attrname, index, **kwargs)
+ _GENERATED_VALUES.setdefault((eschema, attrname), set()).add(value)
+ entity[attrname] = value
return value
- def __generate_value(self, attrname, index, **kwargs):
+ def __generate_value(self, entity, attrname, index, **kwargs):
"""generates a consistent value for 'attrname'"""
- attrtype = str(self.e_schema.destination(attrname)).lower()
+ eschema = self.eschema
+ attrtype = str(eschema.destination(attrname)).lower()
# Before calling generate_%s functions, try to find values domain
- etype = self.e_schema.type
if self.choice_func is not None:
- values_domain = self.choice_func(etype, attrname)
+ values_domain = self.choice_func(eschema, attrname)
if values_domain is not None:
return choice(values_domain)
- gen_func = getattr(self, 'generate_%s_%s' % (self.e_schema.type, attrname), None)
- if gen_func is None:
- gen_func = getattr(self, 'generate_Any_%s' % attrname, None)
+ gen_func = getattr(self, 'generate_%s_%s' % (eschema, attrname),
+ getattr(self, 'generate_Any_%s' % attrname, None))
if gen_func is not None:
- return gen_func(index, **kwargs)
+ return gen_func(entity, index, **kwargs)
# If no specific values domain, then generate a dummy value
gen_func = getattr(self, 'generate_%s' % (attrtype))
- return gen_func(attrname, index, **kwargs)
+ return gen_func(entity, attrname, index, **kwargs)
- def generate_choice(self, attrname, index):
- """generates a consistent value for 'attrname' if it's a choice"""
- choices = get_choices(self.e_schema, attrname)
- if choices is None:
- return None
- return unicode(choice(choices)) # FIXME
-
- def generate_string(self, attrname, index, format=None):
+ def generate_string(self, entity, attrname, index, format=None):
"""generates a consistent value for 'attrname' if it's a string"""
# First try to get choices
- choosed = self.generate_choice(attrname, index)
+ choosed = self.get_choice(entity, attrname)
if choosed is not None:
return choosed
# All other case, generate a default string
- attrlength = get_max_length(self.e_schema, attrname)
+ attrlength = get_max_length(self.eschema, attrname)
num_len = numlen(index)
if num_len >= attrlength:
- ascii = self.e_schema.rproperty(attrname, 'internationalizable')
+ ascii = self.eschema.rdef(attrname).internationalizable
return ('&'+decompose_b26(index, ascii))[:attrlength]
# always use plain text when no format is specified
attrprefix = attrname[:max(attrlength-num_len-1, 0)]
@@ -131,69 +118,104 @@
value = u'é&%s%d' % (attrprefix, index)
return value[:attrlength]
- def generate_password(self, attrname, index):
+ def generate_password(self, entity, attrname, index):
"""generates a consistent value for 'attrname' if it's a password"""
return u'toto'
- def generate_integer(self, attrname, index):
+ def generate_integer(self, entity, attrname, index):
"""generates a consistent value for 'attrname' if it's an integer"""
- choosed = self.generate_choice(attrname, index)
- if choosed is not None:
- return choosed
- minvalue, maxvalue = get_bounds(self.e_schema, attrname)
- if maxvalue is not None and maxvalue <= 0 and minvalue is None:
- minvalue = maxvalue - index # i.e. randint(-index, 0)
- else:
- maxvalue = maxvalue or index
- return randint(minvalue or 0, maxvalue)
-
+ return self._constrained_generate(entity, attrname, 0, 1, index)
generate_int = generate_integer
- def generate_float(self, attrname, index):
+ def generate_float(self, entity, attrname, index):
"""generates a consistent value for 'attrname' if it's a float"""
- return float(randint(-index, index))
+ return self._constrained_generate(entity, attrname, 0.0, 1.0, index)
- def generate_decimal(self, attrname, index):
+ def generate_decimal(self, entity, attrname, index):
"""generates a consistent value for 'attrname' if it's a float"""
- return Decimal(str(self.generate_float(attrname, index)))
+ return Decimal(str(self.generate_float(entity, attrname, index)))
- def generate_date(self, attrname, index):
+ def generate_datetime(self, entity, attrname, index):
+ """generates a random date (format is 'yyyy-mm-dd HH:MM')"""
+ 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)
+
+ def generate_date(self, entity, attrname, index):
"""generates a random date (format is 'yyyy-mm-dd')"""
- return date(randint(2000, 2004), randint(1, 12), randint(1, 28))
+ base = date(randint(2000, 2004), randint(1, 12), randint(1, 28))
+ return self._constrained_generate(entity, attrname, base, timedelta(days=1), index)
- def generate_time(self, attrname, index):
+ def generate_time(self, entity, attrname, index):
"""generates a random time (format is ' HH:MM')"""
return time(11, index%60) #'11:%02d' % (index % 60)
- def generate_datetime(self, attrname, index):
- """generates a random date (format is 'yyyy-mm-dd HH:MM')"""
- return datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
-
-
- def generate_bytes(self, attrname, index, format=None):
- # modpython way
+ def generate_bytes(self, entity, attrname, index, format=None):
fakefile = Binary("%s%s" % (attrname, index))
fakefile.filename = u"file_%s" % attrname
- fakefile.value = fakefile.getvalue()
return fakefile
- def generate_boolean(self, attrname, index):
+ def generate_boolean(self, entity, attrname, index):
"""generates a consistent value for 'attrname' if it's a boolean"""
return index % 2 == 0
- def generate_Any_data_format(self, index, **kwargs):
+ def _constrained_generate(self, entity, attrname, base, step, index):
+ choosed = self.get_choice(entity, attrname)
+ if choosed is not None:
+ return choosed
+ # ensure index > 0
+ index += 1
+ minvalue, maxvalue = self.get_bounds(entity, attrname)
+ if maxvalue is None:
+ if minvalue is not None:
+ base = max(minvalue, base)
+ maxvalue = base + index * step
+ if minvalue is None:
+ minvalue = maxvalue - (index * step) # i.e. randint(-index, 0)
+ return choice(list(custom_range(minvalue, maxvalue, step)))
+
+ def _actual_boundary(self, entity, boundary):
+ if isinstance(boundary, Attribute):
+ # ensure we've a value for this attribute
+ self.generate_attribute_value(entity, boundary.attr)
+ boundary = actual_value(boundary, entity)
+ return boundary
+
+ def get_bounds(self, entity, attrname):
+ minvalue = maxvalue = None
+ for cst in self.eschema.rdef(attrname).constraints:
+ if isinstance(cst, IntervalBoundConstraint):
+ minvalue = self._actual_boundary(entity, cst.minvalue)
+ maxvalue = self._actual_boundary(entity, cst.maxvalue)
+ elif isinstance(cst, BoundConstraint):
+ if cst.operator[0] == '<':
+ maxvalue = self._actual_boundary(entity, cst.boundary)
+ else:
+ minvalue = self._actual_boundary(entity, cst.boundary)
+ return minvalue, maxvalue
+
+ def get_choice(self, entity, attrname):
+ """generates a consistent value for 'attrname' if it has some static
+ vocabulary set, else return None.
+ """
+ for cst in self.eschema.rdef(attrname).constraints:
+ if isinstance(cst, StaticVocabularyConstraint):
+ return unicode(choice(cst.vocabulary()))
+ return None
+
+ # XXX nothing to do here
+ def generate_Any_data_format(self, entity, index, **kwargs):
# data_format attribute of Image/File has no vocabulary constraint, we
# need this method else stupid values will be set which make mtconverter
# raise exception
return u'application/octet-stream'
- def generate_Any_content_format(self, index, **kwargs):
+ def generate_Any_content_format(self, entity, index, **kwargs):
# content_format attribute of EmailPart has no vocabulary constraint, we
# need this method else stupid values will be set which make mtconverter
# raise exception
return u'text/plain'
- def generate_Image_data_format(self, index, **kwargs):
+ def generate_Image_data_format(self, entity, index, **kwargs):
# data_format attribute of Image/File has no vocabulary constraint, we
# need this method else stupid values will be set which make mtconverter
# raise exception
@@ -237,7 +259,7 @@
returns acceptable values for this attribute
"""
# XXX HACK, remove or fix asap
- if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
+ if etype in set(('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
return []
queries = []
for index in xrange(entity_num):
@@ -264,7 +286,7 @@
"""
eschema = schema.eschema(etype)
valgen = ValueGenerator(eschema, choice_func)
- entity = {}
+ entity = attrdict()
# preprocessing to deal with _format fields
attributes = []
relatedfields = {}
@@ -280,12 +302,11 @@
for attrname, attrschema in attributes:
if attrname in relatedfields:
# first generate a format and record it
- format = valgen._generate_value(attrname + '_format', index)
- entity[attrname + '_format'] = format
+ format = valgen.generate_attribute_value(entity, attrname + '_format', index)
# then a value coherent with this format
- value = valgen._generate_value(attrname, index, format=format)
+ value = valgen.generate_attribute_value(entity, attrname, index, format=format)
else:
- value = valgen._generate_value(attrname, index)
+ value = valgen.generate_attribute_value(entity, attrname, index)
if form: # need to encode values
if attrschema.type == 'Bytes':
# twisted way
@@ -303,7 +324,6 @@
value = fmt % value
else:
value = unicode(value)
- entity[attrname] = value
return entity
@@ -340,10 +360,10 @@
def composite_relation(rschema):
for obj in rschema.objects():
- if obj.objrproperty(rschema, 'composite') == 'subject':
+ if obj.rdef(rschema, 'object').composite == 'subject':
return True
for obj in rschema.subjects():
- if obj.subjrproperty(rschema, 'composite') == 'object':
+ if obj.rdef(rschema, 'subject').composite == 'object':
return True
return False
@@ -372,11 +392,11 @@
oedict = deepcopy(edict)
delayed = []
# for each couple (subjschema, objschema), insert relations
- for subj, obj in rschema.iter_rdefs():
+ for subj, obj in rschema.rdefs:
sym.add( (subj, obj) )
- if rschema.symetric and (obj, subj) in sym:
+ if rschema.symmetric and (obj, subj) in sym:
continue
- subjcard, objcard = rschema.rproperty(subj, obj, 'cardinality')
+ subjcard, objcard = rschema.rdef(subj, obj).cardinality
# process mandatory relations first
if subjcard in '1+' or objcard in '1+' or composite_relation(rschema):
for query, args in self.make_relation_queries(sedict, oedict,
@@ -397,14 +417,15 @@
return {'subjeid' : subjeid, 'objeid' : objeid}
def make_relation_queries(self, sedict, oedict, rschema, subj, obj):
- subjcard, objcard = rschema.rproperty(subj, obj, 'cardinality')
+ rdef = rschema.rdef(subj, obj)
+ subjcard, objcard = rdef.cardinality
subjeids = sedict.get(subj, frozenset())
used = self.existingrels[rschema.type]
preexisting_subjrels = set(subj for subj, obj in used)
preexisting_objrels = set(obj for subj, obj in used)
# if there are constraints, only select appropriate objeids
q = self.rql_tmpl % rschema.type
- constraints = [c for c in rschema.rproperty(subj, obj, 'constraints')
+ constraints = [c for c in rdef.constraints
if isinstance(c, RQLConstraint)]
if constraints:
restrictions = ', '.join(c.restriction for c in constraints)
--- a/devtools/htmlparser.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/htmlparser.py Mon Feb 08 13:35:07 2010 +0100
@@ -174,3 +174,5 @@
except KeyError:
continue
return False
+
+VALMAP = {None: None, 'dtd': DTDValidator, 'xml': SaxOnlyValidator}
--- a/devtools/livetest.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/livetest.py Mon Feb 08 13:35:07 2010 +0100
@@ -6,9 +6,10 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+import os
import socket
import logging
-from os.path import join, dirname, exists
+from os.path import join, dirname, normpath, abspath
from StringIO import StringIO
#from twisted.application import service, strports
@@ -21,10 +22,9 @@
from logilab.common.testlib import TestCase
-import cubicweb.web
from cubicweb.dbapi import in_memory_cnx
from cubicweb.etwist.server import CubicWebRootResource
-from cubicweb.devtools import LivetestConfiguration, init_test_database
+from cubicweb.devtools import BaseApptestConfiguration, init_test_database
@@ -50,25 +50,57 @@
+class LivetestConfiguration(BaseApptestConfiguration):
+ init_repository = False
+
+ def __init__(self, cube=None, sourcefile=None, pyro_name=None,
+ log_threshold=logging.CRITICAL):
+ BaseApptestConfiguration.__init__(self, cube, log_threshold=log_threshold)
+ self.appid = pyro_name or cube
+ # don't change this, else some symlink problems may arise in some
+ # environment (e.g. mine (syt) ;o)
+ # XXX I'm afraid this test will prevent to run test from a production
+ # environment
+ self._sources = None
+ # instance cube test
+ if cube is not None:
+ self.apphome = self.cube_dir(cube)
+ elif 'web' in os.getcwd().split(os.sep):
+ # web test
+ self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
+ else:
+ # cube test
+ self.apphome = abspath('..')
+ self.sourcefile = sourcefile
+ self.global_set_option('realm', '')
+ self.use_pyro = pyro_name is not None
+
+ def pyro_enabled(self):
+ if self.use_pyro:
+ return True
+ else:
+ return False
+
+
+
def make_site(cube, options=None):
from cubicweb.etwist import twconfig # trigger configuration registration
- sourcefile = options.sourcefile
- config = LivetestConfiguration(cube, sourcefile,
+ config = LivetestConfiguration(cube, options.sourcefile,
pyro_name=options.pyro_name,
log_threshold=logging.DEBUG)
- source = config.sources()['system']
- init_test_database(driver=source['db-driver'], config=config)
+ init_test_database(config=config)
# if '-n' in sys.argv: # debug mode
cubicweb = LivetestResource(config, debug=True)
toplevel = cubicweb
website = server.Site(toplevel)
cube_dir = config.cube_dir(cube)
+ source = config.sources()['system']
for port in xrange(7777, 7798):
try:
reactor.listenTCP(port, channel.HTTPFactory(website))
saveconf(cube_dir, port, source['db-user'], source['db-password'])
break
- except CannotListenError, exc:
+ except CannotListenError:
print "port %s already in use, I will try another one" % port
else:
raise
@@ -119,7 +151,7 @@
# build a config, and get a connection
self.config = LivetestConfiguration(self.cube, self.sourcefile)
_, user, passwd, _ = loadconf()
- self.repo, self.cnx = in_memory_cnx(self.config, user, passwd)
+ self.repo, self.cnx = in_memory_cnx(self.config, user, password=passwd)
self.setup_db(self.cnx)
def tearDown(self):
--- a/devtools/migrtest.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-"""Migration test script
-
-* migration will be played into a chroot of the local machine
-* the database server used can be configured
-* test tested instance may be on another host
-
-
-We are using postgres'.pgpass file. Here is a copy of postgres documentation
-about that:
-
-The file .pgpass in a user's home directory or the file referenced by
-PGPASSFILE can contain passwords to be used if the connection requires
-a password (and no password has been specified otherwise).
-
-
-This file should contain lines of the following format:
-
-hostname:port:database:username:password
-
-Each of the first four fields may be a literal value, or *, which
-matches anything. The password field from the first line that matches
-the current connection parameters will be used. (Therefore, put
-more-specific entries first when you are using wildcards.) If an entry
-needs to contain : or \, escape this character with \. A hostname of
-localhost matches both host (TCP) and local (Unix domain socket)
-connections coming from the local machine.
-
-The permissions on .pgpass must disallow any access to world or group;
-achieve this by the command chmod 0600 ~/.pgpass. If the permissions
-are less strict than this, the file will be ignored.
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from os import system
-from os.path import join, basename
-
-from logilab.common.shellutils import cp, rm
-
-from cubicweb.toolsutils import read_config
-from cubicweb.server.utils import generate_sources_file
-
-# XXXX use db-copy instead
-
-# test environment configuration
-chrootpath = '/sandbox/cubicwebtest'
-tmpdbhost = 'crater'
-tmpdbuser = 'syt'
-tmpdbpasswd = 'syt'
-
-def play_migration(applhome, applhost='', sudo=False):
- applid = dbname = basename(applhome)
- testapplhome = join(chrootpath, applhome)
- # copy instance into the chroot
- if applhost:
- system('scp -r %s:%s %s' % (applhost, applhome, testapplhome))
- else:
- cp(applhome, testapplhome)
-## # extract db parameters
-## sources = read_config(join(testapplhome, 'sources'))
-## dbname = sources['system']['db-name']
-## dbhost = sources['system'].get('db-host') or ''
-## dbuser = sources['system'].get('db-user') or ''
-## dbpasswd = sources['system'].get('db-password') or ''
- # generate sources file
- # XXX multisources
- sources = {'system': {}}
- sources['system']['db-encoding'] = 'UTF8' # XXX
- sources['system']['db-name'] = dbname
- sources['system']['db-host'] = None
- sources['system']['db-user'] = tmpdbuser
- sources['system']['db-password'] = None
- generate_sources_file(applid, join(testapplhome, 'sources'), sources)
-## # create postgres password file so we won't need anymore passwords
-## # XXX may exist!
-## pgpassfile = expanduser('~/.pgpass')
-## pgpass = open(pgpassfile, 'w')
-## if dbpasswd:
-## pgpass.write('%s:*:%s:%s:%s\n' % (dbhost or applhost or 'localhost',
-## dbname, dbuser, dbpasswd))
-## if tmpdbpasswd:
-## pgpass.write('%s:*:%s:%s:%s\n' % (tmpdbhost or 'localhost', dbname,
-## tmpdbuser, tmpdbpasswd))
-## pgpass.close()
-## chmod(pgpassfile, 0600)
- # dump db
-## dumpcmd = 'pg_dump -Fc -U %s -f /tmp/%s.dump %s' % (
-## dbuser, dbname, dbname)
-## if dbhost:
-## dumpcmd += ' -h %s' % dbhost
- dumpfile = '/tmp/%s.dump' % applid
- dumpcmd = 'cubicweb-ctl db-dump --output=%s %s' % (dumpfile, applid)
- if sudo:
- dumpcmd = 'sudo %s' % dumpcmd
- if applhost:
- dumpcmd = 'ssh %s "%s"' % (applhost, dumpcmd)
- if system(dumpcmd):
- raise Exception('error while dumping the database')
-## if not dbhost and applhost:
- if applhost:
- # retrieve the dump
- if system('scp %s:%s %s' % (applhost, dumpfile, dumpfile)):
- raise Exception('error while retreiving the dump')
- # move the dump into the chroot
- system('mv %s %s%s' % (dumpfile, chrootpath, dumpfile))
- # locate installed versions
- vcconf = read_config(join(testapplhome, 'vc.conf'))
- template = vcconf['TEMPLATE']
- cubicwebversion = vcconf['CW']
- templversion = vcconf['TEMPLATE_VERSION']
- # install the same versions cubicweb and template versions into the chroot
- system('sudo chroot %s apt-get update' % chrootpath)
- system('sudo chroot %s apt-get install cubicweb-server=%s cubicweb-client=%s'
- % (chrootpath, cubicwebversion, cubicwebversion))
- system('sudo chroot %s apt-get install cubicweb-%s-appl-server=%s cubicweb-%s-appl-client=%s'
- % (chrootpath, template, templversion, template, templversion))
- # update and upgrade to the latest version
- system('sudo chroot %s apt-get install cubicweb-server cubicweb-client' % chrootpath)
- system('sudo chroot %s apt-get install cubicweb-%s-appl-server cubicweb-%s-appl-client'
- % (chrootpath, template, template))
- # create and fill the database
- system('sudo chroot cubicweb-ctl db-restore %s %s' % (applid, dumpfile))
-## if not tmpdbhost:
-## system('createdb -U %s -T template0 -E UTF8 %s' % (tmpdbuser, dbname))
-## system('pg_restore -U %s -O -Fc -d %s /tmp/%s.dump'
-## % (tmpdbuser, dbname, dbname))
-## else:
-## system('createdb -h %s -U %s -T template0 -E UTF8 %s'
-## % (tmpdbhost, tmpdbuser, dbname))
-## system('pg_restore -h %s -U %s -O -Fc -d %s /tmp/%s.dump'
-## % (tmpdbhost, tmpdbuser, dbname, dbname))
- # launch upgrade
- system('sudo chroot %s cubicweb-ctl upgrade %s' % (chrootpath, applid))
-
- # cleanup
- rm(testapplhome)
-## rm(pgpassfile)
-## if tmpdbhost:
-## system('dropdb -h %s -U %s %s' % (tmpdbuser, tmpdbhost, dbname))
-## else:
-## system('dropdb -U %s %s' % (tmpdbuser, dbname))
-## if not dbhost and applhost:
- if applhost:
- system('ssh %s rm %s' % (applhost, dumpfile))
- rm('%s%s' % (chrootpath, dumpfile))
-
-
-if __name__ == '__main__':
- play_migration('/etc/cubicweb.d/jpl', 'lepus')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/realdbtest.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,43 @@
+import logging
+from cubicweb import toolsutils
+from cubicweb.devtools import DEFAULT_SOURCES, BaseApptestConfiguration
+
+class RealDatabaseConfiguration(BaseApptestConfiguration):
+ init_repository = False
+ sourcesdef = DEFAULT_SOURCES.copy()
+
+ def sources(self):
+ """
+ By default, we run tests with the sqlite DB backend.
+ One may use its own configuration by just creating a
+ 'sources' file in the test directory from wich tests are
+ launched.
+ """
+ self._sources = self.sourcesdef
+ return self._sources
+
+
+def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None):
+ """convenience function that builds a real-db configuration class"""
+ sourcesdef = {'system': {'adapter' : 'native',
+ 'db-encoding' : 'UTF-8', #'ISO-8859-1',
+ 'db-user' : dbuser,
+ 'db-password' : dbpassword,
+ 'db-name' : dbname,
+ 'db-driver' : 'postgres',
+ 'db-host' : dbhost,
+ },
+ 'admin' : {'login': adminuser,
+ 'password': adminpassword,
+ },
+ }
+ return type('MyRealDBConfig', (RealDatabaseConfiguration,),
+ {'sourcesdef': sourcesdef})
+
+
+def loadconfig(filename):
+ """convenience function that builds a real-db configuration class
+ from a file
+ """
+ return type('MyRealDBConfig', (RealDatabaseConfiguration,),
+ {'sourcesdef': toolsutils.read_config(filename)})
--- a/devtools/repotest.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/repotest.py Mon Feb 08 13:35:07 2010 +0100
@@ -174,17 +174,18 @@
rqlhelper._analyser.uid_func_mapping = {}
return rqlhelper
- def _prepare_plan(self, rql, kwargs=None):
+ def _prepare_plan(self, rql, kwargs=None, simplify=True):
rqlhelper = self._rqlhelper()
rqlst = rqlhelper.parse(rql)
rqlhelper.compute_solutions(rqlst, kwargs=kwargs)
- rqlhelper.simplify(rqlst)
+ if simplify:
+ rqlhelper.simplify(rqlst)
for select in rqlst.children:
select.solutions.sort()
return self.o.plan_factory(rqlst, kwargs, self.session)
def _prepare(self, rql, kwargs=None):
- plan = self._prepare_plan(rql, kwargs)
+ plan = self._prepare_plan(rql, kwargs, simplify=False)
plan.preprocess(plan.rqlst)
rqlst = plan.rqlst.children[0]
rqlst.solutions = remove_unused_solutions(rqlst, rqlst.solutions, {}, self.repo.schema)[0]
--- a/devtools/stresstester.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/stresstester.py Mon Feb 08 13:35:07 2010 +0100
@@ -26,8 +26,6 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-__revision__ = "$Id: stresstester.py,v 1.3 2006-03-05 14:35:27 syt Exp $"
-
import os
import sys
import threading
@@ -156,7 +154,7 @@
# get local access to the repository
print "Creating repo", prof_file
repo = Repository(config, prof_file)
- cnxid = repo.connect(user, password)
+ cnxid = repo.connect(user, password=password)
# connection to the CubicWeb repository
repo_cnx = Connection(repo, cnxid)
repo_cursor = repo_cnx.cursor()
--- a/devtools/test/data/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/test/data/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,5 +16,5 @@
severity = String(vocabulary=('important', 'normal', 'minor'), default='normal')
cost = Int()
description = String(maxsize=4096, fulltextindexed=True)
- identical_to = SubjectRelation('Bug', symetric=True)
+ identical_to = SubjectRelation('Bug', symmetric=True)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/views.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,34 @@
+"""only for unit tests !
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from cubicweb.view import EntityView
+from cubicweb.selectors import implements
+
+HTML_PAGE = u"""<html>
+ <body>
+ <h1>Hello World !</h1>
+ </body>
+</html>
+"""
+
+class SimpleView(EntityView):
+ __regid__ = 'simple'
+ __select__ = implements('Bug',)
+
+ def call(self, **kwargs):
+ self.cell_call(0, 0)
+
+ def cell_call(self, row, col):
+ self.w(HTML_PAGE)
+
+class RaisingView(EntityView):
+ __regid__ = 'raising'
+ __select__ = implements('Bug',)
+
+ def cell_call(self, row, col):
+ raise ValueError()
--- a/devtools/test/data/views/bug.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-"""only for unit tests !
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from cubicweb.view import EntityView
-from cubicweb.selectors import implements
-
-HTML_PAGE = u"""<html>
- <body>
- <h1>Hello World !</h1>
- </body>
-</html>
-"""
-
-class SimpleView(EntityView):
- id = 'simple'
- __select__ = implements('Bug',)
-
- def call(self, **kwargs):
- self.cell_call(0, 0)
-
- def cell_call(self, row, col):
- self.w(HTML_PAGE)
-
-class RaisingView(EntityView):
- id = 'raising'
- __select__ = implements('Bug',)
-
- def cell_call(self, row, col):
- raise ValueError()
--- a/devtools/test/unittest_dbfill.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/test/unittest_dbfill.py Mon Feb 08 13:35:07 2010 +0100
@@ -22,10 +22,10 @@
class MyValueGenerator(ValueGenerator):
- def generate_Bug_severity(self, index):
+ def generate_Bug_severity(self, entity, index):
return u'dangerous'
- def generate_Any_description(self, index, format=None):
+ def generate_Any_description(self, entity, index, format=None):
return u'yo'
@@ -65,12 +65,12 @@
def test_string(self):
"""test string generation"""
- surname = self.person_valgen._generate_value('surname', 12)
+ surname = self.person_valgen.generate_attribute_value({}, 'surname', 12)
self.assertEquals(surname, u'é&surname12')
def test_domain_value(self):
"""test value generation from a given domain value"""
- firstname = self.person_valgen._generate_value('firstname', 12)
+ firstname = self.person_valgen.generate_attribute_value({}, 'firstname', 12)
possible_choices = self._choice_func('Person', 'firstname')
self.failUnless(firstname in possible_choices,
'%s not in %s' % (firstname, possible_choices))
@@ -79,21 +79,21 @@
"""test choice generation"""
# Test for random index
for index in range(5):
- sx_value = self.person_valgen._generate_value('civility', index)
+ sx_value = self.person_valgen.generate_attribute_value({}, 'civility', index)
self.failUnless(sx_value in ('Mr', 'Mrs', 'Ms'))
def test_integer(self):
"""test integer generation"""
# Test for random index
for index in range(5):
- cost_value = self.bug_valgen._generate_value('cost', index)
+ cost_value = self.bug_valgen.generate_attribute_value({}, 'cost', index)
self.failUnless(cost_value in range(index+1))
def test_date(self):
"""test date generation"""
# Test for random index
for index in range(5):
- date_value = self.person_valgen._generate_value('birthday', index)
+ date_value = self.person_valgen.generate_attribute_value({}, 'birthday', index)
self._check_date(date_value)
def test_phone(self):
@@ -102,11 +102,11 @@
def test_customized_generation(self):
- self.assertEquals(self.bug_valgen._generate_value('severity', 12),
+ self.assertEquals(self.bug_valgen.generate_attribute_value({}, 'severity', 12),
u'dangerous')
- self.assertEquals(self.bug_valgen._generate_value('description', 12),
+ self.assertEquals(self.bug_valgen.generate_attribute_value({}, 'description', 12),
u'yo')
- self.assertEquals(self.person_valgen._generate_value('description', 12),
+ self.assertEquals(self.person_valgen.generate_attribute_value({}, 'description', 12),
u'yo')
--- a/devtools/test/unittest_testlib.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/test/unittest_testlib.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,11 +10,11 @@
from unittest import TestSuite
-from logilab.common.testlib import (TestCase, unittest_main, mock_object,
+from logilab.common.testlib import (TestCase, unittest_main,
SkipAwareTextTestRunner)
+
from cubicweb.devtools import htmlparser
-
-from cubicweb.devtools.testlib import WebTest, EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
class WebTestTC(TestCase):
@@ -23,10 +23,10 @@
self.runner = SkipAwareTextTestRunner(stream=output)
def test_error_raised(self):
- class MyWebTest(WebTest):
+ class MyWebTest(CubicWebTC):
def test_error_view(self):
- self.add_entity('Bug', title=u"bt")
+ self.request().create_entity('Bug', title=u"bt")
self.view('raising', self.execute('Bug B'), template=None)
def test_correct_view(self):
@@ -39,17 +39,6 @@
self.assertEquals(len(result.failures), 1)
-class TestLibTC(EnvBasedTC):
- def test_add_entity_with_relation(self):
- bug = self.add_entity(u'Bug', title=u"toto")
- self.add_entity(u'Bug', title=u"tata", identical_to=bug)
-
- rset = self.execute('Any BA WHERE BA is Bug, BA title "toto"')
- self.assertEquals(len(rset), 1)
- bug = tuple(rset.entities())[0]
- self.assertEquals(bug.identical_to[0].title, "tata")
-
-
HTML_PAGE = u"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
--- a/devtools/testlib.py Mon Feb 08 13:33:09 2010 +0100
+++ b/devtools/testlib.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,4 @@
-"""this module contains base classes for web tests
+"""this module contains base classes and utilities for cubicweb tests
:organization: Logilab
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -7,85 +7,538 @@
"""
__docformat__ = "restructuredtext en"
+import os
import sys
+import re
+from urllib import unquote
from math import log
-from logilab.common.debugger import Debugger
-from logilab.common.testlib import InnerTest
-from logilab.common.pytest import nocoverage
+import simplejson
+
+import yams.schema
-from cubicweb import ValidationError
-from cubicweb.devtools import VIEW_VALIDATORS
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools._apptest import unprotected_entities, SYSTEM_RELATIONS
-from cubicweb.devtools.htmlparser import DTDValidator, SaxOnlyValidator, HTMLValidator
-from cubicweb.devtools.fill import insert_entity_queries, make_relations_queries
+from logilab.common.testlib import TestCase, InnerTest
+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.deprecation import deprecated
-from cubicweb.sobjects.notification import NotificationView
-
-from cubicweb.vregistry import NoSelectableObject
+from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
+from cubicweb import cwconfig, devtools, web, server
+from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError
+from cubicweb.sobjects import notification
+from cubicweb.web import Redirect, application
+from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
+from cubicweb.devtools import fake, htmlparser
-## TODO ###############
-# creation tests: make sure an entity was actually created
-# Existing Test Environment
+# low-level utilities ##########################################################
class CubicWebDebugger(Debugger):
-
+ """special debugger class providing a 'view' function which saves some
+ html into a temporary file and open a web browser to examinate it.
+ """
def do_view(self, arg):
import webbrowser
data = self._getval(arg)
file('/tmp/toto.html', 'w').write(data)
webbrowser.open('file:///tmp/toto.html')
-def how_many_dict(schema, cursor, how_many, skip):
- """compute how many entities by type we need to be able to satisfy relations
- cardinality
- """
- # compute how many entities by type we need to be able to satisfy relation constraint
- relmap = {}
- for rschema in schema.relations():
- if rschema.final:
- continue
- for subj, obj in rschema.iter_rdefs():
- card = rschema.rproperty(subj, obj, 'cardinality')
- if card[0] in '1?' and len(rschema.subjects(obj)) == 1:
- relmap.setdefault((rschema, subj), []).append(str(obj))
- if card[1] in '1?' and len(rschema.objects(subj)) == 1:
- relmap.setdefault((rschema, obj), []).append(str(subj))
- unprotected = unprotected_entities(schema)
- for etype in skip:
- unprotected.add(etype)
- howmanydict = {}
- for etype in unprotected_entities(schema, strict=True):
- howmanydict[str(etype)] = cursor.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
- if etype in unprotected:
- howmanydict[str(etype)] += how_many
- for (rschema, etype), targets in relmap.iteritems():
- # XXX should 1. check no cycle 2. propagate changes
- relfactor = sum(howmanydict[e] for e in targets)
- howmanydict[str(etype)] = max(relfactor, howmanydict[etype])
- return howmanydict
-
-
def line_context_filter(line_no, center, before=3, after=None):
"""return true if line are in context
- if after is None: after = before"""
+
+ if after is None: after = before
+ """
if after is None:
after = before
return center - before <= line_no <= center + after
-## base webtest class #########################################################
-VALMAP = {None: None, 'dtd': DTDValidator, 'xml': SaxOnlyValidator}
+
+def unprotected_entities(schema, strict=False):
+ """returned a set of each non final entity type, excluding "system" entities
+ (eg CWGroup, CWUser...)
+ """
+ if strict:
+ protected_entities = yams.schema.BASE_TYPES
+ else:
+ protected_entities = yams.schema.BASE_TYPES.union(SYSTEM_ENTITIES)
+ return set(schema.entities()) - protected_entities
+
+
+def get_versions(self, checkversions=False):
+ """return the a dictionary containing cubes used by this instance
+ as key with their version as value, including cubicweb version. This is a
+ public method, not requiring a session id.
+
+ replace Repository.get_versions by this method if you don't want versions
+ checking
+ """
+ vcconf = {'cubicweb': self.config.cubicweb_version()}
+ self.config.bootstrap_cubes()
+ for pk in self.config.cubes():
+ version = self.config.cube_version(pk)
+ vcconf[pk] = version
+ self.config._cubes = None
+ return vcconf
+
+
+def refresh_repo(repo):
+ devtools.reset_test_database(repo.config)
+ for pool in repo.pools:
+ pool.reconnect()
+ repo._type_source_cache = {}
+ repo._extid_cache = {}
+ repo.querier._rql_cache = {}
+ for source in repo.sources:
+ source.reset_caches()
+
+
+# email handling, to test emails sent by an application ########################
+
+MAILBOX = []
+
+class Email:
+ """you'll get instances of Email into MAILBOX during tests that trigger
+ some notification.
+
+ * `msg` is the original message object
+
+ * `recipients` is a list of email address which are the recipients of this
+ message
+ """
+ def __init__(self, recipients, msg):
+ self.recipients = recipients
+ self.msg = msg
+
+ @property
+ def message(self):
+ return message_from_string(self.msg)
+
+ @property
+ def subject(self):
+ return self.message.get('Subject')
+
+ @property
+ def content(self):
+ return self.message.get_payload(decode=True)
+
+ def __repr__(self):
+ return '<Email to %s with subject %s>' % (','.join(self.recipients),
+ self.message.get('Subject'))
+
+# the trick to get email into MAILBOX instead of actually sent: monkey patch
+# cwconfig.SMTP object
+class MockSMTP:
+ def __init__(self, server, port):
+ pass
+ def close(self):
+ pass
+ def sendmail(self, helo_addr, recipients, msg):
+ MAILBOX.append(Email(recipients, msg))
+
+cwconfig.SMTP = MockSMTP
+
+
+# base class for cubicweb tests requiring a full cw environments ###############
+
+class CubicWebTC(TestCase):
+ """abstract class for test using an apptest environment
+
+ attributes:
+ `vreg`, the vregistry
+ `schema`, self.vreg.schema
+ `config`, cubicweb configuration
+ `cnx`, dbapi connection to the repository using an admin user
+ `session`, server side session associated to `cnx`
+ `app`, the cubicweb publisher (for web testing)
+ `repo`, the repository object
+
+ `admlogin`, login of the admin user
+ `admpassword`, password of the admin user
+
+ """
+ appid = 'data'
+ configcls = devtools.ApptestConfiguration
+
+ @classproperty
+ def config(cls):
+ """return the configuration object. Configuration is cached on the test
+ class.
+ """
+ try:
+ return cls.__dict__['_config']
+ except KeyError:
+ config = cls._config = cls.configcls(cls.appid)
+ config.mode = 'test'
+ return config
+
+ @classmethod
+ def init_config(cls, config):
+ """configuration initialization hooks. You may want to override this."""
+ source = config.sources()['system']
+ cls.admlogin = unicode(source['db-user'])
+ cls.admpassword = source['db-password']
+ # uncomment the line below if you want rql queries to be logged
+ #config.global_set_option('query-log-file',
+ # '/tmp/test_rql_log.' + `os.getpid()`)
+ config.global_set_option('log-file', None)
+ # set default-dest-addrs to a dumb email address to avoid mailbox or
+ # mail queue pollution
+ config.global_set_option('default-dest-addrs', ['whatever'])
+ try:
+ send_to = '%s@logilab.fr' % os.getlogin()
+ # AttributeError since getlogin not available under all platforms
+ except (OSError, AttributeError):
+ send_to = '%s@logilab.fr' % (os.environ.get('USER')
+ or os.environ.get('USERNAME')
+ or os.environ.get('LOGNAME'))
+ config.global_set_option('sender-addr', send_to)
+ config.global_set_option('default-dest-addrs', send_to)
+ config.global_set_option('sender-name', 'cubicweb-test')
+ config.global_set_option('sender-addr', 'cubicweb-test@logilab.fr')
+ # web resources
+ config.global_set_option('base-url', devtools.BASE_URL)
+ try:
+ config.global_set_option('embed-allowed', re.compile('.*'))
+ except: # not in server only configuration
+ pass
+
+ @classmethod
+ def _init_repo(cls):
+ """init the repository and connection to it.
+
+ Repository and connection are cached on the test class. Once
+ initialized, we simply reset connections and repository caches.
+ """
+ if not 'repo' in cls.__dict__:
+ cls._build_repo()
+ else:
+ cls.cnx.rollback()
+ cls._refresh_repo()
+
+ @classmethod
+ def _build_repo(cls):
+ cls.repo, cls.cnx = devtools.init_test_database(config=cls.config)
+ cls.init_config(cls.config)
+ cls.vreg = cls.repo.vreg
+ cls._orig_cnx = cls.cnx
+ cls.config.repository = lambda x=None: cls.repo
+ # necessary for authentication tests
+ cls.cnx.login = cls.admlogin
+ cls.cnx.authinfo = {'password': cls.admpassword}
+
+ @classmethod
+ def _refresh_repo(cls):
+ refresh_repo(cls.repo)
+
+ # global resources accessors ###############################################
+
+ @property
+ def schema(self):
+ """return the application schema"""
+ return self.vreg.schema
+
+ @property
+ def session(self):
+ """return current server side session (using default manager account)"""
+ return self.repo._sessions[self.cnx.sessionid]
+
+ @property
+ def adminsession(self):
+ """return current server side session (using default manager account)"""
+ return self.repo._sessions[self._orig_cnx.sessionid]
+
+ def set_option(self, optname, value):
+ self.config.global_set_option(optname, value)
+
+ def set_debug(self, debugmode):
+ server.set_debug(debugmode)
+
+ # default test setup and teardown #########################################
+
+ def setUp(self):
+ pause_tracing()
+ self._init_repo()
+ resume_tracing()
+ self.setup_database()
+ self.commit()
+ MAILBOX[:] = [] # reset mailbox
+
+ def setup_database(self):
+ """add your database setup code by overriding this method"""
+
+ # user / session management ###############################################
+
+ def user(self, req=None):
+ """return the application schema"""
+ if req is None:
+ req = self.request()
+ return self.cnx.user(req)
+ else:
+ return req.user
+
+ def create_user(self, login, groups=('users',), password=None, req=None,
+ commit=True):
+ """create and return a new user entity"""
+ if password is None:
+ password = login.encode('utf8')
+ cursor = self._orig_cnx.cursor(req or self.request())
+ rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
+ {'login': unicode(login), 'passwd': password})
+ user = rset.get_entity(0, 0)
+ cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'
+ % ','.join(repr(g) for g in groups),
+ {'x': user.eid}, 'x')
+ user.clear_related_cache('in_group', 'subject')
+ if commit:
+ self._orig_cnx.commit()
+ return user
-class WebTest(EnvBasedTC):
- """base class for web tests"""
- __abstract__ = True
+ def login(self, login, **kwargs):
+ """return a connection for the given login/password"""
+ if login == self.admlogin:
+ self.restore_connection()
+ else:
+ if not kwargs:
+ kwargs['password'] = str(login)
+ self.cnx = repo_connect(self.repo, unicode(login),
+ cnxprops=ConnectionProperties('inmemory'),
+ **kwargs)
+ if login == self.vreg.config.anonymous_user()[0]:
+ self.cnx.anonymous_connection = True
+ return self.cnx
+
+ def restore_connection(self):
+ if not self.cnx is self._orig_cnx:
+ try:
+ self.cnx.close()
+ except ProgrammingError:
+ pass # already closed
+ self.cnx = self._orig_cnx
+
+ # db api ##################################################################
+
+ @nocoverage
+ def cursor(self, req=None):
+ return self.cnx.cursor(req or self.request())
+
+ @nocoverage
+ def execute(self, rql, args=None, eidkey=None, req=None):
+ """executes <rql>, builds a resultset, and returns a couple (rset, req)
+ where req is a FakeRequest
+ """
+ req = req or self.request(rql=rql)
+ return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
+
+ @nocoverage
+ def commit(self):
+ self.cnx.commit()
+
+ @nocoverage
+ def rollback(self):
+ try:
+ self.cnx.rollback()
+ except ProgrammingError:
+ pass
+
+ # # server side db api #######################################################
+
+ def sexecute(self, rql, args=None, eid_key=None):
+ self.session.set_pool()
+ return self.session.execute(rql, args, eid_key)
+
+ # other utilities #########################################################
+
+ def entity(self, rql, args=None, eidkey=None, req=None):
+ return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
+
+ # vregistry inspection utilities ###########################################
+
+ def pviews(self, req, rset):
+ return sorted((a.__regid__, a.__class__)
+ for a in self.vreg['views'].possible_views(req, rset=rset))
+
+ def pactions(self, req, rset,
+ skipcategories=('addrelated', 'siteactions', 'useractions', 'footer')):
+ return [(a.__regid__, a.__class__)
+ for a in self.vreg['actions'].poss_visible_objects(req, rset=rset)
+ if a.category not in skipcategories]
+
+ def pactions_by_cats(self, req, rset, categories=('addrelated',)):
+ return [(a.__regid__, a.__class__)
+ for a in self.vreg['actions'].poss_visible_objects(req, rset=rset)
+ if a.category in categories]
+
+ def pactionsdict(self, req, rset,
+ skipcategories=('addrelated', 'siteactions', 'useractions', 'footer')):
+ res = {}
+ for a in self.vreg['actions'].poss_visible_objects(req, rset=rset):
+ if a.category not in skipcategories:
+ res.setdefault(a.category, []).append(a.__class__)
+ return res
+
+ def action_submenu(self, req, rset, id):
+ return self._test_action(self.vreg['actions'].select(id, req, rset=rset))
+
+ def _test_action(self, action):
+ class fake_menu(list):
+ @property
+ def items(self):
+ return self
+ class fake_box(object):
+ def mk_action(self, label, url, **kwargs):
+ return (label, url)
+ def box_action(self, action, **kwargs):
+ return (action.title, action.url())
+ submenu = fake_menu()
+ action.fill_menu(fake_box(), submenu)
+ return submenu
- pdbclass = CubicWebDebugger
- # this is a hook to be able to define a list of rql queries
- # that are application dependent and cannot be guessed automatically
- application_rql = []
+ def list_views_for(self, rset):
+ """returns the list of views that can be applied on `rset`"""
+ req = rset.req
+ only_once_vids = ('primary', 'secondary', 'text')
+ req.data['ex'] = ValueError("whatever")
+ viewsvreg = self.vreg['views']
+ for vid, views in viewsvreg.items():
+ if vid[0] == '_':
+ continue
+ if rset.rowcount > 1 and vid in only_once_vids:
+ continue
+ views = [view for view in views
+ if view.category != 'startupview'
+ and not issubclass(view, notification.NotificationView)]
+ if views:
+ try:
+ view = viewsvreg._select_best(views, req, rset=rset)
+ if view.linkable():
+ yield view
+ else:
+ not_selected(self.vreg, view)
+ # else the view is expected to be used as subview and should
+ # not be tested directly
+ except NoSelectableObject:
+ continue
+
+ def list_actions_for(self, rset):
+ """returns the list of actions that can be applied on `rset`"""
+ req = rset.req
+ for action in self.vreg['actions'].possible_objects(req, rset=rset):
+ yield action
+
+ def list_boxes_for(self, rset):
+ """returns the list of boxes that can be applied on `rset`"""
+ req = rset.req
+ for box in self.vreg['boxes'].possible_objects(req, rset=rset):
+ yield box
+
+ def list_startup_views(self):
+ """returns the list of startup views"""
+ req = self.request()
+ for view in self.vreg['views'].possible_views(req, None):
+ if view.category == 'startupview':
+ yield view.__regid__
+ else:
+ not_selected(self.vreg, view)
+
+ # web ui testing utilities #################################################
+
+ @property
+ @cached
+ def app(self):
+ """return a cubicweb publisher"""
+ publisher = application.CubicWebPublisher(self.config, vreg=self.vreg)
+ def raise_error_handler(*args, **kwargs):
+ raise
+ publisher.error_handler = raise_error_handler
+ return publisher
+
+ requestcls = fake.FakeRequest
+ def request(self, *args, **kwargs):
+ """return a web ui request"""
+ req = self.requestcls(self.vreg, form=kwargs)
+ req.set_connection(self.cnx)
+ return req
+
+ def remote_call(self, fname, *args):
+ """remote json call simulation"""
+ dump = simplejson.dumps
+ args = [dump(arg) for arg in args]
+ req = self.request(fname=fname, pageid='123', arg=args)
+ ctrl = self.vreg['controllers'].select('json', req)
+ return ctrl.publish(), req
+
+ def app_publish(self, req, path='view'):
+ return self.app.publish(path, req)
+
+ def ctrl_publish(self, req):
+ """call the publish method of the edit controller"""
+ ctrl = self.vreg['controllers'].select('edit', req)
+ try:
+ result = ctrl.publish()
+ req.cnx.commit()
+ except web.Redirect:
+ req.cnx.commit()
+ raise
+ return result
+
+ def expect_redirect(self, callback, req):
+ """call the given callback with req as argument, expecting to get a
+ Redirect exception
+ """
+ try:
+ res = callback(req)
+ except Redirect, ex:
+ try:
+ path, params = ex.location.split('?', 1)
+ except ValueError:
+ path = ex.location
+ params = {}
+ else:
+ cleanup = lambda p: (p[0], unquote(p[1]))
+ params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
+ path = path[len(req.base_url()):]
+ return path, params
+ else:
+ self.fail('expected a Redirect exception')
+
+ def expect_redirect_publish(self, req, path='view'):
+ """call the publish method of the application publisher, expecting to
+ get a Redirect exception
+ """
+ return self.expect_redirect(lambda x: self.app_publish(x, path), req)
+
+ def init_authentication(self, authmode, anonuser=None):
+ self.set_option('auth-mode', authmode)
+ self.set_option('anonymous-user', anonuser)
+ req = self.request()
+ origcnx = req.cnx
+ req.cnx = None
+ sh = self.app.session_handler
+ authm = sh.session_manager.authmanager
+ authm.authinforetreivers[-1].anoninfo = self.vreg.config.anonymous_user()
+ # not properly cleaned between tests
+ self.open_sessions = sh.session_manager._sessions = {}
+ return req, origcnx
+
+ def assertAuthSuccess(self, req, origcnx, nbsessions=1):
+ sh = self.app.session_handler
+ path, params = self.expect_redirect(lambda x: self.app.connect(x), req)
+ cnx = req.cnx
+ self.assertEquals(len(self.open_sessions), nbsessions, self.open_sessions)
+ self.assertEquals(cnx.login, origcnx.login)
+ self.assertEquals(cnx.anonymous_connection, False)
+ self.assertEquals(path, 'view')
+ self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login})
+
+ def assertAuthFailure(self, req, nbsessions=0):
+ self.assertRaises(AuthenticationError, self.app.connect, req)
+ self.assertEquals(req.cnx, None)
+ self.assertEquals(len(self.open_sessions), nbsessions)
+ clear_cache(req, 'get_authorization')
+
+ # content validation #######################################################
# validators are used to validate (XML, DTD, whatever) view's content
# validators availables are :
@@ -100,8 +553,8 @@
# snippets
#'text/html': DTDValidator,
#'application/xhtml+xml': DTDValidator,
- 'application/xml': SaxOnlyValidator,
- 'text/xml': SaxOnlyValidator,
+ 'application/xml': htmlparser.SaxOnlyValidator,
+ 'text/xml': htmlparser.SaxOnlyValidator,
'text/plain': None,
'text/comma-separated-values': None,
'text/x-vcard': None,
@@ -110,75 +563,9 @@
'image/png': None,
}
# maps vid : validator name (override content_type_validators)
- vid_validators = dict((vid, VALMAP[valkey])
+ vid_validators = dict((vid, htmlparser.VALMAP[valkey])
for vid, valkey in VIEW_VALIDATORS.iteritems())
- no_auto_populate = ()
- ignored_relations = ()
-
- def custom_populate(self, how_many, cursor):
- pass
-
- def post_populate(self, cursor):
- pass
-
- @nocoverage
- def auto_populate(self, how_many):
- """this method populates the database with `how_many` entities
- of each possible type. It also inserts random relations between them
- """
- cu = self.cursor()
- self.custom_populate(how_many, cu)
- vreg = self.vreg
- howmanydict = how_many_dict(self.schema, cu, how_many, self.no_auto_populate)
- for etype in unprotected_entities(self.schema):
- if etype in self.no_auto_populate:
- continue
- nb = howmanydict.get(etype, how_many)
- for rql, args in insert_entity_queries(etype, self.schema, vreg, nb):
- cu.execute(rql, args)
- edict = {}
- for etype in unprotected_entities(self.schema, strict=True):
- rset = cu.execute('%s X' % etype)
- edict[str(etype)] = set(row[0] for row in rset.rows)
- existingrels = {}
- ignored_relations = SYSTEM_RELATIONS + self.ignored_relations
- for rschema in self.schema.relations():
- if rschema.final or rschema in ignored_relations:
- continue
- rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
- existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
- q = make_relations_queries(self.schema, edict, cu, ignored_relations,
- existingrels=existingrels)
- for rql, args in q:
- try:
- cu.execute(rql, args)
- except ValidationError, ex:
- # failed to satisfy some constraint
- print 'error in automatic db population', ex
- self.post_populate(cu)
- self.commit()
-
- @nocoverage
- def _check_html(self, output, view, template='main-template'):
- """raises an exception if the HTML is invalid"""
- try:
- validatorclass = self.vid_validators[view.id]
- except KeyError:
- if view.content_type in ('text/html', 'application/xhtml+xml'):
- if template is None:
- default_validator = HTMLValidator
- else:
- default_validator = DTDValidator
- else:
- default_validator = None
- validatorclass = self.content_type_validators.get(view.content_type,
- default_validator)
- if validatorclass is None:
- return None
- validator = validatorclass()
- return validator.parse_string(output.strip())
-
def view(self, vid, rset=None, req=None, template='main-template',
**kwargs):
@@ -233,9 +620,9 @@
# is not an AssertionError
klass, exc, tcbk = sys.exc_info()
try:
- msg = '[%s in %s] %s' % (klass, view.id, exc)
+ msg = '[%s in %s] %s' % (klass, view.__regid__, exc)
except:
- msg = '[%s in %s] undisplayable exception' % (klass, view.id)
+ msg = '[%s in %s] undisplayable exception' % (klass, view.__regid__)
if output is not None:
position = getattr(exc, "position", (0,))[0]
if position:
@@ -252,9 +639,163 @@
raise AssertionError, msg, tcbk
+ @nocoverage
+ def _check_html(self, output, view, template='main-template'):
+ """raises an exception if the HTML is invalid"""
+ try:
+ validatorclass = self.vid_validators[view.__regid__]
+ except KeyError:
+ if view.content_type in ('text/html', 'application/xhtml+xml'):
+ if template is None:
+ default_validator = htmlparser.HTMLValidator
+ else:
+ default_validator = htmlparser.DTDValidator
+ else:
+ default_validator = None
+ validatorclass = self.content_type_validators.get(view.content_type,
+ default_validator)
+ if validatorclass is None:
+ return None
+ validator = validatorclass()
+ if isinstance(validator, htmlparser.DTDValidator):
+ # XXX remove <canvas> used in progress widget, unknown in html dtd
+ output = re.sub('<canvas.*?></canvas>', '', output)
+ return validator.parse_string(output.strip())
+
+ # deprecated ###############################################################
+
+ @deprecated('[3.6] use self.request().create_entity(...)')
+ def add_entity(self, etype, req=None, **kwargs):
+ if req is None:
+ req = self.request()
+ return req.create_entity(etype, **kwargs)
+
+ @deprecated('[3.4] use self.vreg["etypes"].etype_class(etype)(self.request())')
+ def etype_instance(self, etype, req=None):
+ req = req or self.request()
+ e = self.vreg['etypes'].etype_class(etype)(req)
+ e.eid = None
+ return e
+
+ @nocoverage
+ @deprecated('[3.4] use req = self.request(); rset = req.execute()',
+ stacklevel=3)
+ def rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
+ """executes <rql>, builds a resultset, and returns a
+ couple (rset, req) where req is a FakeRequest
+ """
+ return (self.execute(rql, args, eidkey),
+ self.request(rql=rql, **optional_args or {}))
+
+
+# auto-populating test classes and utilities ###################################
+
+from cubicweb.devtools.fill import insert_entity_queries, make_relations_queries
+
+def how_many_dict(schema, cursor, how_many, skip):
+ """compute how many entities by type we need to be able to satisfy relations
+ cardinality
+ """
+ # compute how many entities by type we need to be able to satisfy relation constraint
+ relmap = {}
+ for rschema in schema.relations():
+ if rschema.final:
+ continue
+ for subj, obj in rschema.rdefs:
+ card = rschema.rdef(subj, obj).cardinality
+ if card[0] in '1?' and len(rschema.subjects(obj)) == 1:
+ relmap.setdefault((rschema, subj), []).append(str(obj))
+ if card[1] in '1?' and len(rschema.objects(subj)) == 1:
+ relmap.setdefault((rschema, obj), []).append(str(subj))
+ unprotected = unprotected_entities(schema)
+ for etype in skip:
+ unprotected.add(etype)
+ howmanydict = {}
+ for etype in unprotected_entities(schema, strict=True):
+ howmanydict[str(etype)] = cursor.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
+ if etype in unprotected:
+ howmanydict[str(etype)] += how_many
+ for (rschema, etype), targets in relmap.iteritems():
+ # XXX should 1. check no cycle 2. propagate changes
+ relfactor = sum(howmanydict[e] for e in targets)
+ howmanydict[str(etype)] = max(relfactor, howmanydict[etype])
+ return howmanydict
+
+
+class AutoPopulateTest(CubicWebTC):
+ """base class for test with auto-populating of the database"""
+ __abstract__ = True
+
+ pdbclass = CubicWebDebugger
+ # this is a hook to be able to define a list of rql queries
+ # that are application dependent and cannot be guessed automatically
+ application_rql = []
+
+ no_auto_populate = ()
+ ignored_relations = set()
+
def to_test_etypes(self):
return unprotected_entities(self.schema, strict=True)
+ def custom_populate(self, how_many, cursor):
+ pass
+
+ def post_populate(self, cursor):
+ pass
+
+
+ @nocoverage
+ def auto_populate(self, how_many):
+ """this method populates the database with `how_many` entities
+ of each possible type. It also inserts random relations between them
+ """
+ cu = self.cursor()
+ self.custom_populate(how_many, cu)
+ vreg = self.vreg
+ howmanydict = how_many_dict(self.schema, cu, how_many, self.no_auto_populate)
+ for etype in unprotected_entities(self.schema):
+ if etype in self.no_auto_populate:
+ continue
+ nb = howmanydict.get(etype, how_many)
+ for rql, args in insert_entity_queries(etype, self.schema, vreg, nb):
+ cu.execute(rql, args)
+ edict = {}
+ for etype in unprotected_entities(self.schema, strict=True):
+ rset = cu.execute('%s X' % etype)
+ edict[str(etype)] = set(row[0] for row in rset.rows)
+ existingrels = {}
+ ignored_relations = SYSTEM_RELATIONS | self.ignored_relations
+ for rschema in self.schema.relations():
+ if rschema.final or rschema in ignored_relations:
+ continue
+ rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
+ existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
+ q = make_relations_queries(self.schema, edict, cu, ignored_relations,
+ existingrels=existingrels)
+ for rql, args in q:
+ try:
+ cu.execute(rql, args)
+ except ValidationError, ex:
+ # failed to satisfy some constraint
+ print 'error in automatic db population', ex
+ self.post_populate(cu)
+ self.commit()
+
+ def iter_individual_rsets(self, etypes=None, limit=None):
+ etypes = etypes or self.to_test_etypes()
+ for etype in etypes:
+ if limit:
+ rql = 'Any X LIMIT %s WHERE X is %s' % (limit, etype)
+ else:
+ rql = 'Any X WHERE X is %s' % etype
+ rset = self.execute(rql)
+ for row in xrange(len(rset)):
+ if limit and row > limit:
+ break
+ # XXX iirk
+ rset2 = rset.limit(limit=1, offset=row)
+ yield rset2
+
def iter_automatic_rsets(self, limit=10):
"""generates basic resultsets for each entity type"""
etypes = self.to_test_etypes()
@@ -275,54 +816,6 @@
for rql in self.application_rql:
yield self.execute(rql)
-
- def list_views_for(self, rset):
- """returns the list of views that can be applied on `rset`"""
- req = rset.req
- only_once_vids = ('primary', 'secondary', 'text')
- req.data['ex'] = ValueError("whatever")
- viewsvreg = self.vreg['views']
- for vid, views in viewsvreg.items():
- if vid[0] == '_':
- continue
- if rset.rowcount > 1 and vid in only_once_vids:
- continue
- views = [view for view in views
- if view.category != 'startupview'
- and not issubclass(view, NotificationView)]
- if views:
- try:
- view = viewsvreg.select_best(views, req, rset=rset)
- if view.linkable():
- yield view
- else:
- not_selected(self.vreg, view)
- # else the view is expected to be used as subview and should
- # not be tested directly
- except NoSelectableObject:
- continue
-
- def list_actions_for(self, rset):
- """returns the list of actions that can be applied on `rset`"""
- req = rset.req
- for action in self.vreg['actions'].possible_objects(req, rset=rset):
- yield action
-
- def list_boxes_for(self, rset):
- """returns the list of boxes that can be applied on `rset`"""
- req = rset.req
- for box in self.vreg['boxes'].possible_objects(req, rset=rset):
- yield box
-
- def list_startup_views(self):
- """returns the list of startup views"""
- req = self.request()
- for view in self.vreg['views'].possible_views(req, None):
- if view.category == 'startupview':
- yield view.id
- else:
- not_selected(self.vreg, view)
-
def _test_everything_for(self, rset):
"""this method tries to find everything that can be tested
for `rset` and yields a callable test (as needed in generative tests)
@@ -334,24 +827,32 @@
propdefs[k]['default'] = True
for view in self.list_views_for(rset):
backup_rset = rset.copy(rset.rows, rset.description)
- yield InnerTest(self._testname(rset, view.id, 'view'),
- self.view, view.id, rset,
+ yield InnerTest(self._testname(rset, view.__regid__, 'view'),
+ self.view, view.__regid__, rset,
rset.req.reset_headers(), 'main-template')
# We have to do this because some views modify the
# resultset's syntax tree
rset = backup_rset
for action in self.list_actions_for(rset):
- yield InnerTest(self._testname(rset, action.id, 'action'), self._test_action, action)
+ yield InnerTest(self._testname(rset, action.__regid__, 'action'), self._test_action, action)
for box in self.list_boxes_for(rset):
- yield InnerTest(self._testname(rset, box.id, 'box'), box.render)
+ yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render)
@staticmethod
def _testname(rset, objid, objtype):
return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype)
-class AutomaticWebTest(WebTest):
+# concrete class for automated application testing ############################
+
+class AutomaticWebTest(AutoPopulateTest):
"""import this if you wan automatic tests to be ran"""
+ def setUp(self):
+ AutoPopulateTest.setUp(self)
+ # access to self.app for proper initialization of the authentication
+ # machinery (else some views may fail)
+ self.app
+
## one each
def test_one_each_config(self):
self.auto_populate(1)
@@ -373,17 +874,7 @@
yield self.view, vid, None, req
-class RealDBTest(WebTest):
-
- def iter_individual_rsets(self, etypes=None, limit=None):
- etypes = etypes or unprotected_entities(self.schema, strict=True)
- for etype in etypes:
- rset = self.execute('Any X WHERE X is %s' % etype)
- for row in xrange(len(rset)):
- if limit and row > limit:
- break
- rset2 = rset.limit(limit=1, offset=row)
- yield rset2
+# registry instrumentization ###################################################
def not_selected(vreg, appobject):
try:
@@ -391,16 +882,17 @@
except (KeyError, AttributeError):
pass
+
def vreg_instrumentize(testclass):
+ # XXX broken
from cubicweb.devtools.apptest import TestEnvironment
- env = testclass._env = TestEnvironment('data', configcls=testclass.configcls,
- requestcls=testclass.requestcls)
+ env = testclass._env = TestEnvironment('data', configcls=testclass.configcls)
for reg in env.vreg.values():
reg._selected = {}
try:
orig_select_best = reg.__class__.__orig_select_best
except:
- orig_select_best = reg.__class__.select_best
+ orig_select_best = reg.__class__._select_best
def instr_select_best(self, *args, **kwargs):
selected = orig_select_best(self, *args, **kwargs)
try:
@@ -410,9 +902,10 @@
except AttributeError:
pass # occurs on reg used to restore database
return selected
- reg.__class__.select_best = instr_select_best
+ reg.__class__._select_best = instr_select_best
reg.__class__.__orig_select_best = orig_select_best
+
def print_untested_objects(testclass, skipregs=('hooks', 'etypes')):
for regname, reg in testclass._env.vreg.iteritems():
if regname in skipregs:
--- a/doc/book/README Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/README Mon Feb 08 13:35:07 2010 +0100
@@ -9,6 +9,8 @@
Chapter
=======
+.. _Level1AnchorForLaterReference:
+
Level 1 section
---------------
@@ -24,20 +26,29 @@
inline directives:
- :file:
- :envvar:
- :command:
+ :file:`directory/file`
+ :envvar:`AN_ENV_VARIABLE`
+ :command:`command --option arguments`
:ref:, :mod:
-XXX
-* lien vers cw.cwconfig.CW_CUBES_PATH par ex.
-
-
.. sourcecode:: python
class SomePythonCode:
...
-.. XXX a comment
+.. XXX a comment, wont be rendered
+
+
+a [foot note]_
+
+.. [foot note] the foot note content
+
+
+
+XXX
+* lien vers cw.cwconfig.CW_CUBES_PATH par ex.
+
+
+automodule, autofunction, automethod, autofunction
--- a/doc/book/en/.templates/layout.html Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/.templates/layout.html Mon Feb 08 13:35:07 2010 +0100
@@ -111,6 +111,7 @@
<script type="text/javascript" src="{{ pathto('_static/jquery.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/interface.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/doctools.js', 1) }}"></script>
+ <script type="text/javascript" src="{{ pathto('_static/searchtools.js', 1) }}"></script>
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="Search within {{ docstitle }}"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/admin/additional-tips.rst Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,55 @@
+
+.. _Additional Tips:
+
+Additional Tips
+---------------
+
+Here are some additional tips as far as administration of a CubicWeb is concerned.
+
+Backup, backup, backup
+``````````````````````
+
+It is always a good idea to backup. If your system does not do that,
+you should set it up. Note that whenever you do an upgrade,
+`cubicweb-ctl` offers you to backup your database.
+
+There are a number of ways for doing backups. Before you go ahead,
+make sure the following permissions are correct ::
+
+ # chgrp postgres /var/lib/cubicweb/backup
+
+ # chmod g+ws /var/lib/cubicweb/backup
+
+ # chgrp postgres /etc/cubicweb.d/*<instance>*/sources
+
+ # chmod g+r /etc/cubicweb.d/*<instance>*/sources
+
+**Classic way**
+
+Simply use the pg_dump in a cron ::
+
+ pg_dump -Fc --username=cubicweb --no-owner --file=/var/lib/cubicweb/backup/<instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
+
+**CubicWeb way**
+
+The CubicWeb way is to use the `db-dump` command. For that, you have to put your passwords in a user-only-readable file at the
+root of the postgres user. The file is `.pgpass` (`chmod 0600`), in this case for a socket run connection to postgres ::
+
+ /var/run/postgresql:5432:<instance>:cubicweb:<password>
+
+The postgres documentation for the `.pgpass` format can be found `here`_
+
+Then add the following command to the crontab of the postgres user (`su posgres 'crontab -e'`)::
+
+ # m h dom mon dow command
+ 0 2 * * * cubicweb-ctl db-dump <instance>
+
+**The automated sysadmin way**
+
+You can use a combination `backup-ninja`_ (which has a postgres script in the example directory), `backuppc`)_ (for versionning).
+
+Please note that in the *CubicWeb way* it adds a second location for your password which is error-prone.
+
+.. _`here` : http://www.postgresql.org/docs/current/static/libpq-pgpass.html
+.. _`backup-ninja` : https://labs.riseup.net/code/projects/show/backupninja/
+.. _`backuppc` : http://backuppc.sourceforge.net/
--- a/doc/book/en/admin/gae.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/admin/gae.rst Mon Feb 08 13:35:07 2010 +0100
@@ -132,7 +132,7 @@
Otherwise, please skip it.
You will never need to add new entries in the translation catalog. Instead we
-would recommand you to use ``self.req._("msgId")`` in your code to flag new
+would recommand you to use ``self._cw._("msgId")`` in your code to flag new
message id to add to the catalog, where ``_`` refers to xgettext that is used to
collect new strings to translate. While running ``laxctl i18ncube``, new string
will be added to the catalogs.
--- a/doc/book/en/admin/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/admin/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -20,6 +20,7 @@
multisources
ldap
gae
+ additional-tips
RQL logs
--------
--- a/doc/book/en/admin/setup.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/admin/setup.rst Mon Feb 08 13:35:07 2010 +0100
@@ -53,6 +53,8 @@
.. _`Logilab's gnupg key`: http://ftp.logilab.org/dists/logilab-dists-key.asc
+.. _SourceInstallation:
+
Install from source
```````````````````
@@ -83,6 +85,8 @@
Make sure you have installed the dependencies (see appendixes for the list).
+.. _WindowsInstallation:
+
Windows installation
````````````````````
--- a/doc/book/en/conf.py Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/conf.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,6 +19,7 @@
# serve to show the default value.
import sys, os
+from cubicweb import __pkginfo__ as cw
# If your extensions are in another directory, add it here. If the directory
# is relative to the documentation root, use os.path.abspath to make it
@@ -42,16 +43,16 @@
master_doc = 'index'
# General substitutions.
-project = 'Cubicweb'
+project = 'CubicWeb'
copyright = '2008-2010, Logilab'
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
-version = '3.5'
+version = '.'.join(str(n) for n in cw.numversion[:2])
# The full version, including alpha/beta/rc tags.
-release = '3.5'
+release = cw.version
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
--- a/doc/book/en/development/cubes/cc-newcube.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/cubes/cc-newcube.rst Mon Feb 08 13:35:07 2010 +0100
@@ -4,17 +4,16 @@
Let's start by creating the cube environment in which we will develop ::
cd ~/hg
-
- cubicweb-ctl newcube mycube
-
- # answer questions
- hg init moncube
+ # use cubicweb-ctl to generate a template for the cube
+ cubicweb-ctl newcube mycube # will ask some questions, most with nice default
+ # makes the cube source code managed by mercurial
cd mycube
+ hg init
hg add .
hg ci
If all went well, you should see the cube you just created in the list
-returned by ``cubicweb-ctl list`` in the section *Available components*,
+returned by ``cubicweb-ctl list`` in the section *Available cubes*,
and if it is not the case please refer to :ref:`ConfigurationEnv`.
To reuse an existing cube, add it to the list named ``__use__`` and defined in
@@ -24,22 +23,26 @@
work otherwise).
.. note::
- Please note that if you do not wish to use default directory
- for your cubes library, then you want to use the option
- --directory to specify where you would like to place
- the source code of your cube:
- ``cubicweb-ctl newcube --directory=/path/to/cubes/library cube_name``
+
+ Please note that if you do not wish to use default directory for your cubes
+ library, you should set the :envvar:`CW_CUBES_PATH` environment variable to
+ add extra directories where cubes will be search, and you'll then have to use
+ the option `--directory` to specify where you would like to place the source
+ code of your cube:
+
+ ``cubicweb-ctl newcube --directory=/path/to/cubes/library mycube``
-Usage of :command:`cubicweb-ctl liveserver`
--------------------------------------------
+.. XXX resurrect once live-server is back
+.. Usage of :command:`cubicweb-ctl liveserver`
+.. -------------------------------------------
-To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
-which allows to create an instance in memory (using an SQLite database by
-default) and make it accessible through a web server ::
+.. To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
+.. which allows to create an instance in memory (using an SQLite database by
+.. default) and make it accessible through a web server ::
- cubicweb-ctl live-server mycube
+.. cubicweb-ctl live-server mycube
-or by using an existing database (SQLite or Postgres)::
+.. or by using an existing database (SQLite or Postgres)::
- cubicweb-ctl live-server -s myfile_sources mycube
+.. cubicweb-ctl live-server -s myfile_sources mycube
--- a/doc/book/en/development/cubes/layout.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/cubes/layout.rst Mon Feb 08 13:35:07 2010 +0100
@@ -29,6 +29,7 @@
|
|-- i18n/
| |-- en.po
+ | |-- es.po
| `-- fr.po
|
|-- __init__.py
@@ -78,7 +79,7 @@
* ``schema`` contains the schema definition (server side only)
* ``entities`` contains the entities definition (server side and web interface)
-* ``sobjects`` contains hooks and/or views notifications (server side only)
+* ``hooks`` contains hooks and/or views notifications (server side only)
* ``views`` contains the web interface components (web interface only)
* ``test`` contains tests related to the cube (not installed)
* ``i18n`` contains message catalogs for supported languages (server side and
--- a/doc/book/en/development/datamodel/baseschema.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/datamodel/baseschema.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,3 +1,4 @@
+.. _CWBaseEntityTypes:
Pre-defined entities in the library
-----------------------------------
@@ -8,31 +9,35 @@
Entity types used to store the schema
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* `CWEType`, entity type
-* `CWRType`, relation type
-* `CWRelation`, relation definition
-* `CWAttribute`, attribute relation definition
-* `CWConstraint`, `CWConstraintType`, `RQLExpression`
+* _`CWEType`, entity type
+* _`CWRType`, relation type
+* _`CWRelation`, relation definition
+* _`CWAttribute`, attribute relation definition
+* _`CWConstraint`, `CWConstraintType`, `RQLExpression`
Entity types used to manage users and permissions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* `CWUser`, system users
-* `CWGroup`, users groups
-* `CWPermission`, used to configure the security of the instance
+* _`CWUser`, system users
+* _`CWGroup`, users groups
+* _`CWPermission`, used to configure the security of the instance
Entity types used to manage workflows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* `State`, workflow state
-* `Transition`, workflow transition
-* `TrInfo`, record of a transition trafic for an entity
+* _`Workflow`, workflow entity, linked to some entity types which may use this workflow
+* _`State`, workflow state
+* _`Transition`, workflow transition
+* _`TrInfo`, record of a transition trafic for an entity
Other entity types
~~~~~~~~~~~~~~~~~~
-* `CWCache`, cache entities used to improve performances
-* `CWProperty`, used to configure the instance
+* _`CWCache`, cache entities used to improve performances
+* _`CWProperty`, used to configure the instance
-* `EmailAddress`, email address, used by the system to send notifications
+* _`EmailAddress`, email address, used by the system to send notifications
to the users and also used by others optionnals schemas
-* `Bookmark`, an entity type used to allow a user to customize his links within
+* _`Bookmark`, an entity type used to allow a user to customize his links within
the instance
+
+* _`ExternalUri`, used for semantic web site to indicate that an entity is the
+ same as another from an external site
--- a/doc/book/en/development/datamodel/define-workflows.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/datamodel/define-workflows.rst Mon Feb 08 13:35:07 2010 +0100
@@ -122,9 +122,8 @@
If we use an RQL condition on a transition, we can use the following variables:
-* `%(eid)s`, object's eid
-* `%(ueid)s`, user executing the query eid
-* `%(seid)s`, the object's current state eid
+* `X`, the entity on which we may pass the transition
+* `U`, the user executing that may pass the transition
.. image:: ../../images/03-transitions-view.en.png
--- a/doc/book/en/development/datamodel/definition.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/datamodel/definition.rst Mon Feb 08 13:35:07 2010 +0100
@@ -5,66 +5,73 @@
The **schema** is the core piece of a *CubicWeb* instance as it defines
the handled data model. It is based on entity types that are either already
-defined in the *CubicWeb* standard library; or more specific types, that
-*CubicWeb* expects to find in one or more Python files under the directory
-`schema`.
+defined in the *CubicWeb* standard library; or more specific types defined
+in cubes. The schema for a cube is defined in a :file:schema.py file or in
+one or more Python files under the :file:`schema` directory (python package).
At this point, it is important to make clear the difference between
*relation type* and *relation definition*: a *relation type* is only a relation
-name with potentially other additionnal properties (see XXXX), whereas a
+name with potentially other additionnal properties (see below), whereas a
*relation definition* is a complete triplet
"<subject entity type> <relation type> <object entity type>".
A relation type could have been implied if none is related to a
relation definition of the schema.
+Also, it should be clear that to properly handle data migration, an instance'schema
+is stored in the database, so the python schema file used to defined it are only readen
+when the instance is created or upgraded.
-All *CubicWeb* built-in types are available : `String`, `Int`, `Float`,
+The following built-in types are available : `String`, `Int`, `Float`,
`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte`
and `Password`.
-They are implicitely imported (as well as the special the function "_"
-for translation :ref:`internationalization`).
+
+You'll also have access to :ref:`base cubicweb entity types <CWBaseEntityTypes>`.
-The instance schema is defined on all appobjects by a .schema class attribute set
-on registration. It's an instance of :class:`yams.schema.Schema`.
+The instance schema is accessible through the .schema attribute of the
+`vregistry`. It's an instance of :class:`cubicweb.schema.Schema`, which
+extends :class:`yams.schema.Schema`.
+
+:note:
+ In previous yams versions, almost all classes where available without
+ any import, but the should now be explicitely imported.
+
Entity type
~~~~~~~~~~~
-It's an instance of :class:`yams.schema.EntitySchema`
+It's an instance of :class:`yams.schema.EntitySchema`. Each entity types has
+a set of attributes and relation and some permissions, defining who can add, read,
+update or delete entities of this type.
-XXX meta
-XXX permission
XXX yams inheritance
Relation type
~~~~~~~~~~~~~
-It's an instance of :class:`yams.schema.RelationSchema`
+It's an instance of :class:`yams.schema.RelationSchema`. A relation type is simply
+a semantic definition of a kind of relationship that may occurs in your application.
-In addition to the permissions, the properties of the relation types
-(shared also by all definition of relation of this type) are :
+It's important to choose a good name, at least to avoid conflicts with some semantically
+different relation defined in other cubes (since we've no namespace yet).
+A relation type hold the following properties (which are hence shared between all
+relation definitions of that type):
* `inlined` : boolean handling the physical optimization for archiving
the relation in the subject entity table, instead of creating a specific
- table for the relation. This applies to the relation when the cardinality
- of subject->relation->object is 0..1 (`?`) or 1..1 (`1`)
+ table for the relation. This applies to relations where cardinality
+ of subject->relation->object is 0..1 (`?`) or 1..1 (`1`) for *all* its relation
+ definitions.
* `symmetric` : boolean indicating that the relation is symmetrical, which
- means `X relation Y` implies `Y relation X`
-
-XXX meta
-XXX permission
+ means that `X relation Y` implies `Y relation X`.
Relation definition
~~~~~~~~~~~~~~~~~~~
-Relation definition are represented in yams using an internal structure only exposed through the :mod:`api <yams.schema>`.
+It's an instance of :class:`yams.schema.RelationDefinition`. It is a complete triplet
+"<subject entity type> <relation type> <object entity type>".
Properties
``````````
-Properties defined below are accessible through the following api:
-
- RelationSchema.rproperties()
- RelationSchema.rproperty(subjtype, objtype, property name)
* Optional properties for attributes and relations :
@@ -88,13 +95,8 @@
* `+`: 1..n
* `*`: 0..n
- - `meta` : boolean indicating that the relation is a meta-relation (false by
- default, will disappear in *CubicWeb* 3.5)
-
* optional properties for attributes :
- - `required` : boolean indicating if the attribute is required (false by default)
-
- `unique` : boolean indicating if the value of the attribute has to be unique
or not within all entities of the same type (false by default)
@@ -106,8 +108,6 @@
- `default` : default value of the attribute. In case of date types, the values
which could be used correspond to the RQL keywords `TODAY` and `NOW`.
- - `vocabulary` : specify static possible values of an attribute
-
* optional properties of type `String` :
- `fulltextindexed` : boolean indicating if the attribute is part of
@@ -117,22 +117,24 @@
- `internationalizable` : boolean indicating if the value of the attribute
is internationalizable (false by default)
- - `maxsize` : integer providing the maximum size of the string (no limit by default)
-
* optional properties for relations :
- `composite` : string indicating that the subject (composite == 'subject')
is composed of the objects of the relations. For the opposite case (when
the object is composed of the subjects of the relation), we just set
'object' as value. The composition implies that when the relation
- is deleted (so when the composite is deleted), the composed are also deleted.
+ is deleted (so when the composite is deleted, at least), the composed are also deleted.
- `fti_container`: XXX feed me
Constraints
```````````
+
By default, the available constraint types are :
+General Constraints
+......................
+
* `SizeConstraint` : allows to specify a minimum and/or maximum size on
string (generic case of `maxsize`)
@@ -143,8 +145,22 @@
* `StaticVocabularyConstraint` : identical to "vocabulary=(...)"
+XXX Attribute, TODAY, NOW
+
+RQL Based Constraints
+......................
+
+RQL based constraints may take three arguments. The first one is the ``WHERE``
+clause of a RQL query used by the constraint. The second argument ``mainvars``
+is the ``Any`` clause of the query. By default this include `S` reserved for the
+subject of the relation and `O` for the object. Additional variables could be
+specified using ``mainvars``. The argument expects a single string with all
+variable's name separated by spaces. The last one, ``msg``, is the error message
+displayed when the constraint fails. As RQLVocabularyConstraint never fails the
+third argument is not available.
+
* `RQLConstraint` : allows to specify a RQL query that has to be satisfied
- by the subject and/or the object of the relation. In this query the variables
+ by the subject and/or the object of relation. In this query the variables
`S` and `O` are reserved for the entities subject and object of the
relation.
@@ -153,6 +169,44 @@
restrict the values listed in the drop-down menu of editing form, but it does
not prevent another entity to be selected.
+* `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an
+ attribute is unique in a specific context. The Query must **never** return more
+ than a single result to be satisfied. In this query the variables `S` is
+ reserved for the entity subject of the relation. The other variable should be
+ specified with the second constructor argument (mainvars). This constraints
+ should be used when UniqueConstraint doesn't fit. Here is a simple example ::
+
+ # Check that in the same Workflow each state's name is unique. Using
+ # UniqueConstraint (or unique=True) here would prevent states in different
+ # workflows to have the same name.
+
+ # With: State S, Workflow W, String N ; S state_of W, S name N
+
+ RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N',
+ mainvars='Y',
+ msg=_('workflow already have a state of that name'))
+
+
+
+* `RQLUniqueConstraint` : allows to the specify a RQL query that ensure that an
+ attribute is unique in a specific context. The Query must **never** return more
+ than a single result to be satisfied. In this query the variables `S` is
+ reserved for the entity subject of the relation. The other variable should be
+ specified with the second constructor argument (mainvars). This constraints
+ should be used when UniqueConstraint doesn't fit. Here is a simple example ::
+
+ # Check that in the same Workflow each state's name is unique. Using
+ # UniqueConstraint (or unique=True) here would prevent states in different
+ # workflows to have the same name.
+
+ # With: State S, Workflow W, String N ; S state_of W, S name N
+
+ RQLUniqueConstraint('S name N, S state_of WF, Y state_of WF, Y name N',
+ mainvars='Y',
+ msg=_('workflow already have a state of that name'))
+
+
+
XXX note about how to add new constraint
@@ -185,7 +239,7 @@
actions if all the other groups the user belongs to does not provide
those permissions
-Setting permissions is done with the attribute `permissions` of entities and
+Setting permissions is done with the attribute `__permissions__` of entities and
relation types. It defines a dictionary where the keys are the access types
(action), and the values are the authorized groups or expressions.
@@ -217,7 +271,7 @@
Use of RQL expression for write permissions
- ```````````````````````````````````````````
+```````````````````````````````````````````
It is possible to define RQL expression to provide update permission
(`add`, `delete` and `update`) on relation and entity types.
@@ -295,8 +349,17 @@
The class name corresponds to the entity type name. It is exepected to be
defined in the module ``mycube.schema``.
+When defining a schema using python files, you may use the following shortcuts:
-For example ::
+- `required` : boolean indicating if the attribute is required, eg subject cardinality is '1'
+
+- `vocabulary` : specify static possible values of an attribute
+
+- `maxsize` : integer providing the maximum size of a string (no limit by default)
+
+For example:
+
+.. sourcecode:: python
class Person(EntityType):
"""A person with the properties and the relations necessary for my
@@ -319,7 +382,7 @@
An attribute is defined in the schema as follows::
- attr_name = attr_type(properties*)
+ attr_name = attr_type(properties)
where `attr_type` is one of the type listed above and `properties` is
a list of the attribute needs to statisfy (see :ref:`properties`
@@ -344,6 +407,10 @@
* it is possible to use the attribute `meta` to flag an entity type as a `meta`
(e.g. used to describe/categorize other entities)
+*Note* : if you end up with an `if` in the definition of your entity, this probably
+means that you need two separate entities that implement the `ITree` interface and
+get the result from `.children()` which ever entity is concerned.
+
Inheritance
```````````
XXX feed me
@@ -379,56 +446,54 @@
Definition of permissions
~~~~~~~~~~~~~~~~~~~~~~~~~~
-
The entity type `CWPermission` from the standard library
allows to build very complex and dynamic security architectures. The schema of
-this entity type is as follow : ::
+this entity type is as follow :
+
+.. sourcecode:: python
- class CWPermission(MetaEntityType):
- """entity type that may be used to construct some advanced security configuration"""
- name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
- require_group = SubjectRelation('CWGroup', cardinality='+*',
- description=_('groups to which the permission is granted'))
- require_state = SubjectRelation('State',
+ class CWPermission(EntityType):
+ """entity type that may be used to construct some advanced security configuration
+ """
+ name = String(required=True, indexed=True, internationalizable=True, maxsize=100)
+ require_group = SubjectRelation('CWGroup', cardinality='+*',
+ description=_('groups to which the permission is granted'))
+ require_state = SubjectRelation('State',
description=_("entity's state in which the permission is applicable"))
- # can be used on any entity
- require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
- description=_("link a permission to the entity. This "
- "permission should be used in the security "
- "definition of the entity's type to be useful."))
+ # can be used on any entity
+ require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
+ description=_("link a permission to the entity. This "
+ "permission should be used in the security "
+ "definition of the entity's type to be useful."))
-Example of configuration ::
+Example of configuration:
-
- ...
+.. sourcecode:: python
class Version(EntityType):
- """a version is defining the content of a particular project's release"""
+ """a version is defining the content of a particular project's release"""
- permissions = {'read': ('managers', 'users', 'guests',),
- 'update': ('managers', 'logilab', 'owners',),
- 'delete': ('managers', ),
- 'add': ('managers', 'logilab',
- ERQLExpression('X version_of PROJ, U in_group G,'
- 'PROJ require_permission P, P name "add_version",'
- 'P require_group G'),)}
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
+ 'update': ('managers', 'logilab', 'owners',),
+ 'delete': ('managers', ),
+ 'add': ('managers', 'logilab',
+ ERQLExpression('X version_of PROJ, U in_group G,'
+ 'PROJ require_permission P, P name "add_version",'
+ 'P require_group G'),)}
- ...
class version_of(RelationType):
- """link a version to its project. A version is necessarily linked to one and only one project.
- """
- subject = 'Version'
- object = 'Project'
- cardinality = '?*'
- permissions = {'read': ('managers', 'users', 'guests',),
- 'delete': ('managers', ),
- 'add': ('managers', 'logilab',
- RRQLExpression('O require_permission P, P name "add_version",'
- 'U in_group G, P require_group G'),)
- }
- inlined = True
+ """link a version to its project. A version is necessarily linked to one and only one project.
+ """
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
+ 'delete': ('managers', ),
+ 'add': ('managers', 'logilab',
+ RRQLExpression('O require_permission P, P name "add_version",'
+ 'U in_group G, P require_group G'),)
+ }
+ inlined = True
+
This configuration indicates that an entity `CWPermission` named
"add_version" can be associated to a project and provides rights to create
--- a/doc/book/en/development/datamodel/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/datamodel/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -10,4 +10,3 @@
metadata
baseschema
define-workflows
- inheritance
--- a/doc/book/en/development/datamodel/inheritance.rst Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-
-Inheritance
------------
-
-When describing a data model, entities can inherit from other entities as is
-common in object-oriented programming.
-
-You have the possibility to adapt some entity attributes, as follow:
-
-.. sourcecode:: python
-
- from cubes.OTHER_CUBE import entities
- class EntityExample(entities.EntityExample):
- def dc_long_title(self):
- return '%s (%s)' % (self.name, self.description)
-
-
-XXX WRITME
--- a/doc/book/en/development/datamodel/metadata.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/datamodel/metadata.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,43 +1,54 @@
Meta-data
-----------
+---------
-Each entity type has at least the following meta-relations:
+.. index::
+ schema: meta-data;
+ schema: eid; creation_date; modification_data; cwuri
+ schema: created_by; owned_by; is; is_instance;
+
+Each entity type in |cubicweb| has at least the following meta-data attributes and relations:
eid
-~~~
-Each entity in *CubicWeb* has an associated identifier which is unique
-in an instance. We usually call this identifier `eid`.
+ entity's identifier which is unique in an instance. We usually call this identifier `eid` for historical reason.
+
+creation_date
+ Date and time of the creation of the entity.
+
+modification_date
+ Date and time of the latest modification of an entity.
+
+cwuri
+ Reference URL of the entity, which is not expected to change.
-`creation_date` and `modification_date`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Date and time of the creation / lastest modification of an entity.
+created_by
+ Relation to the :ref:`users <CWUser>` who has created the entity
+
+owned_by
+ Relation to :ref:`users <CWUser>` whom the entity belongs; usually the creator but not
+ necessary, and it could have multiple owners notably for permission control
+
+is
+ Relation to the :ref:`entity type <CWEType>` of which type the entity is.
+
+is_instance
+ Relation to the :ref:`entity types <CWEType>` of which type the
+ entity is an instance of.
-`created_by`
-~~~~~~~~~~~~
-relation to the :ref:`users <CWUser>` who has created the entity
+Virtual RQL relations
+---------------------
+XXX move this to the RQL chapter.
+
+Those relations may only be used in RQL query and are not actual attributes of your entities...
-`owned_by`
-~~~~~~~~~~
-relation to :ref:`users <CWUser>` whom the entity belongs; usually the creator but not
-necessary, and it could have multiple owners notably for permission control
+has_text
+ Relation to use to query the full text index (only for entities having fulltextindexed attributes).
-`is`
-~~~~~
-relation to the :ref:`entity type <CWEType>` of which type the entity is.
-
-`is_instance`
-~~~~~~~~~~~~~
-relation to the :ref:`entity types <CWEType>` of which type the entity is an instance of.
+identity
+ Relation to use to tell that a RQL variable should be the same as entity another
+ (but you've to use two different rql variables for querying purpose)
-Special relations
------------------
-`has_text`
-~~~~~~~~~~
-query the full text index (only for entities having fulltextindexed attributes)
-`identity`
-~~~~~~~~~~
-XXX
\ No newline at end of file
+.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
--- a/doc/book/en/development/devcore/appobject.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devcore/appobject.rst Mon Feb 08 13:35:07 2010 +0100
@@ -25,7 +25,7 @@
We also find on instances, the following attributes:
-* `req`, `Request` instance
+* ._cw`, `Request` instance
* `rset`, the *result set* associated to the object if necessary
:URL handling:
--- a/doc/book/en/development/devcore/dbapi.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devcore/dbapi.rst Mon Feb 08 13:35:07 2010 +0100
@@ -7,11 +7,11 @@
with a Connection object having the methods cursor, rollback and commit essentially.
The most important method is the `execute` method of a cursor :
-`execute(rqlstring, args=None, eid_key=None, build_descr=True)`
+`execute(rqlstring, args=None, cachekey=None, build_descr=True)`
:rqlstring: the RQL query to execute (unicode)
:args: if the query contains substitutions, a dictionary containing the values to use
-:eid_key:
+:cachekey:
an implementation detail of the RQL cache implies that if a substitution
is used to introduce an eid *susceptible to raise the ambiguities in the query
type resolution*, then we have to specify the corresponding key in the dictionary
@@ -27,3 +27,56 @@
While executing update queries (SET, INSERT, DELETE), if a query generates
an error related to security, a rollback is automatically done on the current
transaction.
+
+Executing RQL queries from a view or a hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When you're within code of the web interface, the db-api like connexion is
+handled by the request object. You should not have to access it directly, but
+use the `execute` method directly available on the request, eg:
+
+ rset = self._cw.execute(rqlstring, kwargs)
+
+Similarly, on the server side (eg in hooks), there is no db-api connexion (since
+you're directly inside the data-server), so you'll have to use the execute method
+of the session object.
+
+
+Important note about proper usage of .execute
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's say you want to get T which is in configuration C, this translates to:
+
+.. sourcecode:: python
+
+ self._cw.execute('Any T WHERE T in_conf C, C eid %s' % entity.eid)
+
+But it can also be written in a syntax that will benefit from the use
+of a cache on the RQL server side:
+
+.. sourcecode:: python
+
+ self._cw.execute('Any T WHERE T in_conf C, C eid %(x)s', {'x': entity.eid}, 'x')
+
+Beside proper usage of the `args` argument, notice the latest argument: this is what's called
+the cache key. The cache key should be either a string or a tuple containing the names of keys
+in args which are referencing eids. *YOU MUST SET THIS PROPERLY* if you don't want weird result
+on queries which have ambigous solutions deambiguified by specifing an eid. So the good habit is:
+*always put in the cache key all eid keys*.
+
+The syntax tree is build once for the "generic" RQL and can be re-used
+with a number of different eid.
+
+Alternativelly, some of the common data related to an entity can be obtained from
+the top-level `entity.related()` method (which is used under the hood by the orm
+when you use attribute access notation on an entity to get a relation. The above
+would then be translated to:
+
+.. sourcecode:: python
+
+ entity.related('in_conf', 'object')
+
+The `related()` method, as more generally others orm methods, makes extensive use
+of the cache mechanisms so you don't have to worry about them. Additionnaly this
+use will get you commonly used attributes that you will be able to use in your
+view generation without having to ask the data backend.
+
--- a/doc/book/en/development/devcore/selectors.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devcore/selectors.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,101 +1,89 @@
Base selectors
--------------
-Selectors are scoring functions that are called by the view dispatcher to tell
-whenever a view can be applied to a given result set of a request. Selector sets
-are the glue that tie views to the data model. Using them appropriately is an
+Selectors are scoring functions that are called by the registry to tell whenever
+an appobject can be selected in a given context. Selector sets are for instance
+the glue that tie views to the data model. Using them appropriately is an
essential part of the construction of well behaved cubes.
+Of course you may have to write your own set of selectors as your needs grows and
+you get familiar with the framework (see :ref:CustomSelectors).
+
+Here is a description of generic selectors provided by CubicWeb that should suit
+most of your needs.
+
+Bare selectors
+~~~~~~~~~~~~~~
+Those selectors are somewhat dumb, which doesn't mean they're not (very) useful.
+
+.. autoclass:: cubicweb.appobject.yes
+.. autoclass:: cubicweb.selectors.match_kwargs
+.. autoclass:: cubicweb.selectors.appobject_selectable
+
-*CubicWeb* provides its own set of selectors that you can use and here is a
-description of some of the most common used:
-
-Of course you will write your own set of selectors as you get familiar with the
-framework.
+Result set selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for a result set in the context ('rset' argument or
+the input context) and match or not according to its shape. Some of these
+selectors have different behaviour if a particular cell of the result set is
+specified using 'row' and 'col' arguments of the input context or not.
-
-:yes([score=1]):
- Return the score given as parameter (default to 1). Usually used for appobjects
- which can be selected whatever the context, or also sometimes to add arbitrary
- points to a score. Take care, `yes(0)` could be named 'no'...
+.. autoclass:: cubicweb.selectors.none_rset
+.. autoclass:: cubicweb.selectors.any_rset
+.. autoclass:: cubicweb.selectors.nonempty_rset
+.. autoclass:: cubicweb.selectors.empty_rset
+.. autoclass:: cubicweb.selectors.one_line_rset
+.. autoclass:: cubicweb.selectors.multi_lines_rset
+.. autoclass:: cubicweb.selectors.multi_columns_rset
+.. autoclass:: cubicweb.selectors.paginated_rset
+.. autoclass:: cubicweb.selectors.sorted_rset
+.. autoclass:: cubicweb.selectors.one_etype_rset
+.. autoclass:: cubicweb.selectors.multi_etypes_rset
-Rset selectors
-~~~~~~~~~~~~~~
-:none_rset():
- Return 1 if the result set is None.
-
-:any_rset():
- Return 1 for any result set, whatever the number of rows in it.
-
-:nonempty_rset():
- Return 1 for non empty result set.
-
-:empty_rset():
- Return 1 for empty result set.
-
-:one_line_rset():
- Return 1 if the result set is of size 1 or if a row is specified.
+Entity selectors
+~~~~~~~~~~~~~~~~
+Those selectors are looking for either an `entity` argument in the input context,
+or entity found in the result set ('rset' argument or the input context) and
+match or not according to entity's (instance or class) properties.
-:two_lines_rset():
- Return 1 if the result set has *at least* two rows.
-
-:two_cols_rset():
- Return 1 if the result set is not empty and has *at least* two columns per
- row.
+.. autoclass:: cubicweb.selectors.non_final_entity
+.. autoclass:: cubicweb.selectors.implements
+.. autoclass:: cubicweb.selectors.score_entity
+.. autoclass:: cubicweb.selectors.rql_condition
+.. autoclass:: cubicweb.selectors.relation_possible
+.. autoclass:: cubicweb.selectors.partial_relation_possible
+.. autoclass:: cubicweb.selectors.has_related_entities
+.. autoclass:: cubicweb.selectors.partial_has_related_entities
+.. autoclass:: cubicweb.selectors.has_permission
+.. autoclass:: cubicweb.selectors.has_add_permission
-:paginated_rset():
- Return 1 if the result set has more rows the specified by the
- `navigation.page-size` property.
-
-:sorted_rset():
- Return 1 if the result set has an ORDERBY clause.
-
-:one_etype_rset():
- Return 1 if the result set has entities which are all of the same type in a
- given column (default to column 0).
-:non_final_entity():
- Return 1 if the result set contains entities in a given column (the first one
- by default), and no "final" values such as string of int.
+Logged user selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for properties of the user issuing the request.
-:implements(<iface or etype>, ...):
- Return positive score if entities in the result set are of the given entity
- type or implements given interface. If multiple arguments are given, matching
- one of them is enough. Returned score reflects the "distance" between expected
- type or interface and matched entities. Entity types are usually given as
- string, the corresponding class will be fetched from the vregistry.
+.. autoclass:: cubicweb.selectors.anonymous_user
+.. autoclass:: cubicweb.selectors.authenticated_user
+.. autoclass:: cubicweb.selectors.match_user_groups
+
-:two_etypes_rset(): XXX
-:entity_implements(): XXX
-:relation_possible(): XXX
-:partial_relation_possible(): XXX
-:may_add_relation(): XXX
-:partial_may_add_relation(): XXX
-:has_related_entities(): XXX
-:partial_has_related_entities(): XXX
-:has_permission(): XXX
-:has_add_permission(): XXX
-:rql_condition(): XXX
-:but_etype(): XXX
-:score_entity(): XXX
+Web request selectors
+~~~~~~~~~~~~~~~~~~~~~
+Those selectors are looking for properties of *web* request, they can not be
+used on the data repository side.
-Request selectors
-~~~~~~~~~~~~~~~~~~
-:anonymous_user():
- Return 1 if user isn't authenticated (eg is the anonymous user).
+.. autoclass:: cubicweb.selectors.match_form_params
+.. autoclass:: cubicweb.selectors.match_search_state
+.. autoclass:: cubicweb.selectors.match_context_prop
+.. autoclass:: cubicweb.selectors.match_view
+.. autoclass:: cubicweb.selectors.primary_view
+.. autoclass:: cubicweb.selectors.specified_etype_implements
-:authenticated_user():
- Return 1 if user is authenticated.
-
-:match_user_groups(): XXX
-:match_search_state(): XXX
-:match_form_params(): XXX
Other selectors
~~~~~~~~~~~~~~~
-:match_kwargs(): XXX
-:match_context_prop(): XXX
-:appobject_selectable(): XXX
-:specified_etype_implements(): XXX
-:primary_view(): XXX
\ No newline at end of file
+.. autoclass:: cubicweb.selectors.match_transition
+
+You'll also find some other (very) specific selectors hidden in other modules
+than :module:`cubicweb.selectors`.
\ No newline at end of file
--- a/doc/book/en/development/devcore/vreg.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devcore/vreg.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,5 +1,3 @@
-. -*- coding: utf-8 -*-
-
The VRegistry
--------------
@@ -9,21 +7,41 @@
Details of the recording process
````````````````````````````````
-XXX this part needs to be updated and checked
+.. index::
+ vregistry: registration_callback
+
+On startup, |cubicweb| have to fill the vregistry with appobjects defined
+in its library and in cubes used by the instance. Appobjects from the library
+are loaded first, then appobjects provided by cubes are loaded in an ordered
+way (e.g. if your cube depends on an other, appobjects from the dependancy will
+be loaded first). Cube's modules or packages where appobject are looked at is explained
+in :ref:`cubelayout`.
+
+For each module:
* by default all objects are registered automatically
* if some objects have to replace other objects or be included only if a
- condition is true,
- - explicitly register the object by defining `registration_callback(vreg)`
- - call registration methods on objects listed in the vreg registry
+ condition is true, you'll have to define a `registration_callback(vreg)`
+ function in your module and explicitly register *all objects* in this
+ module, using the vregistry api defined below.
.. note::
Once the function `registration_callback(vreg)` is implemented, all the objects
have to be explicitly registered as it disables the automatic object registering.
-Examples:
+API d'enregistrement des objets
+```````````````````````````````
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found
+.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister
+
+
+Examples
+````````
.. sourcecode:: python
# web/views/basecomponents.py
@@ -37,31 +55,17 @@
# goa/appobjects/sessions.py
def registration_callback(vreg):
vreg.register(SessionsCleaner)
- vreg.register(GAEAuthenticationManager, clear=True)
- vreg.register(GAEPersistentSessionManager, clear=True)
-
-
-API d'enregistrement des objets
-```````````````````````````````
-
-.. sourcecode:: python
-
- register(obj, registryname=None, oid=None, clear=False)
-
- register_all(objects, modname, butclasses=())
-
- unregister(obj, registryname=None)
-
- register_and_replace(obj, replaced, registryname=None)
-
- register_if_interface_found(obj, ifaces, **kwargs)
+ # replace AuthenticationManager by GAEAuthenticationManager
+ vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager)
+ # replace PersistentSessionManager by GAEPersistentSessionManager
+ vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
Runtime objects selection
~~~~~~~~~~~~~~~~~~~~~~~~~
-Defining selectors
-``````````````````
+Using and combining existant selectors
+``````````````````````````````````````
The object's selector is defined by its `__select__` class attribute.
@@ -72,10 +76,12 @@
means that one of them should return a positive score. On success, the first
positive score is returned.
+You can also "negate" a selector by precedeing it by the `~` operator.
+
Of course you can use paren to balance expressions.
-For instance, if you are selecting the primary (eg `id = 'primary'`) view (eg
+For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg
`__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects
will probably be selectable:
@@ -148,10 +154,34 @@
* redefine this method on Blog.
When to use selectors?
-```````````````````````
+``````````````````````
+
+Selectors are to be used whenever arises the need of dispatching on the shape or
+content of a result set or whatever else context (value in request form params,
+authenticated user groups, etc...). That is, almost all the time.
+
+XXX add and example of a single view w/ big "if" inside splitted into two views
+with appropriate selectors.
+
+
+.. CustomSelectors_
-Selectors are to be used whenever arises the need of dispatching on
-the shape or content of a result set. That is, almost all the time.
+Defining your own selectors
+```````````````````````````
+.. autoclass:: cubicweb.appobject.Selector
+ :members: __call__
+
+.. autofunction:: cubicweb.appobject.objectify_selector
+.. autofunction:: cubicweb.selectors.lltrace
+
+Selectors __call__ should *always* return a positive integer, and shall never
+return `None`.
+
+Useful abstract base classes for 'entity' selectors:
+
+.. autoclass:: cubicweb.selectors.EClassSelector
+.. autoclass:: cubicweb.selectors.EntitySelector
+
Debugging
`````````
@@ -159,28 +189,32 @@
Once in a while, one needs to understand why a view (or any AppObject)
is, or is not selected appropriately. Looking at which selectors fired
(or did not) is the way. There exists a traced_selection context
-manager to help with that.
+manager to help with that, *if you're running your instance in debug mode*.
Here is an example:
.. sourcecode:: python
- def possible_objects(self, registry, *args, **kwargs):
- """return an iterator on possible objects in a registry for this result set
-
- actions returned are classes, not instances
- """
- from cubicweb.selectors import traced_selection
- with traced_selection():
- for vobjects in self.registry(registry).values():
- try:
- yield self.select(vobjects, *args, **kwargs)
- except NoSelectableObject:
- continue
+ from cubicweb.selectors import traced_selection
+ with traced_selection():
+ mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset)
Don't forget the 'from __future__ import with_statement' at the module
-top-level.
+top-level if you're using python 2.5.
This will yield additional WARNINGs in the logs, like this::
2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
+
+You can also give to traced_selection the registry ids of objects on which to debug
+you want to debug selection ('wfhistory' in the example above).
+
+Also, if you're using python 2.4, which as no 'with' yet, you'll have to to it
+the following way:
+
+.. sourcecode:: python
+
+ from cubicweb import selectors
+ selectors.TRACED_OIDS = ('wfhistory',)
+ mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset)
+ selectors.TRACED_OIDS = ()
--- a/doc/book/en/development/devweb/facets.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/facets.rst Mon Feb 08 13:35:07 2010 +0100
@@ -111,7 +111,7 @@
class CompositionDateFacet(DateRangeFacet):
# 1. make sure this facet is displayed only on Track selection
__select__ = DateRangeFacet.__select__ & implements('Track')
- # 2. give the facet an id (required by CubicWeb)
+ # 2. give the facet an id required by CubicWeb)
id = 'compdate-facet'
# 3. specify the attribute name that actually stores the date in the DB
rtype = 'composition_date'
--- a/doc/book/en/development/devweb/form.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/form.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,28 +1,70 @@
Form construction
------------------
+CubicWeb provides usual form/field/widget/renderer abstraction to provde
+some generic building blocks which will greatly help you in building forms
+properly integrated with |cubicweb| (coherent display, error handling, etc...)
-Forms
-~~~~~
-XXX feed me
-:Vocabulary control on relations:
+A form basically only hold a set of fields, and is bound to a renderer that is
+responsible to layout them. Each field is bound to a widget that will be used
+to fill in value(s) for that field.
+
+The Field class and basic fields
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: cubicweb.web.formfields.Field
+
+
+Existing field types are:
- * `vocabulary(rtype, x='subject', limit=None)`, called by the
- editing views, it returns a list of couples (label, eid) of entities
- that could be related to the entity by the relation `rtype`
- * `subject_relation_vocabulary(rtype, limit=None)`, called internally
- by `vocabulary` in the case of a subject relation
- * `object_relation_vocabulary(rtype, limit=None)`, called internally
- by `vocabulary` in the case of an object relation
- * `relation_vocabulary(rtype, targettype, x, limit=None)`, called
- internally by `subject_relation_vocabulary` and `object_relation_vocabulary`
+.. autoclass:: cubicweb.web.formfields.StringField
+.. autoclass:: cubicweb.web.formfields.PasswordField
+.. autoclass:: cubicweb.web.formfields.RichTextField
+.. autoclass:: cubicweb.web.formfields.FileField
+.. autoclass:: cubicweb.web.formfields.EditableFileField
+.. autoclass:: cubicweb.web.formfields.IntField
+.. autoclass:: cubicweb.web.formfields.BooleanField
+.. autoclass:: cubicweb.web.formfields.FloatField
+.. autoclass:: cubicweb.web.formfields.DateField
+.. autoclass:: cubicweb.web.formfields.DateTimeField
+.. autoclass:: cubicweb.web.formfields.TimeField
+.. autoclass:: cubicweb.web.formfields.RelationField
+.. XXX still necessary?
+.. autoclass:: cubicweb.web.formfields.CompoundField
-Fields
-~~~~~~
-XXX feed me
Widgets
~~~~~~~
-XXX feed me
+Base class for widget is :class:cubicweb.web.formwidgets.FieldWidget class.
+
+Existing widget types are:
+
+.. autoclass:: cubicweb.web.formwidgets.HiddenInput
+.. autoclass:: cubicweb.web.formwidgets.TextInput
+.. autoclass:: cubicweb.web.formwidgets.PasswordInput
+.. autoclass:: cubicweb.web.formwidgets.PasswordSingleInput
+.. autoclass:: cubicweb.web.formwidgets.FileInput
+.. autoclass:: cubicweb.web.formwidgets.ButtonInput
+.. autoclass:: cubicweb.web.formwidgets.TextArea
+.. autoclass:: cubicweb.web.formwidgets.FCKEditor
+.. autoclass:: cubicweb.web.formwidgets.Select
+.. autoclass:: cubicweb.web.formwidgets.CheckBox
+.. autoclass:: cubicweb.web.formwidgets.Radio
+.. autoclass:: cubicweb.web.formwidgets.DateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDateTimePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryDatePicker
+.. autoclass:: cubicweb.web.formwidgets.JQueryTimePicker
+.. autoclass:: cubicweb.web.formwidgets.AjaxWidget
+.. autoclass:: cubicweb.web.formwidgets.AutoCompletionWidget
+.. autoclass:: cubicweb.web.formwidgets.EditableURLWidget
+
+.. XXX StaticFileAutoCompletionWidget, RestrictedAutoCompletionWidget, AddComboBoxWidget, IntervalWidget, HorizontalLayoutWidget
+
+The following classes, which are not proper widget (they are not associated to
+field) but are used as form controls, may also be useful: Button, SubmitButton,
+ResetButton, ImgButton,
+
+
+Of course you can not use any widget with any field...
Renderers
~~~~~~~~~
--- a/doc/book/en/development/devweb/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -12,6 +12,7 @@
property
rtags
views
+ gettingdata
form
facets
httpcaching
--- a/doc/book/en/development/devweb/internationalization.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/internationalization.rst Mon Feb 08 13:35:07 2010 +0100
@@ -43,23 +43,23 @@
def call(self, **kwargs):
self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
- % self.req._('No result matching query'))
+ % self._cw._('No result matching query'))
The goal of the *built-in* function `_` is only **to mark the
translatable strings**, it will only return the string to translate
itself, but not its translation (it's actually another name for the
`unicode` builtin).
-In the other hand the request's method `self.req._` is meant to retrieve the
+In the other hand the request's method `self._cw._` is meant to retrieve the
proper translation of translation strings in the requested language.
Finally you can also use the `__` attribute of request object to get a
translation for a string *which should not itself added to the catalog*,
usually in case where the actual msgid is created by string interpolation ::
- self.req.__('This %s' % etype)
+ self._cw.__('This %s' % etype)
-In this example `req.__` is used instead of `req._` so we don't have 'This %s' in
+In this example ._cw.__` is used instead of ._cw._` so we don't have 'This %s' in
messages catalogs.
--- a/doc/book/en/development/devweb/js.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/js.rst Mon Feb 08 13:35:07 2010 +0100
@@ -27,7 +27,7 @@
~~~~~~~~~~~~~~~~~~~~~~~
Javascript resources are typically loaded on demand, from views. The
-request object (available as self.req from most application objects,
+request object (available as self._cw from most application objects,
for instance views and entities objects) has a few methods to do that:
* `add_js(self, jsfiles, localfile=True)` which takes a sequence of
--- a/doc/book/en/development/devweb/views.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/devweb/views.rst Mon Feb 08 13:35:07 2010 +0100
@@ -15,7 +15,7 @@
A `View` is an object applied to another object such as an entity.
Basic class for views
----------------------
+~~~~~~~~~~~~~~~~~~~~~
Class `View` (`cubicweb.view`)
`````````````````````````````````````
@@ -34,11 +34,11 @@
* the `category` attribute may be used in the interface to regroup related
objects together
-At instantiation time, the standard `req` and `rset` attributes are
+At instantiation time, the standard `_cw` and `cw_rset` attributes are
added and the `w` attribute will be set at rendering time.
A view writes to its output stream thanks to its attribute `w` (an
-`UStreamIO`).
+`UStreamIO`, except for binary views).
The basic interface for views is as follows (remember that the result set has a
tabular structure with rows and columns, hence cells):
@@ -110,7 +110,7 @@
Example of view customization and creation
-------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We'll show you now an example of a ``primary`` view and how to customize it.
@@ -157,16 +157,16 @@
rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s'
def render_entity_relations(self, entity):
- rset = self.req.execute(self.rql, {'b' : entity.eid})
+ rset = self._cw.execute(self.rql, {'b' : entity.eid})
for entry in rset.entities():
self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
class BlogEntryInBlogView(EntityView):
- 'inblogcontext'
+ id = 'inblogcontext'
__select__ = implements('BlogEntry')
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(u'<a href="%s" title="%s">%s</a>' %
entity.absolute_url(),
xml_escape(entity.content[:50]),
@@ -252,8 +252,8 @@
[FILLME]
-XML views, binaries...
-----------------------
+XML views, binaries views...
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For views generating other formats than HTML (an image generated dynamically
for example), and which can not simply be included in the HTML page generated
--- a/doc/book/en/development/entityclasses/data-as-objects.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/entityclasses/data-as-objects.rst Mon Feb 08 13:35:07 2010 +0100
@@ -54,7 +54,7 @@
* `delete()` allows to delete the entity
-Tne :class:`AnyEntity` class
+The :class:`AnyEntity` class
----------------------------
To provide a specific behavior for each entity, we have to define a class
@@ -90,3 +90,22 @@
* `dc_type(form='')`, returns a string to display the entity type by
specifying the preferred form (`plural` for a plural form)
+
+
+Inheritance
+-----------
+
+When describing a data model, entities can inherit from other entities as is
+common in object-oriented programming.
+
+You have the possibility to adapt some entity attributes, as follow:
+
+.. sourcecode:: python
+
+ from cubes.OTHER_CUBE import entities
+ class EntityExample(entities.EntityExample):
+ def dc_long_title(self):
+ return '%s (%s)' % (self.name, self.description)
+
+Notice this is different than yams schema inheritance.
+
--- a/doc/book/en/development/entityclasses/interfaces.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/entityclasses/interfaces.rst Mon Feb 08 13:35:07 2010 +0100
@@ -14,6 +14,7 @@
Interfaces defined in the library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-automodule:: cubicweb.interface :members:
+.. automodule:: cubicweb.interface
+ :members:
--- a/doc/book/en/development/migration/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/migration/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -162,11 +162,8 @@
The following functions for workflow creation are available in `repository`
scripts:
-* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, adds a new state
- in the workflow.
-
-* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`,
- adds a new transition in the workflow.
+* `add_workflow(label, workflowof, initial=False, commit=False, **kwargs)`, adds a new workflow
+ for a given type(s)
You can find more details about workflows in the chapter :ref:`Workflow` .
--- a/doc/book/en/development/webstdlib/basetemplates.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/webstdlib/basetemplates.rst Mon Feb 08 13:35:07 2010 +0100
@@ -45,25 +45,25 @@
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
self.w(u'<td id="firstcolumn">')
- self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+ self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
self.w(u'</td>\n')
# appliname and breadcrumbs
self.w(u'<td id="headtext">')
- comp = self.vreg.select_component('appliname', self.req, self.rset)
+ comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w)
- comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+ comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w, view=view)
self.w(u'</td>')
# logged user and help
#self.w(u'<td>\n')
- #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+ #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
#comp.dispatch(w=self.w)
#self.w(u'</td><td>')
self.w(u'<td>')
- helpcomp = self.vreg.select_component('help', self.req, self.rset)
+ helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
if helpcomp: # may not be available if Card is not defined in the schema
helpcomp.dispatch(w=self.w)
self.w(u'</td>')
@@ -71,7 +71,7 @@
self.w(u'<td id="lastcolumn">')
self.w(u'</td>\n')
self.w(u'</tr></table>\n')
- self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+ self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
title=False, message=False)
@@ -90,21 +90,21 @@
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
self.w(u'<td id="firstcolumn">')
- self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+ self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
self.w(u'</td>\n')
# appliname and breadcrumbs
self.w(u'<td id="headtext">')
- comp = self.vreg.select_component('appliname', self.req, self.rset)
+ comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w)
- comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+ comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w, view=view)
self.w(u'</td>')
# logged user and help
#self.w(u'<td>\n')
- #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+ #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
#comp.dispatch(w=self.w)
#self.w(u'</td><td>')
@@ -114,7 +114,7 @@
self.w(u'</td>')
self.w(u'<td>')
- helpcomp = self.vreg.select_component('help', self.req, self.rset)
+ helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
if helpcomp: # may not be available if Card is not defined in the schema
helpcomp.dispatch(w=self.w)
self.w(u'</td>')
@@ -122,12 +122,12 @@
self.w(u'<td id="lastcolumn">')
self.w(u'</td>\n')
self.w(u'</tr></table>\n')
- self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+ self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
title=False, message=False)
def get_searchbox(self, view, context):
- boxes = list(self.vreg.possible_vobjects('boxes', self.req, self.rset,
- view=view, context=context))
+ boxes = list(self._cw.vreg.poss_visible_objects('boxes', self._cw, self.cw_rset,
+ view=view, context=context))
if boxes:
for box in boxes:
if box.id == 'search_box':
--- a/doc/book/en/development/webstdlib/primary.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/development/webstdlib/primary.rst Mon Feb 08 13:35:07 2010 +0100
@@ -6,6 +6,13 @@
This view is supposed to render a maximum of informations about the entity.
+Beware when overriding this top level `cell_call` in a primary because
+you will loose a bunch of functionnality that automatically comes with
+it : `in-context` boxes, related boxes, some navigation, some
+displaying of the metadata, etc. It might be interresting to
+understand the implementation fo the `cell_call` to override specifics
+bits of it.
+
Rendering methods and attributes for ``PrimaryView``
----------------------------------------------------
@@ -33,7 +40,7 @@
def cell_call(self, row, col):
self.row = row
- self.maxrelated = self.req.property_value('navigation.related-limit')
+ self.maxrelated = self._cw.property_value('navigation.related-limit')
entity = self.complete_entity(row, col)
self.render_entity(entity)
--- a/doc/book/en/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -3,10 +3,10 @@
.. _contents:
=====================================================
-*CubicWeb* - The Semantic Web is a construction game!
+|cubicweb| - The Semantic Web is a construction game!
=====================================================
-*CubicWeb* is a semantic web application framework, licensed under the LGPL,
+|cubicweb| is a semantic web application framework, licensed under the LGPL,
that empowers developers to efficiently build web applications by reusing
components (called `cubes`) and following the well known object-oriented design
principles.
@@ -21,7 +21,7 @@
* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
Built since 2000 from an R&D effort still continued, supporting 100,000s of
-daily visits at some production sites, *CubicWeb* is a proven end to end solution
+daily visits at some production sites, |cubicweb| is a proven end to end solution
for semantic web application development that promotes quality, reusability and
efficiency.
@@ -58,3 +58,4 @@
* the :ref:`modindex`,
* and the :ref:`search`.
+.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
--- a/doc/book/en/intro/concepts/index.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/concepts/index.rst Mon Feb 08 13:35:07 2010 +0100
@@ -2,10 +2,10 @@
.. _Concepts:
-The Core Concepts of CubicWeb
-=============================
+The Core Concepts of |cubicweb|
+===============================
-This section defines some terms and core concepts of the *CubicWeb*
+This section defines some terms and core concepts of the |cubicweb|
framework. To avoid confusion while reading this book, take time to go through
the following definitions and use this section as a reference during your
reading.
@@ -24,19 +24,19 @@
and `cubicweb-comment`_ could be used to make a cube named *myblog* with
commentable blog entries.
-The `CubicWeb Forge`_ offers a large number of cubes developed by the community
+The `|cubicweb| Forge`_ offers a large number of cubes developed by the community
and available under a free software license.
-The command ``cubicweb-ctl list`` displays the list of cubes installed on your
+The command :command:`cubicweb-ctl list` displays the list of cubes installed on your
system.
On a Unix system, the available cubes are usually stored in the directory
-:file:`/usr/share/cubicweb/cubes`. During development, the cubes are commonly
-found in the directory :file:`/path/to/cubicweb_forest/cubes`. The environment
-variable :envvar:`CW_CUBES_PATH` gives additionnal locations where to search for
-cubes.
+:file:`/usr/share/cubicweb/cubes`. If you're using the cubicweb forest
+(:ref:SourceInstallation), the cubes are searched in the directory
+:file:`/path/to/cubicweb_forest/cubes`. The environment variable
+:envvar:`CW_CUBES_PATH` gives additionnal locations where to search for cubes.
-.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
+.. _`|cubicweb| Forge`: http://www.cubicweb.org/project/
.. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
.. _`cubicweb-comment`: http://www.cubicweb.org/project/cubicweb-comment
@@ -59,7 +59,7 @@
.. image:: ../../images/archi_globale.en.png
-The command ``cubicweb-ctl list`` also displays the list of instances
+The command :command:`cubicweb-ctl list` also displays the list of instances
installed on your system.
On a Unix system, the instances are usually stored in the directory
@@ -75,9 +75,9 @@
Data Repository
---------------
-The data repository [#]_ provides access to one or more data sources (including
+The data repository [1]_ provides access to one or more data sources (including
SQL databases, LDAP repositories, Mercurial or Subversion version control
-systems, other CubicWeb instance repositories, GAE's DataStore, etc).
+systems, other |cubicweb| instance repositories, GAE's DataStore, etc).
All interactions with the repository are done using the Relation Query Language
(RQL). The repository federates the data sources and hides them from the
@@ -93,7 +93,7 @@
creation of entities, deletion of relations, etc. This is used for example to
send email notifications when the state of an object changes. See `Hooks` below.
-.. _[#]: not to be confused with a Mercurial repository or a Debian repository.
+.. [1] not to be confused with a Mercurial repository or a Debian repository.
.. _`Python Remote Objects`: http://pyro.sourceforge.net/
Web Engine
@@ -135,7 +135,7 @@
entities like users and groups, the entities used to store the data model
itself and attributes like unique identifier, creation date, creator, etc.
-When you create a new *CubicWeb* instance, the schema is stored in the database.
+When you create a new |cubicweb| instance, the schema is stored in the database.
When the cubes the instance is based on evolve, they may change their data model
and provide migration scripts that will be executed when the administrator will
run the upgrade process for the instance.
@@ -173,7 +173,7 @@
Each appobject has a selector, that is used to compute how well the object fits
a given context. The better the object fits the context, the higher the score.
-CubicWeb provides a set of basic selectors that may be parametrized. Selectors
+|cubicweb| provides a set of basic selectors that may be parametrized. Selectors
can be combined with the binary operators `&` and `|` to build more complex
selector that can be combined too.
@@ -213,7 +213,7 @@
**No need for a complicated ORM when you have a powerful query language**
-All the persistent data in a CubicWeb instance is retrieved and modified by using the
+All the persistent data in a |cubicweb| instance is retrieved and modified by using the
Relation Query Language.
This query language is inspired by SQL but is on a higher level in order to
@@ -231,7 +231,7 @@
Every request made (using RQL) to the data repository returns an
object we call a Result Set. It enables easy use of the retrieved
data, providing a translation layer between the backend's native
-datatypes and *CubicWeb* schema's EntityTypes.
+datatypes and |cubicweb| schema's EntityTypes.
Result sets provide access to the raw data, yielding either basic
Python data types, or schema-defined high-level entities, in a
@@ -241,7 +241,7 @@
Views
-----
-** *CubicWeb* is data driven **
+**CubicWeb| is data driven**
The view system is loosely coupled to data through a selection
system. Views are, in essence, defined by an id, a selection predicate
@@ -252,7 +252,8 @@
Hooks
-----
-** *CubicWeb* provides an extensible data repository **
+
+**CubicWeb provides an extensible data repository**
The data model defined using Yams types allows to express the data
model in a comfortable way. However several aspects of the data model
@@ -282,3 +283,35 @@
They are an essential building block of any moderately complicated
cubicweb application.
+
+
+.. _RunMode:
+
+Running mode
+------------
+
+A running mode is a predifined set of configuration telling where it should look
+for various resources, such as cubes, instances, etc. To ease development with
+the framework, there are two running modes with |cubicweb|:
+
+* 'user', resources are searched / created in the user home directory:
+ - instances are stored in :file:`~/etc/cubicweb.d`
+ - temporary files (such as pid file) in :file:`/tmp`
+
+* 'system', resources are searched / created in the system directories (eg usually requiring root access):
+ - instances are stored in :file:`/etc/cubicweb.d`
+ - temporary files (such as pid file) in :file:`/var/run/cubicweb`
+
+Cubes search path is also affected, see the :ref:Cube section.
+
+By default, the mode automatically set to 'user' if a :file:`.hg` directory is found
+in the cubicweb package, else it's set to 'system'. You can force this by setting
+the :envvar:`CW_MODE` environment variable to either 'user' or 'system'.
+
+If you've a doubt about the mode you're currently running, check the first line
+outputed by the :command:`cubicweb-ctl list` command.
+
+Notice that each resource path may be explicitly set using an environment
+variable if the default doesn't suit your needs.
+
+.. |cubicweb| replace:: *CubicWeb*
\ No newline at end of file
--- a/doc/book/en/intro/history.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/history.rst Mon Feb 08 13:35:07 2010 +0100
@@ -8,8 +8,7 @@
is written in Python and includes a data server and a web engine.
Its data server publishes data federated from different sources like SQL
-databases, LDAP directories and versioning systems (such as subversion or
-mercurial).
+databases, LDAP directories or even from other CubicWeb data servers.
Its web engine was designed to let the final user control what content to select
and how to display it. It allows one to browse the federated data sources and
--- a/doc/book/en/intro/tutorial/blog-in-five-minutes.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/tutorial/blog-in-five-minutes.rst Mon Feb 08 13:35:07 2010 +0100
@@ -5,10 +5,12 @@
Get a blog running in five minutes!
-----------------------------------
-First install the following packages (:ref:`DebianInstallation`)::
+For Debian or Ubuntu users, first install the following packages (:ref:`DebianInstallation`)::
cubicweb, cubicweb-dev, cubicweb-blog
+For Windows or Mac OS X users, you must install cubicweb from source (see :ref:`SourceInstallation` and :ref:`WindowsInstallation`).
+
Then create and initialize your instance::
cubicweb-ctl create blog myblog
@@ -17,9 +19,24 @@
cubicweb-ctl start -D myblog
-This is it. Your blog is running. Visit http://localhost:8080 and enjoy it!
+The -D option is the debugging mode of cubicweb, removing it will lauch the instance in the background.
+
+Permission
+~~~~~~~~~~
+
+This command assumes that you have root access to the /etc/ path. In order to initialize your instance as a `user` (from scratch), please check your current PYTHONPATH then create the ~/etc/cubicweb.d directory.
+
+Instance parameters
+~~~~~~~~~~~~~~~~~~~
-As a developer, you'll want to know more about developing new cubes and
-customizing the look of your instance. This is what the next section is about.
+If the database installation failed, you'd like to change some instance parameters, for example, the database host or the user name. These informations can be edited in the `source` file located in the /etc/cubicweb.d/myblog directory.
+
+Then relaunch the database creation:
+
+ cubicweb-ctl db-create myblog
+
+Other paramaters, like web server or emails parameters, can be modified in the `all-in-one.conf` file.
+
+This is it. Your blog is running. Visit http://localhost:8080 and enjoy it! This blog is fully functionnal. The next section section will present the way to develop new cubes and customizing the look of your instance.
--- a/doc/book/en/intro/tutorial/components.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/tutorial/components.rst Mon Feb 08 13:35:07 2010 +0100
@@ -69,7 +69,7 @@
database with your model. For this purpose, *CubicWeb* provides
a very useful command ``cubicweb-ctl shell blogdemo`` which
launches an interactive shell where you can enter migration
-commands. (see :ref:`cubicweb-ctl` for more details))
+commands (see :ref:`cubicweb-ctl` for more details)).
As you added the cube named `comment`, you need to run:
::
--- a/doc/book/en/intro/tutorial/create-cube.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/tutorial/create-cube.rst Mon Feb 08 13:35:07 2010 +0100
@@ -1,5 +1,44 @@
.. -*- coding: utf-8 -*-
+
+.. _Steps:
+
+Steps for creating your cube
+----------------------------
+
+The following steps will help you to create and customize a new cube.
+
+1. :ref:`CreateYourCube`
+
+Create the directory to hold the code of your cube. The most important files that will be useful to customize your newly created cube are:
+ * schema.py: contains the data model
+ * views.py: contains your custom views
+ * entities.py: contains XXX
+
+The detailed structure of the code directory is described in :ref:`cubelayout`.
+
+2. :ref:`DefineDataModel`
+
+Define the data model of your application.
+
+3. :ref:`ExploreYourInstance`
+
+Create, run, and explore an instance of your cube.
+
+4. :ref:`DefineViews`
+
+Customize the views of your data: how and which part of your data are showed.
+
+Note: views don't concern the look'n'feel or design of the site. For that, you should use CSS instead, and default CSS or your new cube are located in 'blog/data/'.
+
+
+5. :ref:`DefineEntities`
+
+Define your own entities to add useful functions when you manipulate your data, especially when you write view.
+
+
+.. _CreateYourCube:
+
Create your cube
----------------
@@ -16,6 +55,23 @@
installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
a directory named ``blog`` reflecting the structure described in :ref:`Concepts`.
+
+For packages installation, you can still create new cubes in your home directory using the following configuration. Let's say you want to develop your new cubes in `~src/cubes`, then set the following environment variables:
+::
+
+ CW_CUBES_PATH=~/src/cubes
+ CW_MODE=user
+
+and then create your new cube using:
+::
+
+ cubicweb-ctl newcube --directory=~/src/cubes blog
+
+
+
+
+
+
.. _DefineDataModel:
Define your data model
@@ -26,7 +82,9 @@
The data model of your cube ``blog`` is defined in the file ``schema.py``:
-::
+.. sourcecode:: python
+
+ from yams.buildobjs import EntityType, String, SubjectRelation, Date
class Blog(EntityType):
title = String(maxsize=50, required=True)
@@ -38,6 +96,8 @@
content = String(required=True, fulltextindexed=True)
entry_of = SubjectRelation('Blog', cardinality='?*')
+The first step is the import of the EntityType (generic class for entity and
+attributes that will be used in both Blog and BlogEntry entities.
A Blog has a title and a description. The title is a string that is
required and must be less than 50 characters. The
@@ -57,18 +117,26 @@
zero`). For completeness, remember that ``+`` means `one or more`.
+.. _ExploreYourInstance:
+
+Create and explore your instance
+--------------------------------
+.. _CreateYourInstance:
+
Create your instance
---------------------
+~~~~~~~~~~~~~~~~~~~~
To use this cube as an instance and create a new instance named ``blogdemo``, do::
cubicweb-ctl create blog blogdemo
-
This command will create the corresponding database and initialize it.
+
+.. _WelcomeToYourWebInstance:
+
Welcome to your web instance
--------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Start your instance in debug mode with the following command: ::
@@ -94,14 +162,15 @@
Please notice that so far, the *CubicWeb* framework managed all aspects of
the web application based on the schema provided at the beginning.
+.. _AddEntities:
Add entities
-------------
+~~~~~~~~~~~~
We will now add entities in our web application.
Add a Blog
-~~~~~~~~~~
+**********
Let us create a few of these entities. Click on the `[+]` at the left of the
link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
@@ -132,7 +201,7 @@
:alt: displaying a list of two blogs
Add a BlogEntry
-~~~~~~~~~~~~~~~
+***************
Get back to the home page and click on [+] at the left of the link
BlogEntry. Call this new entry ``Hello World`` and type in some text
@@ -245,7 +314,7 @@
from cubicweb.web.views import primary
class BlogEntryPrimaryView(primary.PrimaryView):
- __select__ = implements('BlogEntry')
+ __select__ = implements('BlogEntry')
def render_entity_attributes(self, entity):
self.w(u'<p>published on %s</p>' %
@@ -274,3 +343,88 @@
You can find more details about views and selectors in :ref:`ViewDefinition`.
+.. _DefineEntities:
+
+Write entities to add logic in your data
+----------------------------------------
+
+By default, CubicWeb provides a default entity for each data type defined in the schema.
+A default entity mainly contains the attributes defined in the data model.
+
+You can redefine each entity to provide additional functions to help you write your views.
+
+.. sourcecode:: python
+
+ from cubicweb.entities import AnyEntity
+
+ class BlogEntry(AnyEntity):
+ """customized class for BlogEntry entities"""
+ __regid__ = 'BlogEntry'
+ __implements__ = AnyEntity.__implements__
+
+ def display_cw_logo(self):
+ if 'CW' in self.title:
+ return True
+ else:
+ return False
+
+Customizing an entity requires that your entity:
+ - inherits from ``cubicweb.entities`` or any subclass
+ - defines a ``__regid__`` linked to the corresponding data type of your schema
+ - implements the base class by explicitly using ``__implements__``.
+
+We implemented here a function ``display_cw_logo`` which tests if the blog entry title contains 'CW'.
+This function can then be used when you customize your views. For instance, you can modify your previous ``views.py`` as follows:
+
+.. sourcecode:: python
+
+ class BlogEntryPrimaryView(primary.PrimaryView):
+ __select__ = implements('BlogEntry')
+
+ ...
+
+ def render_entity_title(self, entity):
+ if entity.display_cw_logo():
+ self.w(u'<image src="http://www.cubicweb.org/doc/en/_static/cubicweb.png"/>')
+ super(BlogEntryPrimaryView, self).render_entity_title(entity)
+
+Then each blog entry whose title contains 'CW' is shown with the CubicWeb logo in front of it.
+
+.. _UpdatingSchemaAndSynchronisingInstance:
+
+Updating the schema and synchronising the instance
+--------------------------------------------------
+
+While developping your cube, you may want to update your data model. Let's say you
+want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
+
+The required steps are:
+1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
+
+.. sourcecode:: python
+
+ class Blog(EntityType):
+ title = String(maxsize=50, required=True)
+ description = String()
+ category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
+
+2. stop your ``blogdemo`` instance
+
+3. start the cubicweb shell for your instance by running the following command:
+
+.. sourcecode:: bash
+
+ cubicweb-ctl shell blogdemo
+
+4. in the shell, execute:
+
+.. sourcecode:: python
+
+ add_attribute('Blog', 'category')
+
+5. you can restart your instance, modify a blog entity and check that the new attribute
+``category`` has been added.
+
+Of course, you may also want to add relations, entity types, ... See :ref:`migration`
+for a list of all available migration commands.
+
--- a/doc/book/en/intro/tutorial/maintemplate.rst Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/en/intro/tutorial/maintemplate.rst Mon Feb 08 13:35:07 2010 +0100
@@ -52,25 +52,25 @@
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
self.w(u'<td id="firstcolumn">')
- self.vreg.select_component('logo', self.req, self.rset).dispatch(w=self.w)
+ self._cw.vreg.select_component('logo', self._cw, self.cw_rset).dispatch(w=self.w)
self.w(u'</td>\n')
# appliname and breadcrumbs
self.w(u'<td id="headtext">')
- comp = self.vreg.select_component('appliname', self.req, self.rset)
+ comp = self._cw.vreg.select_component('appliname', self._cw, self.cw_rset)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w)
- comp = self.vreg.select_component('breadcrumbs', self.req, self.rset, view=view)
+ comp = self._cw.vreg.select_component('breadcrumbs', self._cw, self.cw_rset, view=view)
if comp and comp.propval('visible'):
comp.dispatch(w=self.w, view=view)
self.w(u'</td>')
# logged user and help
#self.w(u'<td>\n')
- #comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
+ #comp = self._cw.vreg.select_component('loggeduserlink', self._cw, self.cw_rset)
#comp.dispatch(w=self.w)
#self.w(u'</td><td>')
self.w(u'<td>')
- helpcomp = self.vreg.select_component('help', self.req, self.rset)
+ helpcomp = self._cw.vreg.select_component('help', self._cw, self.cw_rset)
if helpcomp: # may not be available if Card is not defined in the schema
helpcomp.dispatch(w=self.w)
self.w(u'</td>')
@@ -78,7 +78,7 @@
self.w(u'<td id="lastcolumn">')
self.w(u'</td>\n')
self.w(u'</tr></table>\n')
- self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+ self.template('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
title=False, message=False)
--- a/doc/book/fr/01-introduction.fr.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/fr/01-introduction.fr.txt Mon Feb 08 13:35:07 2010 +0100
@@ -6,13 +6,13 @@
===========================
`CubicWeb` nous permet de développer des instances d'applications web
-basées sur un ou plusieurs `cube`.
+basées sur un ou plusieurs `cube`.
-Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant
-des types de données et des vues. Un `cube` est un composant re-utilisable
+Ce à quoi nous réferrons en parlant de `cube` est un modèle définissant
+des types de données et des vues. Un `cube` est un composant re-utilisable
regroupé avec d'autres cubes sur le système de fichiers.
-Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes
+Un `instance` réferre à une installation spécifique d'un ou plusieurs cubes
où sont regroupés tous les fichiers de configuration de l'application web finale.
Dans ce document, nous allons vous montrer comment créer un cube et l'utiliser
@@ -32,7 +32,7 @@
|
|-- data/
| |-- cubes.blog.css
- | |-- cubes.blog.js
+ | |-- cubes.blog.js
| |-- external_resources
|
|-- debian/
@@ -77,7 +77,7 @@
|-- views.py
Toute modification apportée à votre modele de données devra
-etre effectué dans ce répertoire.
+etre effectué dans ce répertoire.
@@ -102,17 +102,17 @@
title = String(required=True, fulltextindexed=True, maxsize=256)
publish_date = Date(default='TODAY')
content = String(required=True, fulltextindexed=True)
- entry_of = SubjectRelation('Blog', cardinality='?*')
+ entry_of = SubjectRelation('Blog', cardinality='?*')
-Un ``Blog`` a un titre et une description. Le titre est une chaîne
+Un ``Blog`` a un titre et une description. Le titre est une chaîne
de caractères requise par la classe parente EntityType et ne doit
-pas excéder 50 caractères. La description est une chaîne de
+pas excéder 50 caractères. La description est une chaîne de
caractères sans contraintes.
Une ``BlogEntry`` a un titre, une date de publication et du texte
-étant son contenu. Le titre est une chaîne de caractères qui ne
+étant son contenu. Le titre est une chaîne de caractères qui ne
doit pas excéder 100 caractères. La date de publication est de type Date et a
-pour valeur par défaut TODAY, ce qui signifie que lorsqu'une
+pour valeur par défaut TODAY, ce qui signifie que lorsqu'une
``BlogEntry`` sera créée, sa date de publication sera la date
courante a moins de modifier ce champ. Le texte est une chaîne de
caractères qui sera indexée en plein texte et sans contraintes.
@@ -121,7 +121,7 @@
relie à un ``Blog``. La cardinalité ``?*`` signifie que BlogEntry
peut faire partie de zero a un Blog (``?`` signifie `zero ou un`) et
qu'un Blog peut avoir une infinité de BlogEntry (``*`` signifie
-`n'importe quel nombre incluant zero`).
+`n'importe quel nombre incluant zero`).
Par soucis de complétude, nous rappellerons que ``+`` signifie
`un ou plus`.
@@ -130,7 +130,7 @@
--------------------
::
-
+
cubicweb-ctl create blog blogdemo
Cette commande va créer un répertoire ``~/etc/cubicweb.d/blogdemo``
@@ -150,7 +150,7 @@
Vous pouvez à présent accéder à votre application web vous permettant de
créer des blogs et d'y poster des messages en visitant l'URL http://localhost:8080/.
Un premier formulaire d'authentification va vous être proposé. Par défaut,
-l'application n'autorisera pas d'utilisateur anonyme à accéder a votre
+l'application n'autorisera pas d'utilisateur anonyme à accéder a votre
application. Vous devrez donc utiliser l'utilisateur administrateur que
vous aurez crée lors de l'initialisation de votre base de données via
``cubicweb-ctl create``.
@@ -166,7 +166,7 @@
Rappelez-vous que pour le moment, tout a été géré par la plate-forme
`CubicWeb` et que la seule chose qui a été fournie est le schéma de
-données.
+données.
Créons des entités
------------------
@@ -186,7 +186,7 @@
:alt: from to create blog
En cliquant sur le logo situé dans le coin gauche de la fenêtre,
-vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez
+vous allez être redirigé vers la page d'accueil. Ensuite, si vous allez
sur le lien Blog, vous devriez voir la liste des entités Blog, en particulier
celui que vous venez juste de créer ``Tech-Blog``.
@@ -212,7 +212,7 @@
un peut de texte avant de ``Valider``. Vous venez d'ajouter un article
sans avoir précisé à quel Blog il appartenait. Dans la colonne de gauche
se trouve une boite intitulé ``actions``, cliquez sur le menu ``modifier``.
-Vous êtes de retour sur le formulaire d'édition de l'article que vous
+Vous êtes de retour sur le formulaire d'édition de l'article que vous
venez de créer, à ceci près que ce formulaire a maintenant une nouvelle
section intitulée ``ajouter relation``. Choisissez ``entry_of`` dans ce menu,
cela va faire apparaitre une deuxième menu déroulant dans lequel vous
@@ -225,7 +225,7 @@
:alt: editing a blog entry to add a relation to a blog
Validez vos modifications en cliquant sur ``Valider``. L'entité article
-qui est listée contient maintenant un lien vers le Blog auquel il
+qui est listée contient maintenant un lien vers le Blog auquel il
appartient, ``MyLife``.
.. image:: images/cbw-detail-one-blogentry.fr.png
@@ -248,7 +248,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Une vue est une classe Python qui inclut:
-
+
- un identifiant (tous les objets dans `CubicWeb` sont listés
dans un registre et cet identifiant est utilisé comme la clé)
@@ -262,32 +262,32 @@
des entités que nous cherchons à appliquer. `CubicWeb` utilise un
sélecteur qui permet de calculer un score et d'identifier la vue
la plus adaptée au `result set` que nous voulons afficher. La librarie
-standard des sélecteurs se trouve dans ``cubicweb.common.selector``
-et une librairie des méthodes utilisées pour calculer les scores
+standard des sélecteurs se trouve dans ``cubicweb.common.selector``
+et une librairie des méthodes utilisées pour calculer les scores
est dans ``cubicweb.vregistry.vreq``.
Il est possible de définir plusieurs vues ayant le meme identifiant
-et d'y associer des sélecteurs et des filtres afin de permettre Ã
-l'application de s'adapter au mieux aux données que nous avons
+et d'y associer des sélecteurs et des filtres afin de permettre Ã
+l'application de s'adapter au mieux aux données que nous avons
besoin d'afficher. Nous verrons cela plus en détails dans :ref:`DefinitionVues`.
On peut citer l'exemple de la vue nommée ``primary`` qui est celle utilisée
-pour visualiser une entité seule. Nous allons vous montrer comment modifier
+pour visualiser une entité seule. Nous allons vous montrer comment modifier
cette vue.
Modification des vues
~~~~~~~~~~~~~~~~~~~~~
Si vous souhaitez modifier la manière dont est rendue un article (`Blogentry`),
-vous devez surcharger la vue ``primary`` définie dans le module ``views`` de
+vous devez surcharger la vue ``primary`` définie dans le module ``views`` de
votre cube, ``cubes/blog/views.py``.
Nous pourrions par exemple ajouter devant la date de publication un préfixe
indiquant que la date visualisée est la date de publication.
-Pour cela appliquez les modifications suivantes:
+Pour cela appliquez les modifications suivantes:
-::
+::
from cubicweb.web.views import baseviews
@@ -303,13 +303,13 @@
return entity.view('reledit', rtype='content_format')
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.rset.get_entity(row, col)
# display entity attributes with prefixes
self.w(u'<h1>%s</h1>' % entity.title)
self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
self.w(u'<p>%s</p>' % entity.content)
-
+
# display relations
siderelations = []
if self.main_related_section:
@@ -329,10 +329,10 @@
-Le code que nous avons modifié définit une vue primaire pour une entité de
-type `BlogEntry`.
+Le code que nous avons modifié définit une vue primaire pour une entité de
+type `BlogEntry`.
-Etant donné que les vues sont appliquées sur des `result sets` et que
+Etant donné que les vues sont appliquées sur des `result sets` et que
les `result sets` peuvent être des tableaux de données, il est indispensable
de récupérer l'entité selon ses coordonnées (row,col).
--- a/doc/book/fr/04-02-schema-definition.fr.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/fr/04-02-schema-definition.fr.txt Mon Feb 08 13:35:07 2010 +0100
@@ -177,7 +177,7 @@
à la relation. Cela se limite donc aux relations dont la cardinalité
sujet->relation->objet vaut 0..1 ('?') ou 1..1 ('1')
-* `symetric` : booléen indiquant que la relation est symétrique. i.e.
+* `symmetric` : booléen indiquant que la relation est symétrique. i.e.
`X relation Y` implique `Y relation X`
Dans le cas de définitions de relations simultanée, `sujet` et `object` peuvent
--- a/doc/book/fr/05-define-views.fr.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/fr/05-define-views.fr.txt Mon Feb 08 13:35:07 2010 +0100
@@ -20,17 +20,17 @@
* `cell_call(row, col, **kwargs)`, appelle la vue pour une cellule donnée d'un
result set
* `url()`, retourne l'url permettant d'obtenir cette vue avec le result set en
- cours
+ cours
* `view(__vid, rset, __fallback_vid=None, **kwargs)`, appelle la vue
d'identificant `__vid` sur le result set donné. Il est possible de données un
identificant de vue de "fallback" qui sera utilisé si la vue demandée n'est
pas applicable au result set
-
+
* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, pareil que `view` mais
passe automatiquement le flux en argument
-
+
* `html_headers()`, retourne une liste d'en-tête HTML à placer par le template
- principal
+ principal
* `page_title()`, retourne le titre à utiliser dans l'en tête HTML `title`
@@ -55,7 +55,7 @@
[FROM-LAX-BOOK]
-Tip: when modifying views, you do not need to restart the local
+Tip: when modifying views, you do not need to restart the local
server. Just save the file in your editor and reload the page in your
browser to see the changes.
@@ -63,7 +63,7 @@
- an identifier (all objects in `LAX` are entered in a registry
and this identifier will be used as a key)
-
+
- a filter to select the resulsets it can be applied to
`LAX` provides a lot of standard views, for a complete list, you
@@ -83,14 +83,14 @@
05. accepts = ('BlogEntry',)
06.
07. def cell_call(self, row, col):
- 08. entity = self.entity(row, col)
+ 08. entity = self.rset.get_entity(row, col)
09. self.w(u'<h1>%s</h1>' % entity.title)
10. self.w(u'<p>published on %s in category %s</p>' % \
11. (entity.publish_date.strftime('%Y-%m-%d'), entity.category))
12. self.w(u'<p>%s</p>' % entity.text)
The above source code defines a new primary view (`line 03`) for
-``BlogEntry`` (`line 05`).
+``BlogEntry`` (`line 05`).
Since views are applied to resultsets and resulsets can be tables of
data, it is needed to recover the entity from its (row,col)
@@ -108,11 +108,11 @@
Let us now improve the primary view of a blog ::
01. class BlogPrimaryView(baseviews.PrimaryView):
- 02.
+ 02.
03. accepts = ('Blog',)
04.
05. def cell_call(self, row, col):
- 06. entity = self.entity(row, col)
+ 06. entity = self.rset.get_entity(row, col)
07. self.w(u'<h1>%s</h1>' % entity.title)
08. self.w(u'<p>%s</p>' % entity.description)
09. rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid)
@@ -127,9 +127,9 @@
about the schema and infer that such entities have to be of the
``BlogEntry`` kind and retrieves them.
-The request returns a selection of data called a resultset. At
+The request returns a selection of data called a resultset. At
`line 10` the view 'primary' is applied to this resultset to output
-HTML.
+HTML.
**This is to be compared to interfaces and protocols in object-oriented
languages. Applying a given view to all the entities of a resultset only
@@ -159,7 +159,7 @@
* create view "blogentry table" with title, publish_date, category
-We will show that by default the view that displays
+We will show that by default the view that displays
"Any E,D,C WHERE E publish_date D, E category C" is the table view.
Of course, the same can be obtained by calling
self.wview('table',rset)
@@ -244,4 +244,4 @@
----------------------------------
Certains navigateurs (dont firefox) n'aime pas les `<div>` vides (par vide
j'entend sans contenu dans la balise, il peut y avoir des attributs), faut
-toujours mettre `<div></div>` même s'il n'y a rien dedans, et non `<div/>`.
+toujours mettre `<div></div>` même s'il n'y a rien dedans, et non `<div/>`.
--- a/doc/book/fr/20-01-intro.fr.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/fr/20-01-intro.fr.txt Mon Feb 08 13:35:07 2010 +0100
@@ -37,7 +37,7 @@
données manipulées. La syntaxe de la définition est la même que celle
proposée par `Google AppEngine` mais il faut remplacer la ligne
d'import::
-
+
from google.appengine.ext import db
par celle-ci::
@@ -48,7 +48,7 @@
Un exemple de schéma de données pour un ``Blog`` pourrait être::
from ginco.goa import db
-
+
class BlogEntry(db.Model):
# un titre à donner à l'entrée
title = db.StringProperty(required=True)
@@ -57,8 +57,8 @@
# le contenu de l'entrée
content = db.TextProperty()
# une entrée peut en citer une autre
- cites = db.SelfReferenceProperty()
-
+ cites = db.SelfReferenceProperty()
+
Personnalisation des vues
-------------------------
@@ -75,7 +75,7 @@
- un identifiant (tous les objets dans `LAX` sont enregistrés
dans un registre et cet identifiant sert de clé pour y retrouver
la vue)
-
+
- une description des types de données auxquels elle s'applique
Il existe dans `LAX` des vues prédéfinies et utilisées par le moteur
@@ -87,17 +87,17 @@
Par exemple, si on souhaite modifier la page principale d'une entrée de
blog, il faut surcharger la vue ``primary`` des objets ``BlogEntry`` dans
le fichier ``myapp/views.py``::
-
+
from ginco.web.views import baseviews
-
+
class BlogEntryPrimaryView(baseviews.PrimaryView):
accepts = ('BlogEntry',)
-
+
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.rset.get_entity(row, col)
self.w(u'<h1>%s</h1>' % entity.title)
self.w(u'<div>%s</div>' entity.content)
-
+
Génération du graphique de schéma
---------------------------------
@@ -105,13 +105,13 @@
Il existe une vue ``schema`` qui permet d'afficher un graphique
représantant les différents types d'entités définis dans le schéma
ainsi que les relations entre ces types. Ce graphique doit être généré
-statiquement. Le script à utiliser pour générer ce schéma est
+statiquement. Le script à utiliser pour générer ce schéma est
dans ``myapp/tools``. Ce script nécessite d'avoir accès aux
bibliothèques fournies par le SDK de ``Google AppEngine``. Il faut
donc modifier son PYTHONPATH::
$ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
- $ python tools/generate_schema_img.py
+ $ python tools/generate_schema_img.py
Génération des fichiers de traduction
--- a/doc/book/fr/20-04-develop-views.fr.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/doc/book/fr/20-04-develop-views.fr.txt Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
- an identifier (all objects in `LAX` are entered in a registry
and this identifier will be used as a key)
-
+
- a filter to select the resulsets it can be applied to
`LAX` provides a lot of standard views, for a complete list, you
@@ -25,13 +25,13 @@
override the view ``primary`` in ``BlogDemo/views.py`` ::
from ginco.web.views import baseviews
-
+
class BlogEntryPrimaryView(baseviews.PrimaryView):
accepts = ('BlogEntry',)
-
+
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.rset.get_entity(row, col)
self.w(u'<h1>%s</h1>' % entity.title)
self.w(u'<div>%s</div>' % entity.publish_date)
self.w(u'<div>%s</div>' % entity.category)
@@ -91,7 +91,7 @@
[WRITE ME]
-* show how urls are mapped to selections and views and explain URLRewriting
+* show how urls are mapped to selections and views and explain URLRewriting
Security
=========
--- a/entities/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -22,7 +22,7 @@
"""an entity instance has e_schema automagically set on the class and
instances have access to their issuing cursor
"""
- id = 'Any'
+ __regid__ = 'Any'
__implements__ = (IBreadCrumbs, IFeed)
fetch_attrs = ('modification_date',)
@@ -82,21 +82,20 @@
def dc_date(self, date_format=None):# XXX default to ISO 8601 ?
"""return latest modification date of this entity"""
- return self.format_date(self.modification_date, date_format=date_format)
+ return self._cw.format_date(self.modification_date, date_format=date_format)
def dc_type(self, form=''):
"""return the display name for the type of this entity (translated)"""
- return self.e_schema.display_name(self.req, form)
+ return self.e_schema.display_name(self._cw, form)
def dc_language(self):
"""return language used by this entity (translated)"""
# check if entities has internationalizable attributes
# XXX one is enough or check if all String attributes are internationalizable?
for rschema, attrschema in self.e_schema.attribute_definitions():
- if rschema.rproperty(self.e_schema, attrschema,
- 'internationalizable'):
- return self.req._(self.req.user.property_value('ui.language'))
- return self.req._(self.vreg.property_value('ui.language'))
+ if rschema.rdef(self.e_schema, attrschema).internationalizable:
+ return self._cw._(self._cw.user.property_value('ui.language'))
+ return self._cw._(self._cw.vreg.property_value('ui.language'))
@property
def creator(self):
@@ -122,11 +121,11 @@
path = parent.breadcrumbs(view) + [self]
if not recurs:
if view is None:
- if 'vtitle' in self.req.form:
+ if 'vtitle' in self._cw.form:
# embeding for instance
- path.append( self.req.form['vtitle'] )
- elif view.id != 'primary' and hasattr(view, 'title'):
- path.append( self.req._(view.title) )
+ path.append( self._cw.form['vtitle'] )
+ elif view.__regid__ != 'primary' and hasattr(view, 'title'):
+ path.append( self._cw._(view.title) )
return path
## IFeed interface ########################################################
@@ -163,7 +162,7 @@
self.__linkto = {}
except KeyError:
pass
- linktos = list(self.req.list_form_param('__linkto'))
+ linktos = list(self._cw.list_form_param('__linkto'))
linkedto = []
for linkto in linktos[:]:
ltrtype, eid, ltrole = linkto.split(':')
@@ -172,7 +171,7 @@
# hidden input
if remove:
linktos.remove(linkto)
- self.req.form['__linkto'] = linktos
+ self._cw.form['__linkto'] = linktos
linkedto.append(typed_eid(eid))
self.__linkto[(rtype, role)] = linkedto
return linkedto
@@ -206,85 +205,6 @@
"""
return ()
- # XXX deprecates, may be killed once old widgets system is gone ###########
-
- @classmethod
- def get_widget(cls, rschema, x='subject'):
- """return a widget to view or edit a relation
-
- notice that when the relation support multiple target types, the widget
- is necessarily the same for all those types
- """
- # let ImportError propage if web par isn't available
- from cubicweb.web.widgets import widget
- if isinstance(rschema, basestring):
- rschema = cls.schema.rschema(rschema)
- if x == 'subject':
- tschema = rschema.objects(cls.e_schema)[0]
- wdg = widget(cls.vreg, cls, rschema, tschema, 'subject')
- else:
- tschema = rschema.subjects(cls.e_schema)[0]
- wdg = widget(cls.vreg, tschema, rschema, cls, 'object')
- return wdg
-
- @deprecated('use EntityFieldsForm.subject_relation_vocabulary')
- def subject_relation_vocabulary(self, rtype, limit):
- form = self.vreg.select('forms', 'edition', self.req, entity=self)
- return form.subject_relation_vocabulary(rtype, limit)
-
- @deprecated('use EntityFieldsForm.object_relation_vocabulary')
- def object_relation_vocabulary(self, rtype, limit):
- form = self.vreg.select('forms', 'edition', self.req, entity=self)
- return form.object_relation_vocabulary(rtype, limit)
-
- @deprecated('use AutomaticEntityForm.[e]relations_by_category')
- def relations_by_category(self, categories=None, permission=None):
- from cubicweb.web.views.autoform import AutomaticEntityForm
- return AutomaticEntityForm.erelations_by_category(self, categories, permission)
-
- @deprecated('use AutomaticEntityForm.[e]srelations_by_category')
- def srelations_by_category(self, categories=None, permission=None):
- from cubicweb.web.views.autoform import AutomaticEntityForm
- return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
-
- def attribute_values(self, attrname):
- if self.has_eid() or attrname in self:
- try:
- values = self[attrname]
- except KeyError:
- values = getattr(self, attrname)
- # actual relation return a list of entities
- if isinstance(values, list):
- return [v.eid for v in values]
- return (values,)
- # the entity is being created, try to find default value for
- # this attribute
- try:
- values = self.req.form[attrname]
- except KeyError:
- try:
- values = self[attrname] # copying
- except KeyError:
- values = getattr(self, 'default_%s' % attrname,
- self.e_schema.default(attrname))
- if callable(values):
- values = values()
- if values is None:
- values = ()
- elif not isinstance(values, (list, tuple)):
- values = (values,)
- return values
-
- def use_fckeditor(self, attr):
- """return True if fckeditor should be used to edit entity's attribute named
- `attr`, according to user preferences
- """
- if self.req.use_fckeditor() and self.e_schema.has_metadata(attr, 'format'):
- if self.has_eid() or '%s_format' % attr in self:
- return self.attr_metadata(attr, 'format') == 'text/html'
- return self.req.property_value('ui.default-text-format') == 'text/html'
- return False
-
# XXX: store a reference to the AnyEntity class since it is hijacked in goa
# configuration and we need the actual reference to avoid infinite loops
# in mro
--- a/entities/authobjs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/authobjs.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
from cubicweb.entities import AnyEntity, fetch_config
class CWGroup(AnyEntity):
- id = 'CWGroup'
+ __regid__ = 'CWGroup'
fetch_attrs, fetch_order = fetch_config(['name'])
fetch_unrelated_order = fetch_order
@@ -23,7 +23,7 @@
class CWUser(AnyEntity):
- id = 'CWUser'
+ __regid__ = 'CWUser'
fetch_attrs, fetch_order = fetch_config(['login', 'firstname', 'surname'])
fetch_unrelated_order = fetch_order
@@ -60,12 +60,13 @@
try:
# properties stored on the user aren't correctly typed
# (e.g. all values are unicode string)
- return self.vreg.typed_value(key, self.properties[key])
+ return self._cw.vreg.typed_value(key, self.properties[key])
except KeyError:
pass
except ValueError:
- self.warning('incorrect value for eproperty %s of user %s', key, self.login)
- return self.vreg.property_value(key)
+ self.warning('incorrect value for eproperty %s of user %s',
+ key, self.login)
+ return self._cw.vreg.property_value(key)
def matching_groups(self, groups):
"""return the number of the given group(s) in which the user is
@@ -92,12 +93,12 @@
return self.groups == frozenset(('guests', ))
def owns(self, eid):
- if hasattr(self.req, 'unsafe_execute'):
+ if hasattr(self._cw, 'unsafe_execute'):
# use unsafe_execute on the repository side, in case
# session's user doesn't have access to CWUser
- execute = self.req.unsafe_execute
+ execute = self._cw.unsafe_execute
else:
- execute = self.req.execute
+ execute = self._cw.execute
try:
return execute('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
{'x': eid, 'u': self.eid}, 'x')
@@ -115,7 +116,7 @@
kwargs['x'] = contexteid
cachekey = 'x'
try:
- return self.req.execute(rql, kwargs, cachekey)
+ return self._cw.execute(rql, kwargs, cachekey)
except Unauthorized:
return False
@@ -125,7 +126,7 @@
"""construct a name using firstname / surname or login if not defined"""
if self.firstname and self.surname:
- return self.req._('%(firstname)s %(surname)s') % {
+ return self._cw._('%(firstname)s %(surname)s') % {
'firstname': self.firstname, 'surname' : self.surname}
if self.firstname:
return self.firstname
--- a/entities/lib.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/lib.py Mon Feb 08 13:35:07 2010 +0100
@@ -24,7 +24,7 @@
return '%s at %s' % (name, host.replace('.', ' dot '))
class EmailAddress(AnyEntity):
- id = 'EmailAddress'
+ __regid__ = 'EmailAddress'
fetch_attrs, fetch_order = fetch_config(['address', 'alias'])
def dc_title(self):
@@ -51,7 +51,7 @@
if not ('sender' in subjrels and 'recipients' in subjrels):
return
rql = 'DISTINCT Any X, S, D ORDERBY D DESC WHERE X sender Y or X recipients Y, X subject S, X date D, Y eid %(y)s'
- rset = self.req.execute(rql, {'y': self.eid}, 'y')
+ rset = self._cw.execute(rql, {'y': self.eid}, 'y')
if skipeids is None:
skipeids = set()
for i in xrange(len(rset)):
@@ -62,7 +62,7 @@
yield rset.get_entity(i, 0)
def display_address(self):
- if self.vreg.config['mangle-emails']:
+ if self._cw.vreg.config['mangle-emails']:
return mangle_email(self.address)
return self.address
@@ -82,23 +82,38 @@
return super(EmailAddress, self).after_deletion_path()
-from logilab.common.deprecation import class_renamed
-Emailaddress = class_renamed('Emailaddress', EmailAddress)
-Emailaddress.id = 'Emailaddress'
+class Bookmark(AnyEntity):
+ """customized class for Bookmark entities"""
+ __regid__ = 'Bookmark'
+ fetch_attrs, fetch_order = fetch_config(['title', 'path'])
+
+ def actual_url(self):
+ url = self._cw.build_url(self.path)
+ if self.title:
+ urlparts = list(urlsplit(url))
+ if urlparts[3]:
+ urlparts[3] += '&vtitle=%s' % self._cw.url_quote(self.title)
+ else:
+ urlparts[3] = 'vtitle=%s' % self._cw.url_quote(self.title)
+ url = urlunsplit(urlparts)
+ return url
+
+ def action_url(self):
+ return self.absolute_url() + '/follow'
class CWProperty(AnyEntity):
- id = 'CWProperty'
+ __regid__ = 'CWProperty'
fetch_attrs, fetch_order = fetch_config(['pkey', 'value'])
rest_attr = 'pkey'
def typed_value(self):
- return self.vreg.typed_value(self.pkey, self.value)
+ return self._cw.vreg.typed_value(self.pkey, self.value)
def dc_description(self, format='text/plain'):
try:
- return self.req._(self.vreg.property_info(self.pkey)['help'])
+ return self._cw._(self._cw.vreg.property_info(self.pkey)['help'])
except UnknownProperty:
return u''
@@ -109,33 +124,13 @@
return 'view', {}
-class Bookmark(AnyEntity):
- """customized class for Bookmark entities"""
- id = 'Bookmark'
- fetch_attrs, fetch_order = fetch_config(['title', 'path'])
-
- def actual_url(self):
- url = self.req.build_url(self.path)
- if self.title:
- urlparts = list(urlsplit(url))
- if urlparts[3]:
- urlparts[3] += '&vtitle=%s' % self.req.url_quote(self.title)
- else:
- urlparts[3] = 'vtitle=%s' % self.req.url_quote(self.title)
- url = urlunsplit(urlparts)
- return url
-
- def action_url(self):
- return self.absolute_url() + '/follow'
-
-
class CWCache(AnyEntity):
"""Cache"""
- id = 'CWCache'
+ __regid__ = 'CWCache'
fetch_attrs, fetch_order = fetch_config(['name'])
def touch(self):
- self.req.execute('SET X timestamp %(t)s WHERE X eid %(x)s',
+ self._cw.execute('SET X timestamp %(t)s WHERE X eid %(x)s',
{'t': datetime.now(), 'x': self.eid}, 'x')
def valid(self, date):
--- a/entities/schemaobjs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/schemaobjs.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,15 +16,15 @@
class CWEType(AnyEntity):
- id = 'CWEType'
+ __regid__ = 'CWEType'
fetch_attrs, fetch_order = fetch_config(['name'])
def dc_title(self):
- return u'%s (%s)' % (self.name, self.req._(self.name))
+ return u'%s (%s)' % (self.name, self._cw._(self.name))
def dc_long_title(self):
stereotypes = []
- _ = self.req._
+ _ = self._cw._
if self.final:
stereotypes.append(_('final'))
if stereotypes:
@@ -37,17 +37,17 @@
class CWRType(AnyEntity):
- id = 'CWRType'
+ __regid__ = 'CWRType'
fetch_attrs, fetch_order = fetch_config(['name'])
def dc_title(self):
- return u'%s (%s)' % (self.name, self.req._(self.name))
+ return u'%s (%s)' % (self.name, self._cw._(self.name))
def dc_long_title(self):
stereotypes = []
- _ = self.req._
- if self.symetric:
- stereotypes.append(_('symetric'))
+ _ = self._cw._
+ if self.symmetric:
+ stereotypes.append(_('symmetric'))
if self.inlined:
stereotypes.append(_('inlined'))
if self.final:
@@ -63,7 +63,7 @@
* raise ValidationError if inlining is'nt possible
* eventually return True
"""
- rschema = self.schema.rschema(self.name)
+ rschema = self._cw.vreg.schema.rschema(self.name)
if inlined == rschema.inlined:
return False
if inlined:
@@ -75,7 +75,7 @@
rtype = self.name
stype = rdef.stype
otype = rdef.otype
- msg = self.req._("can't set inlined=%(inlined)s, "
+ msg = self._cw._("can't set inlined=%(inlined)s, "
"%(stype)s %(rtype)s %(otype)s "
"has cardinality=%(card)s")
raise ValidationError(self.eid, {'inlined': msg % locals()})
@@ -87,7 +87,7 @@
class CWRelation(AnyEntity):
- id = 'CWRelation'
+ __regid__ = 'CWRelation'
fetch_attrs = fetch_config(['cardinality'])[0]
def dc_title(self):
@@ -130,7 +130,7 @@
class CWAttribute(CWRelation):
- id = 'CWAttribute'
+ __regid__ = 'CWAttribute'
def dc_long_title(self):
card = self.cardinality
@@ -144,7 +144,7 @@
class CWConstraint(AnyEntity):
- id = 'CWConstraint'
+ __regid__ = 'CWConstraint'
fetch_attrs, fetch_order = fetch_config(['value'])
def dc_title(self):
@@ -164,7 +164,7 @@
class RQLExpression(AnyEntity):
- id = 'RQLExpression'
+ __regid__ = 'RQLExpression'
fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression'])
def dc_title(self):
@@ -198,13 +198,13 @@
class CWPermission(AnyEntity):
- id = 'CWPermission'
+ __regid__ = 'CWPermission'
fetch_attrs, fetch_order = fetch_config(['name', 'label'])
def dc_title(self):
if self.label:
- return '%s (%s)' % (self.req._(self.name), self.label)
- return self.req._(self.name)
+ return '%s (%s)' % (self._cw._(self.name), self.label)
+ return self._cw._(self.name)
def after_deletion_path(self):
"""return (path, parameters) which should be used as redirect
--- a/entities/test/unittest_base.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/test/unittest_base.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,15 +11,14 @@
from logilab.common.decorators import clear_cache
from logilab.common.interface import implements
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb import ValidationError
from cubicweb.interfaces import IMileStone, IWorkflowable
from cubicweb.entities import AnyEntity
-from cubicweb.web.widgets import AutoCompletionWidget
-class BaseEntityTC(EnvBasedTC):
+class BaseEntityTC(CubicWebTC):
def setup_database(self):
self.member = self.create_user('member')
@@ -30,7 +29,7 @@
def test_creator(self):
self.login(u'member')
- entity = self.add_entity('Bookmark', title=u"hello", path=u'project/cubicweb')
+ entity = self.request().create_entity('Bookmark', title=u"hello", path=u'project/cubicweb')
self.commit()
self.assertEquals(entity.creator.eid, self.member.eid)
self.assertEquals(entity.dc_creator(), u'member')
@@ -100,7 +99,7 @@
)
-class InterfaceTC(EnvBasedTC):
+class InterfaceTC(CubicWebTC):
def test_nonregr_subclasses_and_mixins_interfaces(self):
CWUser = self.vreg['etypes'].etype_class('CWUser')
@@ -108,7 +107,7 @@
class MyUser(CWUser):
__implements__ = (IMileStone,)
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(MyUser)
+ self.vreg.register(MyUser)
self.vreg['etypes'].initialization_completed()
MyUser_ = self.vreg['etypes'].etype_class('CWUser')
# a copy is done systematically
@@ -120,7 +119,7 @@
self.failIf(implements(MyUser, IWorkflowable))
-class SpecializedEntityClassesTC(EnvBasedTC):
+class SpecializedEntityClassesTC(CubicWebTC):
def select_eclass(self, etype):
# clear selector cache
@@ -137,8 +136,8 @@
self.vreg._loadedmods[__name__] = {}
for etype in ('Company', 'Division', 'SubDivision'):
class Foo(AnyEntity):
- id = etype
- self.vreg.register_appobject_class(Foo)
+ __regid__ = etype
+ self.vreg.register(Foo)
eclass = self.select_eclass('SubDivision')
self.failUnless(eclass.__autogenerated__)
self.failIf(eclass is Foo)
@@ -148,7 +147,7 @@
self.assertEquals(eclass.__bases__[0].__bases__, (Foo,))
# check Division eclass is still selected for plain Division entities
eclass = self.select_eclass('Division')
- self.assertEquals(eclass.id, 'Division')
+ self.assertEquals(eclass.__regid__, 'Division')
if __name__ == '__main__':
unittest_main()
--- a/entities/test/unittest_wfobjs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/test/unittest_wfobjs.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,4 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb import ValidationError
def add_wf(self, etype, name=None, default=False):
@@ -18,7 +18,7 @@
for ti in wfhist]
-class WorkflowBuildingTC(EnvBasedTC):
+class WorkflowBuildingTC(CubicWebTC):
def test_wf_construction(self):
wf = add_wf(self, 'Company')
@@ -45,7 +45,6 @@
# gnark gnark
bar = wf.add_state(u'bar')
self.commit()
- print '*'*80
bar.set_attributes(name=u'foo')
ex = self.assertRaises(ValidationError, self.commit)
self.assertEquals(ex.errors, {'name': 'workflow already have a state of that name'})
@@ -72,12 +71,12 @@
self.assertEquals(ex.errors, {'name': 'workflow already have a transition of that name'})
-class WorkflowTC(EnvBasedTC):
+class WorkflowTC(CubicWebTC):
def setup_database(self):
rschema = self.schema['in_state']
- for x, y in rschema.iter_rdefs():
- self.assertEquals(rschema.rproperty(x, y, 'cardinality'), '1*')
+ for rdef in rschema.rdefs.values():
+ self.assertEquals(rdef.cardinality, '1*')
self.member = self.create_user('member')
def test_workflow_base(self):
@@ -127,7 +126,7 @@
wf = add_wf(self, 'CWUser')
s = wf.add_state(u'foo', initial=True)
self.commit()
- ex = self.assertRaises(ValidationError, self.session().unsafe_execute,
+ ex = self.assertRaises(ValidationError, self.session.unsafe_execute,
'SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.user().eid, 's': s.eid}, 'x')
self.assertEquals(ex.errors, {'in_state': "state doesn't belong to entity's workflow. "
@@ -210,7 +209,7 @@
[(swfstate2, state2), (swfstate3, state3)])
self.assertEquals(swftr1.destination().eid, swfstate1.eid)
# workflows built, begin test
- self.group = self.add_entity('CWGroup', name=u'grp1')
+ self.group = self.request().create_entity('CWGroup', name=u'grp1')
self.commit()
self.assertEquals(self.group.current_state.eid, state1.eid)
self.assertEquals(self.group.current_workflow.eid, mwf.eid)
@@ -234,7 +233,7 @@
# subworkflow input transition
ex = self.assertRaises(ValidationError,
self.group.change_state, swfstate1, u'gadget')
- self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's current workflow"})
+ self.assertEquals(ex.errors, {'to_state': "state doesn't belong to entity's workflow"})
self.rollback()
# force back to state1
self.group.change_state('state1', u'gadget')
@@ -295,7 +294,7 @@
twf.add_wftransition(_('close'), subwf, (released,),
[(xsigned, closed), (xaborted, released)])
self.commit()
- group = self.add_entity('CWGroup', name=u'grp1')
+ group = self.request().create_entity('CWGroup', name=u'grp1')
self.commit()
for trans in ('identify', 'release', 'close'):
group.fire_transition(trans)
@@ -320,7 +319,7 @@
twf.add_wftransition(_('release'), subwf, identified,
[(xaborted, None)])
self.commit()
- group = self.add_entity('CWGroup', name=u'grp1')
+ group = self.request().create_entity('CWGroup', name=u'grp1')
self.commit()
for trans, nextstate in (('identify', 'xsigning'),
('xabort', 'created'),
@@ -335,15 +334,11 @@
self.assertEquals(group.state, nextstate)
-class CustomWorkflowTC(EnvBasedTC):
+class CustomWorkflowTC(CubicWebTC):
def setup_database(self):
self.member = self.create_user('member')
- def tearDown(self):
- super(CustomWorkflowTC, self).tearDown()
- self.execute('DELETE X custom_workflow WF')
-
def test_custom_wf_replace_state_no_history(self):
"""member in inital state with no previous history, state is simply
redirected when changing workflow
@@ -394,7 +389,7 @@
wf = add_wf(self, 'Company')
wf.add_state('asleep', initial=True)
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
- {'wf': wf.eid, 'x': self.member.eid})
+ {'wf': wf.eid, 'x': self.member.eid}, 'x')
ex = self.assertRaises(ValidationError, self.commit)
self.assertEquals(ex.errors, {'custom_workflow': 'workflow isn\'t a workflow for this type'})
@@ -422,8 +417,7 @@
('asleep', 'activated', None, 'workflow changed to "default user workflow"'),])
-
-class AutoTransitionTC(EnvBasedTC):
+class AutoTransitionTC(CubicWebTC):
def setup_database(self):
self.wf = add_wf(self, 'CWUser')
@@ -463,18 +457,17 @@
('asleep', 'dead', 'sick', None),])
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-class WorkflowHooksTC(RepositoryBasedTC):
+class WorkflowHooksTC(CubicWebTC):
def setUp(self):
- RepositoryBasedTC.setUp(self)
+ CubicWebTC.setUp(self)
self.wf = self.session.user.current_workflow
+ self.session.set_pool()
self.s_activated = self.wf.state_by_name('activated').eid
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)
+ ueid = self.create_user('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})
@@ -491,13 +484,6 @@
{'wf': self.wf.eid})
self.commit()
- def tearDown(self):
- self.execute('DELETE X require_group G '
- 'WHERE G name "users", X transition_of WF, WF eid %(wf)s',
- {'wf': self.wf.eid})
- self.commit()
- RepositoryBasedTC.tearDown(self)
-
# XXX currently, we've to rely on hooks to set initial state, or to use unsafe_execute
# def test_initial_state(self):
# cnx = self.login('stduser')
@@ -522,7 +508,7 @@
def test_transition_checking1(self):
cnx = self.login('stduser')
- user = cnx.user(self.current_session())
+ user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'activate')
self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
@@ -531,8 +517,7 @@
def test_transition_checking2(self):
cnx = self.login('stduser')
- user = cnx.user(self.current_session())
- assert user.state == 'activated'
+ user = cnx.user(self.session)
ex = self.assertRaises(ValidationError,
user.fire_transition, 'dummy')
self.assertEquals(self._cleanup_msg(ex.errors['by_transition']),
@@ -541,7 +526,7 @@
def test_transition_checking3(self):
cnx = self.login('stduser')
- session = self.current_session()
+ session = self.session
user = cnx.user(session)
user.fire_transition('deactivate')
cnx.commit()
--- a/entities/wfobjs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entities/wfobjs.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,12 +15,12 @@
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.interfaces import IWorkflowable
-from cubicweb.common.mixins import MI_REL_TRIGGERS
+from cubicweb.mixins import MI_REL_TRIGGERS
class WorkflowException(Exception): pass
class Workflow(AnyEntity):
- id = 'Workflow'
+ __regid__ = 'Workflow'
@property
def initial(self):
@@ -52,7 +52,7 @@
_done = set()
yield self
_done.add(self.eid)
- for tr in self.req.execute('Any T WHERE T is WorkflowTransition, '
+ for tr in self._cw.execute('Any T WHERE T is WorkflowTransition, '
'T transition_of WF, WF eid %(wf)s',
{'wf': self.eid}).entities():
if tr.subwf.eid in _done:
@@ -63,7 +63,7 @@
# state / transitions accessors ############################################
def state_by_name(self, statename):
- rset = self.req.execute('Any S, SN WHERE S name SN, S name %(n)s, '
+ rset = self._cw.execute('Any S, SN WHERE S name SN, S name %(n)s, '
'S state_of WF, WF eid %(wf)s',
{'n': statename, 'wf': self.eid}, 'wf')
if rset:
@@ -71,7 +71,7 @@
return None
def state_by_eid(self, eid):
- rset = self.req.execute('Any S, SN WHERE S name SN, S eid %(s)s, '
+ rset = self._cw.execute('Any S, SN WHERE S name SN, S eid %(s)s, '
'S state_of WF, WF eid %(wf)s',
{'s': eid, 'wf': self.eid}, ('wf', 's'))
if rset:
@@ -79,7 +79,7 @@
return None
def transition_by_name(self, trname):
- rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, '
+ rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, '
'T transition_of WF, WF eid %(wf)s',
{'n': trname, 'wf': self.eid}, 'wf')
if rset:
@@ -87,7 +87,7 @@
return None
def transition_by_eid(self, eid):
- rset = self.req.execute('Any T, TN WHERE T name TN, T eid %(t)s, '
+ rset = self._cw.execute('Any T, TN WHERE T name TN, T eid %(t)s, '
'T transition_of WF, WF eid %(wf)s',
{'t': eid, 'wf': self.eid}, ('wf', 't'))
if rset:
@@ -98,20 +98,20 @@
def add_state(self, name, initial=False, **kwargs):
"""add a state to this workflow"""
- state = self.req.create_entity('State', name=unicode(name), **kwargs)
- self.req.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
+ state = self._cw.create_entity('State', name=unicode(name), **kwargs)
+ self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
{'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
if initial:
assert not self.initial, "Initial state already defined as %s" % self.initial
- self.req.execute('SET WF initial_state S '
+ self._cw.execute('SET WF initial_state S '
'WHERE S eid %(s)s, WF eid %(wf)s',
{'s': state.eid, 'wf': self.eid}, ('s', 'wf'))
return state
def _add_transition(self, trtype, name, fromstates,
requiredgroups=(), conditions=(), **kwargs):
- tr = self.req.create_entity(trtype, name=unicode(name), **kwargs)
- self.req.execute('SET T transition_of WF '
+ tr = self._cw.create_entity(trtype, name=unicode(name), **kwargs)
+ self._cw.execute('SET T transition_of WF '
'WHERE T eid %(t)s, WF eid %(wf)s',
{'t': tr.eid, 'wf': self.eid}, ('t', 'wf'))
assert fromstates, fromstates
@@ -120,7 +120,7 @@
for state in fromstates:
if hasattr(state, 'eid'):
state = state.eid
- self.req.execute('SET S allowed_transition T '
+ self._cw.execute('SET S allowed_transition T '
'WHERE S eid %(s)s, T eid %(t)s',
{'s': state, 't': tr.eid}, ('s', 't'))
tr.set_transition_permissions(requiredgroups, conditions, reset=False)
@@ -134,7 +134,7 @@
if tostate is not None:
if hasattr(tostate, 'eid'):
tostate = tostate.eid
- self.req.execute('SET T destination_state S '
+ self._cw.execute('SET T destination_state S '
'WHERE S eid %(s)s, T eid %(t)s',
{'t': tr.eid, 's': tostate}, ('s', 't'))
return tr
@@ -146,7 +146,7 @@
requiredgroups, conditions, **kwargs)
if hasattr(subworkflow, 'eid'):
subworkflow = subworkflow.eid
- assert self.req.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
+ assert self._cw.execute('SET T subworkflow WF WHERE WF eid %(wf)s,T eid %(t)s',
{'t': tr.eid, 'wf': subworkflow}, ('wf', 't'))
for fromstate, tostate in exitpoints:
tr.add_exit_point(fromstate, tostate)
@@ -173,11 +173,11 @@
provides a specific may_be_fired method to check if the relation may be
fired by the logged user
"""
- id = 'BaseTransition'
+ __regid__ = 'BaseTransition'
fetch_attrs, fetch_order = fetch_config(['name'])
def __init__(self, *args, **kwargs):
- if self.id == 'BaseTransition':
+ if self.__regid__ == 'BaseTransition':
raise WorkflowException('should not be instantiated')
super(BaseTransition, self).__init__(*args, **kwargs)
@@ -195,7 +195,7 @@
`eid` is the eid of the object on which we may fire the transition
"""
- user = self.req.user
+ user = self._cw.user
# check user is at least in one of the required groups if any
groups = frozenset(g.name for g in self.require_group)
if groups:
@@ -207,7 +207,7 @@
# check one of the rql expression conditions matches if any
if self.condition:
for rqlexpr in self.condition:
- if rqlexpr.check_expression(self.req, eid):
+ if rqlexpr.check_expression(self._cw, eid):
return True
if self.condition or groups:
return False
@@ -227,12 +227,12 @@
transition
"""
if reset:
- self.req.execute('DELETE T require_group G WHERE T eid %(x)s',
+ self._cw.execute('DELETE T require_group G WHERE T eid %(x)s',
{'x': self.eid}, 'x')
- self.req.execute('DELETE T condition R WHERE T eid %(x)s',
+ self._cw.execute('DELETE T condition R WHERE T eid %(x)s',
{'x': self.eid}, 'x')
for gname in requiredgroups:
- rset = self.req.execute('SET T require_group G '
+ rset = self._cw.execute('SET T require_group G '
'WHERE T eid %(x)s, G name %(gn)s',
{'x': self.eid, 'gn': gname}, 'x')
assert rset, '%s is not a known group' % gname
@@ -246,15 +246,15 @@
kwargs = expr
kwargs['x'] = self.eid
kwargs.setdefault('mainvars', u'X')
- self.req.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
+ self._cw.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
'X expression %(expr)s, X mainvars %(mainvars)s, '
- 'T condition X WHERE T eid %(x)s', kwargs, 'x')
+ 'T condition X WHERE T eid %(x)s',kwargs, 'x')
# XXX clear caches?
class Transition(BaseTransition):
"""customized class for Transition entities"""
- id = 'Transition'
+ __regid__ = 'Transition'
def destination(self):
return self.destination_state[0]
@@ -265,7 +265,7 @@
class WorkflowTransition(BaseTransition):
"""customized class for WorkflowTransition entities"""
- id = 'WorkflowTransition'
+ __regid__ = 'WorkflowTransition'
@property
def subwf(self):
@@ -278,13 +278,13 @@
if hasattr(fromstate, 'eid'):
fromstate = fromstate.eid
if tostate is None:
- self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
+ self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
'X subworkflow_state FS WHERE T eid %(t)s, FS eid %(fs)s',
{'t': self.eid, 'fs': fromstate}, ('t', 'fs'))
else:
if hasattr(tostate, 'eid'):
tostate = tostate.eid
- self.req.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
+ self._cw.execute('INSERT SubWorkflowExitPoint X: T subworkflow_exit X, '
'X subworkflow_state FS, X destination_state TS '
'WHERE T eid %(t)s, FS eid %(fs)s, TS eid %(ts)s',
{'t': self.eid, 'fs': fromstate, 'ts': tostate},
@@ -301,7 +301,7 @@
if tostateeid is None:
# go back to state from which we've entered the subworkflow
return entity.subworkflow_input_trinfo().previous_state
- return self.req.entity_from_eid(tostateeid)
+ return self._cw.entity_from_eid(tostateeid)
@cached
def exit_points(self):
@@ -317,7 +317,7 @@
class SubWorkflowExitPoint(AnyEntity):
"""customized class for SubWorkflowExitPoint entities"""
- id = 'SubWorkflowExitPoint'
+ __regid__ = 'SubWorkflowExitPoint'
@property
def subwf_state(self):
@@ -333,7 +333,7 @@
class State(AnyEntity):
"""customized class for State entities"""
- id = 'State'
+ __regid__ = 'State'
fetch_attrs, fetch_order = fetch_config(['name'])
rest_attr = 'eid'
@@ -349,7 +349,7 @@
class TrInfo(AnyEntity):
"""customized class for Transition information entities
"""
- id = 'TrInfo'
+ __regid__ = 'TrInfo'
fetch_attrs, fetch_order = fetch_config(['creation_date', 'comment'],
pclass=None) # don't want modification_date
@property
@@ -410,7 +410,7 @@
"""return current state name translated to context's language"""
state = self.current_state
if state:
- return self.req._(state.name)
+ return self._cw._(state.name)
return u''
@property
@@ -430,11 +430,12 @@
@cached
def cwetype_workflow(self):
"""return the default workflow for entities of this type"""
- wfrset = self.req.execute('Any WF WHERE ET default_workflow WF, '
- 'ET name %(et)s', {'et': self.id})
+ # XXX CWEType method
+ wfrset = self._cw.execute('Any WF WHERE ET default_workflow WF, '
+ 'ET name %(et)s', {'et': self.__regid__})
if wfrset:
return wfrset.get_entity(0, 0)
- self.warning("can't find any workflow for %s", self.id)
+ self.warning("can't find any workflow for %s", self.__regid__)
return None
def possible_transitions(self, type='normal'):
@@ -444,7 +445,7 @@
"""
if self.current_state is None or self.current_workflow is None:
return
- rset = self.req.execute(
+ rset = self._cw.execute(
'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, '
'T type TT, T type %(type)s, '
'T name TN, T transition_of WF, WF eid %(wfeid)s',
@@ -462,10 +463,10 @@
kwargs['comment_format'] = commentformat
kwargs['wf_info_for'] = self
if treid is not None:
- kwargs['by_transition'] = self.req.entity_from_eid(treid)
+ kwargs['by_transition'] = self._cw.entity_from_eid(treid)
if tseid is not None:
- kwargs['to_state'] = self.req.entity_from_eid(tseid)
- return self.req.create_entity('TrInfo', **kwargs)
+ kwargs['to_state'] = self._cw.entity_from_eid(tseid)
+ return self._cw.create_entity('TrInfo', **kwargs)
def fire_transition(self, tr, comment=None, commentformat=None):
"""change the entity's state by firing transition of the given name in
@@ -474,7 +475,8 @@
assert self.current_workflow
if isinstance(tr, basestring):
_tr = self.current_workflow.transition_by_name(tr)
- assert _tr is not None, 'not a %s transition: %s' % (self.id, tr)
+ assert _tr is not None, 'not a %s transition: %s' % (
+ self.__regid__, tr)
tr = _tr
return self._add_trinfo(comment, commentformat, tr.eid)
@@ -494,7 +496,7 @@
else:
state = self.current_workflow.state_by_name(statename)
if state is None:
- raise WorkflowException('not a %s state: %s' % (self.id,
+ raise WorkflowException('not a %s state: %s' % (self.__regid__,
statename))
stateeid = state.eid
# XXX try to find matching transition?
@@ -532,7 +534,7 @@
super(WorkflowableMixIn, self).clear_all_caches()
clear_cache(self, 'cwetype_workflow')
- @deprecated('get transition from current workflow and use its may_be_fired method')
+ @deprecated('[3.5] get transition from current workflow and use its may_be_fired method')
def can_pass_transition(self, trname):
"""return the Transition instance if the current user can fire the
transition with the given name, else None
@@ -542,8 +544,8 @@
return tr
@property
- @deprecated('use printable_state')
+ @deprecated('[3.5] use printable_state')
def displayable_state(self):
- return self.req._(self.state)
+ return self._cw._(self.state)
MI_REL_TRIGGERS[('in_state', 'subject')] = WorkflowableMixIn
--- a/entity.py Mon Feb 08 13:33:09 2010 +0100
+++ b/entity.py Mon Feb 08 13:35:07 2010 +0100
@@ -22,119 +22,24 @@
from cubicweb.rset import ResultSet
from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint
from cubicweb.rqlrewrite import RQLRewriter
-from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
-from cubicweb.common.uilib import printable_value, soup2xhtml
-from cubicweb.common.mixins import MI_REL_TRIGGERS
-from cubicweb.common.mttransforms import ENGINE
+from cubicweb.uilib import printable_value, soup2xhtml
+from cubicweb.mixins import MI_REL_TRIGGERS
+from cubicweb.mttransforms import ENGINE
_marker = object()
def greater_card(rschema, subjtypes, objtypes, index):
for subjtype in subjtypes:
for objtype in objtypes:
- card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+ card = rschema.rdef(subjtype, objtype).cardinality[index]
if card in '+*':
return card
return '1'
-_MODE_TAGS = set(('link', 'create'))
-_CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
-
-try:
- from cubicweb.web import formwidgets, uicfg
-
- def _dispatch_rtags(tags, rtype, role, stype, otype):
- for tag in tags:
- if tag in _MODE_TAGS:
- uicfg.actionbox_appearsin_addmenu.tag_relation(
- (stype, rtype, otype, role), tag == 'create')
- elif tag in _CATEGORY_TAGS:
- uicfg.autoform_section.tag_relation((stype, rtype, otype, role),
- tag)
- elif tag == 'inlineview':
- uicfg.autoform_is_inlined.tag_relation((stype, rtype, otype, role), True)
- else:
- raise ValueError(tag)
-
-except ImportError:
-
- _dispatch_rtags = None
-
-def _get_etype(bases, classdict):
- try:
- return classdict['id']
- except KeyError:
- for base in bases:
- etype = getattr(base, 'id', None)
- if etype and etype != 'Any':
- return etype
-
-def _get_defs(attr, name, bases, classdict):
- try:
- yield name, classdict.pop(attr)
- except KeyError:
- for base in bases:
- try:
- value = getattr(base, attr)
- delattr(base, attr)
- yield base.__name__, value
- except AttributeError:
- continue
-
-
-class _metaentity(type):
- """this metaclass sets the relation tags on the entity class
- and deals with the `widgets` attribute
- """
- def __new__(mcs, name, bases, classdict):
- # collect baseclass' rtags
- etype = _get_etype(bases, classdict)
- if etype and _dispatch_rtags is not None:
- for name, rtags in _get_defs('__rtags__', name, bases, classdict):
- warn('%s: __rtags__ is deprecated' % name, DeprecationWarning)
- for relation, tags in rtags.iteritems():
- # tags must become an iterable
- if isinstance(tags, basestring):
- tags = (tags,)
- # relation must become a 3-uple (rtype, targettype, role)
- if isinstance(relation, basestring):
- _dispatch_rtags(tags, relation, 'subject', etype, '*')
- _dispatch_rtags(tags, relation, 'object', '*', etype)
- elif len(relation) == 1: # useful ?
- _dispatch_rtags(tags, relation[0], 'subject', etype, '*')
- _dispatch_rtags(tags, relation[0], 'object', '*', etype)
- elif len(relation) == 2:
- rtype, ttype = relation
- ttype = bw_normalize_etype(ttype) # XXX bw compat
- _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
- _dispatch_rtags(tags, rtype, 'object', ttype, etype)
- elif len(relation) == 3:
- rtype, ttype, role = relation
- ttype = bw_normalize_etype(ttype)
- if role == 'subject':
- _dispatch_rtags(tags, rtype, 'subject', etype, ttype)
- else:
- _dispatch_rtags(tags, rtype, 'object', ttype, etype)
- else:
- raise ValueError('bad rtag definition (%r)' % (relation,))
- for name, widgets in _get_defs('widgets', name, bases, classdict):
- warn('%s: widgets is deprecated' % name, DeprecationWarning)
- for rtype, wdgname in widgets.iteritems():
- if wdgname in ('URLWidget', 'EmbededURLWidget', 'RawDynamicComboBoxWidget'):
- warn('%s widget is deprecated' % wdgname, DeprecationWarning)
- continue
- if wdgname == 'StringWidget':
- wdgname = 'TextInput'
- widget = getattr(formwidgets, wdgname)
- assert hasattr(widget, 'render')
- uicfg.autoform_field_kwargs.tag_subject_of(
- (etype, rtype, '*'), {'widget': widget})
- return super(_metaentity, mcs).__new__(mcs, name, bases, classdict)
-
-
class Entity(AppObject, dict):
"""an entity instance has e_schema automagically set on
the class and instances has access to their issuing cursor.
@@ -158,28 +63,24 @@
as composite relations or relations that have '?1' as object
cardinality are always skipped.
"""
- __metaclass__ = _metaentity
__registry__ = 'etypes'
__select__ = yes()
# class attributes that must be set in class definition
- id = None
rest_attr = None
fetch_attrs = None
skip_copy_for = ('in_state',)
# class attributes set automatically at registration time
e_schema = None
- MODE_TAGS = set(('link', 'create'))
- CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
@classmethod
- def __initialize__(cls):
+ def __initialize__(cls, schema):
"""initialize a specific entity class by adding descriptors to access
entity type's attributes and relations
"""
- etype = cls.id
+ etype = cls.__regid__
assert etype != 'Any', etype
- cls.e_schema = eschema = cls.schema.eschema(etype)
+ cls.e_schema = eschema = schema.eschema(etype)
for rschema, _ in eschema.attribute_definitions():
if rschema.type == 'eid':
continue
@@ -218,7 +119,7 @@
"""return a rql to fetch all entities of the class type"""
restrictions = restriction or []
if settype:
- restrictions.append('%s is %s' % (mainvar, cls.id))
+ restrictions.append('%s is %s' % (mainvar, cls.__regid__))
if fetchattrs is None:
fetchattrs = cls.fetch_attrs
selection = [mainvar]
@@ -251,9 +152,10 @@
rschema = eschema.subjrels[attr]
except KeyError:
cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
- attr, cls.id)
+ attr, cls.__regid__)
continue
- if not user.matching_groups(rschema.get_groups('read')):
+ rdef = eschema.rdef(attr)
+ if not user.matching_groups(rdef.get_groups('read')):
continue
var = varmaker.next()
selection.append(var)
@@ -262,7 +164,7 @@
if not rschema.final:
# XXX this does not handle several destination types
desttype = rschema.objects(eschema.type)[0]
- card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+ card = rdef.cardinality[0]
if card not in '?1':
cls.warning('bad relation %s specified in fetch attrs for %s',
attr, cls)
@@ -274,7 +176,8 @@
# that case the relation may still be missing. As we miss this
# later information here, systematically add it.
restrictions[-1] += '?'
- destcls = cls.vreg['etypes'].etype_class(desttype)
+ # XXX user._cw.vreg iiiirk
+ destcls = user._cw.vreg['etypes'].etype_class(desttype)
destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
selection, orderby, restrictions,
user, ordermethod, visited=visited)
@@ -285,14 +188,6 @@
@classmethod
@cached
- def parent_classes(cls):
- parents = [cls.vreg['etypes'].etype_class(e.type)
- for e in cls.e_schema.ancestors()]
- parents.append(cls.vreg['etypes'].etype_class('Any'))
- return parents
-
- @classmethod
- @cached
def _rest_attr_info(cls):
mainattr, needcheck = 'eid', True
if cls.rest_attr:
@@ -309,7 +204,7 @@
return mainattr, needcheck
def __init__(self, req, rset=None, row=None, col=0):
- AppObject.__init__(self, req, rset, row, col)
+ AppObject.__init__(self, req, rset=rset, row=row, col=col)
dict.__init__(self)
self._related_cache = {}
if rset is not None:
@@ -363,24 +258,26 @@
@cached
def metainformation(self):
- res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
- res['source'] = self.req.source_defs()[res['source']]
+ res = dict(zip(('type', 'source', 'extid'), self._cw.describe(self.eid)))
+ res['source'] = self._cw.source_defs()[res['source']]
return res
def clear_local_perm_cache(self, action):
for rqlexpr in self.e_schema.get_rqlexprs(action):
- self.req.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
+ self._cw.local_perm_cache.pop((rqlexpr.eid, (('x', self.eid),)), None)
def check_perm(self, action):
- self.e_schema.check_perm(self.req, action, self.eid)
+ self.e_schema.check_perm(self._cw, action, eid=self.eid)
def has_perm(self, action):
- return self.e_schema.has_perm(self.req, action, self.eid)
+ return self.e_schema.has_perm(self._cw, action, eid=self.eid)
def view(self, __vid, __registry='views', **kwargs):
"""shortcut to apply a view on this entity"""
- return self.vreg[__registry].render(__vid, self.req, rset=self.rset,
- row=self.row, col=self.col, **kwargs)
+ view = self._cw.vreg[__registry].select(__vid, self._cw, rset=self.cw_rset,
+ row=self.cw_row, col=self.cw_col,
+ **kwargs)
+ return view.render(row=self.cw_row, col=self.cw_col, **kwargs)
def absolute_url(self, *args, **kwargs):
"""return an absolute url to view this entity"""
@@ -394,18 +291,18 @@
# in linksearch mode, we don't want external urls else selecting
# the object for use in the relation is tricky
# XXX search_state is web specific
- if getattr(self.req, 'search_state', ('normal',))[0] == 'normal':
+ if getattr(self._cw, 'search_state', ('normal',))[0] == 'normal':
kwargs['base_url'] = self.metainformation()['source'].get('base-url')
if method in (None, 'view'):
try:
kwargs['_restpath'] = self.rest_path(kwargs.get('base_url'))
except TypeError:
- warn('%s: rest_path() now take use_ext_eid argument, '
- 'please update' % self.id, DeprecationWarning)
+ warn('[3.4] %s: rest_path() now take use_ext_eid argument, '
+ 'please update' % self.__regid__, DeprecationWarning)
kwargs['_restpath'] = self.rest_path()
else:
kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
- return self.build_url(method, **kwargs)
+ return self._cw.build_url(method, **kwargs)
def rest_path(self, use_ext_eid=False):
"""returns a REST-like (relative) path for this entity"""
@@ -419,9 +316,12 @@
path += '/eid'
elif needcheck:
# make sure url is not ambiguous
- rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (
- etype, mainattr)
- nbresults = self.req.execute(rql, {'value' : value})[0][0]
+ try:
+ nbresults = self.__unique
+ except AttributeError:
+ rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (
+ etype, mainattr)
+ nbresults = self.__unique = self._cw.execute(rql, {'value' : value})[0][0]
if nbresults != 1: # ambiguity?
mainattr = 'eid'
path += '/eid'
@@ -430,13 +330,13 @@
value = self.metainformation()['extid']
else:
value = self.eid
- return '%s/%s' % (path, self.req.url_quote(value))
+ return '%s/%s' % (path, self._cw.url_quote(value))
def attr_metadata(self, attr, metadata):
"""return a metadata for an attribute (None if unspecified)"""
value = getattr(self, '%s_%s' % (attr, metadata), None)
if value is None and metadata == 'encoding':
- value = self.vreg.property_value('ui.encoding')
+ value = self._cw.vreg.property_value('ui.encoding')
return value
def printable_value(self, attr, value=_marker, attrtype=None,
@@ -453,16 +353,16 @@
return u''
if attrtype is None:
attrtype = self.e_schema.destination(attr)
- props = self.e_schema.rproperties(attr)
+ props = self.e_schema.rdef(attr)
if attrtype == 'String':
# internalinalized *and* formatted string such as schema
# description...
- if props.get('internationalizable'):
- value = self.req._(value)
+ if props.internationalizable:
+ value = self._cw._(value)
attrformat = self.attr_metadata(attr, 'format')
if attrformat:
return self.mtc_transform(value, attrformat, format,
- self.req.encoding)
+ self._cw.encoding)
elif attrtype == 'Bytes':
attrformat = self.attr_metadata(attr, 'format')
if attrformat:
@@ -470,7 +370,7 @@
return self.mtc_transform(value.getvalue(), attrformat, format,
encoding)
return u''
- value = printable_value(self.req, attrtype, value, props,
+ value = printable_value(self._cw, attrtype, value, props,
displaytime=displaytime)
if format == 'text/html':
value = xml_escape(value)
@@ -481,7 +381,7 @@
trdata = TransformData(data, format, encoding, appobject=self)
data = _engine.convert(trdata, target_format).decode()
if format == 'text/html':
- data = soup2xhtml(data, self.req.encoding)
+ data = soup2xhtml(data, self._cw.encoding)
return data
# entity cloning ##########################################################
@@ -495,7 +395,7 @@
Overrides this if you want another behaviour
"""
assert self.has_eid()
- execute = self.req.execute
+ execute = self._cw.execute
for rschema in self.e_schema.subject_relations():
if rschema.final or rschema.meta:
continue
@@ -505,11 +405,12 @@
if rschema.type in self.skip_copy_for:
continue
# skip composite relation
- if self.e_schema.subjrproperty(rschema, 'composite'):
+ rdef = self.e_schema.rdef(rschema)
+ if rdef.composite:
continue
# skip relation with card in ?1 else we either change the copied
# object (inlined relation) or inserting some inconsistency
- if self.e_schema.subjrproperty(rschema, 'cardinality')[1] in '?1':
+ if rdef.cardinality[1] in '?1':
continue
rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
rschema.type, rschema.type)
@@ -519,14 +420,15 @@
if rschema.meta:
continue
# skip already defined relations
- if getattr(self, 'reverse_%s' % rschema.type):
+ if self.related(rschema.type, 'object'):
continue
+ rdef = self.e_schema.rdef(rschema, 'object')
# skip composite relation
- if self.e_schema.objrproperty(rschema, 'composite'):
+ if rdef.composite:
continue
# skip relation with card in ?1 else we either change the copied
# object (inlined relation) or inserting some inconsistency
- if self.e_schema.objrproperty(rschema, 'cardinality')[0] in '?1':
+ if rdef.cardinality[0] in '?1':
continue
rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
rschema.type, rschema.type)
@@ -539,23 +441,24 @@
def as_rset(self):
"""returns a resultset containing `self` information"""
rset = ResultSet([(self.eid,)], 'Any X WHERE X eid %(x)s',
- {'x': self.eid}, [(self.id,)])
- return self.req.decorate_rset(rset)
+ {'x': self.eid}, [(self.__regid__,)])
+ return self._cw.decorate_rset(rset)
def to_complete_relations(self):
"""by default complete final relations to when calling .complete()"""
for rschema in self.e_schema.subject_relations():
if rschema.final:
continue
- if len(rschema.objects(self.e_schema)) > 1:
+ targets = rschema.objects(self.e_schema)
+ if len(targets) > 1:
# ambigous relations, the querier doesn't handle
# outer join correctly in this case
continue
if rschema.inlined:
- matching_groups = self.req.user.matching_groups
- if matching_groups(rschema.get_groups('read')) and \
- all(matching_groups(es.get_groups('read'))
- for es in rschema.objects(self.e_schema)):
+ matching_groups = self._cw.user.matching_groups
+ rdef = rschema.rdef(self.e_schema, targets[0])
+ if matching_groups(rdef.get_groups('read')) and \
+ all(matching_groups(e.get_groups('read')) for e in targets):
yield rschema, 'subject'
def to_complete_attributes(self, skip_bytes=True):
@@ -567,7 +470,8 @@
if attr == 'eid':
continue
# password retreival is blocked at the repository server level
- if not self.req.user.matching_groups(rschema.get_groups('read')) \
+ rdef = rschema.rdef(self.e_schema, attrschema)
+ if not self._cw.user.matching_groups(rdef.get_groups('read')) \
or attrschema.type == 'Password':
self[attr] = None
continue
@@ -608,31 +512,28 @@
if self.relation_cached(rtype, role):
continue
var = varmaker.next()
+ targettype = rschema.targets(self.e_schema, role)[0]
+ rdef = rschema.role_rdef(self.e_schema, targettype, role)
+ card = rdef.role_cardinality(role)
+ assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
+ role, card)
if role == 'subject':
- targettype = rschema.objects(self.e_schema)[0]
- card = rschema.rproperty(self.e_schema, targettype,
- 'cardinality')[0]
if card == '1':
rql.append('%s %s %s' % (V, rtype, var))
- else: # '?"
+ else:
rql.append('%s %s %s?' % (V, rtype, var))
else:
- targettype = rschema.subjects(self.e_schema)[1]
- card = rschema.rproperty(self.e_schema, targettype,
- 'cardinality')[1]
if card == '1':
rql.append('%s %s %s' % (var, rtype, V))
- else: # '?"
+ else:
rql.append('%s? %s %s' % (var, rtype, V))
- assert card in '1?', '%s %s %s %s' % (self.e_schema, rtype,
- role, card)
selected.append(((rtype, role), var))
if selected:
# select V, we need it as the left most selected variable
# if some outer join are included to fetch inlined relations
rql = 'Any %s,%s %s' % (V, ','.join(var for attr, var in selected),
','.join(rql))
- execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+ execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
# handle attributes
for i in xrange(1, lastattr):
@@ -643,9 +544,9 @@
value = rset[i]
if value is None:
rrset = ResultSet([], rql, {'x': self.eid})
- self.req.decorate_rset(rrset)
+ self._cw.decorate_rset(rrset)
else:
- rrset = self.req.eid_rset(value)
+ rrset = self._cw.eid_rset(value)
self.set_related_cache(rtype, role, rrset)
def get_value(self, name):
@@ -663,7 +564,7 @@
rql = "Any A WHERE X eid %%(x)s, X %s A" % name
# XXX should we really use unsafe_execute here? I think so (syt),
# see #344874
- execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+ execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
try:
rset = execute(rql, {'x': self.eid}, 'x')
except Unauthorized:
@@ -678,7 +579,7 @@
name, self.eid)
if self.e_schema.destination(name) == 'String':
# XXX (syt) imo emtpy string is better
- self[name] = value = self.req._('unaccessible')
+ self[name] = value = self._cw._('unaccessible')
else:
self[name] = value = None
return value
@@ -698,13 +599,13 @@
rql = self.related_rql(rtype, role)
# XXX should we really use unsafe_execute here? I think so (syt),
# see #344874
- execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+ execute = getattr(self._cw, 'unsafe_execute', self._cw.execute)
rset = execute(rql, {'x': self.eid}, 'x')
self.set_related_cache(rtype, role, rset)
return self.related(rtype, role, limit, entities)
def related_rql(self, rtype, role='subject', targettypes=None):
- rschema = self.schema[rtype]
+ rschema = self._cw.vreg.schema[rtype]
if role == 'subject':
restriction = 'E eid %%(x)s, E %s X' % rtype
if targettypes is None:
@@ -722,14 +623,14 @@
if len(targettypes) > 1:
fetchattrs_list = []
for ttype in targettypes:
- etypecls = self.vreg['etypes'].etype_class(ttype)
+ etypecls = self._cw.vreg['etypes'].etype_class(ttype)
fetchattrs_list.append(set(etypecls.fetch_attrs))
fetchattrs = reduce(set.intersection, fetchattrs_list)
- rql = etypecls.fetch_rql(self.req.user, [restriction], fetchattrs,
+ rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs,
settype=False)
else:
- etypecls = self.vreg['etypes'].etype_class(targettypes[0])
- rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
+ etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0])
+ rql = etypecls.fetch_rql(self._cw.user, [restriction], settype=False)
# optimisation: remove ORDERBY if cardinality is 1 or ? (though
# greater_card return 1 for those both cases)
if card == '1':
@@ -753,7 +654,7 @@
"""
ordermethod = ordermethod or 'fetch_unrelated_order'
if isinstance(rtype, basestring):
- rtype = self.schema.rschema(rtype)
+ rtype = self._cw.vreg.schema.rschema(rtype)
if role == 'subject':
evar, searchedvar = 'S', 'O'
subjtype, objtype = self.e_schema, targettype
@@ -771,29 +672,29 @@
restriction = []
args = {}
securitycheck_args = {}
- insertsecurity = (rtype.has_local_role('add') and not
- rtype.has_perm(self.req, 'add', **securitycheck_args))
- constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+ 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
if vocabconstraints:
# RQLConstraint is a subclass for RQLVocabularyConstraint, so they
# will be included as well
- restriction += [cstr.restriction for cstr in constraints
+ restriction += [cstr.restriction for cstr in rdef.constraints
if isinstance(cstr, RQLVocabularyConstraint)]
else:
- restriction += [cstr.restriction for cstr in constraints
+ restriction += [cstr.restriction for cstr in rdef.constraints
if isinstance(cstr, RQLConstraint)]
- etypecls = self.vreg['etypes'].etype_class(targettype)
- rql = etypecls.fetch_rql(self.req.user, restriction,
+ etypecls = self._cw.vreg['etypes'].etype_class(targettype)
+ rql = etypecls.fetch_rql(self._cw.user, restriction,
mainvar=searchedvar, ordermethod=ordermethod)
# 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 = rtype.get_rqlexprs('add')
- rewriter = RQLRewriter(self.req)
- rqlst = self.req.vreg.parse(self.req, rql, args)
+ 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:
@@ -812,11 +713,11 @@
try:
rql, args = self.unrelated_rql(rtype, targettype, role, ordermethod)
except Unauthorized:
- return self.req.empty_rset()
+ return self._cw.empty_rset()
if limit is not None:
before, after = rql.split(' WHERE ', 1)
rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
- return self.req.execute(rql, args, tuple(args))
+ return self._cw.execute(rql, args, tuple(args))
# relations cache handling ################################################
@@ -840,15 +741,13 @@
def set_related_cache(self, rtype, role, rset, col=0):
"""set cached values for the given relation"""
if rset:
- related = tuple(rset.entities(col))
- rschema = self.schema.rschema(rtype)
+ related = list(rset.entities(col))
+ rschema = self._cw.vreg.schema.rschema(rtype)
if role == 'subject':
- rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
- 'cardinality')[1]
+ rcard = rschema.rdef(self.e_schema, related[0].e_schema).cardinality[1]
target = 'object'
else:
- rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
- 'cardinality')[0]
+ rcard = rschema.rdef(related[0].e_schema, self.e_schema).cardinality[0]
target = 'subject'
if rcard in '?1':
for rentity in related:
@@ -886,7 +785,12 @@
# clear relations cache
for rschema, _, role in self.e_schema.relation_definitions():
self.clear_related_cache(rschema.type, role)
-
+ # rest path unique cache
+ try:
+ del self.__unique
+ except AttributeError:
+ pass
+
# raw edition utilities ###################################################
def set_attributes(self, _cw_unsafe=False, **kwargs):
@@ -899,10 +803,10 @@
# and now update the database
kwargs['x'] = self.eid
if _cw_unsafe:
- self.req.unsafe_execute(
+ self._cw.unsafe_execute(
'SET %s WHERE X eid %%(x)s' % ','.join(relations), kwargs, 'x')
else:
- self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
+ self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
kwargs, 'x')
def set_relations(self, _cw_unsafe=False, **kwargs):
@@ -913,9 +817,9 @@
relations of the given type from or to this object should be deleted).
"""
if _cw_unsafe:
- execute = self.req.unsafe_execute
+ execute = self._cw.unsafe_execute
else:
- execute = self.req.execute
+ execute = self._cw.execute
# XXX update cache
for attr, values in kwargs.iteritems():
if attr.startswith('reverse_'):
@@ -934,7 +838,7 @@
def delete(self):
assert self.has_eid(), self.eid
- self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
+ self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
{'x': self.eid})
# server side utilities ###################################################
@@ -953,10 +857,10 @@
"""
# necessary since eid is handled specifically and yams require it to be
# in the dictionary
- if self.req is None:
+ if self._cw is None:
_ = unicode
else:
- _ = self.req._
+ _ = self._cw._
self.e_schema.check(self, creation=creation, _=_)
def fti_containers(self, _done=None):
@@ -993,7 +897,7 @@
"""
from indexer.query_objects import tokenize
# take care to cases where we're modyfying the schema
- pending = self.req.transaction_data.setdefault('pendingrdefs', set())
+ pending = self._cw.transaction_data.setdefault('pendingrdefs', set())
words = []
for rschema in self.e_schema.indexable_attributes():
if (self.e_schema, rschema) in pending:
@@ -1018,20 +922,6 @@
words += entity.get_words()
return words
- @deprecated('[3.2] see new form api')
- def vocabulary(self, rtype, role='subject', limit=None):
- """vocabulary functions must return a list of couples
- (label, eid) that will typically be used to fill the
- edition view's combobox.
-
- If `eid` is None in one of these couples, it should be
- interpreted as a separator in case vocabulary results are grouped
- """
- from logilab.common.testlib import mock_object
- form = self.vreg.select('forms', 'edition', self.req, entity=self)
- field = mock_object(name=rtype, role=role)
- return form.form_field_vocabulary(field, limit)
-
# attribute and relation descriptors ##########################################
--- a/etwist/server.py Mon Feb 08 13:33:09 2010 +0100
+++ b/etwist/server.py Mon Feb 08 13:35:07 2010 +0100
@@ -137,7 +137,7 @@
start_task(interval, self.appli.session_handler.clean_sessions)
def set_url_rewriter(self):
- self.url_rewriter = self.appli.vreg['components'].select_object('urlrewriter')
+ self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
def shutdown_event(self):
"""callback fired when the server is shutting down to properly
--- a/etwist/test/unittest_server.py Mon Feb 08 13:33:09 2010 +0100
+++ b/etwist/test/unittest_server.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,11 +5,11 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.etwist.server import host_prefixed_baseurl
-class HostPrefixedBaseURLTC(EnvBasedTC):
+class HostPrefixedBaseURLTC(CubicWebTC):
def _check(self, baseurl, host, waited):
self.assertEquals(host_prefixed_baseurl(baseurl, host), waited,
--- a/etwist/twctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/etwist/twctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,7 +8,6 @@
import sys
-from cubicweb import underline_title
from cubicweb.toolsutils import CommandHandler
from cubicweb.web.webctl import WebCreateHandler
--- a/ext/rest.py Mon Feb 08 13:33:09 2010 +0100
+++ b/ext/rest.py Mon Feb 08 13:35:07 2010 +0100
@@ -70,10 +70,10 @@
# Base URL mainly used by inliner.pep_reference; so this is correct:
context = inliner.document.settings.context
try:
- refedentity = context.req.entity_from_eid(eid_num)
+ refedentity = context._cw.entity_from_eid(eid_num)
except UnknownEid:
ref = '#'
- rest += u' ' + context.req._('(UNEXISTANT EID)')
+ rest += u' ' + context._cw._('(UNEXISTANT EID)')
else:
ref = refedentity.absolute_url()
set_classes(options)
@@ -96,7 +96,7 @@
lineno - state_machine.input_offset - 1)
#source_dir = os.path.dirname(os.path.abspath(source))
fid = arguments[0]
- for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
+ for lang in chain((context._cw.lang, context.vreg.property_value('ui.language')),
context.config.available_languages()):
rid = '%s_%s.rst' % (fid, lang)
resourcedir = context.config.locate_doc_file(rid)
@@ -159,14 +159,16 @@
try:
lexer = get_lexer_by_name(arguments[0])
except ValueError:
- import traceback
- traceback.print_exc()
- print sorted(aliases for module_name, name, aliases, _, _ in LEXERS.itervalues())
# no lexer found
lexer = get_lexer_by_name('text')
parsed = highlight(u'\n'.join(content), lexer, _PYGMENTS_FORMATTER)
- context = state.document.settings.context
- context.req.add_css('pygments.css')
+ # don't fail if no context set on the sourcecode directive
+ try:
+ context = state.document.settings.context
+ context._cw.add_css('pygments.css')
+ except AttributeError:
+ # used outside cubicweb
+ pass
return [nodes.raw('', parsed, format='html')]
pygments_directive.arguments = (1, 0, 1)
@@ -206,7 +208,7 @@
:return:
the data formatted as HTML or the original data if an error occured
"""
- req = context.req
+ req = context._cw
if isinstance(data, unicode):
encoding = 'unicode'
# remove unprintable characters unauthorized in xml
--- a/ext/test/unittest_rest.py Mon Feb 08 13:33:09 2010 +0100
+++ b/ext/test/unittest_rest.py Mon Feb 08 13:35:07 2010 +0100
@@ -6,11 +6,11 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.ext.rest import rest_publish
-class RestTC(EnvBasedTC):
+class RestTC(CubicWebTC):
def context(self):
return self.execute('CWUser X WHERE X login "admin"').get_entity(0, 0)
--- a/goa/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -44,7 +44,7 @@
def rql_for_eid(eid):
return 'Any X WHERE X eid "%s"' % eid
- from cubicweb.common import uilib
+ from cubicweb import uilib
uilib.rql_for_eid = rql_for_eid
def typed_eid(eid):
--- a/goa/appobjects/components.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/appobjects/components.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,7 +12,7 @@
from cubicweb import typed_eid
from cubicweb.selectors import one_line_rset, match_search_state, accept
from cubicweb.schema import display_name
-from cubicweb.common.view import StartupView, EntityView
+from cubicweb.view import StartupView, EntityView
from cubicweb.web import Redirect
from cubicweb.web.views import vid_from_rset
@@ -28,7 +28,7 @@
__select__ = one_line_rset() & match_search_state('linksearch') & accept
def cell_call(self, row, col):
- entity = self.entity(0, 0)
+ entity = self.rset.get_entity(0, 0)
role, eid, rtype, etype = self.req.search_state[1]
assert entity.eid == typed_eid(eid)
rset = entity.unrelated(rtype, etype, role, ordermethod='fetch_order')
--- a/goa/appobjects/dbmgmt.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/appobjects/dbmgmt.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,7 +15,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.selectors import none_rset, match_user_groups
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
from cubicweb.web import Redirect
from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions
--- a/goa/appobjects/sessions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/appobjects/sessions.py Mon Feb 08 13:35:07 2010 +0100
@@ -57,7 +57,7 @@
clear_cache(req, 'cursor')
cnxprops = ConnectionProperties(self.vreg.config.repo_method,
close=False, log=False)
- cnx = repo_connect(self._repo, login, password, cnxprops=cnxprops)
+ cnx = repo_connect(self._repo, login, password=password, cnxprops=cnxprops)
self._init_cnx(cnx, login, password)
# associate the connection to the current request
req.set_connection(cnx)
@@ -73,9 +73,9 @@
class GAEPersistentSessionManager(AbstractSessionManager):
"""manage session data associated to a session identifier"""
- def __init__(self, *args, **kwargs):
- super(GAEPersistentSessionManager, self).__init__(*args, **kwargs)
- self._repo = self.config.repository(vreg=self.vreg)
+ def __init__(self, vreg, *args, **kwargs):
+ super(GAEPersistentSessionManager, self).__init__(vreg, *args, **kwargs)
+ self._repo = self.config.repository(vreg=vreg)
def get_session(self, req, sessionid):
"""return existing session for the given session identifier"""
@@ -251,7 +251,7 @@
set_log_methods(ConnectionProxy, logging.getLogger('cubicweb.web.goa.session'))
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
from cubicweb.web import application
class SessionsCleaner(StartupView):
--- a/goa/db.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/db.py Mon Feb 08 13:35:07 2010 +0100
@@ -35,7 +35,8 @@
from logilab.common.decorators import cached, iclassmethod
-from cubicweb import RequestSessionMixIn, Binary, entities
+from cubicweb import Binary, entities
+from cubicweb.req import RequestSessionBase
from cubicweb.rset import ResultSet
from cubicweb.entity import metaentity
from cubicweb.server.utils import crypt_password
@@ -92,7 +93,7 @@
def needrequest(wrapped):
def wrapper(cls, *args, **kwargs):
req = kwargs.pop('req', None)
- if req is None and args and isinstance(args[0], RequestSessionMixIn):
+ if req is None and args and isinstance(args[0], RequestSessionBase):
args = list(args)
req = args.pop(0)
if req is None:
@@ -155,7 +156,7 @@
#
# Entity prototype:
# __init__(self, req, rset, row=None, col=0)
- if args and isinstance(args[0], RequestSessionMixIn) or 'req' in kwargs:
+ if args and isinstance(args[0], RequestSessionBase) or 'req' in kwargs:
super(Model, self).__init__(*args, **kwargs)
self._gaeinitargs = None
else:
@@ -274,7 +275,7 @@
def view(self, vid, __registry='views', **kwargs):
"""shortcut to apply a view on this entity"""
- return self.vreg[__registry]render(vid, self.req, rset=self.rset,
+ return self.vreg[__registry].render(vid, self.req, rset=self.rset,
row=self.row, col=self.col, **kwargs)
@classmethod
--- a/goa/doc/devmanual_fr/sect_definition_schema.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/doc/devmanual_fr/sect_definition_schema.txt Mon Feb 08 13:35:07 2010 +0100
@@ -168,7 +168,7 @@
à la relation. Cela se limite donc aux relations dont la cardinalité
sujet->relation->objet vaut 0..1 ('?') ou 1..1 ('1')
-* `symetric` : booléen indiquant que la relation est symétrique, i.e. "X relation
+* `symmetric` : booléen indiquant que la relation est symétrique, i.e. "X relation
Y" implique "Y relation X"
Dans le cas de définitions de relations simultanée, `sujet` et `object` peuvent
--- a/goa/doc/quickstart.txt Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/doc/quickstart.txt Mon Feb 08 13:35:07 2010 +0100
@@ -1,3 +1,5 @@
+.. -*- coding: utf-8 -*-
+
Introduction
=============
@@ -11,7 +13,7 @@
application.
*result set*
- objet qui encaspule les résultats d'une requête adressée à l'entrepôt
+ objet qui encaspule les résultats d'une requête adressée à l'entrepôt
de données et des informations sur cette requête.
*vue*
@@ -23,8 +25,8 @@
Définition d'une application de Blog
====================================
-La première chose à faire est de copier le squelette depuis le répertoire
-``lax/skel`` vers un nouveau répertoire qui sera votre application
+La première chose à faire est de copier le squelette depuis le répertoire
+``lax/skel`` vers un nouveau répertoire qui sera votre application
``Google AppEngine``::
$ cp -r lax/skel myapp
@@ -36,7 +38,7 @@
données manipulées. La syntaxe de la définition est la même que celle
proposée par `Google AppEngine`_ mais il faut remplacer la ligne
d'import::
-
+
from google.appengine.ext import db
par celle-ci::
@@ -47,7 +49,7 @@
Un exemple de schéma de données pour un ``Blog`` pourrait être::
from cubicweb.goa import db
-
+
class Blog(db.Model):
# un titre à donner à l'entrée
title = db.StringProperty(required=True)
@@ -56,15 +58,15 @@
# le contenu de l'entrée
content = db.TextProperty()
# une entrée peut en citer une autre
- cites = db.SelfReferenceProperty()
-
+ cites = db.SelfReferenceProperty()
+
Personnalisation des vues
-------------------------
``LAX`` permet de générer directement, à partir de la définition
-du schéma, des vues de consultation, d'ajout et de modification
-pour tous les types de donées manipulés. Il est toutefois
+du schéma, des vues de consultation, d'ajout et de modification
+pour tous les types de donées manipulés. Il est toutefois
généralement souhaitable de personnaliser les vues de consultations.
Dans ``LAX``, les vues sont représentées par des classes Python.
@@ -74,7 +76,7 @@
- un identifiant (tous les objets dans ``LAX`` sont enregistrés
dans un registre et cet identifiant sert de clé pour y retrouver
la vue)
-
+
- une description des types de données auxquels elle s'applique
Il existe dans ``LAX`` des vues prédéfinies et utilisées par le moteur
@@ -86,17 +88,17 @@
Par exemple, si on souhaite modifier la page principale d'une entrée de
blog, il faut surcharger la vue ``primary`` des objets ``Blog`` dans
le fichier ``myapp/views.py``::
-
+
from cubicweb.web.views import baseviews
-
+
class BlogPrimaryView(baseviews.PrimaryView):
accepts = ('Blog',)
-
+
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.rset.get_entity(row, col)
self.w(u'<h1>%s</h1>' % entity.title)
self.w(u'<div>%s</div>' entity.content)
-
+
Génération du graphique de schéma
---------------------------------
@@ -104,13 +106,13 @@
Il existe une vue ``schema`` qui permet d'afficher un graphique
représantant les différents types d'entités définis dans le schéma
ainsi que les relations entre ces types. Ce graphique doit être généré
-statiquement. Le script à utiliser pour générer ce schéma est
+statiquement. Le script à utiliser pour générer ce schéma est
dans ``myapp/tools``. Ce script nécessite d'avoir accès aux
bibliothèques fournies par le SDK de ``Google AppEngine``. Il faut
donc modifier son PYTHONPATH::
$ export PYTHONPATH=GAE_ROOT/google:GAE_ROOT/lib/yaml
- $ python tools/generate_schema_img.py
+ $ python tools/generate_schema_img.py
Génération des fichiers de traduction
--- a/goa/goactl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/goactl.py Mon Feb 08 13:35:07 2010 +0100
@@ -59,21 +59,21 @@
'cwconfig.py',
'entity.py',
'interfaces.py',
+ 'i18n.py',
+ 'mail.py',
+ 'migration.py',
+ 'mixins.py',
+ 'mttransforms.py',
'rqlrewrite.py',
'rset.py',
'schema.py',
'schemaviewer.py',
'selectors.py',
+ 'uilib.py',
'utils.py',
'vregistry.py',
'view.py',
- 'common/mail.py',
- 'common/migration.py',
- 'common/mixins.py',
- 'common/mttransforms.py',
- 'common/uilib.py',
-
'ext/html4zope.py',
'ext/rest.py',
@@ -117,7 +117,6 @@
'web/httpcache.py',
'web/request.py',
'web/webconfig.py',
- 'web/widgets.py',
'web/views/__init__.py',
'web/views/actions.py',
@@ -166,7 +165,7 @@
OVERRIDEN_FILES = (
('toolsutils.py', 'toolsutils.py'),
- ('mttransforms.py', 'common/mttransforms.py'),
+ ('mttransforms.py', 'mttransforms.py'),
('server__init__.py', 'server/__init__.py'),
('rqlannotation.py', 'server/rqlannotation.py'),
)
@@ -210,7 +209,6 @@
create_dir(split(target)[0])
create_symlink(join(CW_SOFTWARE_ROOT, fpath), target)
# overriden files
- create_init_file(join(appldir, 'cubicweb/common'), 'cubicweb.common')
for fpath, subfpath in OVERRIDEN_FILES:
create_symlink(join(CW_SOFTWARE_ROOT, 'goa', 'overrides', fpath),
join(appldir, 'cubicweb', subfpath))
@@ -225,7 +223,7 @@
join(packagesdir, include))
# generate sample config
from cubicweb.goa.goaconfig import GAEConfiguration
- from cubicweb.common.migration import MigrationHelper
+ from cubicweb.migration import MigrationHelper
config = GAEConfiguration(appid, appldir)
if exists(config.main_config_file()):
mih = MigrationHelper(config)
--- a/goa/overrides/mttransforms.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/overrides/mttransforms.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
from logilab.mtconverter.engine import TransformEngine
from logilab.mtconverter.transform import Transform
-from cubicweb.common.uilib import rest_publish, html_publish, remove_html_tags
+from cubicweb.uilib import rest_publish, html_publish, remove_html_tags
HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
# CubicWeb specific transformations
--- a/goa/skel/views.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/skel/views.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,7 +8,8 @@
"""
from datetime import date
-from cubicweb.utils import last_day
+from logilab.common.date import last_day
+
from cubicweb.web.views import baseviews, boxes, calendar
from cubicweb.web.htmlwidgets import BoxLink, BoxWidget
@@ -19,7 +20,7 @@
accepts = ('BlogEntry',)
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.rset.get_entity(row, col)
self.w(u'<h1>%s</h1>' % entity.dc_title())
entity.view('metadata', w=self.w)
self.w(entity.printable_value('text'))
--- a/goa/test/data/views.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/test/data/views.py Mon Feb 08 13:35:07 2010 +0100
@@ -20,7 +20,7 @@
template.VariableNode.encode_output = encode_output
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
INDEX_TEMPLATE = template.Template(u'''
<h1>hellô {{ user.login }}</h1>
--- a/goa/test/unittest_editcontroller.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/test/unittest_editcontroller.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,8 +9,8 @@
from urllib import unquote
-from cubicweb.common import ValidationError
-from cubicweb.common.uilib import rql_for_eid
+from cubicweb import ValidationError
+from cubicweb.uilib import rql_for_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect
@@ -401,11 +401,11 @@
# which fires a Redirect
# 2/ When re-publishing the copy form, the publisher implicitly commits
try:
- self.env.app.publish('edit', self.req)
+ self.app.publish('edit', self.req)
except Redirect:
self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
self.req.form['vid'] = 'copy'
- self.env.app.publish('view', self.req)
+ self.app.publish('view', self.req)
rset = self.req.execute('CWUser P WHERE P surname "Boom"')
self.assertEquals(len(rset), 0)
finally:
--- a/goa/test/unittest_schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/test/unittest_schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -44,7 +44,7 @@
'inlined', 'inlined_relation', 'is', 'is_instance_of',
'label', 'last_login_time', 'login',
'mainvars', 'meta', 'modification_date', 'name', 'owned_by', 'pkey', 'primary_email',
- 'read_permission', 'require_group', 'state_of', 'surname', 'symetric',
+ 'read_permission', 'require_group', 'state_of', 'surname', 'symmetric',
'synopsis', 'talks_about', 'title', 'to_state', 'transition_of',
'update_permission', 'use_email', 'value')))
--- a/goa/testlib.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/testlib.py Mon Feb 08 13:35:07 2010 +0100
@@ -115,7 +115,7 @@
for module in self.LOAD_APP_MODULES:
self.vreg.load_module(module)
for cls in self.MODEL_CLASSES:
- self.vreg.load_object(cls)
+ self.vreg.register(cls)
self.session_manager = self.vreg.select('components', 'sessionmanager')
if need_ds_init:
# create default groups and create entities according to the schema
--- a/goa/tools/laxctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/goa/tools/laxctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,7 +18,7 @@
from logilab.common.clcommands import Command, register_commands, main_run
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.uilib import remove_html_tags
from cubicweb.web.views.schema import SKIP_TYPES
APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
--- a/hercule.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +0,0 @@
-"""RQL client for cubicweb, connecting to instance using pyro
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-import os
-import sys
-
-from logilab.common import flatten
-from logilab.common.cli import CLIHelper
-from logilab.common.clcommands import BadCommandUsage, pop_arg, register_commands
-from cubicweb.toolsutils import CONNECT_OPTIONS, Command
-
-# result formatter ############################################################
-
-PAGER = os.environ.get('PAGER', 'less')
-
-def pager_format_results(writer, layout):
- """pipe results to a pager like more or less"""
- (r, w) = os.pipe()
- pid = os.fork()
- if pid == 0:
- os.dup2(r, 0)
- os.close(r)
- os.close(w)
- if PAGER == 'less':
- os.execlp(PAGER, PAGER, '-r')
- else:
- os.execlp(PAGER, PAGER)
- sys.exit(0)
- stream = os.fdopen(w, "w")
- os.close(r)
- try:
- format_results(writer, layout, stream)
- finally:
- stream.close()
- os.waitpid(pid, 0)
-
-def izip2(list1, list2):
- for i in xrange(len(list1)):
- yield list1[i] + tuple(list2[i])
-
-def format_results(writer, layout, stream=sys.stdout):
- """format result as text into the given file like object"""
- writer.format(layout, stream)
-
-
-try:
- encoding = sys.stdout.encoding
-except AttributeError: # python < 2.3
- encoding = 'UTF-8'
-
-def to_string(value, encoding=encoding):
- """used to converte arbitrary values to encoded string"""
- if isinstance(value, unicode):
- return value.encode(encoding, 'replace')
- return str(value)
-
-# command line querier ########################################################
-
-class RQLCli(CLIHelper):
- """Interactive command line client for CubicWeb, allowing user to execute
- arbitrary RQL queries and to fetch schema information
- """
- # commands are prefixed by ":"
- CMD_PREFIX = ':'
- # map commands to folders
- CLIHelper.CMD_MAP.update({
- 'connect' : "CubicWeb",
- 'schema' : "CubicWeb",
- 'description' : "CubicWeb",
- 'commit' : "CubicWeb",
- 'rollback' : "CubicWeb",
- 'autocommit' : "Others",
- 'debug' : "Others",
- })
-
- def __init__(self, instance=None, user=None, password=None,
- host=None, debug=0):
- CLIHelper.__init__(self, os.path.join(os.environ["HOME"], ".erqlhist"))
- self.cnx = None
- self.cursor = None
- # XXX give a Request like object, not None
- from cubicweb.schemaviewer import SchemaViewer
- self.schema_viewer = SchemaViewer(None, encoding=encoding)
- from logilab.common.ureports import TextWriter
- self.writer = TextWriter()
- self.autocommit = False
- self._last_result = None
- self._previous_lines = []
- if instance is not None:
- self.do_connect(instance, user, password, host)
- self.do_debug(debug)
-
- def do_connect(self, instance, user=None, password=None, host=None):
- """connect to an cubicweb instance"""
- from cubicweb.dbapi import connect
- if user is None:
- user = raw_input('login: ')
- if password is None:
- from getpass import getpass
- password = getpass('password: ')
- if self.cnx is not None:
- self.cnx.close()
- self.cnx = connect(login=user, password=password, host=host,
- database=instance)
- self.schema = self.cnx.get_schema()
- self.cursor = self.cnx.cursor()
- # add entities types to the completion commands
- self._completer.list = (self.commands.keys() +
- self.schema.entities() + ['Any'])
- print _('You are now connected to %s') % instance
-
-
- help_do_connect = ('connect', "connect <instance> [<user> [<password> [<host>]]]",
- _(do_connect.__doc__))
-
- def do_debug(self, debug=1):
- """set debug level"""
- self._debug = debug
- if debug:
- self._format = format_results
- else:
- self._format = pager_format_results
- if self._debug:
- print _('Debug level set to %s'%debug)
-
- help_do_debug = ('debug', "debug [debug_level]", _(do_debug.__doc__))
-
- def do_description(self):
- """display the description of the latest result"""
- if self.rset.description is None:
- print _('No query has been executed')
- else:
- print '\n'.join([', '.join(line_desc)
- for line_desc in self.rset.description])
-
- help_do_description = ('description', "description", _(do_description.__doc__))
-
- def do_schema(self, name=None):
- """display information about the instance schema """
- if self.cnx is None:
- print _('You are not connected to an instance !')
- return
- done = None
- if name is None:
- # display the full schema
- self.display_schema(self.schema)
- done = 1
- else:
- if self.schema.has_entity(name):
- self.display_schema(self.schema.eschema(name))
- done = 1
- if self.schema.has_relation(name):
- self.display_schema(self.schema.rschema(name))
- done = 1
- if done is None:
- print _('Unable to find anything named "%s" in the schema !') % name
-
- help_do_schema = ('schema', "schema [keyword]", _(do_schema.__doc__))
-
-
- def do_commit(self):
- """commit the current transaction"""
- self.cnx.commit()
-
- help_do_commit = ('commit', "commit", _(do_commit.__doc__))
-
- def do_rollback(self):
- """rollback the current transaction"""
- self.cnx.rollback()
-
- help_do_rollback = ('rollback', "rollback", _(do_rollback.__doc__))
-
- def do_autocommit(self):
- """toggle autocommit mode"""
- self.autocommit = not self.autocommit
-
- help_do_autocommit = ('autocommit', "autocommit", _(do_autocommit.__doc__))
-
-
- def handle_line(self, stripped_line):
- """handle non command line :
- if the query is complete, executes it and displays results (if any)
- else, stores the query line and waits for the suite
- """
- if self.cnx is None:
- print _('You are not connected to an instance !')
- return
- # append line to buffer
- self._previous_lines.append(stripped_line)
- # query are ended by a ';'
- if stripped_line[-1] != ';':
- return
- # extract query from the buffer and flush it
- query = '\n'.join(self._previous_lines)
- self._previous_lines = []
- # search results
- try:
- self.rset = rset = self.cursor.execute(query)
- except:
- if self.autocommit:
- self.cnx.rollback()
- raise
- else:
- if self.autocommit:
- self.cnx.commit()
- self.handle_result(rset)
-
- def handle_result(self, rset):
- """display query results if any"""
- if not rset:
- print _('No result matching query')
- else:
- from logilab.common.ureports import Table
- children = flatten(izip2(rset.description, rset.rows), to_string)
- layout = Table(cols=2*len(rset.rows[0]), children=children, cheaders=1)
- self._format(self.writer, layout)
- print _('%s results matching query') % rset.rowcount
-
- def display_schema(self, schema):
- """display a schema object"""
- attr = schema.__class__.__name__.lower().replace('cubicweb', '')
- layout = getattr(self.schema_viewer, 'visit_%s' % attr)(schema)
- self._format(self.writer, layout)
-
-
-class CubicWebClientCommand(Command):
- """A command line querier for CubicWeb, using the Relation Query Language.
-
- <instance>
- identifier of the instance to connect to
- """
- name = 'client'
- arguments = '<instance>'
- options = CONNECT_OPTIONS + (
- ("verbose",
- {'short': 'v', 'type' : 'int', 'metavar': '<level>',
- 'default': 0,
- 'help': 'ask confirmation to continue after an error.',
- }),
- ("batch",
- {'short': 'b', 'type' : 'string', 'metavar': '<file>',
- 'help': 'file containing a batch of RQL statements to execute.',
- }),
- )
-
- def run(self, args):
- """run the command with its specific arguments"""
- appid = pop_arg(args, expected_size_after=None)
- batch_stream = None
- if args:
- if len(args) == 1 and args[0] == '-':
- batch_stream = sys.stdin
- else:
- raise BadCommandUsage('too many arguments')
- if self.config.batch:
- batch_stream = open(self.config.batch)
- cli = RQLCli(appid, self.config.user, self.config.password,
- self.config.host, self.config.debug)
- if batch_stream:
- cli.autocommit = True
- for line in batch_stream:
- line = line.strip()
- if not line:
- continue
- print '>>>', line
- cli.handle_line(line)
- else:
- cli.run()
-
-register_commands((CubicWebClientCommand,))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,1 @@
+"""core hooks"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/bookmark.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,30 @@
+"""bookmark related hooks
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.server import hook
+
+
+class AutoDeleteBookmarkOp(hook.Operation):
+ bookmark = None # make pylint happy
+ def precommit_event(self):
+ if not self.session.deleted_in_transaction(self.bookmark.eid):
+ if not self.bookmark.bookmarked_by:
+ self.bookmark.delete()
+
+
+class DelBookmarkedByHook(hook.Hook):
+ """ensure user logins are stripped"""
+ __regid__ = 'autodelbookmark'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('bookmarked_by',)
+ category = 'bookmark'
+ events = ('after_delete_relation',)
+
+ def __call__(self):
+ AutoDeleteBookmarkOp(self._cw,
+ bookmark=self._cw.entity_from_eid(self.eidfrom))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/email.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,72 @@
+"""hooks to ensure use_email / primary_email relations consistency
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.server import hook
+from cubicweb.server.repository import ensure_card_respected
+
+from logilab.common.compat import any
+
+
+class SetUseEmailRelationOp(hook.Operation):
+ """delay this operation to commit to avoid conflict with a late rql query
+ already setting the relation
+ """
+ rtype = 'use_email'
+ entity = email = None # make pylint happy
+
+ def condition(self):
+ """check entity has use_email set for the email address"""
+ return not any(e for e in self.entity.use_email
+ if self.email.eid == e.eid)
+
+ def precommit_event(self):
+ if self.condition():
+ # we've to handle cardinaly by ourselves since we're using unsafe_execute
+ # but use session.execute and not session.unsafe_execute to check we
+ # can change the relation
+ ensure_card_respected(self.session.execute, self.session,
+ self.entity.eid, self.rtype, self.email.eid)
+ self.session.unsafe_execute(
+ 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
+ {'x': self.entity.eid, 'y': self.email.eid}, 'x')
+
+
+class SetPrimaryEmailRelationOp(SetUseEmailRelationOp):
+ rtype = 'primary_email'
+
+ def condition(self):
+ """check entity has no primary_email set"""
+ return not self.entity.primary_email
+
+
+class SetPrimaryEmailHook(hook.Hook):
+ """notify when a bug or story or version has its state modified"""
+ __regid__ = 'setprimaryemail'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('use_email')
+ category = 'email'
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ entity = self._cw.entity_from_eid(self.eidfrom)
+ if 'primary_email' in entity.e_schema.subject_relations():
+ SetPrimaryEmailRelationOp(self._cw, entity=entity,
+ email=self._cw.entity_from_eid(self.eidto))
+
+class SetUseEmailHook(hook.Hook):
+ """notify when a bug or story or version has its state modified"""
+ __regid__ = 'setprimaryemail'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('primary_email')
+ category = 'email'
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ entity = self._cw.entity_from_eid(self.eidfrom)
+ if 'use_email' in entity.e_schema.subject_relations():
+ SetUseEmailRelationOp(self._cw, entity=entity,
+ email=self._cw.entity_from_eid(self.eidto))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/integrity.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,322 @@
+"""Core hooks: check for data integrity according to the instance'schema
+validity
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from threading import Lock
+
+from cubicweb import ValidationError
+from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
+from cubicweb.selectors import implements
+from cubicweb.uilib import soup2xhtml
+from cubicweb.server import hook
+
+# special relations that don't have to be checked for integrity, usually
+# because they are handled internally by hooks (so we trust ourselves)
+DONT_CHECK_RTYPES_ON_ADD = set(('owned_by', 'created_by',
+ 'is', 'is_instance_of',
+ 'wf_info_for', 'from_state', 'to_state'))
+DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
+ 'wf_info_for', 'from_state', 'to_state'))
+
+_UNIQUE_CONSTRAINTS_LOCK = Lock()
+_UNIQUE_CONSTRAINTS_HOLDER = None
+
+def _acquire_unique_cstr_lock(session):
+ """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session.
+
+ This lock used to avoid potential integrity pb when checking
+ RQLUniqueConstraint in two different transactions, as explained in
+ http://intranet.logilab.fr/jpl/ticket/36564
+ """
+ global _UNIQUE_CONSTRAINTS_HOLDER
+ asession = session.actual_session()
+ if _UNIQUE_CONSTRAINTS_HOLDER is asession:
+ return
+ _UNIQUE_CONSTRAINTS_LOCK.acquire()
+ _UNIQUE_CONSTRAINTS_HOLDER = asession
+ # register operation responsible to release the lock on commit/rollback
+ _ReleaseUniqueConstraintsOperation(asession)
+
+def _release_unique_cstr_lock(session):
+ global _UNIQUE_CONSTRAINTS_HOLDER
+ if _UNIQUE_CONSTRAINTS_HOLDER is session:
+ _UNIQUE_CONSTRAINTS_HOLDER = None
+ _UNIQUE_CONSTRAINTS_LOCK.release()
+
+class _ReleaseUniqueConstraintsOperation(hook.Operation):
+ def commit_event(self):
+ pass
+ def postcommit_event(self):
+ _release_unique_cstr_lock(self.session)
+ def rollback_event(self):
+ _release_unique_cstr_lock(self.session)
+
+
+class _CheckRequiredRelationOperation(hook.LateOperation):
+ """checking relation cardinality has to be done after commit in
+ case the relation is being replaced
+ """
+ eid, rtype = None, None
+
+ def precommit_event(self):
+ # recheck pending eids
+ if self.session.deleted_in_transaction(self.eid):
+ return
+ if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
+ return
+ if self.session.unsafe_execute(*self._rql()).rowcount < 1:
+ etype = self.session.describe(self.eid)[0]
+ _ = self.session._
+ msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
+ msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
+ raise ValidationError(self.eid, {self.rtype: msg})
+
+ def commit_event(self):
+ pass
+
+ def _rql(self):
+ raise NotImplementedError()
+
+
+class _CheckSRelationOp(_CheckRequiredRelationOperation):
+ """check required subject relation"""
+ def _rql(self):
+ return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
+
+class _CheckORelationOp(_CheckRequiredRelationOperation):
+ """check required object relation"""
+ def _rql(self):
+ return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
+
+class IntegrityHook(hook.Hook):
+ __abstract__ = True
+ category = 'integrity'
+
+class UserIntegrityHook(IntegrityHook):
+ __abstract__ = True
+ __select__ = IntegrityHook.__select__ & hook.regular_session()
+
+
+class CheckCardinalityHook(UserIntegrityHook):
+ """check cardinalities are satisfied"""
+ __regid__ = 'checkcard'
+ events = ('after_add_entity', 'before_delete_relation')
+
+ def __call__(self):
+ getattr(self, self.event)()
+
+ def checkrel_if_necessary(self, opcls, rtype, eid):
+ """check an equivalent operation has not already been added"""
+ for op in self._cw.pending_operations:
+ if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid:
+ break
+ else:
+ opcls(self._cw, rtype=rtype, eid=eid)
+
+ def after_add_entity(self):
+ eid = self.entity.eid
+ eschema = self.entity.e_schema
+ for rschema, targetschemas, role in eschema.relation_definitions():
+ # skip automatically handled relations
+ if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
+ continue
+ opcls = role == 'subject' and _CheckSRelationOp or _CheckORelationOp
+ rdef = rschema.role_rdef(eschema, targetschemas[0], role)
+ if rdef.role_cardinality(role) in '1+':
+ self.checkrel_if_necessary(opcls, rschema.type, eid)
+
+ def before_delete_relation(self):
+ rtype = self.rtype
+ if rtype in DONT_CHECK_RTYPES_ON_DEL:
+ return
+ session = self._cw
+ eidfrom, eidto = self.eidfrom, self.eidto
+ card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
+ pendingrdefs = session.transaction_data.get('pendingrdefs', ())
+ if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
+ return
+ if card[0] in '1+' and not session.deleted_in_transaction(eidfrom):
+ self.checkrel_if_necessary(_CheckSRelationOp, rtype, eidfrom)
+ if card[1] in '1+' and not session.deleted_in_transaction(eidto):
+ self.checkrel_if_necessary(_CheckORelationOp, rtype, eidto)
+
+
+class _CheckConstraintsOp(hook.LateOperation):
+ """check a new relation satisfy its constraints
+ """
+ def precommit_event(self):
+ eidfrom, rtype, eidto = self.rdef
+ # first check related entities have not been deleted in the same
+ # transaction
+ if self.session.deleted_in_transaction(eidfrom):
+ return
+ if self.session.deleted_in_transaction(eidto):
+ return
+ for constraint in self.constraints:
+ # XXX
+ # * lock RQLConstraint as well?
+ # * use a constraint id to use per constraint lock and avoid
+ # unnecessary commit serialization ?
+ if isinstance(constraint, RQLUniqueConstraint):
+ _acquire_unique_cstr_lock(self.session)
+ try:
+ constraint.repo_check(self.session, eidfrom, rtype, eidto)
+ except NotImplementedError:
+ self.critical('can\'t check constraint %s, not supported',
+ constraint)
+
+ def commit_event(self):
+ pass
+
+
+class CheckConstraintHook(UserIntegrityHook):
+ """check the relation satisfy its constraints
+
+ this is delayed to a precommit time operation since other relation which
+ will make constraint satisfied (or unsatisfied) may be added later.
+ """
+ __regid__ = 'checkconstraint'
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ # XXX get only RQL[Unique]Constraints?
+ constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+ 'constraints')
+ if constraints:
+ _CheckConstraintsOp(self._cw, constraints=constraints,
+ rdef=(self.eidfrom, self.rtype, self.eidto))
+
+
+class CheckAttributeConstraintHook(UserIntegrityHook):
+ """check the attribute relation satisfy its constraints
+
+ this is delayed to a precommit time operation since other relation which
+ will make constraint satisfied (or unsatisfied) may be added later.
+ """
+ __regid__ = 'checkattrconstraint'
+ events = ('after_add_entity', 'after_update_entity')
+
+ def __call__(self):
+ eschema = self.entity.e_schema
+ for attr in self.entity.edited_attributes:
+ if eschema.subjrels[attr].final:
+ constraints = [c for c in eschema.rdef(attr).constraints
+ if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
+ if constraints:
+ _CheckConstraintsOp(self._cw, constraints=constraints,
+ rdef=(self.entity.eid, attr, None))
+
+
+class CheckUniqueHook(UserIntegrityHook):
+ __regid__ = 'checkunique'
+ events = ('before_add_entity', 'before_update_entity')
+
+ def __call__(self):
+ entity = self.entity
+ eschema = entity.e_schema
+ for attr in entity.edited_attributes:
+ if eschema.subjrels[attr].final and eschema.has_unique_values(attr):
+ val = entity[attr]
+ if val is None:
+ continue
+ rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
+ rset = self._cw.unsafe_execute(rql, {'val': val})
+ if rset and rset[0][0] != entity.eid:
+ msg = self._cw._('the value "%s" is already used, use another one')
+ raise ValidationError(entity.eid, {attr: msg % val})
+
+
+class _DelayedDeleteOp(hook.Operation):
+ """delete the object of composite relation except if the relation
+ has actually been redirected to another composite
+ """
+
+ def precommit_event(self):
+ session = self.session
+ # don't do anything if the entity is being created or deleted
+ if not (session.deleted_in_transaction(self.eid) or
+ session.added_in_transaction(self.eid)):
+ etype = session.describe(self.eid)[0]
+ session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
+ % (etype, self.relation),
+ {'x': self.eid}, 'x')
+
+
+class DeleteCompositeOrphanHook(IntegrityHook):
+ """delete the composed of a composite relation when this relation is deleted
+ """
+ __regid__ = 'deletecomposite'
+ events = ('before_delete_relation',)
+
+ def __call__(self):
+ # if the relation is being delete, don't delete composite's components
+ # automatically
+ pendingrdefs = self._cw.transaction_data.get('pendingrdefs', ())
+ if (self._cw.describe(self.eidfrom)[0], self.rtype,
+ self._cw.describe(self.eidto)[0]) in pendingrdefs:
+ return
+ composite = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
+ 'composite')
+ if composite == 'subject':
+ _DelayedDeleteOp(self._cw, eid=self.eidto,
+ relation='Y %s X' % self.rtype)
+ elif composite == 'object':
+ _DelayedDeleteOp(self._cw, eid=self.eidfrom,
+ relation='X %s Y' % self.rtype)
+
+
+class DontRemoveOwnersGroupHook(IntegrityHook):
+ """delete the composed of a composite relation when this relation is deleted
+ """
+ __regid__ = 'checkownersgroup'
+ __select__ = IntegrityHook.__select__ & implements('CWGroup')
+ events = ('before_delete_entity', 'before_update_entity')
+
+ def __call__(self):
+ if self.event == 'before_delete_entity' and self.entity.name == 'owners':
+ raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ elif self.event == 'before_update_entity' and 'name' in self.entity.edited_attributes:
+ newname = self.entity.pop('name')
+ oldname = self.entity.name
+ if oldname == 'owners' and newname != oldname:
+ raise ValidationError(self.entity.eid, {'name': self._cw._('can\'t be changed')})
+ self.entity['name'] = newname
+
+
+class TidyHtmlFields(UserIntegrityHook):
+ """tidy HTML in rich text strings"""
+ __regid__ = 'htmltidy'
+ events = ('before_add_entity', 'before_update_entity')
+
+ def __call__(self):
+ entity = self.entity
+ metaattrs = entity.e_schema.meta_attributes()
+ for metaattr, (metadata, attr) in metaattrs.iteritems():
+ if metadata == 'format' and attr in entity.edited_attributes:
+ try:
+ value = entity[attr]
+ except KeyError:
+ continue # no text to tidy
+ if isinstance(value, unicode): # filter out None and Binary
+ if getattr(entity, str(metaattr)) == 'text/html':
+ entity[attr] = soup2xhtml(value, self._cw.encoding)
+
+
+class StripCWUserLoginHook(IntegrityHook):
+ """ensure user logins are stripped"""
+ __regid__ = 'stripuserlogin'
+ __select__ = IntegrityHook.__select__ & implements('CWUser')
+ events = ('before_add_entity', 'before_update_entity',)
+
+ def __call__(self):
+ user = self.entity
+ if 'login' in user.edited_attributes and user.login:
+ user.login = user.login.strip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/metadata.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,166 @@
+"""Core hooks: set generic metadata
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+
+from datetime import datetime
+
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+from cubicweb.server.repository import FTIndexEntityOp
+
+
+def eschema_type_eid(session, etype):
+ """get eid of the CWEType entity for the given yams type"""
+ eschema = session.repo.schema.eschema(etype)
+ # eschema.eid is None if schema has been readen from the filesystem, not
+ # from the database (eg during tests)
+ if eschema.eid is None:
+ eschema.eid = session.unsafe_execute(
+ 'Any X WHERE X is CWEType, X name %(name)s',
+ {'name': str(etype)})[0][0]
+ return eschema.eid
+
+
+class MetaDataHook(hook.Hook):
+ __abstract__ = True
+ category = 'metadata'
+
+
+class InitMetaAttrsHook(MetaDataHook):
+ """before create a new entity -> set creation and modification date
+
+ this is a conveniency hook, you shouldn't have to disable it
+ """
+ __regid__ = 'metaattrsinit'
+ events = ('before_add_entity',)
+
+ def __call__(self):
+ timestamp = datetime.now()
+ self.entity.setdefault('creation_date', timestamp)
+ self.entity.setdefault('modification_date', timestamp)
+ if not self._cw.get_shared_data('do-not-insert-cwuri'):
+ cwuri = u'%seid/%s' % (self._cw.base_url(), self.entity.eid)
+ self.entity.setdefault('cwuri', cwuri)
+
+
+class UpdateMetaAttrsHook(MetaDataHook):
+ """update an entity -> set modification date"""
+ __regid__ = 'metaattrsupdate'
+ events = ('before_update_entity',)
+
+ def __call__(self):
+ self.entity.setdefault('modification_date', datetime.now())
+
+
+class _SetCreatorOp(hook.Operation):
+
+ def precommit_event(self):
+ session = self.session
+ if session.deleted_in_transaction(self.entity.eid):
+ # entity have been created and deleted in the same transaction
+ return
+ if not self.entity.created_by:
+ session.add_relation(self.entity.eid, 'created_by', session.user.eid)
+
+
+class SetIsHook(MetaDataHook):
+ """create a new entity -> set is relation"""
+ __regid__ = 'setis'
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ if hasattr(self.entity, '_cw_recreating'):
+ return
+ session = self._cw
+ entity = self.entity
+ try:
+ #session.add_relation(entity.eid, 'is',
+ # eschema_type_eid(session, entity.__regid__))
+ session.system_sql('INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
+ % (entity.eid, eschema_type_eid(session, entity.__regid__)))
+ except IndexError:
+ # during schema serialization, skip
+ return
+ for etype in entity.e_schema.ancestors() + [entity.e_schema]:
+ #session.add_relation(entity.eid, 'is_instance_of',
+ # eschema_type_eid(session, etype))
+ session.system_sql('INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
+ % (entity.eid, eschema_type_eid(session, etype)))
+
+
+class SetOwnershipHook(MetaDataHook):
+ """create a new entity -> set owner and creator metadata"""
+ __regid__ = 'setowner'
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ asession = self._cw.actual_session()
+ if not asession.is_internal_session:
+ self._cw.add_relation(self.entity.eid, 'owned_by', asession.user.eid)
+ _SetCreatorOp(asession, entity=self.entity)
+
+
+class _SyncOwnersOp(hook.Operation):
+ def precommit_event(self):
+ self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
+ 'NOT EXISTS(X owned_by U, X eid %(x)s)',
+ {'c': self.compositeeid, 'x': self.composedeid},
+ ('c', 'x'))
+
+
+class SyncCompositeOwner(MetaDataHook):
+ """when adding composite relation, the composed should have the same owners
+ has the composite
+ """
+ __regid__ = 'synccompositeowner'
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ if self.rtype == 'wf_info_for':
+ # skip this special composite relation # XXX (syt) why?
+ return
+ eidfrom, eidto = self.eidfrom, self.eidto
+ composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite')
+ if composite == 'subject':
+ _SyncOwnersOp(self._cw, compositeeid=eidfrom, composedeid=eidto)
+ elif composite == 'object':
+ _SyncOwnersOp(self._cw, compositeeid=eidto, composedeid=eidfrom)
+
+
+class FixUserOwnershipHook(MetaDataHook):
+ """when a user has been created, add owned_by relation on itself"""
+ __regid__ = 'fixuserowner'
+ __select__ = MetaDataHook.__select__ & implements('CWUser')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ self._cw.add_relation(self.entity.eid, 'owned_by', self.entity.eid)
+
+
+class UpdateFTIHook(MetaDataHook):
+ """sync fulltext index when relevant relation is added / removed
+ """
+ __regid__ = 'updateftirel'
+ events = ('after_add_relation', 'after_delete_relation')
+
+ def __call__(self):
+ rtype = self.rtype
+ session = self._cw
+ if self.event == 'after_add_relation':
+ # Reindexing the contained entity is enough since it will implicitly
+ # reindex the container entity.
+ ftcontainer = session.vreg.schema.rschema(rtype).fulltext_container
+ if ftcontainer == 'subject':
+ FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidto))
+ elif ftcontainer == 'object':
+ FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidfrom))
+ elif session.repo.schema.rschema(rtype).fulltext_container:
+ FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidto))
+ FTIndexEntityOp(session, entity=session.entity_from_eid(self.eidfrom))
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/notification.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,185 @@
+"""some hooks to handle notification on entity's changes
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.textutils import normalize_text
+
+from cubicweb import RegistryException
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+from cubicweb.sobjects.supervising import SupervisionMailOp
+
+class RenderAndSendNotificationView(hook.Operation):
+ """delay rendering of notification view until precommit"""
+ def precommit_event(self):
+ view = self.view
+ if view.cw_rset is not None and not view.cw_rset:
+ return # entity added and deleted in the same transaction (cache effect)
+ if view.cw_rset and self.session.deleted_in_transaction(view.cw_rset[view.cw_row or 0][view.cw_col or 0]):
+ return # entity added and deleted in the same transaction
+ self.view.render_and_send(**getattr(self, 'viewargs', {}))
+
+
+class NotificationHook(hook.Hook):
+ __abstract__ = True
+ category = 'notification'
+
+ def select_view(self, vid, rset, row=0, col=0):
+ return self._cw.vreg['views'].select_or_none(vid, self._cw,
+ rset=rset, row=0, col=0)
+
+
+class StatusChangeHook(NotificationHook):
+ """notify when a workflowable entity has its state modified"""
+ __regid__ = 'notifystatuschange'
+ __select__ = NotificationHook.__select__ & implements('TrInfo')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ if not entity.from_state: # not a transition
+ return
+ rset = entity.related('wf_info_for')
+ view = self.select_view('notif_status_change', rset=rset, row=0)
+ if view is None:
+ return
+ comment = entity.printable_value('comment', format='text/plain')
+ # XXX don't try to wrap rest until we've a proper transformation (see
+ # #103822)
+ if comment and entity.comment_format != 'text/rest':
+ comment = normalize_text(comment, 80)
+ RenderAndSendNotificationView(self._cw, view=view, viewargs={
+ 'comment': comment, 'previous_state': entity.previous_state.name,
+ 'current_state': entity.new_state.name})
+
+
+class RelationChangeHook(NotificationHook):
+ __regid__ = 'notifyrelationchange'
+ events = ('before_add_relation', 'after_add_relation',
+ 'before_delete_relation', 'after_delete_relation')
+
+ def __call__(self):
+ """if a notification view is defined for the event, send notification
+ email defined by the view
+ """
+ rset = self._cw.eid_rset(self.eidfrom)
+ view = self.select_view('notif_%s_%s' % (self.event, self.rtype),
+ rset=rset, row=0)
+ if view is None:
+ return
+ RenderAndSendNotificationView(self._cw, view=view)
+
+
+class EntityChangeHook(NotificationHook):
+ """if a notification view is defined for the event, send notification
+ email defined by the view
+ """
+ __regid__ = 'notifyentitychange'
+ events = ('after_add_entity', 'after_update_entity')
+
+ def __call__(self):
+ rset = self.entity.as_rset()
+ view = self.select_view('notif_%s' % self.event, rset=rset, row=0)
+ if view is None:
+ return
+ RenderAndSendNotificationView(self._cw, view=view)
+
+
+class EntityUpdatedNotificationOp(hook.SingleLastOperation):
+
+ def precommit_event(self):
+ session = self.session
+ for eid in session.transaction_data['changes']:
+ view = session.vreg['views'].select('notif_entity_updated', session,
+ rset=session.eid_rset(eid),
+ row=0)
+ RenderAndSendNotificationView(session, view=view)
+
+
+class EntityUpdateHook(NotificationHook):
+ __regid__ = 'notifentityupdated'
+ __abstract__ = True # do not register by default
+
+ events = ('before_update_entity',)
+ skip_attrs = set()
+
+ def __call__(self):
+ session = self._cw
+ if self.entity.eid in session.transaction_data.get('neweids', ()):
+ return # entity is being created
+ if session.is_super_session:
+ return # ignore changes triggered by hooks
+ # then compute changes
+ changes = session.transaction_data.setdefault('changes', {})
+ thisentitychanges = changes.setdefault(self.entity.eid, set())
+ attrs = [k for k in self.entity.edited_attributes if not k in self.skip_attrs]
+ if not attrs:
+ return
+ rqlsel, rqlrestr = [], ['X eid %(x)s']
+ for i, attr in enumerate(attrs):
+ var = chr(65+i)
+ rqlsel.append(var)
+ rqlrestr.append('X %s %s' % (attr, var))
+ rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
+ rset = session.execute(rql, {'x': self.entity.eid}, 'x')
+ for i, attr in enumerate(attrs):
+ oldvalue = rset[0][i]
+ newvalue = self.entity[attr]
+ if oldvalue != newvalue:
+ thisentitychanges.add((attr, oldvalue, newvalue))
+ if thisentitychanges:
+ EntityUpdatedNotificationOp(session)
+
+
+# supervising ##################################################################
+
+class SomethingChangedHook(NotificationHook):
+ __regid__ = 'supervising'
+ events = ('before_add_relation', 'before_delete_relation',
+ 'after_add_entity', 'before_update_entity')
+
+ def __call__(self):
+ # XXX use proper selectors
+ if self._cw.is_super_session or self._cw.repo.config.repairing:
+ return # ignore changes triggered by hooks or maintainance shell
+ dest = self._cw.vreg.config['supervising-addrs']
+ if not dest: # no supervisors, don't do this for nothing...
+ return
+ if self._call():
+ SupervisionMailOp(self._cw)
+
+ def _call(self):
+ event = self.event.split('_', 1)[1]
+ if event == 'update_entity':
+ if self._cw.added_in_transaction(self.entity.eid):
+ return False
+ if self.entity.e_schema == 'CWUser':
+ if not (self.entity.edited_attributes - frozenset(('eid', 'modification_date',
+ 'last_login_time'))):
+ # don't record last_login_time update which are done
+ # automatically at login time
+ return False
+ self._cw.transaction_data.setdefault('pendingchanges', []).append(
+ (event, self))
+ return True
+
+
+class EntityDeleteHook(SomethingChangedHook):
+ __regid__ = 'supervisingentitydel'
+ events = ('before_delete_entity',)
+
+ def _call(self):
+ try:
+ title = self.entity.dc_title()
+ except:
+ # may raise an error during deletion process, for instance due to
+ # missing required relation
+ title = '#%s' % eid
+ self._cw.transaction_data.setdefault('pendingchanges', []).append(
+ ('delete_entity', (self.entity.eid, str(self.entity.e_schema), title)))
+ return True
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/security.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,139 @@
+"""Security hooks: check permissions to add/delete/update entities according to
+the user connected to a session
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb import Unauthorized
+from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
+
+
+def check_entity_attributes(session, entity):
+ eid = entity.eid
+ eschema = entity.e_schema
+ # ._default_set is only there on entity creation to indicate unspecified
+ # attributes which has been set to a default value defined in the schema
+ defaults = getattr(entity, '_default_set', ())
+ try:
+ editedattrs = entity.edited_attributes
+ except AttributeError:
+ editedattrs = entity
+ for attr in editedattrs:
+ if attr in defaults:
+ continue
+ rdef = eschema.rdef(attr)
+ if rdef.final: # non final relation are checked by other hooks
+ # add/delete should be equivalent (XXX: unify them into 'update' ?)
+ rdef.check_perm(session, 'add', eid=eid)
+
+
+class _CheckEntityPermissionOp(hook.LateOperation):
+ def precommit_event(self):
+ #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
+ self.entity.check_perm(self.action)
+ check_entity_attributes(self.session, self.entity)
+
+ def commit_event(self):
+ pass
+
+
+class _CheckRelationPermissionOp(hook.LateOperation):
+ def precommit_event(self):
+ rdef = self.rschema.rdef(self.session.describe(self.eidfrom)[0],
+ self.session.describe(self.eidto)[0])
+ rdef.check_perm(self.session, self.action,
+ fromeid=self.eidfrom, toeid=self.eidto)
+
+ def commit_event(self):
+ pass
+
+
+class SecurityHook(hook.Hook):
+ __abstract__ = True
+ category = 'security'
+ __select__ = hook.Hook.__select__ & hook.regular_session()
+
+
+class AfterAddEntitySecurityHook(SecurityHook):
+ __regid__ = 'securityafteraddentity'
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ _CheckEntityPermissionOp(self._cw, entity=self.entity, action='add')
+
+
+class AfterUpdateEntitySecurityHook(SecurityHook):
+ __regid__ = 'securityafterupdateentity'
+ events = ('after_update_entity',)
+
+ def __call__(self):
+ try:
+ # check user has permission right now, if not retry at commit time
+ self.entity.check_perm('update')
+ check_entity_attributes(self._cw, self.entity)
+ except Unauthorized:
+ self.entity.clear_local_perm_cache('update')
+ _CheckEntityPermissionOp(self._cw, entity=self.entity, action='update')
+
+
+class BeforeDelEntitySecurityHook(SecurityHook):
+ __regid__ = 'securitybeforedelentity'
+ events = ('before_delete_entity',)
+
+ def __call__(self):
+ self.entity.check_perm('delete')
+
+
+class BeforeAddRelationSecurityHook(SecurityHook):
+ __regid__ = 'securitybeforeaddrelation'
+ events = ('before_add_relation',)
+
+ def __call__(self):
+ if self.rtype in BEFORE_ADD_RELATIONS:
+ nocheck = self._cw.transaction_data.get('skip-security', ())
+ if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+ return
+ rschema = self._cw.repo.schema[self.rtype]
+ rdef = rschema.rdef(self._cw.describe(self.eidfrom)[0],
+ self._cw.describe(self.eidto)[0])
+ rdef.check_perm(self._cw, 'add', fromeid=self.eidfrom, toeid=self.eidto)
+
+
+class AfterAddRelationSecurityHook(SecurityHook):
+ __regid__ = 'securityafteraddrelation'
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ if not self.rtype in BEFORE_ADD_RELATIONS:
+ nocheck = self._cw.transaction_data.get('skip-security', ())
+ if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+ return
+ rschema = self._cw.repo.schema[self.rtype]
+ if self.rtype in ON_COMMIT_ADD_RELATIONS:
+ _CheckRelationPermissionOp(self._cw, action='add',
+ rschema=rschema,
+ eidfrom=self.eidfrom,
+ eidto=self.eidto)
+ else:
+ rdef = rschema.rdef(self._cw.describe(self.eidfrom)[0],
+ self._cw.describe(self.eidto)[0])
+ rdef.check_perm(self._cw, 'add', fromeid=self.eidfrom, toeid=self.eidto)
+
+
+class BeforeDeleteRelationSecurityHook(SecurityHook):
+ __regid__ = 'securitybeforedelrelation'
+ events = ('before_delete_relation',)
+
+ def __call__(self):
+ nocheck = self._cw.transaction_data.get('skip-security', ())
+ if (self.eidfrom, self.rtype, self.eidto) in nocheck:
+ return
+ rschema = self._cw.repo.schema[self.rtype]
+ rdef = rschema.rdef(self._cw.describe(self.eidfrom)[0],
+ self._cw.describe(self.eidto)[0])
+ rdef.check_perm(self._cw, 'delete', fromeid=self.eidfrom, toeid=self.eidto)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/storages.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,45 @@
+"""hooks to handle attributes mapped to a custom storage
+"""
+from os import unlink
+
+from cubicweb.server.hook import Hook
+from cubicweb.server.sources.storages import ETYPE_ATTR_STORAGE
+
+
+class BFSSHook(Hook):
+ """abstract class for bytes file-system storage hooks"""
+ __abstract__ = True
+ category = 'bfss'
+
+
+class PreAddEntityHook(BFSSHook):
+ """"""
+ __regid__ = 'bfss_add_entity'
+ events = ('before_add_entity', )
+ #__select__ = Hook.__select__ & implements('Repository')
+
+ def __call__(self):
+ for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
+ fpath = ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_added(self.entity, attr)
+ if fpath is not None:
+ AddFileOp(filepath=fpath)
+
+class PreUpdateEntityHook(BFSSHook):
+ """"""
+ __regid__ = 'bfss_update_entity'
+ events = ('before_update_entity', )
+ #__select__ = Hook.__select__ & implements('Repository')
+
+ def __call__(self):
+ for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
+ ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_updated(self.entity, attr)
+
+class PreDeleteEntityHook(BFSSHook):
+ """"""
+ __regid__ = 'bfss_delete_entity'
+ events = ('before_delete_entity', )
+ #__select__ = Hook.__select__ & implements('Repository')
+
+ def __call__(self):
+ for attr in ETYPE_ATTR_STORAGE.get(self.entity.__regid__, ()):
+ ETYPE_ATTR_STORAGE[self.entity.__regid__][attr].entity_deleted(self.entity, attr)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/syncschema.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,1150 @@
+"""schema hooks:
+
+- synchronize the living schema object with the persistent schema
+- perform physical update on the source when necessary
+
+checking for schema consistency is done in hooks.py
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from yams.schema import BASE_TYPES, RelationSchema, RelationDefinitionSchema
+from yams.buildobjs import EntityType, RelationType, RelationDefinition
+from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
+
+from logilab.common.decorators import clear_cache
+
+from cubicweb import ValidationError, RepositoryError
+from cubicweb.selectors import implements
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS, display_name
+from cubicweb.server import hook, schemaserial as ss
+from cubicweb.server.sqlutils import SQL_PREFIX
+
+
+TYPE_CONVERTER = { # XXX
+ 'Boolean': bool,
+ 'Int': int,
+ 'Float': float,
+ 'Password': str,
+ 'String': unicode,
+ 'Date' : unicode,
+ 'Datetime' : unicode,
+ 'Time' : unicode,
+ }
+
+# core entity and relation types which can't be removed
+CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
+ 'CWConstraint', 'CWAttribute', 'CWRelation']
+CORE_RTYPES = ['eid', 'creation_date', 'modification_date', 'cwuri',
+ 'login', 'upassword', 'name',
+ 'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
+ 'relation_type', 'from_entity', 'to_entity',
+ 'constrainted_by',
+ 'read_permission', 'add_permission',
+ 'delete_permission', 'updated_permission',
+ ]
+
+def get_constraints(session, entity):
+ constraints = []
+ for cstreid in session.transaction_data.get(entity.eid, ()):
+ cstrent = session.entity_from_eid(cstreid)
+ cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
+ cstr.eid = cstreid
+ constraints.append(cstr)
+ return constraints
+
+def group_mapping(cw):
+ try:
+ return cw.transaction_data['groupmap']
+ except KeyError:
+ cw.transaction_data['groupmap'] = gmap = ss.group_mapping(cw)
+ return gmap
+
+def add_inline_relation_column(session, etype, rtype):
+ """add necessary column and index for an inlined relation"""
+ table = SQL_PREFIX + etype
+ column = SQL_PREFIX + rtype
+ try:
+ session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
+ % (table, column)), rollback_on_failure=False)
+ session.info('added column %s to table %s', column, table)
+ except:
+ # silent exception here, if this error has not been raised because the
+ # column already exists, index creation will fail anyway
+ session.exception('error while adding column %s to table %s',
+ table, column)
+ # create index before alter table which may expectingly fail during test
+ # (sqlite) while index creation should never fail (test for index existence
+ # is done by the dbhelper)
+ session.pool.source('system').create_index(session, table, column)
+ session.info('added index on %s(%s)', table, column)
+ session.transaction_data.setdefault('createdattrs', []).append(
+ '%s.%s' % (etype, rtype))
+
+def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
+ errors = {}
+ # don't use getattr(entity, attr), we would get the modified value if any
+ for attr in entity.edited_attributes:
+ if attr in ro_attrs:
+ newval = entity.pop(attr)
+ origval = getattr(entity, attr)
+ if newval != origval:
+ errors[attr] = session._("can't change the %s attribute") % \
+ display_name(session, attr)
+ entity[attr] = newval
+ if errors:
+ raise ValidationError(entity.eid, errors)
+
+
+# operations for low-level database alteration ################################
+
+class DropTable(hook.Operation):
+ """actually remove a database from the instance's schema"""
+ table = None # make pylint happy
+ def precommit_event(self):
+ dropped = self.session.transaction_data.setdefault('droppedtables',
+ set())
+ if self.table in dropped:
+ return # already processed
+ dropped.add(self.table)
+ self.session.system_sql('DROP TABLE %s' % self.table)
+ self.info('dropped table %s', self.table)
+
+
+class DropRelationTable(DropTable):
+ def __init__(self, session, rtype):
+ super(DropRelationTable, self).__init__(
+ session, table='%s_relation' % rtype)
+ session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
+
+
+class DropColumn(hook.Operation):
+ """actually remove the attribut's column from entity table in the system
+ database
+ """
+ table = column = None # make pylint happy
+ def precommit_event(self):
+ session, table, column = self.session, self.table, self.column
+ # drop index if any
+ session.pool.source('system').drop_index(session, table, column)
+ try:
+ session.system_sql('ALTER TABLE %s DROP COLUMN %s'
+ % (table, column), rollback_on_failure=False)
+ self.info('dropped column %s from table %s', column, table)
+ except Exception, ex:
+ # not supported by sqlite for instance
+ self.error('error while altering table %s: %s', table, ex)
+
+
+# base operations for in-memory schema synchronization ########################
+
+class MemSchemaNotifyChanges(hook.SingleLastOperation):
+ """the update schema operation:
+
+ special operation which should be called once and after all other schema
+ operations. It will trigger internal structures rebuilding to consider
+ schema changes.
+ """
+
+ def __init__(self, session):
+ hook.SingleLastOperation.__init__(self, session)
+
+ def precommit_event(self):
+ for eschema in self.session.repo.schema.entities():
+ if not eschema.final:
+ clear_cache(eschema, 'ordered_relations')
+
+ def commit_event(self):
+ rebuildinfered = self.session.data.get('rebuild-infered', True)
+ repo = self.session.repo
+ repo.set_schema(repo.schema, rebuildinfered=rebuildinfered)
+ # CWUser class might have changed, update current session users
+ cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser')
+ for session in repo._sessions.values():
+ session.user.__class__ = cwuser_cls
+
+ def rollback_event(self):
+ self.precommit_event()
+
+
+class MemSchemaOperation(hook.Operation):
+ """base class for schema operations"""
+ def __init__(self, session, kobj=None, **kwargs):
+ self.kobj = kobj
+ # once Operation.__init__ has been called, event may be triggered, so
+ # do this last !
+ hook.Operation.__init__(self, session, **kwargs)
+ # every schema operation is triggering a schema update
+ MemSchemaNotifyChanges(session)
+
+ def prepare_constraints(self, subjtype, rtype, objtype):
+ rdef = rtype.rdef(subjtype, objtype)
+ constraints = rdef.constraints
+ self.constraints = list(constraints)
+ rdef.constraints = self.constraints
+
+
+class MemSchemaEarlyOperation(MemSchemaOperation):
+ def insert_index(self):
+ """schema operation which are inserted at the begining of the queue
+ (typically to add/remove entity or relation types)
+ """
+ i = -1
+ for i, op in enumerate(self.session.pending_operations):
+ if not isinstance(op, MemSchemaEarlyOperation):
+ return i
+ return i + 1
+
+
+# operations for high-level source database alteration ########################
+
+class SourceDbCWETypeRename(hook.Operation):
+ """this operation updates physical storage accordingly"""
+ oldname = newname = None # make pylint happy
+
+ def precommit_event(self):
+ # we need sql to operate physical changes on the system database
+ sqlexec = self.session.system_sql
+ sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname,
+ SQL_PREFIX, self.newname))
+ self.info('renamed table %s to %s', self.oldname, self.newname)
+ sqlexec('UPDATE entities SET type=%s WHERE type=%s',
+ (self.newname, self.oldname))
+ sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
+ (self.newname, self.oldname))
+
+
+class SourceDbCWRTypeUpdate(hook.Operation):
+ """actually update some properties of a relation definition"""
+ rschema = values = entity = None # make pylint happy
+
+ def precommit_event(self):
+ session = self.session
+ rschema = self.rschema
+ if rschema.final or not 'inlined' in self.values:
+ return # nothing to do
+ inlined = self.values['inlined']
+ entity = self.entity
+ # check in-lining is necessary / possible
+ if not entity.inlined_changed(inlined):
+ return # nothing to do
+ # inlined changed, make necessary physical changes!
+ sqlexec = self.session.system_sql
+ rtype = rschema.type
+ eidcolumn = SQL_PREFIX + 'eid'
+ if not inlined:
+ # need to create the relation if it has not been already done by
+ # another event of the same transaction
+ if not rschema.type in session.transaction_data.get('createdtables', ()):
+ tablesql = rschema2sql(rschema)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ sqlexec(sql)
+ session.transaction_data.setdefault('createdtables', []).append(
+ rschema.type)
+ # copy existant data
+ column = SQL_PREFIX + rtype
+ for etype in rschema.subjects():
+ table = SQL_PREFIX + str(etype)
+ sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
+ % (rtype, eidcolumn, column, table, column))
+ # drop existant columns
+ for etype in rschema.subjects():
+ DropColumn(session, table=SQL_PREFIX + str(etype),
+ column=SQL_PREFIX + rtype)
+ else:
+ for etype in rschema.subjects():
+ try:
+ add_inline_relation_column(session, str(etype), rtype)
+ except Exception, ex:
+ # the column probably already exists. this occurs when the
+ # entity's type has just been added or if the column has not
+ # been previously dropped
+ self.error('error while altering table %s: %s', etype, ex)
+ # copy existant data.
+ # XXX don't use, it's not supported by sqlite (at least at when i tried it)
+ #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
+ # 'FROM %(rtype)s_relation '
+ # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
+ # % locals())
+ table = SQL_PREFIX + str(etype)
+ cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
+ '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
+ '%(rtype)s_relation.eid_from' % locals())
+ args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
+ if args:
+ column = SQL_PREFIX + rtype
+ cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
+ % (table, column, eidcolumn), args)
+ # drop existant table
+ DropRelationTable(session, rtype)
+
+
+class SourceDbCWAttributeAdd(hook.Operation):
+ """an attribute relation (CWAttribute) has been added:
+ * add the necessary column
+ * set default on this column if any and possible
+ * register an operation to add the relation definition to the
+ instance's schema on commit
+
+ constraints are handled by specific hooks
+ """
+ entity = None # make pylint happy
+
+ def init_rdef(self, **kwargs):
+ entity = self.entity
+ fromentity = entity.stype
+ self.session.execute('SET X ordernum Y+1 '
+ 'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
+ 'X ordernum >= %(order)s, NOT X eid %(x)s',
+ {'x': entity.eid, 'se': fromentity.eid,
+ 'order': entity.ordernum or 0})
+ subj = str(fromentity.name)
+ rtype = entity.rtype.name
+ obj = str(entity.otype.name)
+ constraints = get_constraints(self.session, entity)
+ rdef = RelationDefinition(subj, rtype, obj,
+ description=entity.description,
+ cardinality=entity.cardinality,
+ constraints=constraints,
+ order=entity.ordernum,
+ eid=entity.eid,
+ **kwargs)
+ MemSchemaRDefAdd(self.session, rdef)
+ return rdef
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ # entity.defaultval is a string or None, but we need a correctly typed
+ # value
+ default = entity.defaultval
+ if default is not None:
+ default = TYPE_CONVERTER[entity.otype.name](default)
+ props = {'default': default,
+ 'indexed': entity.indexed,
+ 'fulltextindexed': entity.fulltextindexed,
+ 'internationalizable': entity.internationalizable}
+ rdef = self.init_rdef(**props)
+ sysource = session.pool.source('system')
+ attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
+ rdef.constraints)
+ # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
+ # add a new column with UNIQUE, it should be added after the ALTER TABLE
+ # using ADD INDEX
+ if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
+ extra_unique_index = True
+ attrtype = attrtype.replace(' UNIQUE', '')
+ else:
+ extra_unique_index = False
+ # added some str() wrapping query since some backend (eg psycopg) don't
+ # allow unicode queries
+ table = SQL_PREFIX + rdef.subject
+ column = SQL_PREFIX + rdef.name
+ try:
+ session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
+ % (table, column, attrtype)),
+ rollback_on_failure=False)
+ self.info('added column %s to table %s', table, column)
+ except Exception, ex:
+ # the column probably already exists. this occurs when
+ # the entity's type has just been added or if the column
+ # has not been previously dropped
+ self.error('error while altering table %s: %s', table, ex)
+ if extra_unique_index or entity.indexed:
+ try:
+ sysource.create_index(session, table, column,
+ unique=extra_unique_index)
+ except Exception, ex:
+ self.error('error while creating index for %s.%s: %s',
+ table, column, ex)
+ # final relations are not infered, propagate
+ try:
+ eschema = session.vreg.schema.eschema(rdef.subject)
+ except KeyError:
+ return # entity type currently being added
+ # propagate attribute to children classes
+ rschema = session.vreg.schema.rschema(rdef.name)
+ # if relation type has been inserted in the same transaction, its final
+ # attribute is still set to False, so we've to ensure it's False
+ rschema.final = True
+ # XXX 'infered': True/False, not clear actually
+ props.update({'constraints': rdef.constraints,
+ 'description': rdef.description,
+ 'cardinality': rdef.cardinality,
+ 'constraints': rdef.constraints,
+ 'permissions': rdef.get_permissions(),
+ 'order': rdef.order})
+ groupmap = group_mapping(session)
+ for specialization in eschema.specialized_by(False):
+ if (specialization, rdef.object) in rschema.rdefs:
+ continue
+ sperdef = RelationDefinitionSchema(specialization, rschema, rdef.object, props)
+ for rql, args in ss.rdef2rql(rschema, str(specialization),
+ rdef.object, sperdef, groupmap=groupmap):
+ session.execute(rql, args)
+ # set default value, using sql for performance and to avoid
+ # modification_date update
+ if default:
+ session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
+ {'default': default})
+
+
+class SourceDbCWRelationAdd(SourceDbCWAttributeAdd):
+ """an actual relation has been added:
+ * if this is an inlined relation, add the necessary column
+ else if it's the first instance of this relation type, add the
+ necessary table and set default permissions
+ * register an operation to add the relation definition to the
+ instance's schema on commit
+
+ constraints are handled by specific hooks
+ """
+ entity = None # make pylint happy
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ rdef = self.init_rdef(composite=entity.composite)
+ schema = session.vreg.schema
+ rtype = rdef.name
+ rschema = session.vreg.schema.rschema(rtype)
+ # this have to be done before permissions setting
+ if rschema.inlined:
+ # need to add a column if the relation is inlined and if this is the
+ # first occurence of "Subject relation Something" whatever Something
+ # and if it has not been added during other event of the same
+ # transaction
+ key = '%s.%s' % (rdef.subject, rtype)
+ try:
+ alreadythere = bool(rschema.objects(rdef.subject))
+ except KeyError:
+ alreadythere = False
+ if not (alreadythere or
+ key in session.transaction_data.get('createdattrs', ())):
+ add_inline_relation_column(session, rdef.subject, rtype)
+ else:
+ # need to create the relation if no relation definition in the
+ # schema and if it has not been added during other event of the same
+ # transaction
+ if not (rschema.subjects() or
+ rtype in session.transaction_data.get('createdtables', ())):
+ try:
+ rschema = session.vreg.schema.rschema(rtype)
+ tablesql = rschema2sql(rschema)
+ except KeyError:
+ # fake we add it to the schema now to get a correctly
+ # initialized schema but remove it before doing anything
+ # more dangerous...
+ rschema = session.vreg.schema.add_relation_type(rdef)
+ tablesql = rschema2sql(rschema)
+ session.vreg.schema.del_relation_type(rtype)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ session.system_sql(sql)
+ session.transaction_data.setdefault('createdtables', []).append(
+ rtype)
+
+
+class SourceDbRDefUpdate(hook.Operation):
+ """actually update some properties of a relation definition"""
+ rschema = values = None # make pylint happy
+
+ def precommit_event(self):
+ etype = self.kobj[0]
+ table = SQL_PREFIX + etype
+ column = SQL_PREFIX + self.rschema.type
+ if 'indexed' in self.values:
+ sysource = self.session.pool.source('system')
+ if self.values['indexed']:
+ sysource.create_index(self.session, table, column)
+ else:
+ sysource.drop_index(self.session, table, column)
+ if 'cardinality' in self.values and self.rschema.final:
+ adbh = self.session.pool.source('system').dbhelper
+ if not adbh.alter_column_support:
+ # not supported (and NOT NULL not set by yams in that case, so
+ # no worry)
+ return
+ atype = self.rschema.objects(etype)[0]
+ constraints = self.rschema.rdef(etype, atype).constraints
+ coltype = type_from_constraints(adbh, atype, constraints,
+ creating=False)
+ # XXX check self.values['cardinality'][0] actually changed?
+ sql = adbh.sql_set_null_allowed(table, column, coltype,
+ self.values['cardinality'][0] != '1')
+ self.session.system_sql(sql)
+
+
+class SourceDbCWConstraintAdd(hook.Operation):
+ """actually update constraint of a relation definition"""
+ entity = None # make pylint happy
+ cancelled = False
+
+ def precommit_event(self):
+ rdef = self.entity.reverse_constrained_by[0]
+ session = self.session
+ # when the relation is added in the same transaction, the constraint
+ # object is created by the operation adding the attribute or relation,
+ # so there is nothing to do here
+ if session.added_in_transaction(rdef.eid):
+ return
+ rdefschema = session.vreg.schema.schema_by_eid(rdef.eid)
+ subjtype, rtype, objtype = rdefschema.as_triple()
+ cstrtype = self.entity.type
+ oldcstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
+ newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+ table = SQL_PREFIX + str(subjtype)
+ column = SQL_PREFIX + str(rtype)
+ # alter the physical schema on size constraint changes
+ if newcstr.type() == 'SizeConstraint' and (
+ oldcstr is None or oldcstr.max != newcstr.max):
+ adbh = self.session.pool.source('system').dbhelper
+ card = rtype.rdef(subjtype, objtype).cardinality
+ coltype = type_from_constraints(adbh, objtype, [newcstr],
+ creating=False)
+ sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
+ try:
+ session.system_sql(sql, rollback_on_failure=False)
+ self.info('altered column %s of table %s: now VARCHAR(%s)',
+ column, table, newcstr.max)
+ except Exception, ex:
+ # not supported by sqlite for instance
+ self.error('error while altering table %s: %s', table, ex)
+ elif cstrtype == 'UniqueConstraint' and oldcstr is None:
+ session.pool.source('system').create_index(
+ self.session, table, column, unique=True)
+
+
+class SourceDbCWConstraintDel(hook.Operation):
+ """actually remove a constraint of a relation definition"""
+ rtype = subjtype = objtype = None # make pylint happy
+
+ def precommit_event(self):
+ cstrtype = self.cstr.type()
+ table = SQL_PREFIX + str(self.subjtype)
+ column = SQL_PREFIX + str(self.rtype)
+ # alter the physical schema on size/unique constraint changes
+ if cstrtype == 'SizeConstraint':
+ try:
+ self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
+ % (table, column),
+ rollback_on_failure=False)
+ self.info('altered column %s of table %s: now TEXT',
+ column, table)
+ except Exception, ex:
+ # not supported by sqlite for instance
+ self.error('error while altering table %s: %s', table, ex)
+ elif cstrtype == 'UniqueConstraint':
+ self.session.pool.source('system').drop_index(
+ self.session, table, column, unique=True)
+
+
+# operations for in-memory schema synchronization #############################
+
+class MemSchemaCWETypeAdd(MemSchemaEarlyOperation):
+ """actually add the entity type to the instance's schema"""
+ eid = None # make pylint happy
+ def commit_event(self):
+ self.session.vreg.schema.add_entity_type(self.kobj)
+
+
+class MemSchemaCWETypeRename(MemSchemaOperation):
+ """this operation updates physical storage accordingly"""
+ oldname = newname = None # make pylint happy
+
+ def commit_event(self):
+ self.session.vreg.schema.rename_entity_type(self.oldname, self.newname)
+
+
+class MemSchemaCWETypeDel(MemSchemaOperation):
+ """actually remove the entity type from the instance's schema"""
+ def commit_event(self):
+ try:
+ # del_entity_type also removes entity's relations
+ self.session.vreg.schema.del_entity_type(self.kobj)
+ except KeyError:
+ # s/o entity type have already been deleted
+ pass
+
+
+class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation):
+ """actually add the relation type to the instance's schema"""
+ eid = None # make pylint happy
+ def commit_event(self):
+ self.session.vreg.schema.add_relation_type(self.kobj)
+
+
+class MemSchemaCWRTypeUpdate(MemSchemaOperation):
+ """actually update some properties of a relation definition"""
+ rschema = values = None # make pylint happy
+
+ def commit_event(self):
+ # structure should be clean, not need to remove entity's relations
+ # at this point
+ self.rschema.__dict__.update(self.values)
+
+
+class MemSchemaCWRTypeDel(MemSchemaOperation):
+ """actually remove the relation type from the instance's schema"""
+ def commit_event(self):
+ try:
+ self.session.vreg.schema.del_relation_type(self.kobj)
+ except KeyError:
+ # s/o entity type have already been deleted
+ pass
+
+
+class MemSchemaRDefAdd(MemSchemaEarlyOperation):
+ """actually add the attribute relation definition to the instance's
+ schema
+ """
+ def commit_event(self):
+ self.session.vreg.schema.add_relation_def(self.kobj)
+
+
+class MemSchemaRDefUpdate(MemSchemaOperation):
+ """actually update some properties of a relation definition"""
+ rschema = values = None # make pylint happy
+
+ def commit_event(self):
+ # structure should be clean, not need to remove entity's relations
+ # at this point
+ self.rschema.rdefs[self.kobj].update(self.values)
+
+
+class MemSchemaRDefDel(MemSchemaOperation):
+ """actually remove the relation definition from the instance's schema"""
+ def commit_event(self):
+ subjtype, rtype, objtype = self.kobj
+ try:
+ self.session.vreg.schema.del_relation_def(subjtype, rtype, objtype)
+ except KeyError:
+ # relation type may have been already deleted
+ pass
+
+
+class MemSchemaCWConstraintAdd(MemSchemaOperation):
+ """actually update constraint of a relation definition
+
+ has to be called before SourceDbCWConstraintAdd
+ """
+ cancelled = False
+
+ def precommit_event(self):
+ rdef = self.entity.reverse_constrained_by[0]
+ # when the relation is added in the same transaction, the constraint
+ # object is created by the operation adding the attribute or relation,
+ # so there is nothing to do here
+ if self.session.added_in_transaction(rdef.eid):
+ self.cancelled = True
+ return
+ rdef = self.session.vreg.schema.schema_by_eid(rdef.eid)
+ subjtype, rtype, objtype = rdef.as_triple()
+ self.prepare_constraints(subjtype, rtype, objtype)
+ cstrtype = self.entity.type
+ self.cstr = rtype.rdef(subjtype, objtype).constraint_by_type(cstrtype)
+ self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+ self.newcstr.eid = self.entity.eid
+
+ def commit_event(self):
+ if self.cancelled:
+ return
+ # in-place modification
+ if not self.cstr is None:
+ self.constraints.remove(self.cstr)
+ self.constraints.append(self.newcstr)
+
+
+class MemSchemaCWConstraintDel(MemSchemaOperation):
+ """actually remove a constraint of a relation definition
+
+ has to be called before SourceDbCWConstraintDel
+ """
+ rtype = subjtype = objtype = None # make pylint happy
+ def precommit_event(self):
+ self.prepare_constraints(self.subjtype, self.rtype, self.objtype)
+
+ def commit_event(self):
+ self.constraints.remove(self.cstr)
+
+
+class MemSchemaPermissionAdd(MemSchemaOperation):
+ """synchronize schema when a *_permission relation has been added on a group
+ """
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ try:
+ erschema = self.session.vreg.schema.schema_by_eid(self.eid)
+ except KeyError:
+ # duh, schema not found, log error and skip operation
+ self.error('no schema for %s', self.eid)
+ return
+ perms = list(erschema.action_permissions(self.action))
+ if hasattr(self, 'group_eid'):
+ perm = self.session.entity_from_eid(self.group_eid).name
+ else:
+ perm = erschema.rql_expression(self.expr)
+ try:
+ perms.index(perm)
+ self.warning('%s already in permissions for %s on %s',
+ perm, self.action, erschema)
+ except ValueError:
+ perms.append(perm)
+ erschema.set_action_permissions(self.action, perms)
+
+
+class MemSchemaPermissionDel(MemSchemaPermissionAdd):
+ """synchronize schema when a *_permission relation has been deleted from a
+ group
+ """
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ try:
+ erschema = self.session.vreg.schema.schema_by_eid(self.eid)
+ except KeyError:
+ # duh, schema not found, log error and skip operation
+ self.error('no schema for %s', self.eid)
+ return
+ if isinstance(erschema, RelationSchema): # XXX 3.6 migration
+ return
+ perms = list(erschema.action_permissions(self.action))
+ if hasattr(self, 'group_eid'):
+ perm = self.session.entity_from_eid(self.group_eid).name
+ else:
+ perm = erschema.rql_expression(self.expr)
+ try:
+ perms.remove(perm)
+ erschema.set_action_permissions(self.action, perms)
+ except ValueError:
+ self.error('can\'t remove permission %s for %s on %s',
+ perm, self.action, erschema)
+
+
+class MemSchemaSpecializesAdd(MemSchemaOperation):
+
+ def commit_event(self):
+ eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
+ parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
+ eschema._specialized_type = parenteschema.type
+ parenteschema._specialized_by.append(eschema.type)
+
+
+class MemSchemaSpecializesDel(MemSchemaOperation):
+
+ def commit_event(self):
+ try:
+ eschema = self.session.vreg.schema.schema_by_eid(self.etypeeid)
+ parenteschema = self.session.vreg.schema.schema_by_eid(self.parentetypeeid)
+ except KeyError:
+ # etype removed, nothing to do
+ return
+ eschema._specialized_type = None
+ parenteschema._specialized_by.remove(eschema.type)
+
+
+class SyncSchemaHook(hook.Hook):
+ __abstract__ = True
+ category = 'syncschema'
+
+
+# CWEType hooks ################################################################
+
+class DelCWETypeHook(SyncSchemaHook):
+ """before deleting a CWEType entity:
+ * check that we don't remove a core entity type
+ * cascade to delete related CWAttribute and CWRelation entities
+ * instantiate an operation to delete the entity type on commit
+ """
+ __regid__ = 'syncdelcwetype'
+ __select__ = SyncSchemaHook.__select__ & implements('CWEType')
+ events = ('before_delete_entity',)
+
+ def __call__(self):
+ # final entities can't be deleted, don't care about that
+ name = self.entity.name
+ if name in CORE_ETYPES:
+ raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ # delete every entities of this type
+ self._cw.unsafe_execute('DELETE %s X' % name)
+ DropTable(self._cw, table=SQL_PREFIX + name)
+ MemSchemaCWETypeDel(self._cw, name)
+
+
+class AfterDelCWETypeHook(DelCWETypeHook):
+ __regid__ = 'wfcleanup'
+ events = ('after_delete_entity',)
+
+ def __call__(self):
+ # workflow cleanup
+ self._cw.execute('DELETE Workflow X WHERE NOT X workflow_of Y')
+
+
+class AfterAddCWETypeHook(DelCWETypeHook):
+ """after adding a CWEType entity:
+ * create the necessary table
+ * set creation_date and modification_date by creating the necessary
+ CWAttribute entities
+ * add owned_by relation by creating the necessary CWRelation entity
+ * register an operation to add the entity type to the instance's
+ schema on commit
+ """
+ __regid__ = 'syncaddcwetype'
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ if entity.get('final'):
+ return
+ schema = self._cw.vreg.schema
+ name = entity['name']
+ etype = EntityType(name=name, description=entity.get('description'),
+ meta=entity.get('meta')) # don't care about final
+ # fake we add it to the schema now to get a correctly initialized schema
+ # but remove it before doing anything more dangerous...
+ schema = self._cw.vreg.schema
+ eschema = schema.add_entity_type(etype)
+ # generate table sql and rql to add metadata
+ tablesql = eschema2sql(self._cw.pool.source('system').dbhelper, eschema,
+ prefix=SQL_PREFIX)
+ relrqls = []
+ for rtype in (META_RTYPES - VIRTUAL_RTYPES):
+ rschema = schema[rtype]
+ sampletype = rschema.subjects()[0]
+ desttype = rschema.objects()[0]
+ props = rschema.rdef(sampletype, desttype)
+ relrqls += list(ss.rdef2rql(rschema, name, desttype, props,
+ groupmap=group_mapping(self._cw)))
+ # now remove it !
+ schema.del_entity_type(name)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ self._cw.system_sql(sql)
+ # register operation to modify the schema on commit
+ # this have to be done before adding other relations definitions
+ # or permission settings
+ etype.eid = entity.eid
+ MemSchemaCWETypeAdd(self._cw, etype)
+ # add meta relations
+ for rql, kwargs in relrqls:
+ self._cw.execute(rql, kwargs)
+
+
+class BeforeUpdateCWETypeHook(DelCWETypeHook):
+ """check name change, handle final"""
+ __regid__ = 'syncupdatecwetype'
+ events = ('before_update_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ check_valid_changes(self._cw, entity, ro_attrs=('final',))
+ # don't use getattr(entity, attr), we would get the modified value if any
+ if 'name' in entity.edited_attributes:
+ newname = entity.pop('name')
+ oldname = entity.name
+ if newname.lower() != oldname.lower():
+ SourceDbCWETypeRename(self._cw, oldname=oldname, newname=newname)
+ MemSchemaCWETypeRename(self._cw, oldname=oldname, newname=newname)
+ entity['name'] = newname
+
+
+# CWRType hooks ################################################################
+
+class DelCWRTypeHook(SyncSchemaHook):
+ """before deleting a CWRType entity:
+ * check that we don't remove a core relation type
+ * cascade to delete related CWAttribute and CWRelation entities
+ * instantiate an operation to delete the relation type on commit
+ """
+ __regid__ = 'syncdelcwrtype'
+ __select__ = SyncSchemaHook.__select__ & implements('CWRType')
+ events = ('before_delete_entity',)
+
+ def __call__(self):
+ name = self.entity.name
+ if name in CORE_RTYPES:
+ raise ValidationError(self.entity.eid, {None: self._cw._('can\'t be deleted')})
+ # delete relation definitions using this relation type
+ self._cw.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
+ {'x': self.entity.eid})
+ self._cw.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
+ {'x': self.entity.eid})
+ MemSchemaCWRTypeDel(self._cw, name)
+
+
+class AfterAddCWRTypeHook(DelCWRTypeHook):
+ """after a CWRType entity has been added:
+ * register an operation to add the relation type to the instance's
+ schema on commit
+
+ We don't know yet this point if a table is necessary
+ """
+ __regid__ = 'syncaddcwrtype'
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ rtype = RelationType(name=entity.name,
+ description=entity.get('description'),
+ meta=entity.get('meta', False),
+ inlined=entity.get('inlined', False),
+ symmetric=entity.get('symmetric', False),
+ eid=entity.eid)
+ MemSchemaCWRTypeAdd(self._cw, rtype)
+
+
+class BeforeUpdateCWRTypeHook(DelCWRTypeHook):
+ """check name change, handle final"""
+ __regid__ = 'checkupdatecwrtype'
+ events = ('before_update_entity',)
+
+ def __call__(self):
+ check_valid_changes(self._cw, self.entity)
+
+
+class AfterUpdateCWRTypeHook(DelCWRTypeHook):
+ __regid__ = 'syncupdatecwrtype'
+ events = ('after_update_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ rschema = self._cw.vreg.schema.rschema(entity.name)
+ newvalues = {}
+ for prop in ('meta', 'symmetric', 'inlined'):
+ if prop in entity:
+ newvalues[prop] = entity[prop]
+ if newvalues:
+ MemSchemaCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues)
+ SourceDbCWRTypeUpdate(self._cw, rschema=rschema, values=newvalues,
+ entity=entity)
+
+def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
+ errors = {}
+ # don't use getattr(entity, attr), we would get the modified value if any
+ for attr in ro_attrs:
+ if attr in entity.edited_attributes:
+ origval, newval = hook.entity_oldnewvalue(entity, attr)
+ if newval != origval:
+ errors[attr] = session._("can't change the %s attribute") % \
+ display_name(session, attr)
+ if errors:
+ raise ValidationError(entity.eid, errors)
+
+
+class AfterDelRelationTypeHook(SyncSchemaHook):
+ """before deleting a CWAttribute or CWRelation entity:
+ * if this is a final or inlined relation definition, instantiate an
+ operation to drop necessary column, else if this is the last instance
+ of a non final relation, instantiate an operation to drop necessary
+ table
+ * instantiate an operation to delete the relation definition on commit
+ * delete the associated relation type when necessary
+ """
+ __regid__ = 'syncdelrelationtype'
+ __select__ = SyncSchemaHook.__select__ & hook.match_rtype('relation_type')
+ events = ('after_delete_relation',)
+
+ def __call__(self):
+ session = self._cw
+ rdef = session.vreg.schema.schema_by_eid(self.eidfrom)
+ subjschema, rschema, objschema = rdef.as_triple()
+ pendings = session.transaction_data.get('pendingeids', ())
+ pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set())
+ # first delete existing relation if necessary
+ if rschema.final:
+ rdeftype = 'CWAttribute'
+ pendingrdefs.add((subjschema, rschema))
+ else:
+ rdeftype = 'CWRelation'
+ pendingrdefs.add((subjschema, rschema, objschema))
+ if not (subjschema.eid in pendings or objschema.eid in pendings):
+ session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
+ % (rschema, subjschema, objschema))
+ execute = session.unsafe_execute
+ rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
+ 'R eid %%(x)s' % rdeftype, {'x': self.eidto})
+ lastrel = rset[0][0] == 0
+ # we have to update physical schema systematically for final and inlined
+ # relations, but only if it's the last instance for this relation type
+ # for other relations
+
+ if (rschema.final or rschema.inlined):
+ rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
+ 'R eid %%(x)s, X from_entity E, E name %%(name)s'
+ % rdeftype, {'x': self.eidto, 'name': str(subjschema)})
+ if rset[0][0] == 0 and not subjschema.eid in pendings:
+ ptypes = session.transaction_data.setdefault('pendingrtypes', set())
+ ptypes.add(rschema.type)
+ DropColumn(session, table=SQL_PREFIX + subjschema.type,
+ column=SQL_PREFIX + rschema.type)
+ elif lastrel:
+ DropRelationTable(session, rschema.type)
+ # if this is the last instance, drop associated relation type
+ if lastrel and not self.eidto in pendings:
+ execute('DELETE CWRType X WHERE X eid %(x)s', {'x': self.eidto}, 'x')
+ MemSchemaRDefDel(session, (subjschema, rschema, objschema))
+
+
+# CWAttribute / CWRelation hooks ###############################################
+
+class AfterAddCWAttributeHook(SyncSchemaHook):
+ __regid__ = 'syncaddcwattribute'
+ __select__ = SyncSchemaHook.__select__ & implements('CWAttribute')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ SourceDbCWAttributeAdd(self._cw, entity=self.entity)
+
+
+class AfterAddCWRelationHook(AfterAddCWAttributeHook):
+ __regid__ = 'syncaddcwrelation'
+ __select__ = SyncSchemaHook.__select__ & implements('CWRelation')
+
+ def __call__(self):
+ SourceDbCWRelationAdd(self._cw, entity=self.entity)
+
+
+class AfterUpdateCWRDefHook(SyncSchemaHook):
+ __regid__ = 'syncaddcwattribute'
+ __select__ = SyncSchemaHook.__select__ & implements('CWAttribute',
+ 'CWRelation')
+ events = ('after_update_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ if self._cw.deleted_in_transaction(entity.eid):
+ return
+ desttype = entity.otype.name
+ rschema = self._cw.vreg.schema[entity.rtype.name]
+ newvalues = {}
+ for prop in RelationDefinitionSchema.rproperty_defs(desttype):
+ if prop == 'constraints':
+ continue
+ if prop == 'order':
+ prop = 'ordernum'
+ if prop in entity.edited_attributes:
+ newvalues[prop] = entity[prop]
+ if newvalues:
+ subjtype = entity.stype.name
+ MemSchemaRDefUpdate(self._cw, kobj=(subjtype, desttype),
+ rschema=rschema, values=newvalues)
+ SourceDbRDefUpdate(self._cw, kobj=(subjtype, desttype),
+ rschema=rschema, values=newvalues)
+
+
+# constraints synchronization hooks ############################################
+
+class AfterAddCWConstraintHook(SyncSchemaHook):
+ __regid__ = 'syncaddcwconstraint'
+ __select__ = SyncSchemaHook.__select__ & implements('CWConstraint')
+ events = ('after_add_entity', 'after_update_entity')
+
+ def __call__(self):
+ MemSchemaCWConstraintAdd(self._cw, entity=self.entity)
+ SourceDbCWConstraintAdd(self._cw, entity=self.entity)
+
+
+class AfterAddConstrainedByHook(SyncSchemaHook):
+ __regid__ = 'syncdelconstrainedby'
+ __select__ = SyncSchemaHook.__select__ & hook.match_rtype('constrained_by')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ if self._cw.added_in_transaction(self.eidfrom):
+ self._cw.transaction_data.setdefault(self.eidfrom, []).append(self.eidto)
+
+
+class BeforeDeleteConstrainedByHook(AfterAddConstrainedByHook):
+ __regid__ = 'syncdelconstrainedby'
+ events = ('before_delete_relation',)
+
+ def __call__(self):
+ if self._cw.deleted_in_transaction(self.eidfrom):
+ return
+ schema = self._cw.vreg.schema
+ entity = self._cw.entity_from_eid(self.eidto)
+ rdef = schema.schema_by_eid(self.eidfrom)
+ try:
+ cstr = rdef.constraint_by_type(entity.type)
+ except IndexError:
+ self._cw.critical('constraint type no more accessible')
+ else:
+ subjtype, rtype, objtype = rdef.as_triple()
+ SourceDbCWConstraintDel(self._cw, subjtype=subjtype, rtype=rtype,
+ objtype=objtype, cstr=cstr)
+ MemSchemaCWConstraintDel(self._cw, subjtype=subjtype, rtype=rtype,
+ objtype=objtype, cstr=cstr)
+
+
+# permissions synchronization hooks ############################################
+
+class AfterAddPermissionHook(SyncSchemaHook):
+ """added entity/relation *_permission, need to update schema"""
+ __regid__ = 'syncaddperm'
+ __select__ = SyncSchemaHook.__select__ & hook.match_rtype(
+ 'read_permission', 'add_permission', 'delete_permission',
+ 'update_permission')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ action = self.rtype.split('_', 1)[0]
+ if self._cw.describe(self.eidto)[0] == 'CWGroup':
+ MemSchemaPermissionAdd(self._cw, action=action, eid=self.eidfrom,
+ group_eid=self.eidto)
+ else: # RQLExpression
+ expr = self._cw.entity_from_eid(self.eidto).expression
+ MemSchemaPermissionAdd(self._cw, action=action, eid=self.eidfrom,
+ expr=expr)
+
+
+class BeforeDelPermissionHook(AfterAddPermissionHook):
+ """delete entity/relation *_permission, need to update schema
+
+ skip the operation if the related type is being deleted
+ """
+ __regid__ = 'syncdelperm'
+ events = ('before_delete_relation',)
+
+ def __call__(self):
+ if self._cw.deleted_in_transaction(self.eidfrom):
+ return
+ action = self.rtype.split('_', 1)[0]
+ if self._cw.describe(self.eidto)[0] == 'CWGroup':
+ MemSchemaPermissionDel(self._cw, action=action, eid=self.eidfrom,
+ group_eid=self.eidto)
+ else: # RQLExpression
+ expr = self._cw.entity_from_eid(self.eidto).expression
+ MemSchemaPermissionDel(self._cw, action=action, eid=self.eidfrom,
+ expr=expr)
+
+
+# specializes synchronization hooks ############################################
+
+
+class AfterAddSpecializesHook(SyncSchemaHook):
+ __regid__ = 'syncaddspecializes'
+ __select__ = SyncSchemaHook.__select__ & hook.match_rtype('specializes')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ MemSchemaSpecializesAdd(self._cw, etypeeid=self.eidfrom,
+ parentetypeeid=self.eidto)
+
+
+class AfterDelSpecializesHook(SyncSchemaHook):
+ __regid__ = 'syncdelspecializes'
+ __select__ = SyncSchemaHook.__select__ & hook.match_rtype('specializes')
+ events = ('after_delete_relation',)
+
+ def __call__(self):
+ MemSchemaSpecializesDel(self._cw, etypeeid=self.eidfrom,
+ parentetypeeid=self.eidto)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/syncsession.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,235 @@
+"""Core hooks: synchronize living session on persistent data changes
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb import UnknownProperty, ValidationError, BadConnectionId
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+
+
+def get_user_sessions(repo, ueid):
+ for session in repo._sessions.values():
+ if ueid == session.user.eid:
+ yield session
+
+
+class SyncSessionHook(hook.Hook):
+ __abstract__ = True
+ category = 'syncsession'
+
+
+# user/groups synchronisation #################################################
+
+class _GroupOperation(hook.Operation):
+ """base class for group operation"""
+ geid = None
+ def __init__(self, session, *args, **kwargs):
+ """override to get the group name before actual groups manipulation:
+
+ we may temporarily loose right access during a commit event, so
+ no query should be emitted while comitting
+ """
+ rql = 'Any N WHERE G eid %(x)s, G name N'
+ result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
+ hook.Operation.__init__(self, session, *args, **kwargs)
+ self.group = result[0][0]
+
+
+class _DeleteGroupOp(_GroupOperation):
+ """synchronize user when a in_group relation has been deleted"""
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ groups = self.cnxuser.groups
+ try:
+ groups.remove(self.group)
+ except KeyError:
+ self.error('user %s not in group %s', self.cnxuser, self.group)
+ return
+
+
+class _AddGroupOp(_GroupOperation):
+ """synchronize user when a in_group relation has been added"""
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ groups = self.cnxuser.groups
+ if self.group in groups:
+ self.warning('user %s already in group %s', self.cnxuser,
+ self.group)
+ return
+ groups.add(self.group)
+
+
+class SyncInGroupHook(SyncSessionHook):
+ __regid__ = 'syncingroup'
+ __select__ = SyncSessionHook.__select__ & hook.match_rtype('in_group')
+ events = ('after_delete_relation', 'after_add_relation')
+
+ def __call__(self):
+ if self.event == 'after_delete_relation':
+ opcls = _DeleteGroupOp
+ else:
+ opcls = _AddGroupOp
+ for session in get_user_sessions(self._cw.repo, self.eidfrom):
+ opcls(self._cw, cnxuser=session.user, geid=self.eidto)
+
+
+class _DelUserOp(hook.Operation):
+ """close associated user's session when it is deleted"""
+ def __init__(self, session, cnxid):
+ self.cnxid = cnxid
+ hook.Operation.__init__(self, session)
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ try:
+ self.session.repo.close(self.cnxid)
+ except BadConnectionId:
+ pass # already closed
+
+
+class CloseDeletedUserSessionsHook(SyncSessionHook):
+ __regid__ = 'closession'
+ __select__ = SyncSessionHook.__select__ & implements('CWUser')
+ events = ('after_delete_entity',)
+
+ def __call__(self):
+ """modify user permission, need to update users"""
+ for session in get_user_sessions(self._cw.repo, self.entity.eid):
+ _DelUserOp(self._cw, session.id)
+
+
+# CWProperty hooks #############################################################
+
+
+class _DelCWPropertyOp(hook.Operation):
+ """a user's custom properties has been deleted"""
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ try:
+ del self.cwpropdict[self.key]
+ except KeyError:
+ self.error('%s has no associated value', self.key)
+
+
+class _ChangeCWPropertyOp(hook.Operation):
+ """a user's custom properties has been added/changed"""
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ self.cwpropdict[self.key] = self.value
+
+
+class _AddCWPropertyOp(hook.Operation):
+ """a user's custom properties has been added/changed"""
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ cwprop = self.cwprop
+ if not cwprop.for_user:
+ self.session.vreg['propertyvalues'][cwprop.pkey] = cwprop.value
+ # if for_user is set, update is handled by a ChangeCWPropertyOp operation
+
+
+class AddCWPropertyHook(SyncSessionHook):
+ __regid__ = 'addcwprop'
+ __select__ = SyncSessionHook.__select__ & implements('CWProperty')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ key, value = self.entity.pkey, self.entity.value
+ session = self._cw
+ try:
+ value = session.vreg.typed_value(key, value)
+ except UnknownProperty:
+ raise ValidationError(self.entity.eid,
+ {'pkey': session._('unknown property key')})
+ except ValueError, ex:
+ raise ValidationError(self.entity.eid,
+ {'value': session._(str(ex))})
+ if not session.user.matching_groups('managers'):
+ session.add_relation(entity.eid, 'for_user', session.user.eid)
+ else:
+ _AddCWPropertyOp(session, cwprop=self.entity)
+
+
+class UpdateCWPropertyHook(AddCWPropertyHook):
+ __regid__ = 'updatecwprop'
+ events = ('after_update_entity',)
+
+ def __call__(self):
+ entity = self.entity
+ if not ('pkey' in entity.edited_attributes or
+ 'value' in entity.edited_attributes):
+ return
+ key, value = entity.pkey, entity.value
+ session = self._cw
+ try:
+ value = session.vreg.typed_value(key, value)
+ except UnknownProperty:
+ return
+ except ValueError, ex:
+ raise ValidationError(entity.eid, {'value': session._(str(ex))})
+ if entity.for_user:
+ for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
+ _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
+ key=key, value=value)
+ else:
+ # site wide properties
+ _ChangeCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'],
+ key=key, value=value)
+
+
+class DeleteCWPropertyHook(AddCWPropertyHook):
+ __regid__ = 'delcwprop'
+ events = ('before_delete_entity',)
+
+ def __call__(self):
+ eid = self.entity.eid
+ session = self._cw
+ for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()):
+ if rtype == 'for_user' and eidfrom == self.entity.eid:
+ # if for_user was set, delete has already been handled
+ break
+ else:
+ _DelCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'],
+ key=self.entity.pkey)
+
+
+class AddForUserRelationHook(SyncSessionHook):
+ __regid__ = 'addcwpropforuser'
+ __select__ = SyncSessionHook.__select__ & hook.match_rtype('for_user')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ session = self._cw
+ eidfrom = self.eidfrom
+ if not session.describe(eidfrom)[0] == 'CWProperty':
+ return
+ key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
+ {'x': eidfrom}, 'x')[0]
+ if session.vreg.property_info(key)['sitewide']:
+ raise ValidationError(eidfrom,
+ {'for_user': session._("site-wide property can't be set for user")})
+ for session_ in get_user_sessions(session.repo, self.eidto):
+ _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
+ key=key, value=value)
+
+
+class DelForUserRelationHook(AddForUserRelationHook):
+ __regid__ = 'delcwpropforuser'
+ events = ('after_delete_relation',)
+
+ def __call__(self):
+ session = self._cw
+ key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
+ {'x': self.eidfrom}, 'x')[0][0]
+ session.transaction_data.setdefault('pendingrelations', []).append(
+ (self.eidfrom, self.rtype, self.eidto))
+ for session_ in get_user_sessions(session.repo, self.eidto):
+ _DelCWPropertyOp(session, cwpropdict=session_.user.properties, key=key)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/data/bootstrap_cubes Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,1 @@
+email
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/unittest_bookmarks.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,27 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+from logilab.common.testlib import unittest_main
+from cubicweb.devtools.testlib import CubicWebTC
+
+class BookmarkHooksTC(CubicWebTC):
+
+
+ def test_auto_delete_bookmarks(self):
+ beid = self.execute('INSERT Bookmark X: X title "hop", X path "view", X bookmarked_by U '
+ 'WHERE U login "admin"')[0][0]
+ self.execute('SET X bookmarked_by U WHERE U login "anon"')
+ self.commit()
+ self.execute('DELETE X bookmarked_by U WHERE U login "admin"')
+ self.commit()
+ self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': beid}, 'x'))
+ self.execute('DELETE X bookmarked_by U WHERE U login "anon"')
+ self.commit()
+ self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': beid}, 'x'))
+
+if __name__ == '__main__':
+ unittest_main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/test/unittest_hooks.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,507 @@
+# -*- coding: utf-8 -*-
+"""functional tests for core hooks
+
+note: most schemahooks.py hooks are actually tested in unittest_migrations.py
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from datetime import datetime
+
+from cubicweb import (ConnectionError, ValidationError, AuthenticationError,
+ BadConnectionId)
+from cubicweb.devtools.testlib import CubicWebTC, get_versions
+
+from cubicweb.server.sqlutils import SQL_PREFIX
+from cubicweb.server.repository import Repository
+
+orig_get_versions = Repository.get_versions
+
+def setup_module(*args):
+ Repository.get_versions = get_versions
+
+def teardown_module(*args):
+ Repository.get_versions = orig_get_versions
+
+
+
+class CoreHooksTC(CubicWebTC):
+
+ def test_delete_internal_entities(self):
+ self.assertRaises(ValidationError, self.execute,
+ 'DELETE CWEType X WHERE X name "CWEType"')
+ self.assertRaises(ValidationError, self.execute,
+ 'DELETE CWRType X WHERE X name "relation_type"')
+ self.assertRaises(ValidationError, self.execute,
+ 'DELETE CWGroup X WHERE X name "owners"')
+
+ def test_delete_required_relations_subject(self):
+ self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y '
+ 'WHERE Y name "users"')
+ self.commit()
+ self.execute('DELETE X in_group Y WHERE X login "toto", Y name "users"')
+ self.assertRaises(ValidationError, self.commit)
+ self.execute('DELETE X in_group Y WHERE X login "toto"')
+ self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"')
+ self.commit()
+
+ def test_delete_required_relations_object(self):
+ self.skip('no sample in the schema ! YAGNI ? Kermaat ?')
+
+ def test_static_vocabulary_check(self):
+ self.assertRaises(ValidationError,
+ self.execute,
+ 'SET X composite "whatever" WHERE X from_entity FE, FE name "CWUser", X relation_type RT, RT name "in_group"')
+
+ def test_missing_required_relations_subject_inline(self):
+ # missing in_group relation
+ self.execute('INSERT CWUser X: X login "toto", X upassword "hop"')
+ self.assertRaises(ValidationError,
+ self.commit)
+
+ def test_inlined(self):
+ self.assertEquals(self.repo.schema['sender'].inlined, True)
+ self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+ self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+ eeid = self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+ 'WHERE Y is EmailAddress, P is EmailPart')[0][0]
+ self.execute('SET X sender Y WHERE X is Email, Y is EmailAddress')
+ rset = self.execute('Any S WHERE X sender S, X eid %s' % eeid)
+ self.assertEquals(len(rset), 1)
+
+ def test_composite_1(self):
+ self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+ self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+ self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+ 'WHERE Y is EmailAddress, P is EmailPart')
+ self.failUnless(self.execute('Email X WHERE X sender Y'))
+ self.commit()
+ self.execute('DELETE Email X')
+ rset = self.execute('Any X WHERE X is EmailPart')
+ self.assertEquals(len(rset), 1)
+ self.commit()
+ rset = self.execute('Any X WHERE X is EmailPart')
+ self.assertEquals(len(rset), 0)
+
+ def test_composite_2(self):
+ self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+ self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+ self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+ 'WHERE Y is EmailAddress, P is EmailPart')
+ self.commit()
+ self.execute('DELETE Email X')
+ self.execute('DELETE EmailPart X')
+ self.commit()
+ rset = self.execute('Any X WHERE X is EmailPart')
+ self.assertEquals(len(rset), 0)
+
+ def test_composite_redirection(self):
+ self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
+ self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
+ self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
+ 'WHERE Y is EmailAddress, P is EmailPart')
+ self.execute('INSERT Email X: X messageid "<2345>", X subject "test2", X sender Y, X recipients Y '
+ 'WHERE Y is EmailAddress')
+ self.commit()
+ self.execute('DELETE X parts Y WHERE X messageid "<1234>"')
+ self.execute('SET X parts Y WHERE X messageid "<2345>"')
+ self.commit()
+ rset = self.execute('Any X WHERE X is EmailPart')
+ self.assertEquals(len(rset), 1)
+ self.assertEquals(rset.get_entity(0, 0).reverse_parts[0].messageid, '<2345>')
+
+ def test_unsatisfied_constraints(self):
+ releid = self.execute('INSERT CWRelation X: X from_entity FE, X relation_type RT, X to_entity TE '
+ 'WHERE FE name "CWUser", RT name "in_group", TE name "String"')[0][0]
+ self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
+ {'x': releid}, 'x')
+ ex = self.assertRaises(ValidationError,
+ self.commit)
+ self.assertEquals(ex.errors, {'to_entity': 'RQLConstraint O final FALSE failed'})
+
+ 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')
+ self.assertEquals(entity.description, u'yo')
+ entity = req.create_entity('Workflow', name=u'wf2', description_format=u'text/html',
+ description=u'<b>yo')
+ self.assertEquals(entity.description, u'<b>yo</b>')
+ entity = req.create_entity('Workflow', name=u'wf3', description_format=u'text/html',
+ description=u'<b>yo</b>')
+ self.assertEquals(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>')
+ self.assertEquals(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é")
+ self.assertEquals(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.set_attributes(name=u'wf2')
+ self.assertEquals(entity.description, u'yo')
+ entity.set_attributes(description=u'R&D<p>yo')
+ entity.pop('description')
+ self.assertEquals(entity.description, u'R&D<p>yo</p>')
+
+
+ def test_metadata_cwuri(self):
+ entity = self.request().create_entity('Workflow', name=u'wf1')
+ self.assertEquals(entity.cwuri, self.repo.config['base-url'] + 'eid/%s' % entity.eid)
+
+ def test_metadata_creation_modification_date(self):
+ _now = datetime.now()
+ entity = self.request().create_entity('Workflow', name=u'wf1')
+ self.assertEquals((entity.creation_date - _now).seconds, 0)
+ self.assertEquals((entity.modification_date - _now).seconds, 0)
+
+ def test_metadata_created_by(self):
+ entity = self.request().create_entity('Bookmark', title=u'wf1', path=u'/view')
+ self.commit() # fire operations
+ self.assertEquals(len(entity.created_by), 1) # make sure we have only one creator
+ self.assertEquals(entity.created_by[0].eid, self.session.user.eid)
+
+ def test_metadata_owned_by(self):
+ entity = self.request().create_entity('Bookmark', title=u'wf1', path=u'/view')
+ self.commit() # fire operations
+ self.assertEquals(len(entity.owned_by), 1) # make sure we have only one owner
+ self.assertEquals(entity.owned_by[0].eid, self.session.user.eid)
+
+ def test_user_login_stripped(self):
+ u = self.create_user(' joe ')
+ tname = self.execute('Any L WHERE E login L, E eid %(e)s',
+ {'e': u.eid})[0][0]
+ self.assertEquals(tname, 'joe')
+ self.execute('SET X login " jijoe " WHERE X eid %(x)s', {'x': u.eid})
+ tname = self.execute('Any L WHERE E login L, E eid %(e)s',
+ {'e': u.eid})[0][0]
+ self.assertEquals(tname, 'jijoe')
+
+
+
+class UserGroupHooksTC(CubicWebTC):
+
+ def test_user_synchronization(self):
+ self.create_user('toto', password='hop', commit=False)
+ self.assertRaises(AuthenticationError,
+ self.repo.connect, u'toto', password='hop')
+ self.commit()
+ cnxid = self.repo.connect(u'toto', password='hop')
+ self.failIfEqual(cnxid, self.session.id)
+ self.execute('DELETE CWUser X WHERE X login "toto"')
+ self.repo.execute(cnxid, 'State X')
+ self.commit()
+ self.assertRaises(BadConnectionId,
+ self.repo.execute, cnxid, 'State X')
+
+ def test_user_group_synchronization(self):
+ user = self.session.user
+ self.assertEquals(user.groups, set(('managers',)))
+ self.execute('SET X in_group G WHERE X eid %s, G name "guests"' % user.eid)
+ self.assertEquals(user.groups, set(('managers',)))
+ self.commit()
+ self.assertEquals(user.groups, set(('managers', 'guests')))
+ self.execute('DELETE X in_group G WHERE X eid %s, G name "guests"' % user.eid)
+ self.assertEquals(user.groups, set(('managers', 'guests')))
+ self.commit()
+ self.assertEquals(user.groups, set(('managers',)))
+
+ def test_user_composite_owner(self):
+ ueid = self.create_user('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"')
+ self.commit()
+ self.assertEquals(self.execute('Any A WHERE X owned_by U, U use_email X,'
+ 'U login "toto", X address A')[0][0],
+ 'toto@logilab.fr')
+
+ def test_no_created_by_on_deleted_entity(self):
+ eid = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0]
+ self.execute('DELETE EmailAddress X WHERE X eid %s' % eid)
+ self.commit()
+ self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid}))
+
+
+class CWPropertyHooksTC(CubicWebTC):
+
+ def test_unexistant_eproperty(self):
+ ex = self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
+ self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+ ex = self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
+ self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
+
+ def test_site_wide_eproperty(self):
+ ex = self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
+ self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
+
+ def test_bad_type_eproperty(self):
+ ex = self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
+ self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+ ex = self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
+ self.assertEquals(ex.errors, {'value': u'unauthorized value'})
+
+
+class SchemaHooksTC(CubicWebTC):
+
+ def test_duplicate_etype_error(self):
+ # check we can't add a CWEType or CWRType entity if it already exists one
+ # with the same name
+ self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWEType X: X name "CWUser"')
+ self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWRType X: X name "in_group"')
+
+ def test_validation_unique_constraint(self):
+ self.assertRaises(ValidationError,
+ self.execute, 'INSERT CWUser X: X login "admin"')
+ try:
+ self.execute('INSERT CWUser X: X login "admin"')
+ except ValidationError, ex:
+ self.assertIsInstance(ex.entity, int)
+ self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
+
+
+class SchemaModificationHooksTC(CubicWebTC):
+
+ @classmethod
+ def init_config(cls, config):
+ super(SchemaModificationHooksTC, cls).init_config(config)
+ config._cubes = None
+ cls.repo.fill_schema()
+
+ def index_exists(self, etype, attr, unique=False):
+ self.session.set_pool()
+ dbhelper = self.session.pool.source('system').dbhelper
+ sqlcursor = self.session.pool['system']
+ return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
+
+ def _set_perms(self, eid):
+ self.execute('SET X read_permission G WHERE X eid %(x)s, G is CWGroup',
+ {'x': eid}, 'x')
+ self.execute('SET X add_permission G WHERE X eid %(x)s, G is CWGroup, G name "managers"',
+ {'x': eid}, 'x')
+ self.execute('SET X delete_permission G WHERE X eid %(x)s, G is CWGroup, G name "owners"',
+ {'x': eid}, 'x')
+
+ def test_base(self):
+ schema = self.repo.schema
+ self.session.set_pool()
+ dbhelper = self.session.pool.source('system').dbhelper
+ sqlcursor = self.session.pool['system']
+ self.failIf(schema.has_entity('Societe2'))
+ self.failIf(schema.has_entity('concerne2'))
+ # schema should be update on insertion (after commit)
+ eeid = self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')[0][0]
+ self._set_perms(eeid)
+ self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symmetric FALSE')
+ self.failIf(schema.has_entity('Societe2'))
+ self.failIf(schema.has_entity('concerne2'))
+ # have to commit before adding definition relations
+ self.commit()
+ self.failUnless(schema.has_entity('Societe2'))
+ self.failUnless(schema.has_relation('concerne2'))
+ attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", '
+ ' X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+ 'WHERE RT name "name", E name "Societe2", F name "String"')[0][0]
+ self._set_perms(attreid)
+ concerne2_rdef_eid = self.execute(
+ 'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
+ 'WHERE RT name "concerne2", E name "Societe2"')[0][0]
+ self._set_perms(concerne2_rdef_eid)
+ self.failIf('name' in schema['Societe2'].subject_relations())
+ self.failIf('concerne2' in schema['Societe2'].subject_relations())
+ self.failIf(self.index_exists('Societe2', 'name'))
+ self.commit()
+ self.failUnless('name' in schema['Societe2'].subject_relations())
+ self.failUnless('concerne2' in schema['Societe2'].subject_relations())
+ self.failUnless(self.index_exists('Societe2', 'name'))
+ # now we should be able to insert and query Societe2
+ s2eid = self.execute('INSERT Societe2 X: X name "logilab"')[0][0]
+ self.execute('Societe2 X WHERE X name "logilab"')
+ self.execute('SET X concerne2 X WHERE X name "logilab"')
+ rset = self.execute('Any X WHERE X concerne2 Y')
+ self.assertEquals(rset.rows, [[s2eid]])
+ # check that when a relation definition is deleted, existing relations are deleted
+ rdefeid = self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, '
+ ' X from_entity E, X to_entity E '
+ 'WHERE RT name "concerne2", E name "CWUser"')[0][0]
+ self._set_perms(rdefeid)
+ self.commit()
+ self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
+ self.commit()
+ self.failUnless('concerne2' in schema['CWUser'].subject_relations())
+ self.failIf('concerne2' in schema['Societe2'].subject_relations())
+ self.failIf(self.execute('Any X WHERE X concerne2 Y'))
+ # schema should be cleaned on delete (after commit)
+ self.execute('DELETE CWEType X WHERE X name "Societe2"')
+ self.execute('DELETE CWRType X WHERE X name "concerne2"')
+ self.failUnless(self.index_exists('Societe2', 'name'))
+ self.failUnless(schema.has_entity('Societe2'))
+ self.failUnless(schema.has_relation('concerne2'))
+ self.commit()
+ self.failIf(self.index_exists('Societe2', 'name'))
+ self.failIf(schema.has_entity('Societe2'))
+ self.failIf(schema.has_entity('concerne2'))
+ self.failIf('concerne2' in schema['CWUser'].subject_relations())
+
+ def test_is_instance_of_insertions(self):
+ seid = self.execute('INSERT Transition T: T name "subdiv"')[0][0]
+ is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
+ self.assertEquals(is_etypes, ['Transition'])
+ instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
+ self.assertEquals(sorted(instanceof_etypes), ['BaseTransition', 'Transition'])
+ snames = [name for name, in self.execute('Any N WHERE S is BaseTransition, S name N')]
+ self.failIf('subdiv' in snames)
+ snames = [name for name, in self.execute('Any N WHERE S is_instance_of BaseTransition, S name N')]
+ self.failUnless('subdiv' in snames)
+
+
+ def test_perms_synchronization_1(self):
+ schema = self.repo.schema
+ self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
+ self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
+ self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+ self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
+ self.commit()
+ self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
+ self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
+ self.commit()
+ self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
+
+ def test_perms_synchronization_2(self):
+ schema = self.repo.schema['in_group'].rdefs[('CWUser', 'CWGroup')]
+ self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+ self.execute('DELETE X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
+ self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+ self.commit()
+ self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+ self.execute('SET X read_permission Y WHERE X relation_type RT, RT name "in_group", Y name "guests"')
+ self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
+ self.commit()
+ self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
+
+ def test_nonregr_user_edit_itself(self):
+ ueid = self.session.user.eid
+ groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
+ self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
+ self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
+ self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
+ self.commit()
+ eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
+ self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
+ self.execute('SET X final FALSE WHERE X eid %s' % eeid)
+ self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
+ % (eeid, groupeids[0], groupeids[1]))
+ self.commit()
+ self.execute('Any X WHERE X is CWEType, X name "CWEType"')
+
+ # schema modification hooks tests #########################################
+
+ def test_uninline_relation(self):
+ self.session.set_pool()
+ dbhelper = self.session.pool.source('system').dbhelper
+ sqlcursor = self.session.pool['system']
+ self.failUnless(self.schema['state_of'].inlined)
+ try:
+ self.execute('SET X inlined FALSE WHERE X name "state_of"')
+ self.failUnless(self.schema['state_of'].inlined)
+ self.commit()
+ self.failIf(self.schema['state_of'].inlined)
+ self.failIf(self.index_exists('State', 'state_of'))
+ rset = self.execute('Any X, Y WHERE X state_of Y')
+ self.assertEquals(len(rset), 2) # user states
+ finally:
+ self.execute('SET X inlined TRUE WHERE X name "state_of"')
+ self.failIf(self.schema['state_of'].inlined)
+ self.commit()
+ self.failUnless(self.schema['state_of'].inlined)
+ self.failUnless(self.index_exists('State', 'state_of'))
+ rset = self.execute('Any X, Y WHERE X state_of Y')
+ self.assertEquals(len(rset), 2)
+
+ def test_indexed_change(self):
+ self.session.set_pool()
+ dbhelper = self.session.pool.source('system').dbhelper
+ sqlcursor = self.session.pool['system']
+ try:
+ self.execute('SET X indexed FALSE WHERE X relation_type R, R name "name"')
+ self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
+ self.failUnless(self.index_exists('Workflow', 'name'))
+ self.commit()
+ self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
+ self.failIf(self.index_exists('Workflow', 'name'))
+ finally:
+ self.execute('SET X indexed TRUE WHERE X relation_type R, R name "name"')
+ self.failIf(self.schema['name'].rdef('Workflow', 'String').indexed)
+ self.failIf(self.index_exists('Workflow', 'name'))
+ self.commit()
+ self.failUnless(self.schema['name'].rdef('Workflow', 'String').indexed)
+ self.failUnless(self.index_exists('Workflow', 'name'))
+
+ def test_unique_change(self):
+ self.session.set_pool()
+ dbhelper = self.session.pool.source('system').dbhelper
+ sqlcursor = self.session.pool['system']
+ try:
+ self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
+ 'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+ 'RT name "name", E name "Workflow"')
+ self.failIf(self.schema['Workflow'].has_unique_values('name'))
+ self.failIf(self.index_exists('Workflow', 'name', unique=True))
+ self.commit()
+ self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+ self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+ finally:
+ self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
+ 'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
+ 'RT name "name", E name "Workflow"')
+ self.failUnless(self.schema['Workflow'].has_unique_values('name'))
+ self.failUnless(self.index_exists('Workflow', 'name', unique=True))
+ self.commit()
+ self.failIf(self.schema['Workflow'].has_unique_values('name'))
+ self.failIf(self.index_exists('Workflow', 'name', unique=True))
+
+ def test_required_change_1(self):
+ self.execute('SET DEF cardinality "?1" '
+ 'WHERE DEF relation_type RT, DEF from_entity E,'
+ 'RT name "title", E name "Bookmark"')
+ self.commit()
+ # should now be able to add bookmark without title
+ self.execute('INSERT Bookmark X: X path "/view"')
+ self.commit()
+
+ def test_required_change_2(self):
+ self.execute('SET DEF cardinality "11" '
+ 'WHERE DEF relation_type RT, DEF from_entity E,'
+ '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")
+ self.execute('SET DEF cardinality "?1" '
+ 'WHERE DEF relation_type RT, DEF from_entity E,'
+ 'RT name "surname", E name "CWUser"')
+ self.commit()
+
+
+ def test_add_attribute_to_base_class(self):
+ attreid = self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
+ 'WHERE RT name "messageid", E name "BaseTransition", F name "String"')[0][0]
+ assert self.execute('SET X read_permission Y WHERE X eid %(x)s, Y name "managers"',
+ {'x': attreid}, 'x')
+ self.commit()
+ self.schema.rebuild_infered_relations()
+ self.failUnless('Transition' in self.schema['messageid'].subjects())
+ self.failUnless('WorkflowTransition' in self.schema['messageid'].subjects())
+ self.execute('Any X WHERE X is_instance_of BaseTransition, X messageid "hop"')
+
+if __name__ == '__main__':
+ unittest_main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hooks/workflow.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,348 @@
+"""Core hooks: workflow related hooks
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from datetime import datetime
+
+from cubicweb import RepositoryError, ValidationError
+from cubicweb.interfaces import IWorkflowable
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+from cubicweb.entities.wfobjs import WorkflowTransition
+
+
+def _change_state(session, x, oldstate, newstate):
+ nocheck = session.transaction_data.setdefault('skip-security', set())
+ nocheck.add((x, 'in_state', oldstate))
+ nocheck.add((x, 'in_state', newstate))
+ # delete previous state first in case we're using a super session,
+ # unless in_state isn't stored in the system source
+ fromsource = session.describe(x)[1]
+ if fromsource == 'system' or \
+ not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
+ session.delete_relation(x, 'in_state', oldstate)
+ session.add_relation(x, 'in_state', newstate)
+
+
+# operations ###################################################################
+
+class _SetInitialStateOp(hook.Operation):
+ """make initial state be a default state"""
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ # if there is an initial state and the entity's state is not set,
+ # use the initial state as a default state
+ if not (session.deleted_in_transaction(entity.eid) or entity.in_state) \
+ and entity.current_workflow:
+ state = entity.current_workflow.initial
+ if state:
+ # use super session to by-pass security checks
+ session.super_session.add_relation(entity.eid, 'in_state',
+ state.eid)
+
+
+class _FireAutotransitionOp(hook.Operation):
+ """try to fire auto transition after state changes"""
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ autotrs = list(entity.possible_transitions('auto'))
+ if autotrs:
+ assert len(autotrs) == 1
+ entity.fire_transition(autotrs[0])
+
+
+class _WorkflowChangedOp(hook.Operation):
+ """fix entity current state when changing its workflow"""
+
+ def precommit_event(self):
+ # notice that enforcement that new workflow apply to the entity's type is
+ # done by schema rule, no need to check it here
+ session = self.session
+ pendingeids = session.transaction_data.get('pendingeids', ())
+ if self.eid in pendingeids:
+ return
+ entity = session.entity_from_eid(self.eid)
+ # check custom workflow has not been rechanged to another one in the same
+ # transaction
+ mainwf = entity.main_workflow
+ if mainwf.eid == self.wfeid:
+ deststate = mainwf.initial
+ if not deststate:
+ msg = session._('workflow has no initial state')
+ raise ValidationError(entity.eid, {'custom_workflow': msg})
+ if mainwf.state_by_eid(entity.current_state.eid):
+ # nothing to do
+ return
+ # if there are no history, simply go to new workflow's initial state
+ if not entity.workflow_history:
+ if entity.current_state.eid != deststate.eid:
+ _change_state(session, entity.eid,
+ entity.current_state.eid, deststate.eid)
+ return
+ msg = session._('workflow changed to "%s"')
+ msg %= session._(mainwf.name)
+ session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
+ entity.change_state(deststate, msg, u'text/plain')
+
+
+class _CheckTrExitPoint(hook.Operation):
+
+ def precommit_event(self):
+ tr = self.session.entity_from_eid(self.treid)
+ outputs = set()
+ for ep in tr.subworkflow_exit:
+ if ep.subwf_state.eid in outputs:
+ msg = self.session._("can't have multiple exits on the same state")
+ raise ValidationError(self.treid, {'subworkflow_exit': msg})
+ outputs.add(ep.subwf_state.eid)
+
+
+class _SubWorkflowExitOp(hook.Operation):
+
+ def precommit_event(self):
+ session = self.session
+ forentity = self.forentity
+ trinfo = self.trinfo
+ # we're in a subworkflow, check if we've reached an exit point
+ wftr = forentity.subworkflow_input_transition()
+ if wftr is None:
+ # inconsistency detected
+ msg = session._("state doesn't belong to entity's current workflow")
+ raise ValidationError(self.trinfo.eid, {'to_state': msg})
+ tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
+ if tostate is not None:
+ # reached an exit point
+ msg = session._('exiting from subworkflow %s')
+ msg %= session._(forentity.current_workflow.name)
+ session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
+ # XXX iirk
+ req = forentity._cw
+ forentity._cw = session.super_session
+ try:
+ trinfo = forentity.change_state(tostate, msg, u'text/plain',
+ tr=wftr)
+ finally:
+ forentity._cw = req
+
+
+# hooks ########################################################################
+
+class WorkflowHook(hook.Hook):
+ __abstract__ = True
+ category = 'worfklow'
+
+
+class SetInitialStateHook(WorkflowHook):
+ __regid__ = 'wfsetinitial'
+ __select__ = WorkflowHook.__select__ & implements(IWorkflowable)
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ _SetInitialStateOp(self._cw, entity=self.entity)
+
+
+class PrepareStateChangeHook(WorkflowHook):
+ """record previous state information"""
+ __regid__ = 'cwdelstate'
+ __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+ events = ('before_delete_relation',)
+
+ def __call__(self):
+ self._cw.transaction_data.setdefault('pendingrelations', []).append(
+ (self.eidfrom, self.rtype, self.eidto))
+
+
+class FireTransitionHook(WorkflowHook):
+ """check the transition is allowed, add missing information. Expect that:
+ * wf_info_for inlined relation is set
+ * by_transition or to_state (managers only) inlined relation is set
+ """
+ __regid__ = 'wffiretransition'
+ __select__ = WorkflowHook.__select__ & implements('TrInfo')
+ events = ('before_add_entity',)
+
+ def __call__(self):
+ session = self._cw
+ entity = self.entity
+ # first retreive entity to which the state change apply
+ try:
+ foreid = entity['wf_info_for']
+ except KeyError:
+ msg = session._('mandatory relation')
+ raise ValidationError(entity.eid, {'wf_info_for': msg})
+ forentity = session.entity_from_eid(foreid)
+ # then check it has a workflow set, unless we're in the process of changing
+ # entity's workflow
+ if session.transaction_data.get((forentity.eid, 'customwf')):
+ wfeid = session.transaction_data[(forentity.eid, 'customwf')]
+ wf = session.entity_from_eid(wfeid)
+ else:
+ wf = forentity.current_workflow
+ if wf is None:
+ msg = session._('related entity has no workflow set')
+ raise ValidationError(entity.eid, {None: msg})
+ # then check it has a state set
+ fromstate = forentity.current_state
+ if fromstate is None:
+ msg = session._('related entity has no state')
+ raise ValidationError(entity.eid, {None: msg})
+ # True if we are coming back from subworkflow
+ swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
+ cowpowers = session.is_super_session or 'managers' in session.user.groups
+ # no investigate the requested state change...
+ try:
+ treid = entity['by_transition']
+ except KeyError:
+ # no transition set, check user is a manager and destination state
+ # is specified (and valid)
+ if not cowpowers:
+ msg = session._('mandatory relation')
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ deststateeid = entity.get('to_state')
+ if not deststateeid:
+ msg = session._('mandatory relation')
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ deststate = wf.state_by_eid(deststateeid)
+ if deststate is None:
+ msg = session._("state doesn't belong to entity's workflow")
+ raise ValidationError(entity.eid, {'to_state': msg})
+ else:
+ # check transition is valid and allowed, unless we're coming back
+ # from subworkflow
+ tr = session.entity_from_eid(treid)
+ if swtr is None:
+ if tr is None:
+ msg = session._("transition doesn't belong to entity's workflow")
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ if not tr.has_input_state(fromstate):
+ msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
+ 'tr': session._(tr.name), 'st': session._(fromstate.name)}
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ if not tr.may_be_fired(foreid):
+ msg = session._("transition may not be fired")
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ if entity.get('to_state'):
+ deststateeid = entity['to_state']
+ if not cowpowers and deststateeid != tr.destination().eid:
+ msg = session._("transition isn't allowed")
+ raise ValidationError(entity.eid, {'by_transition': msg})
+ if swtr is None:
+ deststate = session.entity_from_eid(deststateeid)
+ if not cowpowers and deststate is None:
+ msg = session._("state doesn't belong to entity's workflow")
+ raise ValidationError(entity.eid, {'to_state': msg})
+ else:
+ deststateeid = tr.destination().eid
+ # everything is ok, add missing information on the trinfo entity
+ entity['from_state'] = fromstate.eid
+ entity['to_state'] = deststateeid
+ nocheck = session.transaction_data.setdefault('skip-security', set())
+ nocheck.add((entity.eid, 'from_state', fromstate.eid))
+ nocheck.add((entity.eid, 'to_state', deststateeid))
+ _FireAutotransitionOp(session, entity=forentity)
+
+
+class FiredTransitionHook(WorkflowHook):
+ """change related entity state"""
+ __regid__ = 'wffiretransition'
+ __select__ = WorkflowHook.__select__ & implements('TrInfo')
+ events = ('after_add_entity',)
+
+ def __call__(self):
+ trinfo = self.entity
+ _change_state(self._cw, trinfo['wf_info_for'],
+ trinfo['from_state'], trinfo['to_state'])
+ forentity = self._cw.entity_from_eid(trinfo['wf_info_for'])
+ assert forentity.current_state.eid == trinfo['to_state']
+ if forentity.main_workflow.eid != forentity.current_workflow.eid:
+ _SubWorkflowExitOp(self._cw, forentity=forentity, trinfo=trinfo)
+
+
+class CheckInStateChangeAllowed(WorkflowHook):
+ """check state apply, in case of direct in_state change using unsafe_execute
+ """
+ __regid__ = 'wfcheckinstate'
+ __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+ events = ('before_add_relation',)
+
+ def __call__(self):
+ session = self._cw
+ nocheck = session.transaction_data.get('skip-security', ())
+ if (self.eidfrom, 'in_state', self.eidto) in nocheck:
+ # state changed through TrInfo insertion, so we already know it's ok
+ return
+ entity = session.entity_from_eid(self.eidfrom)
+ mainwf = entity.main_workflow
+ if mainwf is None:
+ msg = session._('entity has no workflow set')
+ raise ValidationError(entity.eid, {None: msg})
+ for wf in mainwf.iter_workflows():
+ if wf.state_by_eid(self.eidto):
+ break
+ else:
+ msg = session._("state doesn't belong to entity's workflow. You may "
+ "want to set a custom workflow for this entity first.")
+ raise ValidationError(self.eidfrom, {'in_state': msg})
+ if entity.current_workflow and wf.eid != entity.current_workflow.eid:
+ msg = session._("state doesn't belong to entity's current workflow")
+ raise ValidationError(self.eidfrom, {'in_state': msg})
+
+
+class SetModificationDateOnStateChange(WorkflowHook):
+ """update entity's modification date after changing its state"""
+ __regid__ = 'wfsyncmdate'
+ __select__ = WorkflowHook.__select__ & hook.match_rtype('in_state')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ if self._cw.added_in_transaction(self.eidfrom):
+ # new entity, not needed
+ return
+ entity = self._cw.entity_from_eid(self.eidfrom)
+ try:
+ entity.set_attributes(modification_date=datetime.now(),
+ _cw_unsafe=True)
+ except RepositoryError, ex:
+ # usually occurs if entity is coming from a read-only source
+ # (eg ldap user)
+ self.warning('cant change modification date for %s: %s', entity, ex)
+
+
+class CheckWorkflowTransitionExitPoint(WorkflowHook):
+ """check that there is no multiple exits from the same state"""
+ __regid__ = 'wfcheckwftrexit'
+ __select__ = WorkflowHook.__select__ & hook.match_rtype('subworkflow_exit')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ _CheckTrExitPoint(self._cw, treid=self.eidfrom)
+
+
+class SetCustomWorkflow(WorkflowHook):
+ __regid__ = 'wfsetcustom'
+ __select__ = WorkflowHook.__select__ & hook.match_rtype('custom_workflow')
+ events = ('after_add_relation',)
+
+ def __call__(self):
+ _WorkflowChangedOp(self._cw, eid=self.eidfrom, wfeid=self.eidto)
+
+
+class DelCustomWorkflow(SetCustomWorkflow):
+ __regid__ = 'wfdelcustom'
+ events = ('after_delete_relation',)
+
+ def __call__(self):
+ entity = self._cw.entity_from_eid(self.eidfrom)
+ typewf = entity.cwetype_workflow()
+ if typewf is not None:
+ _WorkflowChangedOp(self._cw, eid=self.eidfrom, wfeid=typewf.eid)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,99 @@
+"""Some i18n/gettext utilities.
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import re
+import os
+import sys
+from os.path import join, basename, splitext, exists
+from glob import glob
+
+from cubicweb.toolsutils import create_dir
+
+def extract_from_tal(files, output_file):
+ """extract i18n strings from tal and write them into the given output file
+ using standard python gettext marker (_)
+ """
+ output = open(output_file, 'w')
+ for filepath in files:
+ for match in re.finditer('i18n:(content|replace)="([^"]+)"', open(filepath).read()):
+ print >> output, '_("%s")' % match.group(2)
+ output.close()
+
+
+def add_msg(w, msgid, msgctx=None):
+ """write an empty pot msgid definition"""
+ if isinstance(msgid, unicode):
+ msgid = msgid.encode('utf-8')
+ if msgctx:
+ if isinstance(msgctx, unicode):
+ msgctx = msgctx.encode('utf-8')
+ w('msgctxt "%s"\n' % msgctx)
+ msgid = msgid.replace('"', r'\"').splitlines()
+ if len(msgid) > 1:
+ w('msgid ""\n')
+ for line in msgid:
+ w('"%s"' % line.replace('"', r'\"'))
+ else:
+ w('msgid "%s"\n' % msgid[0])
+ w('msgstr ""\n\n')
+
+
+def execute(cmd):
+ """display the command, execute it and raise an Exception if returned
+ status != 0
+ """
+ from subprocess import call
+ print cmd.replace(os.getcwd() + os.sep, '')
+ status = call(cmd, shell=True)
+ if status != 0:
+ raise Exception('status = %s' % status)
+
+
+def available_catalogs(i18ndir=None):
+ if i18ndir is None:
+ wildcard = '*.po'
+ else:
+ wildcard = join(i18ndir, '*.po')
+ for popath in glob(wildcard):
+ lang = splitext(basename(popath))[0]
+ yield lang, popath
+
+
+def compile_i18n_catalogs(sourcedirs, destdir, langs):
+ """generate .mo files for a set of languages into the `destdir` i18n directory
+ """
+ from logilab.common.fileutils import ensure_fs_mode
+ print '-> compiling %s catalogs...' % destdir
+ errors = []
+ for lang in langs:
+ langdir = join(destdir, lang, 'LC_MESSAGES')
+ if not exists(langdir):
+ create_dir(langdir)
+ pofiles = [join(path, '%s.po' % lang) for path in sourcedirs]
+ pofiles = [pof for pof in pofiles if exists(pof)]
+ mergedpo = join(destdir, '%s_merged.po' % lang)
+ try:
+ # merge instance/cubes messages catalogs with the stdlib's one
+ execute('msgcat --use-first --sort-output --strict -o "%s" %s'
+ % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+ # make sure the .mo file is writeable and compiles with *msgfmt*
+ applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
+ try:
+ ensure_fs_mode(applmo)
+ except OSError:
+ pass # suppose not exists
+ execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
+ except Exception, ex:
+ errors.append('while handling language %s: %s' % (lang, ex))
+ try:
+ # clean everything
+ os.unlink(mergedpo)
+ except Exception:
+ continue
+ return errors
--- a/i18n/en.po Mon Feb 08 13:33:09 2010 +0100
+++ b/i18n/en.po Mon Feb 08 13:35:07 2010 +0100
@@ -47,10 +47,6 @@
msgstr ""
#, python-format
-msgid "%(fmt1)s, or without time: %(fmt2)s"
-msgstr ""
-
-#, python-format
msgid "%(subject)s %(etype)s #%(eid)s (%(login)s)"
msgstr ""
@@ -119,10 +115,6 @@
msgstr ""
#, python-format
-msgid "%s results matching query"
-msgstr ""
-
-#, python-format
msgid "%s software version of the database"
msgstr ""
@@ -265,6 +257,14 @@
msgid "CWEType"
msgstr "Entity type"
+msgctxt "inlined:CWRelation.from_entity.subject"
+msgid "CWEType"
+msgstr "Entity type"
+
+msgctxt "inlined:CWRelation.to_entity.subject"
+msgid "CWEType"
+msgstr "Entity type"
+
msgid "CWEType_plural"
msgstr "Entity types"
@@ -289,6 +289,10 @@
msgid "CWRType"
msgstr "Relation type"
+msgctxt "inlined:CWRelation.relation_type.subject"
+msgid "CWRType"
+msgstr "Relation type"
+
msgid "CWRType_plural"
msgstr "Relation types"
@@ -316,10 +320,6 @@
msgid "Datetime_plural"
msgstr "Dates and times"
-#, python-format
-msgid "Debug level set to %s"
-msgstr ""
-
msgid "Decimal"
msgstr "Decimal number"
@@ -335,6 +335,10 @@
msgid "Download page as pdf"
msgstr ""
+msgctxt "inlined:CWUser.use_email.subject"
+msgid "EmailAddress"
+msgstr ""
+
msgid "EmailAddress"
msgstr "Email address"
@@ -429,7 +433,7 @@
msgstr "New email address"
msgid "New ExternalUri"
-msgstr ""
+msgstr "New external URI"
msgid "New RQLExpression"
msgstr "New RQL expression"
@@ -452,9 +456,6 @@
msgid "New WorkflowTransition"
msgstr "New workflow-transition"
-msgid "No query has been executed"
-msgstr ""
-
msgid "No result matching query"
msgstr ""
@@ -584,14 +585,6 @@
msgid "This CWEType"
msgstr "This entity type"
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "This CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "This CWEType"
-msgstr ""
-
msgid "This CWGroup"
msgstr "This group"
@@ -604,25 +597,17 @@
msgid "This CWRType"
msgstr "This relation type"
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "This CWRType"
-msgstr "This relation type"
-
msgid "This CWRelation"
msgstr "This relation"
msgid "This CWUser"
msgstr "This user"
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "This EmailAddress"
-msgstr "email address"
-
msgid "This EmailAddress"
msgstr "This email address"
msgid "This ExternalUri"
-msgstr ""
+msgstr "This external URI"
msgid "This RQLExpression"
msgstr "This RQL expression"
@@ -663,10 +648,6 @@
msgid "Transition_plural"
msgstr "Transitions"
-#, python-format
-msgid "Unable to find anything named \"%s\" in the schema !"
-msgstr ""
-
msgid "UniqueConstraint"
msgstr "unique constraint"
@@ -694,13 +675,6 @@
msgid "Workflow_plural"
msgstr "Workflows"
-msgid "You are not connected to an instance !"
-msgstr ""
-
-#, python-format
-msgid "You are now connected to %s"
-msgstr ""
-
msgid ""
"You can either submit a new file using the browse button above, or choose to "
"remove already uploaded file by checking the \"detach attached file\" check-"
@@ -738,6 +712,9 @@
msgid "a URI representing an object in external data store"
msgstr ""
+msgid "a float is expected"
+msgstr ""
+
msgid ""
"a simple cache entity characterized by a name and a validity date. The "
"target application is responsible for updating timestamp when necessary to "
@@ -820,6 +797,12 @@
msgid "actions_embed_description"
msgstr ""
+msgid "actions_entitiesoftype"
+msgstr ""
+
+msgid "actions_entitiesoftype_description"
+msgstr ""
+
msgid "actions_follow"
msgstr "follow"
@@ -934,9 +917,18 @@
msgid "add Bookmark bookmarked_by CWUser object"
msgstr "bookmark"
+msgid "add CWAttribute add_permission RQLExpression subject"
+msgstr "add rql expression"
+
msgid "add CWAttribute constrained_by CWConstraint subject"
msgstr "constraint"
+msgid "add CWAttribute delete_permission RQLExpression subject"
+msgstr "delete rql expression"
+
+msgid "add CWAttribute read_permission RQLExpression subject"
+msgstr "read rql expression"
+
msgid "add CWAttribute relation_type CWRType object"
msgstr "attribute definition"
@@ -955,18 +947,18 @@
msgid "add CWProperty for_user CWUser object"
msgstr "property"
-msgid "add CWRType add_permission RQLExpression subject"
-msgstr "rql expression for the add permission"
-
-msgid "add CWRType delete_permission RQLExpression subject"
-msgstr "rql expression for the delete permission"
-
-msgid "add CWRType read_permission RQLExpression subject"
-msgstr "rql expression for the read permission"
+msgid "add CWRelation add_permission RQLExpression subject"
+msgstr "add rql expression"
msgid "add CWRelation constrained_by CWConstraint subject"
msgstr "constraint"
+msgid "add CWRelation delete_permission RQLExpression subject"
+msgstr "delete rql expression"
+
+msgid "add CWRelation read_permission RQLExpression subject"
+msgstr "read rql expression"
+
msgid "add CWRelation relation_type CWRType object"
msgstr "relation definition"
@@ -1028,9 +1020,6 @@
msgid "add a new permission"
msgstr ""
-msgid "add relation"
-msgstr ""
-
msgid "add_perm"
msgstr "add permission"
@@ -1045,7 +1034,11 @@
msgid "add_permission"
msgstr "add permission"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
+msgid "add_permission"
+msgstr "add permission"
+
+msgctxt "CWRelation"
msgid "add_permission"
msgstr "add permission"
@@ -1066,8 +1059,8 @@
#, python-format
msgid ""
-"added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
+"added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
msgstr ""
msgid "addrelated"
@@ -1142,6 +1135,9 @@
msgid "an error occured, the request cannot be fulfilled"
msgstr ""
+msgid "an integer is expected"
+msgstr ""
+
msgid "and linked"
msgstr ""
@@ -1280,9 +1276,6 @@
msgid "button_ok"
msgstr "validate"
-msgid "button_reset"
-msgstr "reset"
-
msgid "by"
msgstr ""
@@ -1329,6 +1322,12 @@
msgid "can not resolve entity types:"
msgstr ""
+msgid "can't be changed"
+msgstr ""
+
+msgid "can't be deleted"
+msgstr ""
+
#, python-format
msgid "can't change the %s attribute"
msgstr ""
@@ -1345,6 +1344,10 @@
msgstr ""
#, python-format
+msgid "can't parse %(value)r (expected %(format)s)"
+msgstr ""
+
+#, python-format
msgid ""
"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
@@ -1421,6 +1424,12 @@
msgid "components_etypenavigation_description"
msgstr "permit to filter search results by entity type"
+msgid "components_help"
+msgstr "help button"
+
+msgid "components_help_description"
+msgstr "the help button on the top right-hand corner"
+
msgid "components_loggeduserlink"
msgstr "user link"
@@ -1525,6 +1534,12 @@
msgid "contentnavigation_metadata_description"
msgstr ""
+msgid "contentnavigation_pdfview"
+msgstr ""
+
+msgid "contentnavigation_pdfview_description"
+msgstr ""
+
msgid "contentnavigation_prevnext"
msgstr "previous / next entity"
@@ -1541,12 +1556,6 @@
"section containing entities related by the \"see also\" relation on entities "
"supporting it."
-msgid "contentnavigation_view_page_as_pdf"
-msgstr "icon to display page as pdf"
-
-msgid "contentnavigation_view_page_as_pdf_description"
-msgstr ""
-
msgid "contentnavigation_wfhistory"
msgstr "workflow history"
@@ -1568,9 +1577,6 @@
msgid "copy"
msgstr ""
-msgid "copy edition"
-msgstr ""
-
msgid ""
"core relation giving to a group the permission to add an entity or relation "
"type"
@@ -1655,6 +1661,19 @@
msgstr "creating email address for user %(linkto)s"
msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
+msgstr "RQL expression granting add permission on %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr "RQL expression granting delete permission on %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr "RQL expression granting read permission on %(linkto)s"
+
+msgid ""
"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
msgstr "creating rql expression for add permission on %(linkto)s"
@@ -1671,16 +1690,17 @@
msgstr "creating rql expression for update permission on %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
-msgstr "creating rql expression for add permission on relations %(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s add_permission RQLExpression)"
+msgstr "RQL expression granting add permission on %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
-msgstr "creating rql expression for delete permission on relations %(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr "RQL expression granting delete permission on %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
-msgstr "creating rql expression for read permission on relations %(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s read_permission RQLExpression)"
+msgstr "RQL expression granting read permission on %(linkto)s"
msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
msgstr "creating rql expression for transition %(linkto)s"
@@ -1755,10 +1775,6 @@
msgid "ctxtoolbar"
msgstr "toolbar"
-#, python-format
-msgid "currently attached file: %s"
-msgstr ""
-
msgid "custom_workflow"
msgstr "custom workflow"
@@ -1847,7 +1863,7 @@
msgid "define a relation type, used to build the instance schema"
msgstr ""
-msgid "define a rql expression used to define permissions"
+msgid "define a rql expression used to define __permissions__"
msgstr ""
msgid "define a schema constraint"
@@ -1889,9 +1905,13 @@
msgid "delete_permission"
msgstr "delete permission"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
msgid "delete_permission"
-msgstr "delete permission"
+msgstr ""
+
+msgctxt "CWRelation"
+msgid "delete_permission"
+msgstr ""
msgctxt "CWGroup"
msgid "delete_permission_object"
@@ -1910,8 +1930,8 @@
#, python-format
msgid ""
-"deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
+"deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
msgstr ""
msgid "depends on the constraint type"
@@ -2030,10 +2050,6 @@
msgid "detach attached file"
msgstr ""
-#, python-format
-msgid "detach attached file %s"
-msgstr ""
-
msgid "display order of the action"
msgstr ""
@@ -2088,15 +2104,6 @@
msgid "eid"
msgstr ""
-msgid "element copied"
-msgstr ""
-
-msgid "element created"
-msgstr ""
-
-msgid "element edited"
-msgstr ""
-
msgid "email address to use for notification"
msgstr ""
@@ -2432,6 +2439,12 @@
msgid "how to format time in the ui (\"man strftime\" for format description)"
msgstr ""
+msgid "i18n_bookmark_url_fqs"
+msgstr ""
+
+msgid "i18n_bookmark_url_path"
+msgstr ""
+
msgid "i18n_login_popup"
msgstr "login"
@@ -2572,15 +2585,6 @@
msgid "invalid action %r"
msgstr ""
-msgid "invalid date"
-msgstr ""
-
-msgid "invalid float value"
-msgstr ""
-
-msgid "invalid integer value"
-msgstr ""
-
msgid "is"
msgstr ""
@@ -2878,6 +2882,10 @@
msgid "no associated permissions"
msgstr ""
+#, python-format
+msgid "no edited fields specified for entity %s"
+msgstr ""
+
msgid "no related project"
msgstr ""
@@ -2900,9 +2908,6 @@
msgid "not selected"
msgstr ""
-msgid "nothing to edit"
-msgstr ""
-
msgid "november"
msgstr ""
@@ -3078,9 +3083,13 @@
msgid "read_permission"
msgstr "read permission"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
msgid "read_permission"
-msgstr "read permission"
+msgstr ""
+
+msgctxt "CWRelation"
+msgid "read_permission"
+msgstr ""
msgctxt "CWGroup"
msgid "read_permission_object"
@@ -3133,20 +3142,7 @@
msgid "relative url of the bookmarked page"
msgstr ""
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "remove this CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "remove this CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "remove this CWRType"
-msgstr ""
-
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "remove this EmailAddress"
+msgid "remove-inlined-entity-form"
msgstr ""
msgid "require_group"
@@ -3486,11 +3482,11 @@
msgid "surname"
msgstr ""
-msgid "symetric"
+msgid "symmetric"
msgstr ""
msgctxt "CWRType"
-msgid "symetric"
+msgid "symmetric"
msgstr ""
msgid "system entities"
@@ -3817,11 +3813,6 @@
"\n"
msgstr ""
-msgid ""
-"user for which this property is applying. If this relation is not set, the "
-"property is considered as a global property"
-msgstr ""
-
msgid "user interface encoding"
msgstr ""
@@ -3951,6 +3942,10 @@
msgid "workflow_of_object"
msgstr ""
+#, python-format
+msgid "wrong query parameter line %s"
+msgstr ""
+
msgid "xbel"
msgstr ""
@@ -3968,25 +3963,3 @@
msgid "you should probably delete that property"
msgstr ""
-
-#~ msgid "components_help"
-#~ msgstr "help button"
-
-#~ msgid "components_help_description"
-#~ msgstr "the help button on the top right-hand corner"
-
-#~ msgctxt "inlined:CWRelation:from_entity:subject"
-#~ msgid "remove this CWEType"
-#~ msgstr "remove this entity type"
-
-#~ msgctxt "inlined:CWRelation:to_entity:subject"
-#~ msgid "remove this CWEType"
-#~ msgstr "remove this entity type"
-
-#~ msgctxt "inlined:CWRelation:relation_type:subject"
-#~ msgid "remove this CWRType"
-#~ msgstr "remove this relation type"
-
-#~ msgctxt "inlined:CWUser:use_email:subject"
-#~ msgid "remove this EmailAddress"
-#~ msgstr "remove this email address"
--- a/i18n/es.po Mon Feb 08 13:33:09 2010 +0100
+++ b/i18n/es.po Mon Feb 08 13:35:07 2010 +0100
@@ -52,10 +52,6 @@
msgstr "%(firstname)s %(surname)s"
#, python-format
-msgid "%(fmt1)s, or without time: %(fmt2)s"
-msgstr "%(fmt1)s, o bien sin especificar horario: %(fmt2)s"
-
-#, python-format
msgid "%(subject)s %(etype)s #%(eid)s (%(login)s)"
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
@@ -124,10 +120,6 @@
msgstr "%s no estimado(s)"
#, python-format
-msgid "%s results matching query"
-msgstr "%s resultados de la demanda"
-
-#, python-format
msgid "%s software version of the database"
msgstr "versión sistema de la base para %s"
@@ -273,6 +265,14 @@
msgid "CWEType"
msgstr "Tipo de entidad"
+msgctxt "inlined:CWRelation.from_entity.subject"
+msgid "CWEType"
+msgstr ""
+
+msgctxt "inlined:CWRelation.to_entity.subject"
+msgid "CWEType"
+msgstr ""
+
msgid "CWEType_plural"
msgstr "Tipos de entidades"
@@ -297,6 +297,10 @@
msgid "CWRType"
msgstr "Tipo de relación"
+msgctxt "inlined:CWRelation.relation_type.subject"
+msgid "CWRType"
+msgstr ""
+
msgid "CWRType_plural"
msgstr "Tipos de relación"
@@ -324,10 +328,6 @@
msgid "Datetime_plural"
msgstr "Fechas y horas"
-#, python-format
-msgid "Debug level set to %s"
-msgstr "Nivel de debug puesto a %s"
-
msgid "Decimal"
msgstr "Decimal"
@@ -343,6 +343,10 @@
msgid "Download page as pdf"
msgstr ""
+msgctxt "inlined:CWUser.use_email.subject"
+msgid "EmailAddress"
+msgstr ""
+
msgid "EmailAddress"
msgstr "Correo Electrónico"
@@ -460,9 +464,6 @@
msgid "New WorkflowTransition"
msgstr ""
-msgid "No query has been executed"
-msgstr "Ninguna búsqueda ha sido ejecutada"
-
msgid "No result matching query"
msgstr "Ningún resultado corresponde a su búsqueda"
@@ -592,14 +593,6 @@
msgid "This CWEType"
msgstr "Este tipo de Entidad"
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "This CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "This CWEType"
-msgstr ""
-
msgid "This CWGroup"
msgstr "Este grupo"
@@ -612,20 +605,12 @@
msgid "This CWRType"
msgstr "Este tipo de relación"
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "This CWRType"
-msgstr ""
-
msgid "This CWRelation"
msgstr "Esta definición de relación no final"
msgid "This CWUser"
msgstr "Este usuario"
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "This EmailAddress"
-msgstr ""
-
msgid "This EmailAddress"
msgstr "Esta dirección electrónica"
@@ -671,10 +656,6 @@
msgid "Transition_plural"
msgstr "Transiciones"
-#, python-format
-msgid "Unable to find anything named \"%s\" in the schema !"
-msgstr "No encontramos el nombre \"%s\" en el esquema"
-
msgid "UniqueConstraint"
msgstr ""
@@ -702,13 +683,6 @@
msgid "Workflow_plural"
msgstr ""
-msgid "You are not connected to an instance !"
-msgstr ""
-
-#, python-format
-msgid "You are now connected to %s"
-msgstr "Usted esta conectado a %s"
-
msgid ""
"You can either submit a new file using the browse button above, or choose to "
"remove already uploaded file by checking the \"detach attached file\" check-"
@@ -761,6 +735,9 @@
msgid "a URI representing an object in external data store"
msgstr ""
+msgid "a float is expected"
+msgstr ""
+
msgid ""
"a simple cache entity characterized by a name and a validity date. The "
"target application is responsible for updating timestamp when necessary to "
@@ -843,6 +820,12 @@
msgid "actions_embed_description"
msgstr ""
+msgid "actions_entitiesoftype"
+msgstr ""
+
+msgid "actions_entitiesoftype_description"
+msgstr ""
+
msgid "actions_follow"
msgstr "Seguir"
@@ -957,9 +940,18 @@
msgid "add Bookmark bookmarked_by CWUser object"
msgstr "Agregar a los favoritos "
+msgid "add CWAttribute add_permission RQLExpression subject"
+msgstr ""
+
msgid "add CWAttribute constrained_by CWConstraint subject"
msgstr "Restricción"
+msgid "add CWAttribute delete_permission RQLExpression subject"
+msgstr ""
+
+msgid "add CWAttribute read_permission RQLExpression subject"
+msgstr ""
+
msgid "add CWAttribute relation_type CWRType object"
msgstr "Definición de atributo"
@@ -978,18 +970,18 @@
msgid "add CWProperty for_user CWUser object"
msgstr "Propiedad"
-msgid "add CWRType add_permission RQLExpression subject"
-msgstr "Expresión RQL de agregación"
-
-msgid "add CWRType delete_permission RQLExpression subject"
-msgstr "Expresión RQL de eliminación"
-
-msgid "add CWRType read_permission RQLExpression subject"
-msgstr "Expresión RQL de lectura"
+msgid "add CWRelation add_permission RQLExpression subject"
+msgstr ""
msgid "add CWRelation constrained_by CWConstraint subject"
msgstr "Restricción"
+msgid "add CWRelation delete_permission RQLExpression subject"
+msgstr ""
+
+msgid "add CWRelation read_permission RQLExpression subject"
+msgstr ""
+
msgid "add CWRelation relation_type CWRType object"
msgstr "Definición de relación"
@@ -1051,9 +1043,6 @@
msgid "add a new permission"
msgstr "Agregar una autorización"
-msgid "add relation"
-msgstr "Agregar una relación"
-
msgid "add_perm"
msgstr "Agregado"
@@ -1068,7 +1057,11 @@
msgid "add_permission"
msgstr ""
-msgctxt "CWRType"
+msgctxt "CWAttribute"
+msgid "add_permission"
+msgstr ""
+
+msgctxt "CWRelation"
msgid "add_permission"
msgstr ""
@@ -1089,11 +1082,9 @@
#, python-format
msgid ""
-"added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
-msgstr ""
-"Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)s #"
-"%(toeid)s"
+"added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
+msgstr ""
msgid "addrelated"
msgstr ""
@@ -1167,6 +1158,9 @@
msgid "an error occured, the request cannot be fulfilled"
msgstr "un error ha ocurrido, la búsqueda no ha podido ser realizada"
+msgid "an integer is expected"
+msgstr ""
+
msgid "and linked"
msgstr "y ligada"
@@ -1308,9 +1302,6 @@
msgid "button_ok"
msgstr "Validar"
-msgid "button_reset"
-msgstr "Cancelar los cambios"
-
msgid "by"
msgstr "por"
@@ -1357,6 +1348,12 @@
msgid "can not resolve entity types:"
msgstr ""
+msgid "can't be changed"
+msgstr ""
+
+msgid "can't be deleted"
+msgstr ""
+
#, python-format
msgid "can't change the %s attribute"
msgstr "no puede modificar el atributo %s"
@@ -1373,6 +1370,10 @@
msgstr ""
#, python-format
+msgid "can't parse %(value)r (expected %(format)s)"
+msgstr ""
+
+#, python-format
msgid ""
"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
@@ -1453,6 +1454,12 @@
msgid "components_etypenavigation_description"
msgstr "Permite filtrar por tipo de entidad los resultados de búsqueda"
+msgid "components_help"
+msgstr "Botón de ayuda"
+
+msgid "components_help_description"
+msgstr "El botón de ayuda, en el encabezado de página"
+
msgid "components_loggeduserlink"
msgstr "Liga usuario"
@@ -1558,6 +1565,12 @@
msgid "contentnavigation_metadata_description"
msgstr ""
+msgid "contentnavigation_pdfview"
+msgstr ""
+
+msgid "contentnavigation_pdfview_description"
+msgstr ""
+
msgid "contentnavigation_prevnext"
msgstr "Elemento anterior / siguiente"
@@ -1574,12 +1587,6 @@
"sección que muestra las entidades ligadas por la relación \"vea también\" , "
"si la entidad soporta esta relación."
-msgid "contentnavigation_view_page_as_pdf"
-msgstr ""
-
-msgid "contentnavigation_view_page_as_pdf_description"
-msgstr ""
-
msgid "contentnavigation_wfhistory"
msgstr "Histórico del workflow."
@@ -1603,9 +1610,6 @@
msgid "copy"
msgstr "Copiar"
-msgid "copy edition"
-msgstr "Edición de una copia"
-
msgid ""
"core relation giving to a group the permission to add an entity or relation "
"type"
@@ -1704,6 +1708,19 @@
msgstr "Creación de una dirección electrónica para el usuario %(linkto)s"
msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
+msgstr ""
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr ""
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr ""
+
+msgid ""
"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
msgstr ""
"Creación de una expresión RQL para la autorización de agregar %(linkto)s"
@@ -1722,22 +1739,17 @@
msgstr "Creación de una expresión RQL para autorizar actualizar %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
-msgstr ""
-"Creación de una expresión RQL para la autorización de agregar relaciones %"
-"(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s add_permission RQLExpression)"
+msgstr ""
msgid ""
-"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
-msgstr ""
-"creación de una expresión RQL para autorizar la eliminación de relaciones %"
-"(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr ""
msgid ""
-"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
-msgstr ""
-"Creación de una expresión RQL para autorizar la lectura de relaciones %"
-"(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s read_permission RQLExpression)"
+msgstr ""
msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
msgstr "Creación de una expresión RQL para la transición %(linkto)s"
@@ -1812,10 +1824,6 @@
msgid "ctxtoolbar"
msgstr ""
-#, python-format
-msgid "currently attached file: %s"
-msgstr "archivo adjunto: %s"
-
msgid "custom_workflow"
msgstr ""
@@ -1904,8 +1912,8 @@
msgid "define a relation type, used to build the instance schema"
msgstr ""
-msgid "define a rql expression used to define permissions"
-msgstr "Expresión RQL utilizada para definir los derechos de acceso"
+msgid "define a rql expression used to define __permissions__"
+msgstr ""
msgid "define a schema constraint"
msgstr "Define una condición de esquema"
@@ -1948,7 +1956,11 @@
msgid "delete_permission"
msgstr ""
-msgctxt "CWRType"
+msgctxt "CWAttribute"
+msgid "delete_permission"
+msgstr ""
+
+msgctxt "CWRelation"
msgid "delete_permission"
msgstr ""
@@ -1969,11 +1981,9 @@
#, python-format
msgid ""
-"deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
-msgstr ""
-"Eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
-"(toetype)s #%(toeid)s"
+"deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
+msgstr ""
msgid "depends on the constraint type"
msgstr "Depende del tipo de condición"
@@ -2091,10 +2101,6 @@
msgid "detach attached file"
msgstr "soltar el archivo existente"
-#, python-format
-msgid "detach attached file %s"
-msgstr "Quitar archivo adjunto %s"
-
msgid "display order of the action"
msgstr "Orden de aparición de la acción"
@@ -2151,15 +2157,6 @@
msgid "eid"
msgstr "eid"
-msgid "element copied"
-msgstr "Elemento copiado"
-
-msgid "element created"
-msgstr "Elemento creado"
-
-msgid "element edited"
-msgstr "Elemento editado"
-
msgid "email address to use for notification"
msgstr "Dirección electrónica a utilizarse para notificar"
@@ -2506,6 +2503,12 @@
"Como formatear la hora en la interface (\"man strftime\" por la descripción "
"del formato)"
+msgid "i18n_bookmark_url_fqs"
+msgstr ""
+
+msgid "i18n_bookmark_url_path"
+msgstr ""
+
msgid "i18n_login_popup"
msgstr "Identificarse"
@@ -2649,15 +2652,6 @@
msgid "invalid action %r"
msgstr "Acción %r invalida"
-msgid "invalid date"
-msgstr "Esta fecha no es válida"
-
-msgid "invalid float value"
-msgstr ""
-
-msgid "invalid integer value"
-msgstr ""
-
msgid "is"
msgstr "es"
@@ -2970,6 +2964,10 @@
msgid "no associated permissions"
msgstr "no autorización relacionada"
+#, python-format
+msgid "no edited fields specified for entity %s"
+msgstr ""
+
msgid "no related project"
msgstr "no hay proyecto relacionado"
@@ -2992,9 +2990,6 @@
msgid "not selected"
msgstr "no seleccionado"
-msgid "nothing to edit"
-msgstr "nada que editar"
-
msgid "november"
msgstr "noviembre"
@@ -3169,7 +3164,11 @@
msgid "read_permission"
msgstr ""
-msgctxt "CWRType"
+msgctxt "CWAttribute"
+msgid "read_permission"
+msgstr ""
+
+msgctxt "CWRelation"
msgid "read_permission"
msgstr ""
@@ -3224,20 +3223,7 @@
msgid "relative url of the bookmarked page"
msgstr "Url relativa de la pagina"
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "remove this CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "remove this CWEType"
-msgstr ""
-
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "remove this CWRType"
-msgstr ""
-
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "remove this EmailAddress"
+msgid "remove-inlined-entity-form"
msgstr ""
msgid "require_group"
@@ -3585,11 +3571,11 @@
msgid "surname"
msgstr ""
-msgid "symetric"
+msgid "symmetric"
msgstr "simetrico"
msgctxt "CWRType"
-msgid "symetric"
+msgid "symmetric"
msgstr ""
msgid "system entities"
@@ -3924,13 +3910,6 @@
"el usuario %s ha efectuado los siguentes cambios:\n"
"\n"
-msgid ""
-"user for which this property is applying. If this relation is not set, the "
-"property is considered as a global property"
-msgstr ""
-"usuario para el cual aplica esta propiedad. Si no se establece esta "
-"relación, la propiedad es considerada como una propiedad global."
-
msgid "user interface encoding"
msgstr "codificación de la interfaz de usuario"
@@ -4060,6 +4039,10 @@
msgid "workflow_of_object"
msgstr ""
+#, python-format
+msgid "wrong query parameter line %s"
+msgstr ""
+
msgid "xbel"
msgstr "xbel"
@@ -4078,9 +4061,27 @@
msgid "you should probably delete that property"
msgstr "deberia probablamente suprimir esta propriedad"
+#~ msgid "%(fmt1)s, or without time: %(fmt2)s"
+#~ msgstr "%(fmt1)s, o bien sin especificar horario: %(fmt2)s"
+
+#~ msgid "%s results matching query"
+#~ msgstr "%s resultados de la demanda"
+
+#~ msgid "Debug level set to %s"
+#~ msgstr "Nivel de debug puesto a %s"
+
+#~ msgid "No query has been executed"
+#~ msgstr "Ninguna búsqueda ha sido ejecutada"
+
#~ msgid "There is no workflow defined for this entity."
#~ msgstr "No hay workflow para este entidad"
+#~ msgid "Unable to find anything named \"%s\" in the schema !"
+#~ msgstr "No encontramos el nombre \"%s\" en el esquema"
+
+#~ msgid "You are now connected to %s"
+#~ msgstr "Usted esta conectado a %s"
+
#~ msgid ""
#~ "You have no access to this view or it's not applyable to current data"
#~ msgstr "No tiene acceso a esta vista o No es aplicable a los datos actuales"
@@ -4091,6 +4092,15 @@
#~ msgid "account state"
#~ msgstr "Estado de la Cuenta"
+#~ msgid "add CWRType add_permission RQLExpression subject"
+#~ msgstr "Expresión RQL de agregación"
+
+#~ msgid "add CWRType delete_permission RQLExpression subject"
+#~ msgstr "Expresión RQL de eliminación"
+
+#~ msgid "add CWRType read_permission RQLExpression subject"
+#~ msgstr "Expresión RQL de lectura"
+
#~ msgid "add State state_of CWEType object"
#~ msgstr "Estado"
@@ -4148,17 +4158,46 @@
#~ msgid "add a Transition"
#~ msgstr "Agregar una transición"
+#~ msgid "add relation"
+#~ msgstr "Agregar una relación"
+
+#~ msgid ""
+#~ "added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
+#~ "(toeid)s"
+#~ msgstr ""
+#~ "Relación agregada %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)"
+#~ "s #%(toeid)s"
+
+#~ msgid "button_reset"
+#~ msgstr "Cancelar los cambios"
+
#~ msgid "canonical"
#~ msgstr "canónico"
#~ msgid "comment:"
#~ msgstr "Comentario:"
-#~ msgid "components_help"
-#~ msgstr "Botón de ayuda"
-
-#~ msgid "components_help_description"
-#~ msgstr "El botón de ayuda, en el encabezado de página"
+#~ msgid "copy edition"
+#~ msgstr "Edición de una copia"
+
+#~ msgid ""
+#~ "creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
+#~ msgstr ""
+#~ "Creación de una expresión RQL para la autorización de agregar relaciones %"
+#~ "(linkto)s"
+
+#~ msgid ""
+#~ "creating RQLExpression (CWRType %(linkto)s delete_permission "
+#~ "RQLExpression)"
+#~ msgstr ""
+#~ "creación de una expresión RQL para autorizar la eliminación de relaciones "
+#~ "%(linkto)s"
+
+#~ msgid ""
+#~ "creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
+#~ msgstr ""
+#~ "Creación de una expresión RQL para autorizar la lectura de relaciones %"
+#~ "(linkto)s"
#~ msgid "creating State (State state_of CWEType %(linkto)s)"
#~ msgstr "Creación de un estado por el tipo %(linkto)s"
@@ -4166,6 +4205,31 @@
#~ msgid "creating Transition (Transition transition_of CWEType %(linkto)s)"
#~ msgstr "Creación de una transición para el tipo %(linkto)s"
+#~ msgid "currently attached file: %s"
+#~ msgstr "archivo adjunto: %s"
+
+#~ msgid "define a rql expression used to define permissions"
+#~ msgstr "Expresión RQL utilizada para definir los derechos de acceso"
+
+#~ msgid ""
+#~ "deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s "
+#~ "#%(toeid)s"
+#~ msgstr ""
+#~ "Eliminación de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
+#~ "(toetype)s #%(toeid)s"
+
+#~ msgid "detach attached file %s"
+#~ msgstr "Quitar archivo adjunto %s"
+
+#~ msgid "element copied"
+#~ msgstr "Elemento copiado"
+
+#~ msgid "element created"
+#~ msgstr "Elemento creado"
+
+#~ msgid "element edited"
+#~ msgstr "Elemento editado"
+
#~ msgid "entity types which may use this state"
#~ msgstr "Tipo de entidades que pueden utilizar este estado"
@@ -4175,12 +4239,18 @@
#~ msgid "initial state for entities of this type"
#~ msgstr "Estado inicial para las entidades de este tipo"
+#~ msgid "invalid date"
+#~ msgstr "Esta fecha no es válida"
+
#~ msgid "link a state to one or more entity type"
#~ msgstr "liga un estado a una o mas entidades"
#~ msgid "link a transition to one or more entity type"
#~ msgstr "liga una transición a una o mas tipos de entidad"
+#~ msgid "nothing to edit"
+#~ msgstr "nada que editar"
+
#~ msgid "remove this Bookmark"
#~ msgstr "Eliminar este Favorito"
@@ -4233,6 +4303,13 @@
#~ msgstr "Eliminar esta transición"
#~ msgid ""
+#~ "user for which this property is applying. If this relation is not set, "
+#~ "the property is considered as a global property"
+#~ msgstr ""
+#~ "usuario para el cual aplica esta propiedad. Si no se establece esta "
+#~ "relación, la propiedad es considerada como una propiedad global."
+
+#~ msgid ""
#~ "when multiple addresses are equivalent (such as python-projects@logilab."
#~ "org and python-projects@lists.logilab.org), set this to true on one of "
#~ "them which is the preferred form."
--- a/i18n/fr.po Mon Feb 08 13:33:09 2010 +0100
+++ b/i18n/fr.po Mon Feb 08 13:35:07 2010 +0100
@@ -52,10 +52,6 @@
msgstr "%(firstname)s %(surname)s"
#, python-format
-msgid "%(fmt1)s, or without time: %(fmt2)s"
-msgstr "%(fmt1)s, ou bien sans spécifier l'heure: %(fmt2)s"
-
-#, python-format
msgid "%(subject)s %(etype)s #%(eid)s (%(login)s)"
msgstr "%(subject)s %(etype)s #%(eid)s (%(login)s)"
@@ -124,10 +120,6 @@
msgstr "%s non estimé(s)"
#, python-format
-msgid "%s results matching query"
-msgstr "%s résultats pour la requête"
-
-#, python-format
msgid "%s software version of the database"
msgstr "version logicielle de la base pour %s"
@@ -272,6 +264,14 @@
msgid "CWEType"
msgstr "Type d'entité"
+msgctxt "inlined:CWRelation.from_entity.subject"
+msgid "CWEType"
+msgstr "Type d'entité"
+
+msgctxt "inlined:CWRelation.to_entity.subject"
+msgid "CWEType"
+msgstr "Type d'entité"
+
msgid "CWEType_plural"
msgstr "Types d'entité"
@@ -296,6 +296,10 @@
msgid "CWRType"
msgstr "Type de relation"
+msgctxt "inlined:CWRelation.relation_type.subject"
+msgid "CWRType"
+msgstr "Type de relation"
+
msgid "CWRType_plural"
msgstr "Types de relation"
@@ -323,10 +327,6 @@
msgid "Datetime_plural"
msgstr "Date et heure"
-#, python-format
-msgid "Debug level set to %s"
-msgstr "Niveau de debug mis à %s"
-
msgid "Decimal"
msgstr "Nombre décimal"
@@ -342,6 +342,10 @@
msgid "Download page as pdf"
msgstr "télécharger la page au format PDF"
+msgctxt "inlined:CWUser.use_email.subject"
+msgid "EmailAddress"
+msgstr "Adresse électronique"
+
msgid "EmailAddress"
msgstr "Adresse électronique"
@@ -459,9 +463,6 @@
msgid "New WorkflowTransition"
msgstr "Nouvelle transition workflow"
-msgid "No query has been executed"
-msgstr "Aucune requête n'a été éxécuté"
-
msgid "No result matching query"
msgstr "aucun résultat"
@@ -591,14 +592,6 @@
msgid "This CWEType"
msgstr "Ce type d'entité"
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "This CWEType"
-msgstr "type d'entité sujet"
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "This CWEType"
-msgstr "type d'entité objet"
-
msgid "This CWGroup"
msgstr "Ce groupe"
@@ -611,20 +604,12 @@
msgid "This CWRType"
msgstr "Ce type de relation"
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "This CWRType"
-msgstr "Ce type de relation"
-
msgid "This CWRelation"
msgstr "Cette définition de relation"
msgid "This CWUser"
msgstr "Cet utilisateur"
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "This EmailAddress"
-msgstr "adresse électronique"
-
msgid "This EmailAddress"
msgstr "Cette adresse électronique"
@@ -670,10 +655,6 @@
msgid "Transition_plural"
msgstr "Transitions"
-#, python-format
-msgid "Unable to find anything named \"%s\" in the schema !"
-msgstr "Rien de nommé \"%s\" dans le schéma"
-
msgid "UniqueConstraint"
msgstr "contrainte d'unicité"
@@ -701,13 +682,6 @@
msgid "Workflow_plural"
msgstr "Workflows"
-msgid "You are not connected to an instance !"
-msgstr "Vous n'êtes pas connecté à une instance"
-
-#, python-format
-msgid "You are now connected to %s"
-msgstr "Vous êtes connecté à l'application %s"
-
msgid ""
"You can either submit a new file using the browse button above, or choose to "
"remove already uploaded file by checking the \"detach attached file\" check-"
@@ -761,6 +735,9 @@
msgid "a URI representing an object in external data store"
msgstr "une Uri désignant un objet dans un entrepôt de données externe"
+msgid "a float is expected"
+msgstr "un nombre flottant est attendu"
+
msgid ""
"a simple cache entity characterized by a name and a validity date. The "
"target application is responsible for updating timestamp when necessary to "
@@ -848,6 +825,12 @@
msgid "actions_embed_description"
msgstr ""
+msgid "actions_entitiesoftype"
+msgstr "voir les entités de ce type"
+
+msgid "actions_entitiesoftype_description"
+msgstr ""
+
msgid "actions_follow"
msgstr "suivre"
@@ -962,9 +945,18 @@
msgid "add Bookmark bookmarked_by CWUser object"
msgstr "signet"
+msgid "add CWAttribute add_permission RQLExpression subject"
+msgstr "expression rql d'ajout"
+
msgid "add CWAttribute constrained_by CWConstraint subject"
msgstr "contrainte"
+msgid "add CWAttribute delete_permission RQLExpression subject"
+msgstr "expression rql de suppression"
+
+msgid "add CWAttribute read_permission RQLExpression subject"
+msgstr "expression rql de lecture"
+
msgid "add CWAttribute relation_type CWRType object"
msgstr "définition d'attribut"
@@ -983,18 +975,18 @@
msgid "add CWProperty for_user CWUser object"
msgstr "propriété"
-msgid "add CWRType add_permission RQLExpression subject"
-msgstr "expression RQL d'ajout"
-
-msgid "add CWRType delete_permission RQLExpression subject"
-msgstr "expression RQL de suppression"
-
-msgid "add CWRType read_permission RQLExpression subject"
-msgstr "expression RQL de lecture"
+msgid "add CWRelation add_permission RQLExpression subject"
+msgstr "expression rql d'ajout"
msgid "add CWRelation constrained_by CWConstraint subject"
msgstr "contrainte"
+msgid "add CWRelation delete_permission RQLExpression subject"
+msgstr "expression rql de suppression"
+
+msgid "add CWRelation read_permission RQLExpression subject"
+msgstr "expression rql de lecture"
+
msgid "add CWRelation relation_type CWRType object"
msgstr "définition de relation"
@@ -1056,9 +1048,6 @@
msgid "add a new permission"
msgstr "ajouter une permission"
-msgid "add relation"
-msgstr "ajouter une relation"
-
msgid "add_perm"
msgstr "ajout"
@@ -1073,7 +1062,11 @@
msgid "add_permission"
msgstr "permission d'ajout"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
+msgid "add_permission"
+msgstr "permission d'ajout"
+
+msgctxt "CWRelation"
msgid "add_permission"
msgstr "permission d'ajout"
@@ -1094,11 +1087,11 @@
#, python-format
msgid ""
-"added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
+"added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
msgstr ""
-"ajout de la relation %(rtype)s de %(frometype)s #%(fromeid)s vers %(toetype)"
-"s #%(toeid)s"
+"la relation %(rtype)s de %(frometype)s #%(eidfrom)s vers %(toetype)s #%"
+"(eidto)s a été ajoutée"
msgid "addrelated"
msgstr "ajouter"
@@ -1172,8 +1165,11 @@
msgid "an error occured, the request cannot be fulfilled"
msgstr "une erreur est survenue, la requête ne peut être complétée"
+msgid "an integer is expected"
+msgstr "un nombre entier est attendu"
+
msgid "and linked"
-msgstr "et lié"
+msgstr "et liée"
msgid "and/or between different values"
msgstr "et/ou entre les différentes valeurs"
@@ -1314,9 +1310,6 @@
msgid "button_ok"
msgstr "valider"
-msgid "button_reset"
-msgstr "annuler les changements"
-
msgid "by"
msgstr "par"
@@ -1363,6 +1356,12 @@
msgid "can not resolve entity types:"
msgstr "impossible d'interpréter les types d'entités :"
+msgid "can't be changed"
+msgstr "ne peut-être modifié"
+
+msgid "can't be deleted"
+msgstr "ne peut-être supprimé"
+
#, python-format
msgid "can't change the %s attribute"
msgstr "ne peut changer l'attribut %s"
@@ -1379,6 +1378,10 @@
msgstr "ne peut avoir plusieurs sorties sur le même état"
#, python-format
+msgid "can't parse %(value)r (expected %(format)s)"
+msgstr "ne peut analyser %(value)r (format attendu : %(format)s)"
+
+#, python-format
msgid ""
"can't set inlined=%(inlined)s, %(stype)s %(rtype)s %(otype)s has cardinality="
"%(card)s"
@@ -1458,6 +1461,12 @@
msgid "components_etypenavigation_description"
msgstr "permet de filtrer par type d'entité les résultats d'une recherche"
+msgid "components_help"
+msgstr "bouton aide"
+
+msgid "components_help_description"
+msgstr "le bouton d'aide, dans l'en-tête de page"
+
msgid "components_loggeduserlink"
msgstr "lien utilisateur"
@@ -1565,6 +1574,12 @@
msgid "contentnavigation_metadata_description"
msgstr ""
+msgid "contentnavigation_pdfview"
+msgstr "icône pdf"
+
+msgid "contentnavigation_pdfview_description"
+msgstr ""
+
msgid "contentnavigation_prevnext"
msgstr "élément précedent / suivant"
@@ -1581,12 +1596,6 @@
"section affichant les entités liées par la relation \"voir aussi\" si "
"l'entité supporte cette relation."
-msgid "contentnavigation_view_page_as_pdf"
-msgstr "icône pdf"
-
-msgid "contentnavigation_view_page_as_pdf_description"
-msgstr "l'icône pdf pour obtenir la page courant au format PDF"
-
msgid "contentnavigation_wfhistory"
msgstr "historique du workflow."
@@ -1610,9 +1619,6 @@
msgid "copy"
msgstr "copier"
-msgid "copy edition"
-msgstr "édition d'une copie"
-
msgid ""
"core relation giving to a group the permission to add an entity or relation "
"type"
@@ -1711,6 +1717,19 @@
msgstr "création d'une adresse électronique pour l'utilisateur %(linkto)s"
msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s add_permission RQLExpression)"
+msgstr "création d'une expression rql pour le droit d'ajout de %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr "création d'une expression rql pour le droit de suppression de %(linkto)s"
+
+msgid ""
+"creating RQLExpression (CWAttribute %(linkto)s read_permission RQLExpression)"
+msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s"
+
+msgid ""
"creating RQLExpression (CWEType %(linkto)s add_permission RQLExpression)"
msgstr "création d'une expression RQL pour la permission d'ajout de %(linkto)s"
@@ -1729,22 +1748,17 @@
"création d'une expression RQL pour la permission de mise à jour de %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s add_permission RQLExpression)"
-msgstr ""
-"création d'une expression RQL pour la permission d'ajout des relations %"
-"(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s add_permission RQLExpression)"
+msgstr "création d'une expression rql pour le droit d'ajout de %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s delete_permission RQLExpression)"
-msgstr ""
-"création d'une expression RQL pour la permission de suppression des "
-"relations %(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s delete_permission "
+"RQLExpression)"
+msgstr "création d'une expression rql pour le droit de suppression de %(linkto)s"
msgid ""
-"creating RQLExpression (CWRType %(linkto)s read_permission RQLExpression)"
-msgstr ""
-"création d'une expression RQL pour la permission de lire les relations %"
-"(linkto)s"
+"creating RQLExpression (CWRelation %(linkto)s read_permission RQLExpression)"
+msgstr "création d'une expression rql pour le droit de lecture de %(linkto)s"
msgid "creating RQLExpression (Transition %(linkto)s condition RQLExpression)"
msgstr "création d'une expression RQL pour la transition %(linkto)s"
@@ -1819,10 +1833,6 @@
msgid "ctxtoolbar"
msgstr "barre d'outils"
-#, python-format
-msgid "currently attached file: %s"
-msgstr "fichie actuellement attaché %s"
-
msgid "custom_workflow"
msgstr "workflow spécifique"
@@ -1916,8 +1926,8 @@
msgid "define a relation type, used to build the instance schema"
msgstr "définit un type de relation"
-msgid "define a rql expression used to define permissions"
-msgstr "RQL expression utilisée pour définir les droits d'accès"
+msgid "define a rql expression used to define __permissions__"
+msgstr ""
msgid "define a schema constraint"
msgstr "définit une contrainte de schema"
@@ -1960,9 +1970,13 @@
msgid "delete_permission"
msgstr "permission de supprimer"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
msgid "delete_permission"
-msgstr "permission de supprimer"
+msgstr ""
+
+msgctxt "CWRelation"
+msgid "delete_permission"
+msgstr ""
msgctxt "CWGroup"
msgid "delete_permission_object"
@@ -1981,11 +1995,9 @@
#, python-format
msgid ""
-"deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
-"(toeid)s"
+"deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%"
+"(eidto)s"
msgstr ""
-"suppression de la relation %(rtype)s de %(frometype)s #%(fromeid)s vers %"
-"(toetype)s #%(toeid)s"
msgid "depends on the constraint type"
msgstr "dépend du type de contrainte"
@@ -2108,10 +2120,6 @@
msgid "detach attached file"
msgstr "détacher le fichier existant"
-#, python-format
-msgid "detach attached file %s"
-msgstr "détacher le fichier existant %s"
-
msgid "display order of the action"
msgstr "ordre d'affichage de l'action"
@@ -2168,15 +2176,6 @@
msgid "eid"
msgstr "eid"
-msgid "element copied"
-msgstr "élément copié"
-
-msgid "element created"
-msgstr "élément créé"
-
-msgid "element edited"
-msgstr "élément édité"
-
msgid "email address to use for notification"
msgstr "adresse email à utiliser pour la notification"
@@ -2523,6 +2522,12 @@
"comment formater l'heure dans l'interface (\"man strftime\" pour la "
"description du format)"
+msgid "i18n_bookmark_url_fqs"
+msgstr ""
+
+msgid "i18n_bookmark_url_path"
+msgstr ""
+
msgid "i18n_login_popup"
msgstr "s'authentifier"
@@ -2666,15 +2671,6 @@
msgid "invalid action %r"
msgstr "action %r invalide"
-msgid "invalid date"
-msgstr "cette date n'est pas valide"
-
-msgid "invalid float value"
-msgstr "nombre flottant non valide"
-
-msgid "invalid integer value"
-msgstr "nombre entier non valide"
-
msgid "is"
msgstr "de type"
@@ -2984,6 +2980,10 @@
msgid "no associated permissions"
msgstr "aucune permission associée"
+#, python-format
+msgid "no edited fields specified for entity %s"
+msgstr ""
+
msgid "no related project"
msgstr "pas de projet rattaché"
@@ -3006,9 +3006,6 @@
msgid "not selected"
msgstr "non sélectionné"
-msgid "nothing to edit"
-msgstr "rien à éditer"
-
msgid "november"
msgstr "novembre"
@@ -3185,9 +3182,13 @@
msgid "read_permission"
msgstr "permission d'ajouter"
-msgctxt "CWRType"
+msgctxt "CWAttribute"
msgid "read_permission"
-msgstr "permission d'ajouter"
+msgstr ""
+
+msgctxt "CWRelation"
+msgid "read_permission"
+msgstr ""
msgctxt "CWGroup"
msgid "read_permission_object"
@@ -3240,21 +3241,8 @@
msgid "relative url of the bookmarked page"
msgstr "url relative de la page"
-msgctxt "inlined:CWRelation.from_entity.subject"
-msgid "remove this CWEType"
-msgstr "supprimer ce sujet de relation"
-
-msgctxt "inlined:CWRelation.to_entity.subject"
-msgid "remove this CWEType"
-msgstr "supprimer cet objet de la relation"
-
-msgctxt "inlined:CWRelation.relation_type.subject"
-msgid "remove this CWRType"
-msgstr "supprimer cette relation"
-
-msgctxt "inlined:CWUser.use_email.subject"
-msgid "remove this EmailAddress"
-msgstr "supprimer cette adresse électronique"
+msgid "remove-inlined-entity-form"
+msgstr ""
msgid "require_group"
msgstr "nécessite le groupe"
@@ -3609,11 +3597,11 @@
msgid "surname"
msgstr "nom de famille"
-msgid "symetric"
+msgid "symmetric"
msgstr "symétrique"
msgctxt "CWRType"
-msgid "symetric"
+msgid "symmetric"
msgstr "symétrique"
msgid "system entities"
@@ -3947,13 +3935,6 @@
"l'utilisateur %s a effectué le(s) changement(s) suivant(s):\n"
"\n"
-msgid ""
-"user for which this property is applying. If this relation is not set, the "
-"property is considered as a global property"
-msgstr ""
-"utilisateur a qui s'applique cette propriété. Si cette relation n'est pas "
-"spécifiée la propriété est considérée comme globale."
-
msgid "user interface encoding"
msgstr "encodage utilisé dans l'interface utilisateur"
@@ -4087,6 +4068,10 @@
msgid "workflow_of_object"
msgstr "a pour workflow"
+#, python-format
+msgid "wrong query parameter line %s"
+msgstr ""
+
msgid "xbel"
msgstr "xbel"
@@ -4104,31 +4089,3 @@
msgid "you should probably delete that property"
msgstr "vous devriez probablement supprimer cette propriété"
-
-#~ msgid "components_help"
-#~ msgstr "bouton aide"
-
-#~ msgid "components_help_description"
-#~ msgstr "le bouton d'aide, dans l'en-tête de page"
-
-#~ msgid "destination state"
-#~ msgstr "état de destination"
-
-#~ msgid "display the pdf icon or not"
-#~ msgstr "afficher l'icône pdf ou non"
-
-#~ msgctxt "inlined:CWRelation:from_entity:subject"
-#~ msgid "remove this CWEType"
-#~ msgstr "supprimer ce type d'entité"
-
-#~ msgctxt "inlined:CWRelation:to_entity:subject"
-#~ msgid "remove this CWEType"
-#~ msgstr "supprimer ce type d'entité"
-
-#~ msgctxt "inlined:CWRelation:relation_type:subject"
-#~ msgid "remove this CWRType"
-#~ msgstr "supprimer ce type de relation"
-
-#~ msgctxt "inlined:CWUser:use_email:subject"
-#~ msgid "remove this EmailAddress"
-#~ msgstr "supprimer cette adresse électronique"
--- a/interfaces.py Mon Feb 08 13:33:09 2010 +0100
+++ b/interfaces.py Mon Feb 08 13:35:07 2010 +0100
@@ -56,7 +56,7 @@
class IProgress(Interface):
"""something that has a cost, a state and a progression
- Take a look at cubicweb.common.mixins.ProgressMixIn for some
+ Take a look at cubicweb.mixins.ProgressMixIn for some
default implementations
"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,272 @@
+"""Common utilies to format / semd emails.
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from base64 import b64encode, b64decode
+from itertools import repeat
+from time import time
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEImage import MIMEImage
+from email.Header import Header
+try:
+ from socket import gethostname
+except ImportError:
+ def gethostname(): # gae
+ return 'XXX'
+
+from cubicweb.view import EntityView
+from cubicweb.entity import Entity
+
+def header(ustring):
+ return Header(ustring.encode('UTF-8'), 'UTF-8')
+
+def addrheader(uaddr, uname=None):
+ # even if an email address should be ascii, encode it using utf8 since
+ # automatic tests may generate non ascii email address
+ addr = uaddr.encode('UTF-8')
+ if uname:
+ return '%s <%s>' % (header(uname).encode(), addr)
+ return addr
+
+
+def construct_message_id(appid, eid, withtimestamp=True):
+ if withtimestamp:
+ addrpart = 'eid=%s×tamp=%.10f' % (eid, time())
+ else:
+ addrpart = 'eid=%s' % eid
+ # we don't want any equal sign nor trailing newlines
+ leftpart = b64encode(addrpart, '.-').rstrip().rstrip('=')
+ return '<%s@%s.%s>' % (leftpart, appid, gethostname())
+
+
+def parse_message_id(msgid, appid):
+ if msgid[0] == '<':
+ msgid = msgid[1:]
+ if msgid[-1] == '>':
+ msgid = msgid[:-1]
+ try:
+ values, qualif = msgid.split('@')
+ padding = len(values) % 4
+ values = b64decode(str(values + '='*padding), '.-')
+ values = dict(v.split('=') for v in values.split('&'))
+ fromappid, host = qualif.split('.', 1)
+ except:
+ return None
+ if appid != fromappid or host != gethostname():
+ return None
+ return values
+
+
+def format_mail(uinfo, to_addrs, content, subject="",
+ cc_addrs=(), msgid=None, references=(), config=None):
+ """Sends an Email to 'e_addr' with content 'content', and subject 'subject'
+
+ to_addrs and cc_addrs are expected to be a list of email address without
+ name
+ """
+ assert type(content) is unicode, repr(content)
+ msg = MIMEText(content.encode('UTF-8'), 'plain', 'UTF-8')
+ # safety: keep only the first newline
+ subject = subject.splitlines()[0]
+ msg['Subject'] = header(subject)
+ if uinfo.get('email'):
+ email = uinfo['email']
+ elif config and config['sender-addr']:
+ email = unicode(config['sender-addr'])
+ else:
+ email = u''
+ if uinfo.get('name'):
+ name = uinfo['name']
+ elif config and config['sender-addr']:
+ name = unicode(config['sender-name'])
+ else:
+ name = u''
+ msg['From'] = addrheader(email, name)
+ if config and config['sender-addr'] and config['sender-addr'] != email:
+ appaddr = addrheader(config['sender-addr'], config['sender-name'])
+ msg['Reply-to'] = '%s, %s' % (msg['From'], appaddr)
+ elif email:
+ msg['Reply-to'] = msg['From']
+ if config is not None:
+ msg['X-CW'] = config.appid
+ unique_addrs = lambda addrs: sorted(set(addr for addr in addrs if addr is not None))
+ msg['To'] = ', '.join(addrheader(addr) for addr in unique_addrs(to_addrs))
+ if cc_addrs:
+ msg['Cc'] = ', '.join(addrheader(addr) for addr in unique_addrs(cc_addrs))
+ if msgid:
+ msg['Message-id'] = msgid
+ if references:
+ msg['References'] = ', '.join(references)
+ return msg
+
+
+class HtmlEmail(MIMEMultipart):
+
+ def __init__(self, subject, textcontent, htmlcontent,
+ sendermail=None, sendername=None, recipients=None, ccrecipients=None):
+ MIMEMultipart.__init__(self, 'related')
+ self['Subject'] = header(subject)
+ self.preamble = 'This is a multi-part message in MIME format.'
+ # Attach alternative text message
+ alternative = MIMEMultipart('alternative')
+ self.attach(alternative)
+ msgtext = MIMEText(textcontent.encode('UTF-8'), 'plain', 'UTF-8')
+ alternative.attach(msgtext)
+ # Attach html message
+ msghtml = MIMEText(htmlcontent.encode('UTF-8'), 'html', 'UTF-8')
+ alternative.attach(msghtml)
+ if sendermail or sendername:
+ self['From'] = addrheader(sendermail, sendername)
+ if recipients:
+ self['To'] = ', '.join(addrheader(addr) for addr in recipients if addr is not None)
+ if ccrecipients:
+ self['Cc'] = ', '.join(addrheader(addr) for addr in ccrecipients if addr is not None)
+
+ def attach_image(self, data, htmlId):
+ image = MIMEImage(data)
+ image.add_header('Content-ID', '<%s>' % htmlId)
+ self.attach(image)
+
+
+class NotificationView(EntityView):
+ """abstract view implementing the "email" API (eg to simplify sending
+ notification)
+ """
+ # XXX refactor this class to work with len(rset) > 1
+
+ msgid_timestamp = True
+
+ # this is usually the method to call
+ def render_and_send(self, **kwargs):
+ """generate and send an email message for this view"""
+ delayed = kwargs.pop('delay_to_commit', None)
+ for recipients, msg in self.render_emails(**kwargs):
+ if delayed is None:
+ self.send(recipients, msg)
+ elif delayed:
+ self.send_on_commit(recipients, msg)
+ else:
+ self.send_now(recipients, msg)
+
+ def cell_call(self, row, col=0, **kwargs):
+ self.w(self._cw._(self.content) % self.context(**kwargs))
+
+ def render_emails(self, **kwargs):
+ """generate and send emails for this view (one per recipient)"""
+ self._kwargs = kwargs
+ recipients = self.recipients()
+ if not recipients:
+ self.info('skipping %s notification, no recipients', self.__regid__)
+ return
+ if self.cw_rset is not None:
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ # if the view is using timestamp in message ids, no way to reference
+ # previous email
+ if not self.msgid_timestamp:
+ refs = [self.construct_message_id(eid)
+ for eid in entity.notification_references(self)]
+ else:
+ refs = ()
+ msgid = self.construct_message_id(entity.eid)
+ else:
+ refs = ()
+ msgid = None
+ req = self._cw
+ self.user_data = req.user_data()
+ origlang = req.lang
+ for something in recipients:
+ if isinstance(something, Entity):
+ # hi-jack self._cw to get a session for the returned user
+ self._cw = self._cw.hijack_user(something)
+ emailaddr = something.get_email()
+ else:
+ emailaddr, lang = something
+ self._cw.set_language(lang)
+ # since the same view (eg self) may be called multiple time and we
+ # need a fresh stream at each iteration, reset it explicitly
+ self.w = None
+ # XXX call render before subject to set .row/.col attributes on the
+ # view
+ try:
+ content = self.render(row=0, col=0, **kwargs)
+ subject = self.subject()
+ except SkipEmail:
+ continue
+ except Exception, ex:
+ # shouldn't make the whole transaction fail because of rendering
+ # error (unauthorized or such)
+ self.exception(str(ex))
+ continue
+ msg = format_mail(self.user_data, [emailaddr], content, subject,
+ config=self._cw.vreg.config, msgid=msgid, references=refs)
+ yield [emailaddr], msg
+ # restore language
+ req.set_language(origlang)
+
+ # recipients / email sending ###############################################
+
+ def recipients(self):
+ """return a list of either 2-uple (email, language) or user entity to
+ who this email should be sent
+ """
+ # use super_session when available, we don't want to consider security
+ # when selecting recipients_finder
+ try:
+ req = self._cw.super_session
+ except AttributeError:
+ req = self._cw
+ finder = self._cw.vreg['components'].select('recipients_finder', req,
+ rset=self.cw_rset,
+ row=self.cw_row or 0,
+ col=self.cw_col or 0)
+ return finder.recipients()
+
+ def send_now(self, recipients, msg):
+ self._cw.vreg.config.sendmails([(msg, recipients)])
+
+ def send_on_commit(self, recipients, msg):
+ raise NotImplementedError
+
+ send = send_now
+
+ # email generation helpers #################################################
+
+ def construct_message_id(self, eid):
+ return construct_message_id(self._cw.vreg.config.appid, eid, self.msgid_timestamp)
+
+ def format_field(self, attr, value):
+ return ':%(attr)s: %(value)s' % {'attr': attr, 'value': value}
+
+ def format_section(self, attr, value):
+ return '%(attr)s\n%(ul)s\n%(value)s\n' % {
+ 'attr': attr, 'ul': '-'*len(attr), 'value': value}
+
+ def subject(self):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ subject = self._cw._(self.message)
+ etype = entity.dc_type()
+ eid = entity.eid
+ login = self.user_data['login']
+ return self._cw._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
+
+ def context(self, **kwargs):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ for key, val in kwargs.iteritems():
+ if val and isinstance(val, unicode) and val.strip():
+ kwargs[key] = self._cw._(val)
+ kwargs.update({'user': self.user_data['login'],
+ 'eid': entity.eid,
+ 'etype': entity.dc_type(),
+ 'url': entity.absolute_url(),
+ 'title': entity.dc_long_title(),})
+ return kwargs
+
+
+class SkipEmail(Exception):
+ """raise this if you decide to skip an email during its generation"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/migration.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,373 @@
+"""utilities for instances migration
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import sys
+import os
+import logging
+import tempfile
+from os.path import exists, join, basename, splitext
+
+from logilab.common.decorators import cached
+from logilab.common.configuration import REQUIRED, read_old_config
+from logilab.common.shellutils import ASK
+
+from cubicweb import ConfigurationError
+
+
+def filter_scripts(config, directory, fromversion, toversion, quiet=True):
+ """return a list of paths of migration files to consider to upgrade
+ from a version to a greater one
+ """
+ from logilab.common.changelog import Version # doesn't work with appengine
+ assert fromversion
+ assert toversion
+ assert isinstance(fromversion, tuple), fromversion.__class__
+ assert isinstance(toversion, tuple), toversion.__class__
+ assert fromversion <= toversion, (fromversion, toversion)
+ if not exists(directory):
+ if not quiet:
+ print directory, "doesn't exists, no migration path"
+ return []
+ if fromversion == toversion:
+ return []
+ result = []
+ for fname in os.listdir(directory):
+ if fname.endswith('.pyc') or fname.endswith('.pyo') \
+ or fname.endswith('~'):
+ continue
+ fpath = join(directory, fname)
+ try:
+ tver, mode = fname.split('_', 1)
+ except ValueError:
+ continue
+ mode = mode.split('.', 1)[0]
+ if not config.accept_mode(mode):
+ continue
+ try:
+ tver = Version(tver)
+ except ValueError:
+ continue
+ if tver <= fromversion:
+ continue
+ if tver > toversion:
+ continue
+ result.append((tver, fpath))
+ # be sure scripts are executed in order
+ return sorted(result)
+
+
+IGNORED_EXTENSIONS = ('.swp', '~')
+
+
+def execscript_confirm(scriptpath):
+ """asks for confirmation before executing a script and provides the
+ ability to show the script's content
+ """
+ while True:
+ answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
+ if answer == 'n':
+ return False
+ elif answer == 'show':
+ stream = open(scriptpath)
+ scriptcontent = stream.read()
+ stream.close()
+ print
+ print scriptcontent
+ print
+ else:
+ return True
+
+def yes(*args, **kwargs):
+ return True
+
+
+class MigrationHelper(object):
+ """class holding CubicWeb Migration Actions used by migration scripts"""
+
+ def __init__(self, config, interactive=True, verbosity=1):
+ self.config = config
+ if config:
+ # no config on shell to a remote instance
+ self.config.init_log(logthreshold=logging.ERROR, debug=True)
+ # 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
+ self.verbosity = verbosity
+ self.need_wrap = True
+ if not interactive or not verbosity:
+ self.confirm = yes
+ self.execscript_confirm = yes
+ else:
+ self.execscript_confirm = execscript_confirm
+ self._option_changes = []
+ self.__context = {'confirm': self.confirm,
+ 'config': self.config,
+ 'interactive_mode': interactive,
+ }
+
+ def __getattribute__(self, name):
+ try:
+ return object.__getattribute__(self, name)
+ except AttributeError:
+ cmd = 'cmd_%s' % name
+ if hasattr(self, cmd):
+ meth = getattr(self, cmd)
+ return lambda *args, **kwargs: self.interact(args, kwargs,
+ meth=meth)
+ raise
+ raise AttributeError(name)
+
+ def repo_connect(self):
+ return self.config.repository()
+
+ def migrate(self, vcconf, toupgrade, options):
+ """upgrade the given set of cubes
+
+ `cubes` is an ordered list of 3-uple:
+ (cube, fromversion, toversion)
+ """
+ if options.fs_only:
+ # monkey path configuration.accept_mode so database mode (e.g. Any)
+ # won't be accepted
+ orig_accept_mode = self.config.accept_mode
+ def accept_mode(mode):
+ if mode == 'Any':
+ return False
+ return orig_accept_mode(mode)
+ self.config.accept_mode = accept_mode
+ # may be an iterator
+ toupgrade = tuple(toupgrade)
+ vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
+ ctx = self.__context
+ ctx['versions_map'] = vmap
+ if self.config.accept_mode('Any') and 'cubicweb' in vmap:
+ migrdir = self.config.migration_scripts_dir()
+ self.cmd_process_script(join(migrdir, 'bootstrapmigration_repository.py'))
+ for cube, fromversion, toversion in toupgrade:
+ if cube == 'cubicweb':
+ migrdir = self.config.migration_scripts_dir()
+ else:
+ migrdir = self.config.cube_migration_scripts_dir(cube)
+ scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
+ if scripts:
+ prevversion = None
+ for version, script in scripts:
+ # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call
+ # cube_upgraded once all script of X.Y.Z have been executed
+ if prevversion is not None and version != prevversion:
+ self.cube_upgraded(cube, prevversion)
+ prevversion = version
+ self.cmd_process_script(script)
+ self.cube_upgraded(cube, toversion)
+ else:
+ self.cube_upgraded(cube, toversion)
+
+ def cube_upgraded(self, cube, version):
+ pass
+
+ def shutdown(self):
+ pass
+
+ def interact(self, args, kwargs, meth):
+ """execute the given method according to user's confirmation"""
+ msg = 'Execute command: %s(%s) ?' % (
+ meth.__name__[4:],
+ ', '.join([repr(arg) for arg in args] +
+ ['%s=%r' % (n,v) for n,v in kwargs.items()]))
+ if 'ask_confirm' in kwargs:
+ ask_confirm = kwargs.pop('ask_confirm')
+ else:
+ ask_confirm = True
+ if not ask_confirm or self.confirm(msg):
+ return meth(*args, **kwargs)
+
+ def confirm(self, question, shell=True, abort=True, retry=False, default='y'):
+ """ask for confirmation and return true on positive answer
+
+ if `retry` is true the r[etry] answer may return 2
+ """
+ possibleanswers = ['y','n']
+ if abort:
+ possibleanswers.append('abort')
+ if shell:
+ possibleanswers.append('shell')
+ if retry:
+ possibleanswers.append('retry')
+ try:
+ answer = ASK.ask(question, possibleanswers, default)
+ except (EOFError, KeyboardInterrupt):
+ answer = 'abort'
+ if answer == 'n':
+ return False
+ if answer == 'retry':
+ return 2
+ if answer == 'abort':
+ raise SystemExit(1)
+ if shell and answer == 'shell':
+ self.interactive_shell()
+ return self.confirm(question)
+ return True
+
+ def interactive_shell(self):
+ self.confirm = yes
+ self.need_wrap = False
+ # avoid '_' to be added to builtins by sys.display_hook
+ def do_not_add___to_builtins(obj):
+ if obj is not None:
+ print repr(obj)
+ sys.displayhook = do_not_add___to_builtins
+ local_ctx = self._create_context()
+ try:
+ import readline
+ from rlcompleter import Completer
+ except ImportError:
+ # readline not available
+ pass
+ else:
+ readline.set_completer(Completer(local_ctx).complete)
+ readline.parse_and_bind('tab: complete')
+ home_key = 'HOME'
+ if sys.platform == 'win32':
+ home_key = 'USERPROFILE'
+ histfile = os.path.join(os.environ[home_key], ".eshellhist")
+ try:
+ readline.read_history_file(histfile)
+ except IOError:
+ pass
+ from code import interact
+ banner = """entering the migration python shell
+just type migration commands or arbitrary python code and type ENTER to execute it
+type "exit" or Ctrl-D to quit the shell and resume operation"""
+ # give custom readfunc to avoid http://bugs.python.org/issue1288615
+ def unicode_raw_input(prompt):
+ return unicode(raw_input(prompt), sys.stdin.encoding)
+ interact(banner, readfunc=unicode_raw_input, local=local_ctx)
+ readline.write_history_file(histfile)
+ # delete instance's confirm attribute to avoid questions
+ del self.confirm
+ self.need_wrap = True
+
+ @cached
+ def _create_context(self):
+ """return a dictionary to use as migration script execution context"""
+ context = self.__context
+ for attr in dir(self):
+ if attr.startswith('cmd_'):
+ if self.need_wrap:
+ context[attr[4:]] = getattr(self, attr[4:])
+ else:
+ context[attr[4:]] = getattr(self, attr)
+ return context
+
+ def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
+ """execute a migration script
+ in interactive mode, display the migration script path, ask for
+ confirmation and execute it if confirmed
+ """
+ migrscript = os.path.normpath(migrscript)
+ if migrscript.endswith('.py'):
+ script_mode = 'python'
+ elif migrscript.endswith('.txt') or migrscript.endswith('.rst'):
+ script_mode = 'doctest'
+ else:
+ raise Exception('This is not a valid cubicweb shell input')
+ if not self.execscript_confirm(migrscript):
+ return
+ scriptlocals = self._create_context().copy()
+ if script_mode == 'python':
+ if funcname is None:
+ pyname = '__main__'
+ else:
+ pyname = splitext(basename(migrscript))[0]
+ scriptlocals.update({'__file__': migrscript, '__name__': pyname})
+ execfile(migrscript, scriptlocals)
+ if funcname is not None:
+ try:
+ func = scriptlocals[funcname]
+ self.info('found %s in locals', funcname)
+ assert callable(func), '%s (%s) is not callable' % (func, funcname)
+ except KeyError:
+ self.critical('no %s in script %s', funcname, migrscript)
+ return None
+ return func(*args, **kwargs)
+ else: # script_mode == 'doctest'
+ import doctest
+ doctest.testfile(migrscript, module_relative=False,
+ optionflags=doctest.ELLIPSIS, globs=scriptlocals)
+
+ def cmd_option_renamed(self, oldname, newname):
+ """a configuration option has been renamed"""
+ self._option_changes.append(('renamed', oldname, newname))
+
+ def cmd_option_group_change(self, option, oldgroup, newgroup):
+ """a configuration option has been moved in another group"""
+ self._option_changes.append(('moved', option, oldgroup, newgroup))
+
+ def cmd_option_added(self, optname):
+ """a configuration option has been added"""
+ self._option_changes.append(('added', optname))
+
+ def cmd_option_removed(self, optname):
+ """a configuration option has been removed"""
+ # can safely be ignored
+ #self._option_changes.append(('removed', optname))
+
+ def cmd_option_type_changed(self, optname, oldtype, newvalue):
+ """a configuration option's type has changed"""
+ self._option_changes.append(('typechanged', optname, oldtype, newvalue))
+
+ def cmd_add_cubes(self, cubes):
+ """modify the list of used cubes in the in-memory config
+ returns newly inserted cubes, including dependencies
+ """
+ if isinstance(cubes, basestring):
+ cubes = (cubes,)
+ origcubes = self.config.cubes()
+ newcubes = [p for p in self.config.expand_cubes(cubes)
+ if not p in origcubes]
+ if newcubes:
+ for cube in cubes:
+ assert cube in newcubes
+ self.config.add_cubes(newcubes)
+ return newcubes
+
+ def cmd_remove_cube(self, cube, removedeps=False):
+ if removedeps:
+ toremove = self.config.expand_cubes([cube])
+ else:
+ toremove = (cube,)
+ origcubes = self.config._cubes
+ basecubes = [c for c in origcubes if not c in toremove]
+ self.config._cubes = tuple(self.config.expand_cubes(basecubes))
+ removed = [p for p in origcubes if not p in self.config._cubes]
+ if not cube in removed:
+ raise ConfigurationError("can't remove cube %s, "
+ "used as a dependency" % cube)
+ return removed
+
+ def rewrite_configuration(self):
+ # import locally, show_diffs unavailable in gae environment
+ from cubicweb.toolsutils import show_diffs
+ configfile = self.config.main_config_file()
+ if self._option_changes:
+ read_old_config(self.config, self._option_changes, configfile)
+ fd, newconfig = tempfile.mkstemp()
+ for optdescr in self._option_changes:
+ if optdescr[0] == 'added':
+ optdict = self.config.get_option_def(optdescr[1])
+ if optdict.get('default') is REQUIRED:
+ self.config.input_option(optdescr[1], optdict)
+ self.config.generate_config(open(newconfig, 'w'))
+ show_diffs(configfile, newconfig)
+ os.close(fd)
+ if exists(newconfig):
+ os.unlink(newconfig)
+
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(MigrationHelper, getLogger('cubicweb.migration'))
--- a/misc/migration/2.99.0_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-from cubicweb import CW_MIGRATION_MAP
-
-for pk, in rql('Any K WHERE X is CWProperty, X pkey IN (%s), X pkey K'
- % ','.join("'system.version.%s'" % cube for cube in CW_MIGRATION_MAP),
- ask_confirm=False):
- cube = pk.split('.')[-1]
- newk = pk.replace(cube, CW_MIGRATION_MAP[cube])
- rql('SET X pkey %(newk)s WHERE X pkey %(oldk)s',
- {'oldk': pk, 'newk': newk}, ask_confirm=False)
- print 'renamed', pk, 'to', newk
-
-add_entity_type('CWCache')
--- a/misc/migration/3.1.5_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-synchronize_permissions('condition')
--- a/misc/migration/3.2.0_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-rql('SET X value "main-template" WHERE X is CWProperty, '
- 'X pkey "ui.main-template", X value "main"')
-checkpoint()
-
--- a/misc/migration/3.5.0_Any.py Mon Feb 08 13:33:09 2010 +0100
+++ b/misc/migration/3.5.0_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,7 +1,7 @@
add_relation_type('prefered_form')
rql('SET X prefered_form Y WHERE Y canonical TRUE, X identical_to Y')
-checkpoint()
+commit()
drop_attribute('EmailAddress', 'canonical')
drop_relation_definition('EmailAddress', 'identical_to', 'EmailAddress')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.6.0_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,1 @@
+sync_schema_props_perms('read_permission', syncperms=False) # fix read_permission cardinality
--- a/misc/migration/bootstrapmigration_repository.py Mon Feb 08 13:33:09 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,12 +10,49 @@
applcubicwebversion, cubicwebversion = versions_map['cubicweb']
+if applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+ from cubicweb.server import schemaserial as ss
+ session.set_pool()
+ session.execute = session.unsafe_execute
+ permsdict = ss.deserialize_ertype_permissions(session)
+ def _add_relation_definition_no_perms(subjtype, rtype, objtype):
+ rschema = fsschema.rschema(rtype)
+ for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
+ rql(query, args, ask_confirm=False)
+ commit(ask_confirm=False)
+
+ config.disabled_hooks_categories.add('integrity')
+ for rschema in repo.schema.relations():
+ rpermsdict = permsdict.get(rschema.eid, {})
+ for rdef in rschema.rdefs.values():
+ for action in ('read', 'add', 'delete'):
+ actperms = []
+ for something in rpermsdict.get(action, ()):
+ if isinstance(something, tuple):
+ actperms.append(rdef.rql_expression(*something))
+ else: # group name
+ actperms.append(something)
+ rdef.set_action_permissions(action, actperms)
+ for action in ('read', 'add', 'delete'):
+ _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup')
+ _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression')
+ _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup')
+ _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression')
+ for action in ('read', 'add', 'delete'):
+ rql('SET X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+ 'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action))
+ rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+ 'X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+ 'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
+ 'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
+ drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
+ drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
+ config.disabled_hooks_categories.add('integrity')
+
if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
- from cubicweb import RepositoryError
- from cubicweb.server.hooks import uniquecstrcheck_before_modification
+
session.set_shared_data('do-not-insert-cwuri', True)
- repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
- repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+ deactivate_verification_hooks()
add_relation_type('cwuri')
base_url = session.base_url()
# use an internal session since some entity might forbid modifications to admin
@@ -26,8 +63,7 @@
isession.execute('SET X cwuri %(u)s WHERE X eid %(x)s',
{'x': eid, 'u': base_url + u'eid/%s' % eid})
isession.commit()
- repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
- repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+ reactivate_verification_hooks()
session.set_shared_data('do-not-insert-cwuri', False)
if applcubicwebversion < (3, 5, 0) and cubicwebversion >= (3, 5, 0):
@@ -49,14 +85,22 @@
# drop explicit 'State allowed_transition Transition' since it should be
# infered due to yams inheritance. However we've to disable the schema
# sync hook first to avoid to destroy existing data...
- from cubicweb.server.schemahooks import after_del_relation_type
- repo.hm.unregister_hook(after_del_relation_type,
- 'after_delete_relation', 'relation_type')
try:
- drop_relation_definition('State', 'allowed_transition', 'Transition')
- finally:
- repo.hm.register_hook(after_del_relation_type,
- 'after_delete_relation', 'relation_type')
+ from cubicweb.hooks import syncschema
+ repo.vreg.unregister(syncschema.AfterDelRelationTypeHook)
+ try:
+ drop_relation_definition('State', 'allowed_transition', 'Transition')
+ finally:
+ repo.vreg.register(syncschema.AfterDelRelationTypeHook)
+ except ImportError: # syncschema is in CW >= 3.6 only
+ from cubicweb.server.schemahooks import after_del_relation_type
+ repo.hm.unregister_hook(after_del_relation_type,
+ 'after_delete_relation', 'relation_type')
+ try:
+ drop_relation_definition('State', 'allowed_transition', 'Transition')
+ finally:
+ repo.hm.register_hook(after_del_relation_type,
+ 'after_delete_relation', 'relation_type')
schema.rebuild_infered_relations() # need to be explicitly called once everything is in place
for et in rql('DISTINCT Any ET,ETN WHERE S state_of ET, ET name ETN',
@@ -74,7 +118,7 @@
rql('DELETE TrInfo TI WHERE NOT TI from_state S')
rql('SET TI by_transition T WHERE TI from_state FS, TI to_state TS, '
'FS allowed_transition T, T destination_state TS')
- checkpoint()
+ commit()
drop_relation_definition('State', 'state_of', 'CWEType')
drop_relation_definition('Transition', 'transition_of', 'CWEType')
@@ -89,7 +133,7 @@
% table, ask_confirm=False):
sql('UPDATE %s SET extid=%%(extid)s WHERE eid=%%(eid)s' % table,
{'extid': b64encode(extid), 'eid': eid}, ask_confirm=False)
- checkpoint()
+ commit()
if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0):
add_cube('card', update_database=False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mixins.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,316 @@
+"""mixins of entity/views organized somewhat in a graph or tree structure
+
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from itertools import chain
+
+from logilab.common.deprecation import deprecated
+from logilab.common.decorators import cached
+
+from cubicweb import typed_eid
+from cubicweb.selectors import implements
+from cubicweb.interfaces import IEmailable, ITree
+
+
+class TreeMixIn(object):
+ """base tree-mixin providing the tree interface
+
+ This mixin has to be inherited explicitly and configured using the
+ tree_attribute, parent_target and children_target class attribute to
+ benefit from this default implementation
+ """
+ tree_attribute = None
+ # XXX misnamed
+ parent_target = 'subject'
+ children_target = 'object'
+
+ def different_type_children(self, entities=True):
+ """return children entities of different type as this entity.
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ res = self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+ if entities:
+ return [e for e in res if e.e_schema != self.e_schema]
+ return res.filtered_rset(lambda x: x.e_schema != self.e_schema, self.cw_col)
+
+ def same_type_children(self, entities=True):
+ """return children entities of the same type as this entity.
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ res = self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+ if entities:
+ return [e for e in res if e.e_schema == self.e_schema]
+ return res.filtered_rset(lambda x: x.e_schema is self.e_schema, self.cw_col)
+
+ def iterchildren(self, _done=None):
+ if _done is None:
+ _done = set()
+ for child in self.children():
+ if child.eid in _done:
+ self.error('loop in %s tree', self.__regid__.lower())
+ continue
+ yield child
+ _done.add(child.eid)
+
+ def prefixiter(self, _done=None):
+ if _done is None:
+ _done = set()
+ if self.eid in _done:
+ return
+ _done.add(self.eid)
+ yield self
+ for child in self.same_type_children():
+ for entity in child.prefixiter(_done):
+ yield entity
+
+ @cached
+ def path(self):
+ """returns the list of eids from the root object to this object"""
+ path = []
+ parent = self
+ while parent:
+ if parent.eid in path:
+ self.error('loop in %s tree', self.__regid__.lower())
+ break
+ path.append(parent.eid)
+ try:
+ # check we are not leaving the tree
+ if (parent.tree_attribute != self.tree_attribute or
+ parent.parent_target != self.parent_target):
+ break
+ parent = parent.parent()
+ except AttributeError:
+ break
+
+ path.reverse()
+ return path
+
+ def iterparents(self, strict=True):
+ def _uptoroot(self):
+ curr = self
+ while True:
+ curr = curr.parent()
+ if curr is None:
+ break
+ yield curr
+ if not strict:
+ return chain([self], _uptoroot(self))
+ return _uptoroot(self)
+
+ def notification_references(self, view):
+ """used to control References field of email send on notification
+ for this entity. `view` is the notification view.
+
+ Should return a list of eids which can be used to generate message ids
+ of previously sent email
+ """
+ return self.path()[:-1]
+
+
+ ## ITree interface ########################################################
+ def parent(self):
+ """return the parent entity if any, else None (e.g. if we are on the
+ root
+ """
+ try:
+ return self.related(self.tree_attribute, self.parent_target,
+ entities=True)[0]
+ except (KeyError, IndexError):
+ return None
+
+ def children(self, entities=True, sametype=False):
+ """return children entities
+
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set
+ """
+ if sametype:
+ return self.same_type_children(entities)
+ else:
+ return self.related(self.tree_attribute, self.children_target,
+ entities=entities)
+
+ def children_rql(self):
+ return self.related_rql(self.tree_attribute, self.children_target)
+
+ def is_leaf(self):
+ return len(self.children()) == 0
+
+ def is_root(self):
+ return self.parent() is None
+
+ def root(self):
+ """return the root object"""
+ return self._cw.entity_from_eid(self.path()[0])
+
+
+class EmailableMixIn(object):
+ """base mixin providing the default get_email() method used by
+ the massmailing view
+
+ NOTE: The default implementation is based on the
+ primary_email / use_email scheme
+ """
+ __implements__ = (IEmailable,)
+
+ def get_email(self):
+ if getattr(self, 'primary_email', None):
+ return self.primary_email[0].address
+ if getattr(self, 'use_email', None):
+ return self.use_email[0].address
+ return None
+
+ @classmethod
+ def allowed_massmail_keys(cls):
+ """returns a set of allowed email substitution keys
+
+ The default is to return the entity's attribute list but an
+ entity class might override this method to allow extra keys.
+ For instance, the Person class might want to return a `companyname`
+ key.
+ """
+ return set(rschema.type
+ for rschema, attrtype in cls.e_schema.attribute_definitions()
+ if attrtype.type not in ('Password', 'Bytes'))
+
+ def as_email_context(self):
+ """returns the dictionary as used by the sendmail controller to
+ build email bodies.
+
+ NOTE: the dictionary keys should match the list returned by the
+ `allowed_massmail_keys` method.
+ """
+ return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
+
+
+"""pluggable mixins system: plug classes registered in MI_REL_TRIGGERS on entity
+classes which have the relation described by the dict's key.
+
+NOTE: pluggable mixins can't override any method of the 'explicit' user classes tree
+(eg without plugged classes). This includes bases Entity and AnyEntity classes.
+"""
+MI_REL_TRIGGERS = {
+ ('primary_email', 'subject'): EmailableMixIn,
+ ('use_email', 'subject'): EmailableMixIn,
+ }
+
+
+
+def _done_init(done, view, row, col):
+ """handle an infinite recursion safety belt"""
+ if done is None:
+ done = set()
+ entity = view.cw_rset.get_entity(row, col)
+ if entity.eid in done:
+ msg = entity._cw._('loop in %(rel)s relation (%(eid)s)') % {
+ 'rel': entity.tree_attribute,
+ 'eid': entity.eid
+ }
+ return None, msg
+ done.add(entity.eid)
+ return done, entity
+
+
+class TreeViewMixIn(object):
+ """a recursive tree view"""
+ __regid__ = 'tree'
+ item_vid = 'treeitem'
+ __select__ = implements(ITree)
+
+ def call(self, done=None, **kwargs):
+ if done is None:
+ done = set()
+ super(TreeViewMixIn, self).call(done=done, **kwargs)
+
+ def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+ done, entity = _done_init(done, self, row, col)
+ if done is None:
+ # entity is actually an error message
+ self.w(u'<li class="badcontent">%s</li>' % entity)
+ return
+ self.open_item(entity)
+ entity.view(vid or self.item_vid, w=self.w, **kwargs)
+ relatedrset = entity.children(entities=False)
+ self.wview(self.__regid__, relatedrset, 'null', done=done, **kwargs)
+ self.close_item(entity)
+
+ def open_item(self, entity):
+ self.w(u'<li class="%s">\n' % entity.__regid__.lower())
+ def close_item(self, entity):
+ self.w(u'</li>\n')
+
+
+class TreePathMixIn(object):
+ """a recursive path view"""
+ __regid__ = 'path'
+ item_vid = 'oneline'
+ separator = u' > '
+
+ def call(self, **kwargs):
+ self.w(u'<div class="pathbar">')
+ super(TreePathMixIn, self).call(**kwargs)
+ self.w(u'</div>')
+
+ def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
+ done, entity = _done_init(done, self, row, col)
+ if done is None:
+ # entity is actually an error message
+ self.w(u'<span class="badcontent">%s</span>' % entity)
+ return
+ parent = entity.parent()
+ if parent:
+ parent.view(self.__regid__, w=self.w, done=done)
+ self.w(self.separator)
+ entity.view(vid or self.item_vid, w=self.w)
+
+
+class ProgressMixIn(object):
+ """provide default implementations for IProgress interface methods"""
+ # This is an adapter isn't it ?
+
+ @property
+ def cost(self):
+ return self.progress_info()['estimated']
+
+ @property
+ def revised_cost(self):
+ return self.progress_info().get('estimatedcorrected', self.cost)
+
+ @property
+ def done(self):
+ return self.progress_info()['done']
+
+ @property
+ def todo(self):
+ return self.progress_info()['todo']
+
+ @cached
+ def progress_info(self):
+ raise NotImplementedError()
+
+ def finished(self):
+ return not self.in_progress()
+
+ def in_progress(self):
+ raise NotImplementedError()
+
+ def progress(self):
+ try:
+ return 100. * self.done / self.revised_cost
+ except ZeroDivisionError:
+ # total cost is 0 : if everything was estimated, task is completed
+ if self.progress_info().get('notestimated'):
+ return 0.
+ return 100
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mttransforms.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,102 @@
+"""mime type transformation engine for cubicweb, based on mtconverter
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab import mtconverter
+
+from logilab.mtconverter.engine import TransformEngine
+from logilab.mtconverter.transform import Transform
+from logilab.mtconverter import (register_base_transforms,
+ register_pil_transforms,
+ register_pygments_transforms)
+
+from cubicweb.utils import UStringIO
+from cubicweb.uilib import rest_publish, html_publish
+
+HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
+
+# CubicWeb specific transformations
+
+class rest_to_html(Transform):
+ inputs = ('text/rest', 'text/x-rst')
+ output = 'text/html'
+ def _convert(self, trdata):
+ return rest_publish(trdata.appobject, trdata.decode())
+
+class html_to_html(Transform):
+ inputs = HTML_MIMETYPES
+ output = 'text/html'
+ def _convert(self, trdata):
+ return html_publish(trdata.appobject, trdata.data)
+
+
+# Instantiate and configure the transformation engine
+
+mtconverter.UNICODE_POLICY = 'replace'
+
+ENGINE = TransformEngine()
+ENGINE.add_transform(rest_to_html())
+ENGINE.add_transform(html_to_html())
+
+try:
+ from cubicweb.ext.tal import CubicWebContext, compile_template
+except ImportError:
+ HAS_TAL = False
+ from cubicweb import schema
+ schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template')
+
+else:
+ HAS_TAL = True
+
+ class ept_to_html(Transform):
+ inputs = ('text/cubicweb-page-template',)
+ output = 'text/html'
+ output_encoding = 'utf-8'
+ def _convert(self, trdata):
+ context = CubicWebContext()
+ appobject = trdata.appobject
+ context.update({'self': appobject, 'rset': appobject.cw_rset,
+ 'req': appobject._cw,
+ '_' : appobject._cw._,
+ 'user': appobject._cw.user})
+ output = UStringIO()
+ template = compile_template(trdata.encode(self.output_encoding))
+ template.expand(context, output)
+ return output.getvalue()
+
+ ENGINE.add_transform(ept_to_html())
+
+if register_pil_transforms(ENGINE, verb=False):
+ HAS_PIL_TRANSFORMS = True
+else:
+ HAS_PIL_TRANSFORMS = False
+
+try:
+ from logilab.mtconverter.transforms import pygmentstransforms
+ for mt in ('text/plain',) + HTML_MIMETYPES:
+ try:
+ pygmentstransforms.mimetypes.remove(mt)
+ except ValueError:
+ continue
+ register_pygments_transforms(ENGINE, verb=False)
+
+ def patch_convert(cls):
+ def _convert(self, trdata, origconvert=cls._convert):
+ try:
+ trdata.appobject._cw.add_css('pygments.css')
+ except AttributeError: # session has no add_css, only http request
+ pass
+ return origconvert(self, trdata)
+ cls._convert = _convert
+ patch_convert(pygmentstransforms.PygmentsHTMLTransform)
+
+ HAS_PYGMENTS_TRANSFORMS = True
+except ImportError:
+ HAS_PYGMENTS_TRANSFORMS = False
+
+register_base_transforms(ENGINE, verb=False)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/req.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,401 @@
+"""Base class for request/session
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: Library General Public License version 2 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from urllib import quote as urlquote, unquote as urlunquote
+from datetime import time, datetime, timedelta
+from cgi import parse_qsl
+
+from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
+from logilab.common.date import ustrftime, strptime, todate, todatetime
+
+from cubicweb import Unauthorized, RegistryException, typed_eid
+from cubicweb.rset import ResultSet
+
+ONESECOND = timedelta(0, 1, 0)
+CACHE_REGISTRY = {}
+
+
+class Cache(dict):
+ def __init__(self):
+ super(Cache, self).__init__()
+ _now = datetime.now()
+ self.cache_creation_date = _now
+ self.latest_cache_lookup = _now
+
+
+class RequestSessionBase(object):
+ """base class containing stuff shared by server session and web request
+
+ request/session is the main resources accessor, mainly through it's vreg
+ attribute:
+ :vreg:
+ the instance's registry
+ :vreg.schema:
+ the instance's schema
+ :vreg.config:
+ the instance's configuration
+ """
+ def __init__(self, vreg):
+ self.vreg = vreg
+ try:
+ encoding = vreg.property_value('ui.encoding')
+ except: # no vreg or property not registered
+ encoding = 'utf-8'
+ self.encoding = encoding
+ # cache result of execution for (rql expr / eids),
+ # should be emptied on commit/rollback of the server session / web
+ # connection
+ self.local_perm_cache = {}
+ self._ = unicode
+
+ def property_value(self, key):
+ """return value of the property with the given key, giving priority to
+ user specific value if any, else using site value
+ """
+ if self.user:
+ return self.user.property_value(key)
+ return self.vreg.property_value(key)
+
+ def etype_rset(self, etype, size=1):
+ """return a fake result set for a particular entity type"""
+ rset = ResultSet([('A',)]*size, '%s X' % etype,
+ description=[(etype,)]*size)
+ def get_entity(row, col=0, etype=etype, req=self, rset=rset):
+ return req.vreg.etype_class(etype)(req, rset, row, col)
+ rset.get_entity = get_entity
+ return self.decorate_rset(rset)
+
+ def eid_rset(self, eid, etype=None):
+ """return a result set for the given eid without doing actual query
+ (we have the eid, we can suppose it exists and user has access to the
+ entity)
+ """
+ eid = typed_eid(eid)
+ if etype is None:
+ etype = self.describe(eid)[0]
+ rset = ResultSet([(eid,)], 'Any X WHERE X eid %(x)s', {'x': eid},
+ [(etype,)])
+ return self.decorate_rset(rset)
+
+ def empty_rset(self):
+ """return a result set for the given eid without doing actual query
+ (we have the eid, we can suppose it exists and user has access to the
+ entity)
+ """
+ return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
+
+ def entity_from_eid(self, eid, etype=None):
+ """return an entity instance for the given eid. No query is done"""
+ try:
+ return self.entity_cache(eid)
+ except KeyError:
+ rset = self.eid_rset(eid, etype)
+ entity = rset.get_entity(0, 0)
+ self.set_entity_cache(entity)
+ return entity
+
+ def entity_cache(self, eid):
+ raise KeyError
+
+ def set_entity_cache(self, entity):
+ pass
+
+ # XXX move to CWEntityManager or even better as factory method (unclear
+ # where yet...)
+
+ def create_entity(self, etype, _cw_unsafe=False, **kwargs):
+ """add a new entity of the given type
+
+ Example (in a shell session):
+
+ c = create_entity('Company', name=u'Logilab')
+ create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe')
+
+ """
+ if _cw_unsafe:
+ execute = self.unsafe_execute
+ else:
+ execute = self.execute
+ rql = 'INSERT %s X' % etype
+ relations = []
+ restrictions = set()
+ cachekey = []
+ pending_relations = []
+ for attr, value in kwargs.items():
+ if isinstance(value, (tuple, list, set, frozenset)):
+ if len(value) == 1:
+ value = iter(value).next()
+ else:
+ del kwargs[attr]
+ pending_relations.append( (attr, value) )
+ continue
+ if hasattr(value, 'eid'): # non final relation
+ rvar = attr.upper()
+ # XXX safer detection of object relation
+ if attr.startswith('reverse_'):
+ relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
+ else:
+ relations.append('X %s %s' % (attr, rvar))
+ restriction = '%s eid %%(%s)s' % (rvar, attr)
+ if not restriction in restrictions:
+ restrictions.add(restriction)
+ cachekey.append(attr)
+ kwargs[attr] = value.eid
+ else: # attribute
+ relations.append('X %s %%(%s)s' % (attr, attr))
+ if relations:
+ rql = '%s: %s' % (rql, ', '.join(relations))
+ if restrictions:
+ rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
+ created = execute(rql, kwargs, cachekey).get_entity(0, 0)
+ for attr, values in pending_relations:
+ if attr.startswith('reverse_'):
+ restr = 'Y %s X' % attr[len('reverse_'):]
+ else:
+ restr = 'X %s Y' % attr
+ execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
+ restr, ','.join(str(r.eid) for r in values)),
+ {'x': created.eid}, 'x')
+ return created
+
+ def ensure_ro_rql(self, rql):
+ """raise an exception if the given rql is not a select query"""
+ first = rql.split(' ', 1)[0].lower()
+ if first in ('insert', 'set', 'delete'):
+ raise Unauthorized(self._('only select queries are authorized'))
+
+ def get_cache(self, cachename):
+ """
+ NOTE: cachename should be dotted names as in :
+ - cubicweb.mycache
+ - cubes.blog.mycache
+ - etc.
+ """
+ if cachename in CACHE_REGISTRY:
+ cache = CACHE_REGISTRY[cachename]
+ else:
+ cache = CACHE_REGISTRY[cachename] = Cache()
+ _now = datetime.now()
+ if _now > cache.latest_cache_lookup + ONESECOND:
+ ecache = self.execute(
+ 'Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
+ {'name':cachename}).get_entity(0,0)
+ cache.latest_cache_lookup = _now
+ if not ecache.valid(cache.cache_creation_date):
+ cache.clear()
+ cache.cache_creation_date = _now
+ return cache
+
+ # url generation methods ##################################################
+
+ def build_url(self, *args, **kwargs):
+ """return an absolute URL using params dictionary key/values as URL
+ parameters. Values are automatically URL quoted, and the
+ publishing method to use may be specified or will be guessed.
+ """
+ # use *args since we don't want first argument to be "anonymous" to
+ # avoid potential clash with kwargs
+ if args:
+ assert len(args) == 1, 'only 0 or 1 non-named-argument expected'
+ method = args[0]
+ else:
+ method = None
+ # XXX I (adim) think that if method is passed explicitly, we should
+ # not try to process it and directly call req.build_url()
+ if method is None:
+ if self.from_controller() == 'view' and not '_restpath' in kwargs:
+ method = self.relative_path(includeparams=False) or 'view'
+ else:
+ method = 'view'
+ base_url = kwargs.pop('base_url', None)
+ if base_url is None:
+ base_url = self.base_url()
+ if '_restpath' in kwargs:
+ assert method == 'view', method
+ path = kwargs.pop('_restpath')
+ else:
+ path = method
+ if not kwargs:
+ return u'%s%s' % (base_url, path)
+ return u'%s%s?%s' % (base_url, path, self.build_url_params(**kwargs))
+
+
+ def build_url_params(self, **kwargs):
+ """return encoded params to incorporate them in an URL"""
+ args = []
+ for param, values in kwargs.items():
+ if not isinstance(values, (list, tuple)):
+ values = (values,)
+ for value in values:
+ args.append(u'%s=%s' % (param, self.url_quote(value)))
+ return '&'.join(args)
+
+ def url_quote(self, value, safe=''):
+ """urllib.quote is not unicode safe, use this method to do the
+ necessary encoding / decoding. Also it's designed to quote each
+ part of a url path and so the '/' character will be encoded as well.
+ """
+ if isinstance(value, unicode):
+ quoted = urlquote(value.encode(self.encoding), safe=safe)
+ return unicode(quoted, self.encoding)
+ return urlquote(str(value), safe=safe)
+
+ def url_unquote(self, quoted):
+ """returns a unicode unquoted string
+
+ decoding is based on `self.encoding` which is the encoding
+ used in `url_quote`
+ """
+ if isinstance(quoted, unicode):
+ quoted = quoted.encode(self.encoding)
+ try:
+ return unicode(urlunquote(quoted), self.encoding)
+ except UnicodeDecodeError: # might occurs on manually typed URLs
+ return unicode(urlunquote(quoted), 'iso-8859-1')
+
+ def url_parse_qsl(self, querystring):
+ """return a list of (key, val) found in the url quoted query string"""
+ if isinstance(querystring, unicode):
+ querystring = querystring.encode(self.encoding)
+ for key, val in parse_qsl(querystring):
+ try:
+ yield unicode(key, self.encoding), unicode(val, self.encoding)
+ except UnicodeDecodeError: # might occurs on manually typed URLs
+ yield unicode(key, 'iso-8859-1'), unicode(val, 'iso-8859-1')
+
+ # bound user related methods ###############################################
+
+ @cached
+ def user_data(self):
+ """returns a dictionnary with this user's information"""
+ userinfo = {}
+ if self.is_internal_session:
+ userinfo['login'] = "cubicweb"
+ userinfo['name'] = "cubicweb"
+ userinfo['email'] = ""
+ return userinfo
+ user = self.actual_session().user
+ userinfo['login'] = user.login
+ userinfo['name'] = user.name()
+ userinfo['email'] = user.get_email()
+ return userinfo
+
+ def is_internal_session(self):
+ """overrided on the server-side"""
+ return False
+
+ # formating methods #######################################################
+
+ def view(self, __vid, rset=None, __fallback_oid=None, __registry='views',
+ initargs=None, **kwargs):
+ """Select object with the given id (`__oid`) then render it. If the
+ object isn't selectable, try to select fallback object if
+ `__fallback_oid` is specified.
+
+ If specified `initargs` is expected to be a dictionnary containing
+ arguments that should be given to selection (hence to object's __init__
+ as well), but not to render(). Other arbitrary keyword arguments will be
+ given to selection *and* to render(), and so should be handled by
+ object's call or cell_call method..
+ """
+ if initargs is None:
+ initargs = kwargs
+ else:
+ initargs.update(kwargs)
+ try:
+ view = self.vreg[__registry].select(__vid, self, rset=rset, **initargs)
+ except RegistryException:
+ view = self.vreg[__registry].select(__fallback_oid, self,
+ rset=rset, **initargs)
+ return view.render(**kwargs)
+
+ def format_date(self, date, date_format=None, time=False):
+ """return a string for a date time according to instance's
+ configuration
+ """
+ if date:
+ if date_format is None:
+ if time:
+ date_format = self.property_value('ui.datetime-format')
+ else:
+ date_format = self.property_value('ui.date-format')
+ return ustrftime(date, date_format)
+ return u''
+
+ def format_time(self, time):
+ """return a string for a time according to instance's
+ configuration
+ """
+ if time:
+ return ustrftime(time, self.property_value('ui.time-format'))
+ return u''
+
+ def format_float(self, num):
+ """return a string for floating point number according to instance's
+ configuration
+ """
+ if num is not None:
+ return self.property_value('ui.float-format') % num
+ return u''
+
+ def parse_datetime(self, value, etype='Datetime'):
+ """get a datetime or time from a string (according to etype)
+ Datetime formatted as Date are accepted
+ """
+ assert etype in ('Datetime', 'Date', 'Time'), etype
+ # XXX raise proper validation error
+ if etype == 'Datetime':
+ format = self.property_value('ui.datetime-format')
+ try:
+ return todatetime(strptime(value, format))
+ except ValueError:
+ pass
+ elif etype == 'Time':
+ format = self.property_value('ui.time-format')
+ try:
+ # (adim) I can't find a way to parse a Time with a custom format
+ date = strptime(value, format) # this returns a DateTime
+ return time(date.hour, date.minute, date.second)
+ except ValueError:
+ raise ValueError(self._('can\'t parse %(value)r (expected %(format)s)')
+ % {'value': value, 'format': format})
+ try:
+ format = self.property_value('ui.date-format')
+ dt = strptime(value, format)
+ if etype == 'Datetime':
+ return todatetime(dt)
+ return todate(dt)
+ except ValueError:
+ raise ValueError(self._('can\'t parse %(value)r (expected %(format)s)')
+ % {'value': value, 'format': format})
+
+ # abstract methods to override according to the web front-end #############
+
+ def base_url(self):
+ """return the root url of the instance"""
+ raise NotImplementedError
+
+ def decorate_rset(self, rset):
+ """add vreg/req (at least) attributes to the given result set """
+ raise NotImplementedError
+
+ def describe(self, eid):
+ """return a tuple (type, sourceuri, extid) for the entity with id <eid>"""
+ raise NotImplementedError
+
+ @property
+ @deprecated('[3.6] use _cw.vreg.config')
+ def config(self):
+ return self.vreg.config
+
+ @property
+ @deprecated('[3.6] use _cw.vreg.schema')
+ def schema(self):
+ return self.vreg.schema
--- a/rqlrewrite.py Mon Feb 08 13:33:09 2010 +0100
+++ b/rqlrewrite.py Mon Feb 08 13:35:07 2010 +0100
@@ -402,12 +402,12 @@
orel = self.varinfo['lhs_rels'][sniprel.r_type]
cardindex = 0
ttypes_func = rschema.objects
- rprop = rschema.rproperty
+ rdef = rschema.rdef
else: # target == 'subject':
orel = self.varinfo['rhs_rels'][sniprel.r_type]
cardindex = 1
ttypes_func = rschema.subjects
- rprop = lambda x, y, z: rschema.rproperty(y, x, z)
+ rdef = lambda x, y: rschema.rdef(y, x)
except KeyError, ex:
# may be raised by self.varinfo['xhs_rels'][sniprel.r_type]
return None
@@ -419,7 +419,7 @@
# variable from the original query
for etype in self.varinfo['stinfo']['possibletypes']:
for ttype in ttypes_func(etype):
- if rprop(etype, ttype, 'cardinality')[cardindex] in '+*':
+ if rdef(etype, ttype).cardinality[cardindex] in '+*':
return None
return orel
--- a/rset.py Mon Feb 08 13:33:09 2010 +0100
+++ b/rset.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,7 +9,7 @@
from logilab.common.decorators import cached, clear_cache, copy_cache
-from rql import nodes
+from rql import nodes, stmts
from cubicweb import NotAnEntity
@@ -83,7 +83,7 @@
try:
return self._rsetactions[key]
except KeyError:
- actions = self.vreg['actions'].possible_vobjects(
+ actions = self.vreg['actions'].poss_visible_objects(
self.req, rset=self, **kwargs)
self._rsetactions[key] = actions
return actions
@@ -243,8 +243,6 @@
rset = mapping[key]
rset.rows.append(self.rows[idx])
rset.description.append(self.description[idx])
-
-
for rset in result:
rset.rowcount = len(rset.rows)
if return_dict:
@@ -252,6 +250,51 @@
else:
return result
+ def limited_rql(self):
+ """return a printable rql for the result set associated to the object,
+ with limit/offset correctly set according to maximum page size and
+ currently displayed page when necessary
+ """
+ # try to get page boundaries from the navigation component
+ # XXX we should probably not have a ref to this component here (eg in
+ # cubicweb)
+ nav = self.vreg['components'].select_or_none('navigation', self.req,
+ rset=self)
+ if nav:
+ start, stop = nav.page_boundaries()
+ rql = self._limit_offset_rql(stop - start, start)
+ # result set may have be limited manually in which case navigation won't
+ # apply
+ elif self.limited:
+ rql = self._limit_offset_rql(*self.limited)
+ # navigation component doesn't apply and rset has not been limited, no
+ # need to limit query
+ else:
+ rql = self.printable_rql()
+ return rql
+
+ def _limit_offset_rql(self, limit, offset):
+ rqlst = self.syntax_tree()
+ if len(rqlst.children) == 1:
+ select = rqlst.children[0]
+ olimit, ooffset = select.limit, select.offset
+ select.limit, select.offset = limit, offset
+ rql = rqlst.as_string(kwargs=self.args)
+ # restore original limit/offset
+ select.limit, select.offset = olimit, ooffset
+ else:
+ newselect = stmts.Select()
+ newselect.limit = limit
+ newselect.offset = offset
+ aliases = [nodes.VariableRef(newselect.get_variable(vref.name, i))
+ for i, vref in enumerate(rqlst.selection)]
+ newselect.set_with([nodes.SubQuery(aliases, rqlst)], check=False)
+ newunion = stmts.Union()
+ newunion.append(newselect)
+ rql = rqlst.as_string(kwargs=self.args)
+ rqlst.parent = None
+ return rql
+
def limit(self, limit, offset=0, inplace=False):
"""limit the result set to the given number of rows optionaly starting
from an index different than 0
@@ -282,9 +325,9 @@
# we also have to fix/remove from the request entity cache entities
# which get a wrong rset reference by this limit call
for entity in self.req.cached_entities():
- if entity.rset is self:
- if offset <= entity.row < stop:
- entity.row = entity.row - offset
+ if entity.cw_rset is self:
+ if offset <= entity.cw_row < stop:
+ entity.cw_row = entity.cw_row - offset
else:
self.req.drop_entity_cache(entity.eid)
else:
@@ -321,8 +364,16 @@
if self.rows[i][col] is not None:
yield self.get_entity(i, col)
+ def complete_entity(self, row, col=0, skip_bytes=True):
+ """short cut to get an completed entity instance for a particular
+ row (all instance's attributes have been fetched)
+ """
+ entity = self.get_entity(row, col)
+ entity.complete(skip_bytes=skip_bytes)
+ return entity
+
@cached
- def get_entity(self, row, col=None):
+ def get_entity(self, row, col):
"""special method for query retreiving a single entity, returns a
partially initialized Entity instance.
@@ -336,11 +387,6 @@
:return: the partially initialized `Entity` instance
"""
- if col is None:
- from warnings import warn
- msg = 'col parameter will become mandatory in future version'
- warn(msg, DeprecationWarning, stacklevel=3)
- col = 0
etype = self.description[row][col]
try:
eschema = self.vreg.schema.eschema(etype)
@@ -374,16 +420,17 @@
# new attributes found in this resultset ?
try:
entity = req.entity_cache(eid)
- if entity.rset is None:
- # entity has no rset set, this means entity has been cached by
- # the repository (req is a repository session) which had no rset
- # info. Add id.
- entity.rset = self
- entity.row = row
- entity.col = col
- return entity
except KeyError:
pass
+ else:
+ if entity.cw_rset is None:
+ # entity has no rset set, this means entity has been created by
+ # the querier (req is a repository session) and so jas no rset
+ # info. Add it.
+ entity.cw_rset = self
+ entity.cw_row = row
+ entity.cw_col = col
+ return entity
# build entity instance
etype = self.description[row][col]
entity = self.vreg['etypes'].etype_class(etype)(req, rset=self,
@@ -403,25 +450,22 @@
select = rqlst
# take care, due to outer join support, we may find None
# values for non final relation
- for i, attr, x in attr_desc_iterator(select, col):
+ for i, attr, role in attr_desc_iterator(select, col):
outerselidx = rqlst.subquery_selection_index(select, i)
if outerselidx is None:
continue
- if x == 'subject':
+ if role == 'subject':
rschema = eschema.subjrels[attr]
if rschema.final:
entity[attr] = rowvalues[outerselidx]
continue
- tetype = rschema.objects(etype)[0]
- card = rschema.rproperty(etype, tetype, 'cardinality')[0]
else:
rschema = eschema.objrels[attr]
- tetype = rschema.subjects(etype)[0]
- card = rschema.rproperty(tetype, etype, 'cardinality')[1]
+ rdef = eschema.rdef(attr, role)
# only keep value if it can't be multivalued
- if card in '1?':
+ if rdef.role_cardinality(role) in '1?':
if rowvalues[outerselidx] is None:
- if x == 'subject':
+ if role == 'subject':
rql = 'Any Y WHERE X %s Y, X eid %s'
else:
rql = 'Any Y WHERE Y %s X, X eid %s'
@@ -429,7 +473,7 @@
req.decorate_rset(rrset)
else:
rrset = self._build_entity(row, outerselidx).as_rset()
- entity.set_related_cache(attr, x, rrset)
+ entity.set_related_cache(attr, role, rrset)
return entity
@cached
@@ -481,26 +525,45 @@
result[-1][1] = i
return result
+ def _locate_query_params(self, rqlst, row, col):
+ locate_query_col = col
+ etype = self.description[row][col]
+ # final type, find a better one to locate the correct subquery
+ # (ambiguous if possible)
+ eschema = self.vreg.schema.eschema
+ if eschema(etype).final:
+ for select in rqlst.children:
+ try:
+ myvar = select.selection[col].variable
+ except AttributeError:
+ # not a variable
+ continue
+ for i in xrange(len(select.selection)):
+ if i == col:
+ continue
+ coletype = self.description[row][i]
+ # None description possible on column resulting from an outer join
+ if coletype is None or eschema(coletype).final:
+ continue
+ try:
+ ivar = select.selection[i].variable
+ except AttributeError:
+ # not a variable
+ continue
+ # check variables don't comes from a subquery or are both
+ # coming from the same subquery
+ if getattr(ivar, 'query', None) is getattr(myvar, 'query', None):
+ etype = coletype
+ locate_query_col = i
+ if len(self.column_types(i)) > 1:
+ return etype, locate_query_col
+ return etype, locate_query_col
+
@cached
def related_entity(self, row, col):
"""try to get the related entity to extract format information if any"""
- locate_query_col = col
rqlst = self.syntax_tree()
- etype = self.description[row][col]
- if self.vreg.schema.eschema(etype).final:
- # final type, find a better one to locate the correct subquery
- # (ambiguous if possible)
- for i in xrange(len(rqlst.children[0].selection)):
- if i == col:
- continue
- coletype = self.description[row][i]
- if coletype is None:
- continue
- if not self.vreg.schema.eschema(coletype).final:
- etype = coletype
- locate_query_col = i
- if len(self.column_types(i)) > 1:
- break
+ etype, locate_query_col = self._locate_query_params(rqlst, row, col)
# UNION query, find the subquery from which this entity has been found
select = rqlst.locate_subquery(locate_query_col, etype, self.args)[0]
col = rqlst.subquery_selection_index(select, col)
--- a/rtags.py Mon Feb 08 13:33:09 2010 +0100
+++ b/rtags.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,6 +15,9 @@
def register_rtag(rtag):
RTAGS.append(rtag)
+def _ensure_str_key(key):
+ return tuple(str(k) for k in key)
+
class RelationTags(object):
"""a tag store for full relation definitions :
@@ -25,12 +28,14 @@
This class associates a single tag to each key.
"""
_allowed_values = None
+ _initfunc = None
def __init__(self, name=None, initfunc=None, allowed_values=None):
self._name = name or '<unknown>'
self._tagdefs = {}
if allowed_values is not None:
self._allowed_values = allowed_values
- self._initfunc = initfunc
+ if initfunc is not None:
+ self._initfunc = initfunc
register_rtag(self)
def __repr__(self):
@@ -45,22 +50,22 @@
self._tagdefs.clear()
def _get_keys(self, stype, rtype, otype, tagged):
- keys = [(rtype, tagged, '*', '*'),
- (rtype, tagged, '*', otype),
- (rtype, tagged, stype, '*'),
- (rtype, tagged, stype, otype)]
+ keys = [('*', rtype, '*', tagged),
+ ('*', rtype, otype, tagged),
+ (stype, rtype, '*', tagged),
+ (stype, rtype, otype, tagged)]
if stype == '*' or otype == '*':
- keys.remove((rtype, tagged, '*', '*'))
+ keys.remove( ('*', rtype, '*', tagged) )
if stype == '*':
- keys.remove((rtype, tagged, '*', otype))
+ keys.remove( ('*', rtype, otype, tagged) )
if otype == '*':
- keys.remove((rtype, tagged, stype, '*'))
+ keys.remove( (stype, rtype, '*', tagged) )
return keys
def init(self, schema, check=True):
# XXX check existing keys against schema
if check:
- for (rtype, tagged, stype, otype), value in self._tagdefs.items():
+ for (stype, rtype, otype, tagged), value in self._tagdefs.items():
for ertype in (stype, rtype, otype):
if ertype != '*' and not ertype in schema:
self.warning('removing rtag %s: %s, %s undefined in schema',
@@ -68,51 +73,52 @@
self.del_rtag(stype, rtype, otype, tagged)
break
if self._initfunc is not None:
- for eschema in schema.entities():
- for rschema, tschemas, role in eschema.relation_definitions(True):
- for tschema in tschemas:
- if role == 'subject':
- sschema, oschema = eschema, tschema
- else:
- sschema, oschema = tschema, eschema
- self._initfunc(self, sschema, rschema, oschema, role)
+ self.apply(schema, self._initfunc)
+
+ def apply(self, schema, func):
+ for eschema in schema.entities():
+ for rschema, tschemas, role in eschema.relation_definitions(True):
+ for tschema in tschemas:
+ if role == 'subject':
+ sschema, oschema = eschema, tschema
+ else:
+ sschema, oschema = tschema, eschema
+ func(self, sschema, rschema, oschema, role)
# rtag declaration api ####################################################
- def tag_attribute(self, key, tag):
+ def tag_attribute(self, key, *args, **kwargs):
key = list(key)
key.append('*')
- self.tag_subject_of(key, tag)
+ key.append('subject')
+ self.tag_relation(key, *args, **kwargs)
- def tag_subject_of(self, key, tag):
+ def tag_subject_of(self, key, *args, **kwargs):
key = list(key)
key.append('subject')
- self.tag_relation(key, tag)
+ self.tag_relation(key, *args, **kwargs)
- def tag_object_of(self, key, tag):
+ def tag_object_of(self, key, *args, **kwargs):
key = list(key)
key.append('object')
- self.tag_relation(key, tag)
+ self.tag_relation(key, *args, **kwargs)
def tag_relation(self, key, tag):
- #if isinstance(key, basestring):
- # stype, rtype, otype = key.split()
- #else:
- stype, rtype, otype, tagged = [str(k) for k in key]
+ assert len(key) == 4, 'bad key: %s' % list(key)
if self._allowed_values is not None:
assert tag in self._allowed_values, \
'%r is not an allowed tag (should be in %s)' % (
tag, self._allowed_values)
- self._tagdefs[(rtype, tagged, stype, otype)] = tag
+ self._tagdefs[_ensure_str_key(key)] = tag
return tag
# rtag runtime api ########################################################
- def del_rtag(self, stype, rtype, otype, tagged):
- del self._tagdefs[(rtype, tagged, stype, otype)]
+ def del_rtag(self, *key):
+ del self._tagdefs[key]
- def get(self, stype, rtype, otype, tagged):
- for key in reversed(self._get_keys(stype, rtype, otype, tagged)):
+ def get(self, *key):
+ for key in reversed(self._get_keys(*key)):
try:
return self._tagdefs[key]
except KeyError:
@@ -132,8 +138,7 @@
tag_container_cls = set
def tag_relation(self, key, tag):
- stype, rtype, otype, tagged = [str(k) for k in key]
- rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype),
+ rtags = self._tagdefs.setdefault(_ensure_str_key(key),
self.tag_container_cls())
rtags.add(tag)
return rtags
@@ -153,24 +158,24 @@
tag_container_cls = dict
def tag_relation(self, key, tag):
- stype, rtype, otype, tagged = [str(k) for k in key]
+ key = _ensure_str_key(key)
try:
- rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+ rtags = self._tagdefs[key]
rtags.update(tag)
return rtags
except KeyError:
- self._tagdefs[(rtype, tagged, stype, otype)] = tag
+ self._tagdefs[key] = tag
return tag
def setdefault(self, key, tagkey, tagvalue):
- stype, rtype, otype, tagged = [str(k) for k in key]
+ key = _ensure_str_key(key)
try:
- rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+ rtags = self._tagdefs[key]
rtags.setdefault(tagkey, tagvalue)
return rtags
except KeyError:
- self._tagdefs[(rtype, tagged, stype, otype)] = {tagkey: tagvalue}
- return self._tagdefs[(rtype, tagged, stype, otype)]
+ self._tagdefs[key] = {tagkey: tagvalue}
+ return self._tagdefs[key]
class RelationTagsBool(RelationTags):
--- a/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -20,7 +20,8 @@
from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
-from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema
+from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
+ RelationDefinitionSchema, PermissionMixIn
from yams.constraints import (BaseConstraint, StaticVocabularyConstraint,
FormatConstraint)
from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
@@ -31,11 +32,6 @@
import cubicweb
from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized
-# XXX <3.2 bw compat
-from yams import schema
-schema.use_py_datetime()
-nodes.use_py_datetime()
-
PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
@@ -54,6 +50,12 @@
'constrained_by', 'cstrtype',
))
+WORKFLOW_TYPES = set(('Transition', 'State', 'TrInfo', 'Workflow',
+ 'WorkflowTransition', 'BaseTransition',
+ 'SubWorkflowExitPoint'))
+INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri'))
+
+
_LOGGER = getLogger('cubicweb.schemaloader')
# schema entities created from serialized schema have an eid rproperty
@@ -117,7 +119,7 @@
else:
return unicode(req._(key)).lower()
-__builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
+__builtins__['display_name'] = deprecated('[3.4] display_name should be imported from cubicweb.schema')(display_name)
# rql expression utilities function ############################################
@@ -151,15 +153,15 @@
# Schema objects definition ###################################################
-def ERSchema_display_name(self, req, form=''):
+def ERSchema_display_name(self, req, form='', context=None):
"""return a internationalized string for the entity/relation type name in
a given form
"""
- return display_name(req, self.type, form)
+ return display_name(req, self.type, form, context)
ERSchema.display_name = ERSchema_display_name
@cached
-def ERSchema_get_groups(self, action):
+def get_groups(self, action):
"""return the groups authorized to perform <action> on entities of
this type
@@ -172,28 +174,13 @@
assert action in self.ACTIONS, action
#assert action in self._groups, '%s %s' % (self, action)
try:
- return frozenset(g for g in self._groups[action] if isinstance(g, basestring))
+ return frozenset(g for g in self.permissions[action] if isinstance(g, basestring))
except KeyError:
return ()
-ERSchema.get_groups = ERSchema_get_groups
-
-def ERSchema_set_groups(self, action, groups):
- """set the groups allowed to perform <action> on entities of this type. Don't
- change rql expressions for the same action.
-
- :type action: str
- :param action: the name of a permission
-
- :type groups: list or tuple
- :param groups: names of the groups granted to do the given action
- """
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_groups')
- self._groups[action] = tuple(groups) + self.get_rqlexprs(action)
-ERSchema.set_groups = ERSchema_set_groups
+PermissionMixIn.get_groups = get_groups
@cached
-def ERSchema_get_rqlexprs(self, action):
+def get_rqlexprs(self, action):
"""return the rql expressions representing queries to check the user is allowed
to perform <action> on entities of this type
@@ -206,27 +193,13 @@
assert action in self.ACTIONS, action
#assert action in self._rqlexprs, '%s %s' % (self, action)
try:
- return tuple(g for g in self._groups[action] if not isinstance(g, basestring))
+ return tuple(g for g in self.permissions[action] if not isinstance(g, basestring))
except KeyError:
return ()
-ERSchema.get_rqlexprs = ERSchema_get_rqlexprs
-
-def ERSchema_set_rqlexprs(self, action, rqlexprs):
- """set the rql expression allowing to perform <action> on entities of this type. Don't
- change groups for the same action.
-
- :type action: str
- :param action: the name of a permission
+PermissionMixIn.get_rqlexprs = get_rqlexprs
- :type rqlexprs: list or tuple
- :param rqlexprs: the rql expressions allowing the given action
- """
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_rqlexprs')
- self._groups[action] = tuple(self.get_groups(action)) + tuple(rqlexprs)
-ERSchema.set_rqlexprs = ERSchema_set_rqlexprs
-
-def ERSchema_set_permissions(self, action, permissions):
+orig_set_action_permissions = PermissionMixIn.set_action_permissions
+def set_action_permissions(self, action, permissions):
"""set the groups and rql expressions allowing to perform <action> on
entities of this type
@@ -236,22 +209,12 @@
:type permissions: tuple
:param permissions: the groups and rql expressions allowing the given action
"""
- assert action in self.ACTIONS, action
- clear_cache(self, 'ERSchema_get_rqlexprs')
- clear_cache(self, 'ERSchema_get_groups')
- self._groups[action] = tuple(permissions)
-ERSchema.set_permissions = ERSchema_set_permissions
+ orig_set_action_permissions(self, action, tuple(permissions))
+ clear_cache(self, 'get_rqlexprs')
+ clear_cache(self, 'get_groups')
+PermissionMixIn.set_action_permissions = set_action_permissions
-def ERSchema_has_perm(self, session, action, *args, **kwargs):
- """return true if the action is granted globaly or localy"""
- try:
- self.check_perm(session, action, *args, **kwargs)
- return True
- except Unauthorized:
- return False
-ERSchema.has_perm = ERSchema_has_perm
-
-def ERSchema_has_local_role(self, action):
+def has_local_role(self, action):
"""return true if the action *may* be granted localy (eg either rql
expressions or the owners group are used in security definition)
@@ -262,9 +225,83 @@
if self.get_rqlexprs(action):
return True
if action in ('update', 'delete'):
- return self.has_group(action, 'owners')
+ return 'owners' in self.get_groups(action)
return False
-ERSchema.has_local_role = ERSchema_has_local_role
+PermissionMixIn.has_local_role = has_local_role
+
+def may_have_permission(self, action, req):
+ if action != 'read' and not (self.has_local_role('read') or
+ self.has_perm(req, 'read')):
+ return False
+ return self.has_local_role(action) or self.has_perm(req, action)
+PermissionMixIn.may_have_permission = may_have_permission
+
+def has_perm(self, session, action, **kwargs):
+ """return true if the action is granted globaly or localy"""
+ try:
+ self.check_perm(session, action, **kwargs)
+ return True
+ except Unauthorized:
+ return False
+PermissionMixIn.has_perm = has_perm
+
+def check_perm(self, session, action, **kwargs):
+ # NB: session may be a server session or a request object check user is
+ # in an allowed group, if so that's enough internal sessions should
+ # always stop there
+ groups = self.get_groups(action)
+ if session.user.matching_groups(groups):
+ return
+ # if 'owners' in allowed groups, check if the user actually owns this
+ # object, if so that's enough
+ if 'owners' in groups and 'eid' in kwargs and session.user.owns(kwargs['eid']):
+ return
+ # else if there is some rql expressions, check them
+ if any(rqlexpr.check(session, **kwargs)
+ for rqlexpr in self.get_rqlexprs(action)):
+ return
+ raise Unauthorized(action, str(self))
+PermissionMixIn.check_perm = check_perm
+
+
+RelationDefinitionSchema._RPROPERTIES['eid'] = None
+
+def rql_expression(self, expression, mainvars=None, eid=None):
+ """rql expression factory"""
+ if self.rtype.final:
+ return ERQLExpression(expression, mainvars, eid)
+ return RRQLExpression(expression, mainvars, eid)
+RelationDefinitionSchema.rql_expression = rql_expression
+
+orig_check_permission_definitions = RelationDefinitionSchema.check_permission_definitions
+def check_permission_definitions(self):
+ orig_check_permission_definitions(self)
+ schema = self.subject.schema
+ for action, groups in self.permissions.iteritems():
+ for group_or_rqlexpr in groups:
+ if action == 'read' and \
+ isinstance(group_or_rqlexpr, RQLExpression):
+ msg = "can't use rql expression for read permission of %s"
+ raise BadSchemaDefinition(msg % self)
+ elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
+ if schema.reading_from_database:
+ # we didn't have final relation earlier, so turn
+ # RRQLExpression into ERQLExpression now
+ rqlexpr = group_or_rqlexpr
+ newrqlexprs = [x for x in self.get_rqlexprs(action)
+ if not x is rqlexpr]
+ newrqlexprs.append(ERQLExpression(rqlexpr.expression,
+ rqlexpr.mainvars,
+ rqlexpr.eid))
+ self.set_rqlexprs(action, newrqlexprs)
+ else:
+ msg = "can't use RRQLExpression on %s, use an ERQLExpression"
+ raise BadSchemaDefinition(msg % self)
+ elif not self.final and \
+ isinstance(group_or_rqlexpr, ERQLExpression):
+ msg = "can't use ERQLExpression on %s, use a RRQLExpression"
+ raise BadSchemaDefinition(msg % self)
+RelationDefinitionSchema.check_permission_definitions = check_permission_definitions
class CubicWebEntitySchema(EntitySchema):
@@ -278,8 +315,8 @@
eid = getattr(edef, 'eid', None)
self.eid = eid
# take care: no _groups attribute when deep-copying
- if getattr(self, '_groups', None):
- for groups in self._groups.itervalues():
+ if getattr(self, 'permissions', None):
+ for groups in self.permissions.itervalues():
for group_or_rqlexpr in groups:
if isinstance(group_or_rqlexpr, RRQLExpression):
msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)"
@@ -326,7 +363,7 @@
if rschema.final:
if rschema == 'has_text':
has_has_text = True
- elif self.rproperty(rschema, 'fulltextindexed'):
+ elif self.rdef(rschema).get('fulltextindexed'):
may_need_has_text = True
elif rschema.fulltext_container:
if rschema.fulltext_container == 'subject':
@@ -351,32 +388,12 @@
"""return True if this entity type is used to build the schema"""
return self.type in SCHEMA_TYPES
- def check_perm(self, session, action, eid=None):
- # NB: session may be a server session or a request object
- user = session.user
- # check user is in an allowed group, if so that's enough
- # internal sessions should always stop there
- if user.matching_groups(self.get_groups(action)):
- return
- # if 'owners' in allowed groups, check if the user actually owns this
- # object, if so that's enough
- if eid is not None and 'owners' in self.get_groups(action) and \
- user.owns(eid):
- return
- # else if there is some rql expressions, check them
- if any(rqlexpr.check(session, eid)
- for rqlexpr in self.get_rqlexprs(action)):
- return
- raise Unauthorized(action, str(self))
-
def rql_expression(self, expression, mainvars=None, eid=None):
"""rql expression factory"""
return ERQLExpression(expression, mainvars, eid)
class CubicWebRelationSchema(RelationSchema):
- RelationSchema._RPROPERTIES['eid'] = None
- _perms_checked = False
def __init__(self, schema=None, rdef=None, eid=None, **kwargs):
if rdef is not None:
@@ -391,73 +408,53 @@
def meta(self):
return self.type in META_RTYPES
- def update(self, subjschema, objschema, rdef):
- super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef)
- if not self._perms_checked and self._groups:
- for action, groups in self._groups.iteritems():
- for group_or_rqlexpr in groups:
- if action == 'read' and \
- isinstance(group_or_rqlexpr, RQLExpression):
- msg = "can't use rql expression for read permission of "\
- "a relation type (%s)"
- raise BadSchemaDefinition(msg % self.type)
- elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
- if self.schema.reading_from_database:
- # we didn't have final relation earlier, so turn
- # RRQLExpression into ERQLExpression now
- rqlexpr = group_or_rqlexpr
- newrqlexprs = [x for x in self.get_rqlexprs(action) if not x is rqlexpr]
- newrqlexprs.append(ERQLExpression(rqlexpr.expression,
- rqlexpr.mainvars,
- rqlexpr.eid))
- self.set_rqlexprs(action, newrqlexprs)
- else:
- msg = "can't use RRQLExpression on a final relation "\
- "type (eg attribute relation), use an ERQLExpression (%s)"
- raise BadSchemaDefinition(msg % self.type)
- elif not self.final and \
- isinstance(group_or_rqlexpr, ERQLExpression):
- msg = "can't use ERQLExpression on a relation type, use "\
- "a RRQLExpression (%s)"
- raise BadSchemaDefinition(msg % self.type)
- self._perms_checked = True
-
- def cardinality(self, subjtype, objtype, target):
- card = self.rproperty(subjtype, objtype, 'cardinality')
- return (target == 'subject' and card[0]) or \
- (target == 'object' and card[1])
-
def schema_relation(self):
"""return True if this relation type is used to build the schema"""
return self.type in SCHEMA_TYPES
- def physical_mode(self):
- """return an appropriate mode for physical storage of this relation type:
- * 'subjectinline' if every possible subject cardinalities are 1 or ?
- * 'objectinline' if 'subjectinline' mode is not possible but every
- possible object cardinalities are 1 or ?
- * None if neither 'subjectinline' and 'objectinline'
- """
- assert not self.final
- return self.inlined and 'subjectinline' or None
+ def may_have_permission(self, action, req, eschema=None, role=None):
+ if eschema is not None:
+ for tschema in self.targets(eschema, role):
+ rdef = self.role_rdef(eschema, tschema, role)
+ if rdef.may_have_permission(action, req):
+ return True
+ else:
+ for rdef in self.rdefs.itervalues():
+ if rdef.may_have_permission(action, req):
+ return True
+ return False
- def check_perm(self, session, action, *args, **kwargs):
- # NB: session may be a server session or a request object check user is
- # in an allowed group, if so that's enough internal sessions should
- # always stop there
- if session.user.matching_groups(self.get_groups(action)):
- return
- # else if there is some rql expressions, check them
- if any(rqlexpr.check(session, *args, **kwargs)
- for rqlexpr in self.get_rqlexprs(action)):
- return
- raise Unauthorized(action, str(self))
+ def has_perm(self, session, action, **kwargs):
+ """return true if the action is granted globaly or localy"""
+ if 'fromeid' in kwargs:
+ subjtype = session.describe(kwargs['fromeid'])[0]
+ else:
+ subjtype = None
+ if 'toeid' in kwargs:
+ objtype = session.describe(kwargs['toeid'])[0]
+ else:
+ objtype = None
+ if objtype and subjtype:
+ return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
+ elif subjtype:
+ for tschema in self.targets(subjtype, 'subject'):
+ rdef = self.rdef(subjtype, tschema)
+ if not rdef.has_perm(session, action, **kwargs):
+ return False
+ elif objtype:
+ for tschema in self.targets(objtype, 'object'):
+ rdef = self.rdef(tschema, objtype)
+ if not rdef.has_perm(session, action, **kwargs):
+ return False
+ else:
+ for rdef in self.rdefs.itervalues():
+ if not rdef.has_perm(session, action, **kwargs):
+ return False
+ return True
- def rql_expression(self, expression, mainvars=None, eid=None):
- """rql expression factory"""
- if self.final:
- return ERQLExpression(expression, mainvars, eid)
- return RRQLExpression(expression, mainvars, eid)
+ @deprecated('use .rdef(subjtype, objtype).role_cardinality(role)')
+ def cardinality(self, subjtype, objtype, target):
+ return self.rdef(subjtype, objtype).role_cardinality(target)
class CubicWebSchema(Schema):
@@ -482,13 +479,10 @@
ybo.register_base_types(self)
rschema = self.add_relation_type(ybo.RelationType('eid'))
rschema.final = True
- rschema.set_default_groups()
rschema = self.add_relation_type(ybo.RelationType('has_text'))
rschema.final = True
- rschema.set_default_groups()
rschema = self.add_relation_type(ybo.RelationType('identity'))
rschema.final = False
- rschema.set_default_groups()
def add_entity_type(self, edef):
edef.name = edef.name.encode()
@@ -530,13 +524,13 @@
rdef.name = rdef.name.lower()
rdef.subject = bw_normalize_etype(rdef.subject)
rdef.object = bw_normalize_etype(rdef.object)
- if super(CubicWebSchema, self).add_relation_def(rdef):
+ rdefs = super(CubicWebSchema, self).add_relation_def(rdef)
+ if rdefs:
try:
- self._eid_index[rdef.eid] = (self.eschema(rdef.subject),
- self.rschema(rdef.name),
- self.eschema(rdef.object))
+ self._eid_index[rdef.eid] = rdefs
except AttributeError:
pass # not a serialized schema
+ return rdefs
def del_relation_type(self, rtype):
rschema = self.rschema(rtype)
@@ -545,7 +539,9 @@
def del_relation_def(self, subjtype, rtype, objtype):
for k, v in self._eid_index.items():
- if v == (subjtype, rtype, objtype):
+ if not isinstance(v, RelationDefinitionSchema):
+ continue
+ if v.subject == subjtype and v.rtype == rtype and v.object == objtype:
del self._eid_index[k]
break
super(CubicWebSchema, self).del_relation_def(subjtype, rtype, objtype)
@@ -730,6 +726,11 @@
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.full_rql)
+ def __cmp__(self, other):
+ if hasattr(other, 'expression'):
+ return cmp(other.expression, self.expression)
+ return -1
+
def __deepcopy__(self, memo):
return self.__class__(self.expression, self.mainvars)
def __getstate__(self):
@@ -831,7 +832,7 @@
for eaction, var, col in has_perm_defs:
for i in xrange(len(rset)):
eschema = get_eschema(rset.description[i][col])
- eschema.check_perm(session, eaction, rset[i][col])
+ eschema.check_perm(session, eaction, eid=rset[i][col])
if self.eid is not None:
session.local_perm_cache[key] = True
return True
@@ -1036,12 +1037,12 @@
@monkeypatch(FormatConstraint)
def vocabulary(self, entity=None, form=None):
- req = None
+ cw = None
if form is None and entity is not None:
- req = entity.req
+ cw = entity._cw
elif form is not None:
- req = form.req
- if req is not None and req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
+ cw = form._cw
+ if cw is not None and cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
return self.regular_formats + tuple(NEED_PERM_FORMATS)
return self.regular_formats
@@ -1076,10 +1077,8 @@
# XXX deprecated
-from yams.constraints import format_constraint
from yams.buildobjs import RichString
PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
-PyFileReader.context['format_constraint'] = format_constraint
--- a/schemas/Bookmark.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schemas/Bookmark.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
class Bookmark(EntityType):
"""bookmarks are used to have user's specific internal links"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users',),
'delete': ('managers', 'owners',),
@@ -29,7 +29,7 @@
class bookmarked_by(RelationType):
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
# test user in users group to avoid granting permission to anonymous user
'add': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
'delete': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
--- a/schemas/_regproc.postgres.sql Mon Feb 08 13:33:09 2010 +0100
+++ b/schemas/_regproc.postgres.sql Mon Feb 08 13:35:07 2010 +0100
@@ -1,7 +1,7 @@
-/* -*- sql -*-
+/* -*- sql -*-
- postgres specific registered procedures,
- require the plpgsql language installed
+ postgres specific registered procedures,
+ require the plpgsql language installed
*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/_regproc_bss.postgres.sql Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,38 @@
+/* -*- sql -*-
+
+ postgres specific registered procedures for the Bytes File System storage,
+ require the plpythonu language installed
+
+*/
+
+
+CREATE OR REPLACE FUNCTION _fsopen(bytea) RETURNS bytea AS $$
+ fpath = args[0]
+ if fpath:
+ try:
+ data = file(fpath, 'rb').read()
+ #/* XXX due to plpython bug we have to replace some characters... */
+ return data.replace("\\", r"\134").replace("\000", r"\000").replace("'", r"\047") #'
+ except Exception, ex:
+ plpy.warning('failed to get content for %s: %s', fpath, ex)
+ return None
+$$ LANGUAGE plpythonu
+/* WITH(ISCACHABLE) XXX does postgres handle caching of large data nicely */
+;;
+
+/* fspath(eid, entity type, attribute) */
+CREATE OR REPLACE FUNCTION fspath(bigint, text, text) RETURNS bytea AS $$
+ pkey = 'plan%s%s' % (args[1], args[2])
+ try:
+ plan = SD[pkey]
+ except KeyError:
+ #/* then prepare and cache plan to get versioned file information from a
+ # version content eid */
+ plan = plpy.prepare(
+ 'SELECT X.cw_%s FROM cw_%s as X WHERE X.cw_eid=$1' % (args[2], args[1]),
+ ['bigint'])
+ SD[pkey] = plan
+ return plpy.execute(plan, [args[0]])[0]
+$$ LANGUAGE plpythonu
+/* WITH(ISCACHABLE) XXX does postgres handle caching of large data nicely */
+;;
--- a/schemas/base.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schemas/base.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,7 +16,7 @@
class CWUser(WorkflowableEntityType):
"""define a CubicWeb user"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', ERQLExpression('X identity U')),
'add': ('managers',),
'delete': ('managers',),
@@ -42,7 +42,7 @@
class EmailAddress(EntityType):
"""an electronic mail address associated to a short alias"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',), # XXX if P use_email X, U has_read_permission P
'add': ('managers', 'users',),
'delete': ('managers', 'owners', ERQLExpression('P use_email X, U has_update_permission P')),
@@ -59,7 +59,7 @@
class use_email(RelationType):
""" """
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', RRQLExpression('U has_update_permission S'),),
'delete': ('managers', RRQLExpression('U has_update_permission S'),),
@@ -68,12 +68,12 @@
class primary_email(RelationType):
"""the prefered email"""
- permissions = use_email.permissions
+ __permissions__ = use_email.__permissions__
class prefered_form(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
- # XXX should have update permissions on both subject and object,
+ # XXX should have update __permissions__ on both subject and object,
# though by doing this we will probably have no way to add
# this relation in the web ui. The easiest way to acheive this
# is probably to be able to have "U has_update_permission O" as
@@ -85,13 +85,13 @@
class in_group(RelationType):
"""core relation indicating a user's groups"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class owned_by(RelationType):
"""core relation indicating owners of an entity. This relation
implicitly put the owner into the owners group for the entity
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('S owned_by U'),),
'delete': ('managers', RRQLExpression('S owned_by U'),),
@@ -104,7 +104,7 @@
class created_by(RelationType):
"""core relation indicating the original creator of an entity"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -135,46 +135,27 @@
object = 'String'
-class CWProperty(EntityType):
- """used for cubicweb configuration. Once a property has been created you
- can't change the key.
- """
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', 'users',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners',),
- }
- # key is a reserved word for mysql
- pkey = String(required=True, internationalizable=True, maxsize=256,
- description=_('defines what\'s the property is applied for. '
- 'You must select this first to be able to set '
- 'value'))
- value = String(internationalizable=True, maxsize=256)
-
- for_user = SubjectRelation('CWUser', cardinality='?*', composite='object',
- description=_('user for which this property is '
- 'applying. If this relation is not '
- 'set, the property is considered as'
- ' a global property'))
-
-
+# XXX find a better relation name
class for_user(RelationType):
"""link a property to the user which want this property customization. Unless
you're a site manager, this relation will be handled automatically.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
}
inlined = True
+ subject = 'CWProperty'
+ object = 'CWUser'
+ composite = 'object'
+ cardinality = '?*'
class CWPermission(EntityType):
"""entity type that may be used to construct some advanced security configuration
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
description=_('name or identifier of the permission'))
@@ -189,7 +170,7 @@
"""link a permission to the entity. This permission should be used in the
security definition of the entity's type to be useful.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -197,7 +178,7 @@
class require_group(RelationType):
"""used to grant a permission to a group"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
@@ -217,13 +198,13 @@
NOTE: You'll have to explicitly declare which entity types can have a
same_as relation
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users'),
'delete': ('managers', 'owners'),
}
cardinality = '*1'
- symetric = True
+ symmetric = True
# NOTE: the 'object = ExternalUri' declaration will still be mandatory
# in the cube's schema.
object = 'ExternalUri'
@@ -237,7 +218,7 @@
Also, checkout the AppObject.get_cache() method.
"""
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'update': ('managers', 'users',), # XXX
@@ -253,10 +234,10 @@
class identical_to(RelationType):
"""identical to"""
- symetric = True
- permissions = {
+ symmetric = True
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
- # XXX should have update permissions on both subject and object,
+ # XXX should have update __permissions__ on both subject and object,
# though by doing this we will probably have no way to add
# this relation in the web ui. The easiest way to acheive this
# is probably to be able to have "U has_update_permission O" as
@@ -268,8 +249,8 @@
class see_also(RelationType):
"""generic relation to link one entity to another"""
- symetric = True
- permissions = {
+ symmetric = True
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', RRQLExpression('U has_update_permission S'),),
'delete': ('managers', RRQLExpression('U has_update_permission S'),),
--- a/schemas/bootstrap.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schemas/bootstrap.py Mon Feb 08 13:35:07 2010 +0100
@@ -17,7 +17,7 @@
# access to this
class CWEType(EntityType):
"""define an entity type, used to build the instance schema"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
description = RichString(internationalizable=True,
@@ -28,12 +28,12 @@
class CWRType(EntityType):
"""define a relation type, used to build the instance schema"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
description = RichString(internationalizable=True,
description=_('semantic description of this relation type'))
- symetric = Boolean(description=_('is this relation equivalent in both direction ?'))
+ symmetric = Boolean(description=_('is this relation equivalent in both direction ?'))
inlined = Boolean(description=_('is this relation physically inlined? you should know what you\'re doing if you are changing this!'))
fulltext_container = String(description=_('if full text content of subject/object entity '
'should be added to other side entity (the container).'),
@@ -48,7 +48,7 @@
used to build the instance schema
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final TRUE')],
composite='object')
@@ -85,7 +85,7 @@
used to build the instance schema
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final FALSE')],
composite='object')
@@ -115,8 +115,8 @@
# not restricted since it has to be read when checking allowed transitions
class RQLExpression(EntityType):
- """define a rql expression used to define permissions"""
- permissions = META_ETYPE_PERMS
+ """define a rql expression used to define __permissions__"""
+ __permissions__ = META_ETYPE_PERMS
exprtype = String(required=True, vocabulary=['ERQLExpression', 'RRQLExpression'])
mainvars = String(maxsize=8,
description=_('name of the main variables which should be '
@@ -131,11 +131,11 @@
'relation\'subject, object and to '
'the request user. '))
- read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+?', composite='subject',
+ read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
description=_('rql expression allowing to read entities/relations of this type'))
- add_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+ add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
description=_('rql expression allowing to add entities/relations of this type'))
- delete_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='*?', composite='subject',
+ delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
description=_('rql expression allowing to delete entities/relations of this type'))
update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject',
description=_('rql expression allowing to update entities of this type'))
@@ -143,14 +143,14 @@
class CWConstraint(EntityType):
"""define a schema constraint"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
value = String(description=_('depends on the constraint type'))
class CWConstraintType(EntityType):
"""define a schema constraint type"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
@@ -158,67 +158,84 @@
# not restricted since it has to be read when checking allowed transitions
class CWGroup(EntityType):
"""define a CubicWeb users group"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
- read_permission = ObjectRelation(('CWEType', 'CWRType'), cardinality='+*',
+ read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='**',
description=_('groups allowed to read entities/relations of this type'))
- add_permission = ObjectRelation(('CWEType', 'CWRType'),
+ add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
description=_('groups allowed to add entities/relations of this type'))
- delete_permission = ObjectRelation(('CWEType', 'CWRType'),
+ delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
description=_('groups allowed to delete entities/relations of this type'))
update_permission = ObjectRelation('CWEType',
description=_('groups allowed to update entities of this type'))
+class CWProperty(EntityType):
+ """used for cubicweb configuration. Once a property has been created you
+ can't change the key.
+ """
+ __permissions__ = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', 'users',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners',),
+ }
+ # key is a reserved word for mysql
+ pkey = String(required=True, internationalizable=True, maxsize=256,
+ description=_('defines what\'s the property is applied for. '
+ 'You must select this first to be able to set '
+ 'value'))
+ value = String(internationalizable=True, maxsize=256)
+
class relation_type(RelationType):
"""link a relation definition to its relation type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class from_entity(RelationType):
"""link a relation definition to its subject entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class to_entity(RelationType):
"""link a relation definition to its object entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class constrained_by(RelationType):
"""constraints applying on this relation"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class cstrtype(RelationType):
"""constraint factory"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class read_permission(RelationType):
"""core relation giving to a group the permission to read an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class add_permission(RelationType):
"""core relation giving to a group the permission to add an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class delete_permission(RelationType):
"""core relation giving to a group the permission to delete an entity or
relation type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class update_permission(RelationType):
"""core relation giving to a group the permission to update an entity type
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class is_(RelationType):
@@ -227,7 +244,7 @@
name = 'is'
# don't explicitly set composite here, this is handled anyway
#composite = 'object'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': (),
'delete': (),
@@ -242,7 +259,7 @@
"""
# don't explicitly set composite here, this is handled anyway
#composite = 'object'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': (),
'delete': (),
@@ -253,7 +270,7 @@
class specializes(RelationType):
name = 'specializes'
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
'delete': ('managers',),
--- a/schemas/workflow.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schemas/workflow.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,7 +15,7 @@
HOOKS_RTYPE_PERMS)
class Workflow(EntityType):
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
@@ -34,7 +34,7 @@
class default_workflow(RelationType):
"""default workflow for an entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
subject = 'CWEType'
object = 'Workflow'
@@ -47,7 +47,7 @@
"""used to associate simple states to an entity type and/or to define
workflows
"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256,
@@ -70,7 +70,7 @@
class BaseTransition(EntityType):
"""abstract base class for transitions"""
- permissions = META_ETYPE_PERMS
+ __permissions__ = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256,
@@ -140,7 +140,7 @@
class TrInfo(EntityType):
"""workflow history item"""
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',), # XXX U has_read_permission O ?
'add': ('managers', 'users', 'guests',),
'delete': (), # XXX should we allow managers to delete TrInfo?
@@ -156,20 +156,16 @@
# get actor and date time using owned_by and creation_date
class from_state(RelationType):
- permissions = HOOKS_RTYPE_PERMS.copy()
+ __permissions__ = HOOKS_RTYPE_PERMS.copy()
inlined = True
class to_state(RelationType):
- permissions = {
- 'read': ('managers', 'users', 'guests',),
- 'add': ('managers',),
- 'delete': (),
- }
+ __permissions__ = HOOKS_RTYPE_PERMS.copy()
inlined = True
class by_transition(RelationType):
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users', 'guests',),
'delete': (),
@@ -178,52 +174,54 @@
class workflow_of(RelationType):
"""link a workflow to one or more entity type"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class state_of(RelationType):
"""link a state to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
+ inlined = True
class transition_of(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
+ inlined = True
class subworkflow(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class exit_point(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
class subworkflow_state(RelationType):
"""link a transition to one or more workflow"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class initial_state(RelationType):
"""indicate which state should be used by default when an entity using
states is created
"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class destination_state(RelationType):
"""destination state of a transition"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
inlined = True
class allowed_transition(RelationType):
"""allowed transition from this state"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
# "abstract" relations, set by WorkflowableEntityType ##########################
class custom_workflow(RelationType):
"""allow to set a specific workflow for an entity"""
- permissions = META_RTYPE_PERMS
+ __permissions__ = META_RTYPE_PERMS
cardinality = '?*'
constraints = [RQLConstraint('S is ET, O workflow_of ET',
@@ -234,7 +232,7 @@
class wf_info_for(RelationType):
"""link a transition information to its object"""
# 'add' security actually done by hooks
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests',),
'add': ('managers', 'users', 'guests',),
'delete': (),
@@ -249,7 +247,7 @@
class in_state(RelationType):
"""indicate the current state of an entity"""
- permissions = HOOKS_RTYPE_PERMS
+ __permissions__ = HOOKS_RTYPE_PERMS
# not inlined intentionnaly since when using ldap sources, user'state
# has to be stored outside the CWUser table
--- a/schemaviewer.py Mon Feb 08 13:33:09 2010 +0100
+++ b/schemaviewer.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,10 +9,13 @@
_ = unicode
from logilab.common.ureports import Section, Title, Table, Link, Span, Text
+
from yams.schema2dot import CARD_MAP
+from yams.schema import RelationDefinitionSchema
I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
+
class SchemaViewer(object):
"""return an ureport layout for some part of a schema"""
def __init__(self, req=None, encoding=None):
@@ -68,7 +71,8 @@
_ = self.req._
data = [_('attribute'), _('type'), _('default'), _('constraints')]
for rschema, aschema in eschema.attribute_definitions():
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ rdef = eschema.rdef(rschema)
+ if not rdef.may_have_permission('read', self.req):
continue
aname = rschema.type
if aname == 'eid':
@@ -78,7 +82,7 @@
defaultval = eschema.default(aname)
if defaultval is not None:
default = self.to_string(defaultval)
- elif eschema.rproperty(rschema, 'cardinality')[0] == '1':
+ elif rdef.cardinality[0] == '1':
default = _('required field')
else:
default = ''
@@ -119,20 +123,23 @@
t_vars = []
rels = []
first = True
- for rschema, targetschemas, x in eschema.relation_definitions():
+ for rschema, targetschemas, role in eschema.relation_definitions():
if rschema.type in skiptypes:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
- continue
rschemaurl = self.rschema_link_url(rschema)
for oeschema in targetschemas:
+ rdef = rschema.role_rdef(eschema, oeschema, role)
+ if not rdef.may_have_permission('read', self.req):
+ continue
label = rschema.type
- if x == 'subject':
+ if role == 'subject':
cards = rschema.rproperty(eschema, oeschema, 'cardinality')
else:
cards = rschema.rproperty(oeschema, eschema, 'cardinality')
cards = cards[::-1]
- label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label, display_name(self.req, label, x), CARD_MAP[cards[0]])
+ label = '%s %s (%s) %s' % (CARD_MAP[cards[1]], label,
+ display_name(self.req, label, role),
+ CARD_MAP[cards[0]])
rlink = Link(rschemaurl, label)
elink = Link(self.eschema_link_url(oeschema), oeschema.type)
if first:
@@ -165,8 +172,8 @@
stereotypes = []
if rschema.meta:
stereotypes.append('meta')
- if rschema.symetric:
- stereotypes.append('symetric')
+ if rschema.symmetric:
+ stereotypes.append('symmetric')
if rschema.inlined:
stereotypes.append('inlined')
title = Section(children=(title, ' (%s)'%rschema.display_name(self.req)), klass='title')
@@ -180,7 +187,7 @@
rschema_objects = rschema.objects()
if rschema_objects:
# might be empty
- properties = [p for p in rschema.rproperty_defs(rschema_objects[0])
+ properties = [p for p in RelationDefinitionSchema.rproperty_defs(rschema_objects[0])
if not p in ('cardinality', 'composite', 'eid')]
else:
properties = []
@@ -192,12 +199,13 @@
if (subjtype, objtype) in done:
continue
done.add((subjtype, objtype))
- if rschema.symetric:
+ if rschema.symmetric:
done.add((objtype, subjtype))
data.append(Link(self.eschema_link_url(schema[subjtype]), subjtype))
data.append(Link(self.eschema_link_url(schema[objtype]), objtype))
+ rdef = rschema.rdef(subjtype, objtype)
for prop in properties:
- val = rschema.rproperty(subjtype, objtype, prop)
+ val = getattr(rdef, prop)
if val is None:
val = ''
elif isinstance(val, (list, tuple)):
@@ -209,8 +217,8 @@
data.append(Text(val))
table = Table(cols=cols, rheaders=1, children=data, klass='listing')
layout.append(Section(children=(table,), klass='relationDefinition'))
- if not self.req.cnx.anonymous_connection:
- layout.append(self.format_acls(rschema, ('read', 'add', 'delete')))
+ #if self.req.user.matching_groups('managers'):
+ # layout.append(self.format_acls(rschema, ('read', 'add', 'delete')))
layout.append(Section(children='', klass='clear'))
return layout
--- a/selectors.py Mon Feb 08 13:33:09 2010 +0100
+++ b/selectors.py Mon Feb 08 13:35:07 2010 +0100
@@ -45,8 +45,8 @@
import logging
from warnings import warn, filterwarnings
+from logilab.common.deprecation import class_renamed
from logilab.common.compat import all, any
-from logilab.common.deprecation import deprecated
from logilab.common.interface import implements as implements_iface
from yams import BASE_TYPES
@@ -55,6 +55,7 @@
role, typed_eid)
# even if not used, let yes here so it's importable through this module
from cubicweb.appobject import Selector, objectify_selector, yes
+from cubicweb.vregistry import class_regid
from cubicweb.cwconfig import CubicWebConfiguration
from cubicweb.schema import split_expression
@@ -75,13 +76,14 @@
else:
selname = selector.__name__
vobj = cls
- oid = vobj.id
+ oid = class_regid(vobj)
ret = selector(cls, *args, **kwargs)
if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
#SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
- print '%s -> %s for %s' % (selname, ret, vobj)
+ print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__)
return ret
traced.__name__ = selector.__name__
+ traced.__doc__ = selector.__doc__
return traced
class traced_selection(object):
@@ -113,13 +115,13 @@
return traceback is None
-def score_interface(cls_or_inst, cls, iface):
+def score_interface(etypesreg, cls_or_inst, cls, iface):
"""Return XXX if the give object (maybe an instance or class) implements
the interface.
"""
if getattr(iface, '__registry__', None) == 'etypes':
# adjust score if the interface is an entity class
- parents = cls_or_inst.parent_classes()
+ parents = etypesreg.parent_classes(cls_or_inst.__regid__)
if iface is cls:
return len(parents) + 4
if iface is parents[-1]: # Any
@@ -134,7 +136,7 @@
return 0
-# abstract selectors ##########################################################
+# abstract selectors / mixin helpers ###########################################
class PartialSelectorMixIn(object):
"""convenience mix-in for selectors that will look into the containing
@@ -158,32 +160,44 @@
return '%s(%s)' % (self.__class__.__name__,
','.join(str(s) for s in self.expected_ifaces))
- def score_interfaces(self, cls_or_inst, cls):
+ def score_interfaces(self, req, cls_or_inst, cls):
score = 0
- vreg, eschema = cls_or_inst.vreg, cls_or_inst.e_schema
+ etypesreg = req.vreg['etypes']
+ eschema = cls_or_inst.e_schema
for iface in self.expected_ifaces:
if isinstance(iface, basestring):
# entity type
try:
- iface = vreg['etypes'].etype_class(iface)
+ iface = etypesreg.etype_class(iface)
except KeyError:
continue # entity type not in the schema
- score += score_interface(cls_or_inst, cls, iface)
+ score += score_interface(etypesreg, cls_or_inst, cls, iface)
return score
class EClassSelector(Selector):
- """abstract class for selectors working on the entity classes of the result
- set. Its __call__ method has the following behaviour:
+ """abstract class for selectors working on *entity class(es)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity's class
- * if row is specified, return the score returned by the score_class method
- called with the entity class found in the specified cell
- * else return the sum of score returned by the score_class method for each
- entity type found in the specified column, unless:
+ * elif `row` is specified, return score for the class of the entity
+ found in the specified cell, using column specified by `col` or 0
+
+ * else return the sum of scores for each entity class found in the column
+ specified specified by the `col` argument or in column 0 if not specified,
+ unless:
+
+ - `once_is_enough` is False (the default) and some entity class is scored
+ to 0, in which case 0 is returned
+
- `once_is_enough` is True, in which case the first non-zero score is
returned
- - `once_is_enough` is False, in which case if score_class return 0, 0 is
- returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
"""
def __init__(self, once_is_enough=False, accept_none=True):
self.once_is_enough = once_is_enough
@@ -191,6 +205,8 @@
@lltrace
def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if kwargs.get('entity'):
+ return self.score_class(kwargs['entity'].__class__, req)
if not rset:
return 0
score = 0
@@ -216,29 +232,42 @@
def score(self, cls, req, etype):
if etype in BASE_TYPES:
return 0
- return self.score_class(cls.vreg['etypes'].etype_class(etype), req)
+ return self.score_class(req.vreg['etypes'].etype_class(etype), req)
def score_class(self, eclass, req):
raise NotImplementedError()
class EntitySelector(EClassSelector):
- """abstract class for selectors working on the entity instances of the
- result set. Its __call__ method has the following behaviour:
+ """abstract class for selectors working on *entity instance(s)* specified
+ explicitly or found of the result set.
+
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, return score for this entity
- * if 'entity' find in kwargs, return the score returned by the score_entity
- method for this entity
- * if row is specified, return the score returned by the score_entity method
- called with the entity instance found in the specified cell
- * else return the sum of score returned by the score_entity method for each
- entity found in the specified column, unless:
+ * elif `row` is specified, return score for the entity found in the
+ specified cell, using column specified by `col` or 0
+
+ * else return the sum of scores for each entity found in the column
+ specified specified by the `col` argument or in column 0 if not specified,
+ unless:
+
+ - `once_is_enough` is False (the default) and some entity is scored
+ to 0, in which case 0 is returned
+
- `once_is_enough` is True, in which case the first non-zero score is
returned
- - `once_is_enough` is False, in which case if score_class return 0, 0 is
- returned
+
+ - `accept_none` is False and some cell in the column has a None value
+ (this may occurs with outer join)
- note: None values (resulting from some outer join in the query) are not
- considered.
+ .. note::
+ using EntitySelector or EClassSelector as base selector class impacts
+ performance, since when no entity or row is specified the later works on
+ every different *entity class* found in the result set, while the former
+ works on each *entity* (eg each row of the result set), which may be much
+ more costly.
"""
@lltrace
@@ -252,6 +281,8 @@
col = col or 0
for row, rowvalue in enumerate(rset.rows):
if rowvalue[col] is None: # outer join
+ if not self.accept_none:
+ return 0
continue
escore = self.score(req, rset, row, col)
if not escore and not self.once_is_enough:
@@ -276,174 +307,9 @@
raise NotImplementedError()
-# very basic selectors ########################################################
-
-@objectify_selector
-@lltrace
-def none_rset(cls, req, rset=None, **kwargs):
- """accept no result set (e.g. given rset is None)"""
- if rset is None:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def any_rset(cls, req, rset=None, **kwargs):
- """accept result set, whatever the number of result it contains"""
- if rset is not None:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def nonempty_rset(cls, req, rset=None, **kwargs):
- """accept any non empty result set"""
- if rset is not None and rset.rowcount:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def empty_rset(cls, req, rset=None, **kwargs):
- """accept empty result set"""
- if rset is not None and rset.rowcount == 0:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def one_line_rset(cls, req, rset=None, row=None, **kwargs):
- """if row is specified, accept result set with a single line of result,
- else accepts anyway
- """
- if rset is not None and (row is not None or rset.rowcount == 1):
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def two_lines_rset(cls, req, rset=None, **kwargs):
- """accept result set with *at least* two lines of result"""
- if rset is not None and rset.rowcount > 1:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def two_cols_rset(cls, req, rset=None, **kwargs):
- """accept result set with at least one line and two columns of result"""
- if rset is not None and rset.rowcount and len(rset.rows[0]) > 1:
- return 1
- return 0
-
-@objectify_selector
-@lltrace
-def paginated_rset(cls, req, rset=None, **kwargs):
- """accept result set with more lines than the page size.
-
- Page size is searched in (respecting order):
- * a page_size argument
- * a page_size form parameters
- * the navigation.page-size property
- """
- page_size = kwargs.get('page_size')
- if page_size is None:
- page_size = req.form.get('page_size')
- if page_size is None:
- page_size = req.property_value('navigation.page-size')
- else:
- page_size = int(page_size)
- if rset is None or rset.rowcount <= page_size:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def sorted_rset(cls, req, rset=None, **kwargs):
- """accept sorted result set"""
- rqlst = rset.syntax_tree()
- if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
- return 0
- return 2
-
-@objectify_selector
-@lltrace
-def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
- """accept result set where entities in the specified column (or 0) are all
- of the same type
- """
- if rset is None:
- return 0
- if len(rset.column_types(col)) != 1:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def two_etypes_rset(cls, req, rset=None, col=0, **kwargs):
- """accept result set where entities in the specified column (or 0) are not
- of the same type
- """
- if rset:
- etypes = rset.column_types(col)
- if len(etypes) > 1:
- return 1
- return 0
-
-class non_final_entity(EClassSelector):
- """accept if entity type found in the result set is non final.
-
- See `EClassSelector` documentation for behaviour when row is not specified.
- """
- def score(self, cls, req, etype):
- if etype in BASE_TYPES:
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
- """accept if user is authenticated"""
- if req.cnx.anonymous_connection:
- return 0
- return 1
-
-def anonymous_user():
- return ~ authenticated_user()
-
-@objectify_selector
-@lltrace
-def primary_view(cls, req, rset=None, row=None, col=0, view=None, **kwargs):
- """accept if view given as named argument is a primary view, or if no view
- is given
- """
- if view is not None and not view.is_primary():
- return 0
- return 1
-
-@objectify_selector
-@lltrace
-def match_context_prop(cls, req, rset=None, row=None, col=0, context=None,
- **kwargs):
- """accept if:
- * no context given
- * context (`basestring`) is matching the context property value for the
- given cls
- """
- propval = req.property_value('%s.%s.context' % (cls.__registry__, cls.id))
- if not propval:
- propval = cls.context
- if context is not None and propval and context != propval:
- return 0
- return 1
-
-
-class match_search_state(Selector):
- """accept if the current request search state is in one of the expected
- states given to the initializer
-
- :param expected: either 'normal' or 'linksearch' (eg searching for an
- object to create a relation with another)
+class ExpectedValueSelector(Selector):
+ """Take a list of expected values as initializer argument, check
+ _get_value method return one of these expected values.
"""
def __init__(self, *expected):
assert expected, self
@@ -454,356 +320,402 @@
','.join(sorted(str(s) for s in self.expected)))
@lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- try:
- if not req.search_state[0] in self.expected:
- return 0
- except AttributeError:
- return 1 # class doesn't care about search state, accept it
- return 1
+ def __call__(self, cls, req, **kwargs):
+ if self._get_value(cls, req, **kwargs) in self.expected:
+ return 1
+ return 0
+
+ def _get_value(self, cls, req, **kwargs):
+ raise NotImplementedError()
-class match_form_params(match_search_state):
- """accept if parameters specified as initializer arguments are specified
- in request's form parameters
+# bare selectors ##############################################################
- :param *expected: parameters (eg `basestring`) which are expected to be
- found in request's form parameters
+class match_kwargs(ExpectedValueSelector):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in the input context. When multiple parameters are
+ specified, all of them should be specified in the input context. Return a
+ score corresponding to the number of expected parameters.
"""
@lltrace
- def __call__(self, cls, req, *args, **kwargs):
- score = 0
- for param in self.expected:
- if not param in req.form:
- return 0
- score += 1
- return len(self.expected)
-
-
-class match_kwargs(match_search_state):
- """accept if parameters specified as initializer arguments are specified
- in named arguments given to the selector
-
- :param *expected: parameters (eg `basestring`) which are expected to be
- found in named arguments (kwargs)
- """
-
- @lltrace
- def __call__(self, cls, req, *args, **kwargs):
+ def __call__(self, cls, req, **kwargs):
for arg in self.expected:
if not arg in kwargs:
return 0
return len(self.expected)
-class match_user_groups(match_search_state):
- """accept if logged users is in at least one of the given groups. Returned
- score is the number of groups in which the user is.
-
- If the special 'owners' group is given:
- * if row is specified check the entity at the given row/col is owned by the
- logged user
- * if row is not specified check all entities in col are owned by the logged
- user
-
- :param *required_groups: name of groups (`basestring`) in which the logged
- user should be
- """
-
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- user = req.user
- if user is None:
- return int('guests' in self.expected)
- score = user.matching_groups(self.expected)
- if not score and 'owners' in self.expected and rset:
- if row is not None:
- if not user.owns(rset[row][col]):
- return 0
- score = 1
- else:
- score = all(user.owns(r[col]) for r in rset)
- return score
-
+class appobject_selectable(Selector):
+ """return 1 if another appobject is selectable using the same input context.
-class match_transition(match_search_state):
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
- try:
- # XXX check this is a transition that apply to the object?
- if not kwargs['transition'].name in self.expected:
- return 0
- except KeyError:
- return 0
- return 1
-
-
-class match_view(match_search_state):
- """accept if the current view is in one of the expected vid given to the
- initializer
+ Initializer arguments:
+ * `registry`, a registry name
+ * `regid`, an object identifier in this registry
"""
- @lltrace
- def __call__(self, cls, req, rset=None, row=None, col=0, view=None, **kwargs):
- if view is None or not view.id in self.expected:
- return 0
- return 1
-
-
-class appobject_selectable(Selector):
- """accept with another appobject is selectable using selector's input
- context.
-
- :param registry: a registry name (`basestring`)
- :param oid: an object identifier (`basestring`)
- """
- def __init__(self, registry, oid):
+ def __init__(self, registry, regid):
self.registry = registry
- self.oid = oid
+ self.regid = regid
def __call__(self, cls, req, **kwargs):
try:
- cls.vreg[self.registry].select(self.oid, req, **kwargs)
+ req.vreg[self.registry].select(self.regid, req, **kwargs)
return 1
except NoSelectableObject:
return 0
-# not so basic selectors ######################################################
+# rset selectors ##############################################################
-class implements(ImplementsMixIn, EClassSelector):
- """accept if entity classes found in the result set implements at least one
- of the interfaces given as argument. Returned score is the number of
- implemented interfaces.
+@objectify_selector
+@lltrace
+def none_rset(cls, req, rset=None, **kwargs):
+ """Return 1 if the result set is None (eg usually not specified)."""
+ if rset is None:
+ return 1
+ return 0
- See `EClassSelector` documentation for behaviour when row is not specified.
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+# XXX == ~ none_rset
+@objectify_selector
+@lltrace
+def any_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for any result set, whatever the number of rows in it, even 0."""
+ if rset is not None:
+ return 1
+ return 0
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
- """
- def score_class(self, eclass, req):
- return self.score_interfaces(eclass, eclass)
+
+@objectify_selector
+@lltrace
+def nonempty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set containing one ore more rows."""
+ if rset is not None and rset.rowcount:
+ return 1
+ return 0
-class specified_etype_implements(implements):
- """accept if entity class specified using an 'etype' parameters in name
- argument or request form implements at least one of the interfaces given as
- argument. Returned score is the number of implemented interfaces.
+# XXX == ~ nonempty_rset
+@objectify_selector
+@lltrace
+def empty_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set which doesn't contain any row."""
+ if rset is not None and rset.rowcount == 0:
+ return 1
+ return 0
+
+
+# XXX == multi_lines_rset(1)
+@objectify_selector
+@lltrace
+def one_line_rset(cls, req, rset=None, row=None, **kwargs):
+ """Return 1 if the result set is of size 1 or if a specific row in the
+ result set is specified ('row' argument).
+ """
+ if rset is not None and (row is not None or rset.rowcount == 1):
+ return 1
+ return 0
+
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+class multi_lines_rset(Selector):
+ """If `nb`is specified, return 1 if the result set has exactly `nb` row of
+ result. Else (`nb` is None), return 1 if the result set contains *at least*
+ two rows.
+ """
+ def __init__(self, nb=None):
+ self.expected = nb
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
+ def match_expected(self, num):
+ if self.expected is None:
+ return num > 1
+ return num == self.expected
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, **kwargs):
+ return rset is not None and self.match_expected(rset.rowcount)
+
+
+class multi_columns_rset(multi_lines_rset):
+ """If `nb`is specified, return 1 if the result set has exactly `nb` column
+ per row. Else (`nb` is None), return 1 if the result set contains *at least*
+ two columns per row. Return 0 for empty result set.
"""
@lltrace
- def __call__(self, cls, req, *args, **kwargs):
- try:
- etype = kwargs['etype']
- except KeyError:
- try:
- etype = req.form['etype']
- except KeyError:
- return 0
- else:
- # only check this is a known type if etype comes from req.form,
- # else we want the error to propagate
- try:
- etype = cls.vreg.case_insensitive_etypes[etype.lower()]
- req.form['etype'] = etype
- except KeyError:
- return 0
- score = self.score_class(cls.vreg['etypes'].etype_class(etype), req)
- if score:
- eschema = req.vreg.schema.eschema(etype)
- if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
- return score
+ def __call__(self, cls, req, rset=None, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.rows[0])) or 0
+
+
+@objectify_selector
+@lltrace
+def paginated_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for result set with more rows than a page size.
+
+ Page size is searched in (respecting order):
+ * a `page_size` argument
+ * a `page_size` form parameters
+ * the :ref:`navigation.page-size` property
+ """
+ if rset is None:
+ return 0
+ page_size = kwargs.get('page_size')
+ if page_size is None:
+ page_size = req.form.get('page_size')
+ if page_size is None:
+ page_size = req.property_value('navigation.page-size')
+ else:
+ page_size = int(page_size)
+ if rset.rowcount <= page_size:
return 0
+ return 1
+
+
+@objectify_selector
+@lltrace
+def sorted_rset(cls, req, rset=None, **kwargs):
+ """Return 1 for sorted result set (e.g. from an RQL query containing an
+ :ref:ORDERBY clause.
+ """
+ if rset is None:
+ return 0
+ rqlst = rset.syntax_tree()
+ if len(rqlst.children) > 1 or not rqlst.children[0].orderby:
+ return 0
+ return 2
+
+
+# XXX == multi_etypes_rset(1)
+@objectify_selector
+@lltrace
+def one_etype_rset(cls, req, rset=None, col=0, **kwargs):
+ """Return 1 if the result set contains entities which are all of the same
+ type in the column specified by the `col` argument of the input context, or
+ in column 0.
+ """
+ if rset is None:
+ return 0
+ if len(rset.column_types(col)) != 1:
+ return 0
+ return 1
+
+
+class multi_etypes_rset(multi_lines_rset):
+ """If `nb` is specified, return 1 if the result set contains `nb` different
+ types of entities in the column specified by the `col` argument of the input
+ context, or in column 0. If `nb` is None, return 1 if the result set contains
+ *at least* two different types of entities.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, col=0, **kwargs):
+ # 'or 0' since we *must not* return None
+ return rset and self.match_expected(len(rset.column_types(col))) or 0
-class entity_implements(ImplementsMixIn, EntitySelector):
- """accept if entity instances found in the result set implements at least one
- of the interfaces given as argument. Returned score is the number of
- implemented interfaces.
+# entity selectors #############################################################
+
+class non_final_entity(EClassSelector):
+ """Return 1 for entity of a non final entity type(s). Remember, "final"
+ entity types are String, Int, etc... This is equivalent to
+ `implements('Any')` but more optimized.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context.
+ """
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return 1
+
- :param *expected_ifaces: expected interfaces. An interface may be a class
- or an entity type (e.g. `basestring`) in which case
- the associated class will be searched in the
- registry (at selection time)
+class implements(ImplementsMixIn, EClassSelector):
+ """Return non-zero score for entity that are of the given type(s) or
+ implements at least one of the given interface(s). If multiple arguments are
+ given, matching one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the entity types registry at selection time.
- note: when interface is an entity class, the score will reflect class
- proximity so the most specific object'll be selected
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context.
+
+ .. note:: when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
"""
- def score_entity(self, entity):
- return self.score_interfaces(entity, entity.__class__)
+ def score_class(self, eclass, req):
+ return self.score_interfaces(req, eclass, eclass)
-class relation_possible(EClassSelector):
- """accept if entity class found in the result set support the relation.
+class score_entity(EntitySelector):
+ """Return score according to an arbitrary function given as argument which
+ will be called with input content entity as argument.
+
+ This is a very useful selector that will usually interest you since it
+ allows a lot of things without having to write a specific selector.
- See `EClassSelector` documentation for behaviour when row is not specified.
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
+ """
+ def __init__(self, scorefunc, once_is_enough=False):
+ super(score_entity, self).__init__(once_is_enough)
+ def intscore(*args, **kwargs):
+ score = scorefunc(*args, **kwargs)
+ if not score:
+ return 0
+ if isinstance(score, (int, long)):
+ return score
+ return 1
+ self.score_entity = intscore
+
+
+class relation_possible(EntitySelector):
+ """Return 1 for entity that supports the relation, provided that the
+ request's user may do some `action` on it (see below).
+
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- :param target_type: if specified, check the relation's end may be of this
- target type (`basestring`)
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'
+
+ * `target_etype`, optional name of an entity type that should be supported
+ at the other end of the relation
+
+ * `action`, a relation schema action (e.g. one of 'read', 'add', 'delete',
+ default to 'read') which must be granted to the user, else a 0 score will
+ be returned
+
+ * `strict`, boolean (default to False) telling what to do when the user has
+ not globally the permission for the action (eg the action is not granted
+ to one of the user's groups)
+
+ - when strict is False, if there are some local role defined for this
+ action (e.g. using rql expressions), then the permission will be
+ considered as granted
+
+ - when strict is True, then the permission will be actually checked for
+ each entity
+
+ Setting `strict` to True impacts performance for large result set since
+ you'll then get the :class:`~cubicweb.selectors.EntitySelector` behaviour
+ while otherwise you get the :class:`~cubicweb.selectors.EClassSelector`'s
+ one. See those classes documentation for entity lookup / score rules
+ according to the input context.
"""
+
def __init__(self, rtype, role='subject', target_etype=None,
- action='read', once_is_enough=False):
- super(relation_possible, self).__init__(once_is_enough)
+ action='read', strict=False, **kwargs):
+ super(relation_possible, self).__init__(**kwargs)
self.rtype = rtype
self.role = role
self.target_etype = target_etype
self.action = action
+ self.strict = strict
- @lltrace
- def __call__(self, cls, req, *args, **kwargs):
- rschema = cls.schema.rschema(self.rtype)
- if not (rschema.has_perm(req, self.action)
- or rschema.has_local_role(self.action)):
- return 0
- if self.action != 'read':
- if not (rschema.has_perm(req, 'read')
- or rschema.has_local_role('read')):
- return 0
- score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
- return score
+ # hack hack hack
+ def __call__(self, cls, req, **kwargs):
+ if self.strict:
+ return EntitySelector.__call__(self, cls, req, **kwargs)
+ return EClassSelector.__call__(self, cls, req, **kwargs)
- def score_class(self, eclass, req):
+ def score(self, *args):
+ if self.strict:
+ return EntitySelector.score(self, *args)
+ return EClassSelector.score(self, *args)
+
+ def _get_rschema(self, eclass):
eschema = eclass.e_schema
try:
if self.role == 'object':
- rschema = eschema.objrels[self.rtype]
+ return eschema.objrels[self.rtype]
else:
- rschema = eschema.subjrels[self.rtype]
+ return eschema.subjrels[self.rtype]
except KeyError:
- return 0
+ return None
+
+ def score_class(self, eclass, req):
+ rschema = self._get_rschema(eclass)
+ if rschema is None:
+ return 0 # relation not supported
+ eschema = eclass.e_schema
if self.target_etype is not None:
try:
- if self.role == 'subject':
- return int(self.target_etype in rschema.objects(eschema))
- else:
- return int(self.target_etype in rschema.subjects(eschema))
+ rdef = rschema.role_rdef(eschema, self.target_etype, self.role)
+ if not rdef.may_have_permission(self.action, req):
+ return 0
except KeyError:
return 0
+ else:
+ return rschema.may_have_permission(self.action, req, eschema, self.role)
+ return 1
+
+ def score_entity(self, entity):
+ rschema = self._get_rschema(entity)
+ if rschema is None:
+ return 0 # relation not supported
+ if self.target_etype is not None:
+ rschema = rschema.role_rdef(entity.e_schema, self.target_etype, self.role)
+ if self.role == 'subject':
+ if not rschema.has_perm(entity._cw, 'add', fromeid=entity.eid):
+ return 0
+ elif not rschema.has_perm(entity._cw, 'add', toeid=entity.eid):
+ return 0
return 1
class partial_relation_possible(PartialSelectorMixIn, relation_possible):
- """partial version of the relation_possible selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+ """Same as :class:~`cubicweb.selectors.relation_possible`, but will look for
+ attributes of the selected class to get information which is otherwise
+ expected by the initializer, except for `action` and `strict` which are kept
+ as initializer arguments.
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation
-
- - `etype` (optional): the entity type on the other side of the relation
-
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
+ This is useful to predefine selector of an abstract class designed to be
+ customized.
"""
- def __init__(self, action='read', once_is_enough=False):
+ def __init__(self, action='read', **kwargs):
super(partial_relation_possible, self).__init__(None, None, None,
- action, once_is_enough)
+ action, **kwargs)
def complete(self, cls):
self.rtype = cls.rtype
self.role = role(cls)
self.target_etype = getattr(cls, 'etype', None)
-
-
-class may_add_relation(EntitySelector):
- """accept if the relation can be added to an entity found in the result set
- by the logged user.
-
- See `EntitySelector` documentation for behaviour when row is not specified.
-
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- """
-
- def __init__(self, rtype, role='subject', once_is_enough=False):
- super(may_add_relation, self).__init__(once_is_enough)
- self.rtype = rtype
- self.role = role
-
- def score_entity(self, entity):
- rschema = entity.schema.rschema(self.rtype)
- if self.role == 'subject':
- if not rschema.has_perm(entity.req, 'add', fromeid=entity.eid):
- return 0
- elif not rschema.has_perm(entity.req, 'add', toeid=entity.eid):
- return 0
- return 1
-
-
-class partial_may_add_relation(PartialSelectorMixIn, may_add_relation):
- """partial version of the may_add_relation selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
-
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation.
-
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
- """
- def __init__(self, once_is_enough=False):
- super(partial_may_add_relation, self).__init__(None, None, once_is_enough)
-
- def complete(self, cls):
- self.rtype = cls.rtype
- self.role = role(cls)
+ if self.target_etype is not None:
+ warn('[3.6] please rename etype to target_etype on %s' % cls,
+ DeprecationWarning)
+ else:
+ self.target_etype = getattr(cls, 'target_etype', None)
class has_related_entities(EntitySelector):
- """accept if entity found in the result set has some linked entities using
- the specified relation (optionaly filtered according to the specified target
- type). Checks first if the relation is possible.
+ """Return 1 if entity support the specified relation and has some linked
+ entities by this relation , optionaly filtered according to the specified
+ target type.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ The relation is specified by the following initializer arguments:
+
+ * `rtype`, the name of the relation
- :param rtype: a relation type (`basestring`)
- :param role: the role of the result set entity in the relation. 'subject' or
- 'object', default to 'subject'.
- :param target_type: if specified, check the relation's end may be of this
- target type (`basestring`)
+ * `role`, the role of the entity in the relation, either 'subject' or
+ 'object', default to 'subject'.
+
+ * `target_etype`, optional name of an entity type that should be found
+ at the other end of the relation
+
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
"""
- def __init__(self, rtype, role='subject', target_etype=None,
- once_is_enough=False):
- super(has_related_entities, self).__init__(once_is_enough)
+ def __init__(self, rtype, role='subject', target_etype=None, **kwargs):
+ super(has_related_entities, self).__init__(**kwargs)
self.rtype = rtype
self.role = role
self.target_etype = target_etype
def score_entity(self, entity):
relpossel = relation_possible(self.rtype, self.role, self.target_etype)
- if not relpossel.score_class(entity.__class__, entity.req):
+ if not relpossel.score_class(entity.__class__, entity._cw):
return 0
rset = entity.related(self.rtype, self.role)
if self.target_etype:
@@ -812,52 +724,52 @@
class partial_has_related_entities(PartialSelectorMixIn, has_related_entities):
- """partial version of the has_related_entities selector
-
- The selector will look for class attributes to find its missing
- information. The list of attributes required on the class
- for this selector are:
-
- - `rtype`: same as `rtype` parameter of the `relation_possible` selector
+ """Same as :class:~`cubicweb.selectors.has_related_entity`, but will look
+ for attributes of the selected class to get information which is otherwise
+ expected by the initializer.
- - `role`: this attribute will be passed to the `cubicweb.role` function
- to determine the role of class in the relation.
-
- - `etype` (optional): the entity type on the other side of the relation
+ This is useful to predefine selector of an abstract class designed to be
+ customized.
+ """
+ def __init__(self, **kwargs):
+ super(partial_has_related_entities, self).__init__(None, None, None,
+ **kwargs)
- :param action: a relation schema action (one of 'read', 'add', 'delete')
- which must be granted to the logged user, else a 0 score will
- be returned
- """
- def __init__(self, once_is_enough=False):
- super(partial_has_related_entities, self).__init__(None, None,
- None, once_is_enough)
def complete(self, cls):
self.rtype = cls.rtype
self.role = role(cls)
self.target_etype = getattr(cls, 'etype', None)
+ if self.target_etype is not None:
+ warn('[3.6] please rename etype to target_etype on %s' % cls,
+ DeprecationWarning)
+ else:
+ self.target_etype = getattr(cls, 'target_etype', None)
class has_permission(EntitySelector):
- """accept if user has the permission to do the requested action on a result
- set entity.
+ """Return non-zero score if request's user has the permission to do the
+ requested action on the entity. `action` is an entity schema action (eg one
+ of 'read', 'add', 'delete', 'update').
- * if row is specified, return 1 if user has the permission on the entity
- instance found in the specified cell
- * else return a positive score if user has the permission for every entity
- in the found in the specified column
+ Here are entity lookup / scoring rules:
+
+ * if `entity` is specified, check permission is granted for this entity
- note: None values (resulting from some outer join in the query) are not
- considered.
+ * elif `row` is specified, check permission is granted for the entity found
+ in the specified cell
- :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+ * else check permission is granted for each entity found in the column
+ specified specified by the `col` argument or in column 0
"""
- def __init__(self, action, once_is_enough=False):
- super(has_permission, self).__init__(once_is_enough)
+ def __init__(self, action):
self.action = action
+ # don't use EntitySelector.__call__ but this optimized implementation to
+ # avoid considering each entity when it's not necessary
@lltrace
def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ if kwargs.get('entity'):
+ return self.score_entity(kwargs['entity'])
if rset is None:
return 0
user = req.user
@@ -865,7 +777,7 @@
if row is None:
score = 0
need_local_check = []
- geteschema = cls.schema.eschema
+ geteschema = req.vreg.schema.eschema
for etype in rset.column_types(0):
if etype in BASE_TYPES:
return 0
@@ -897,32 +809,47 @@
class has_add_permission(EClassSelector):
- """accept if logged user has the add permission on entity class found in the
- result set, and class is not a strict subobject.
+ """Return 1 if request's user has the add permission on entity type
+ specified in the `etype` initializer argument, or according to entity found
+ in the input content if not specified.
- See `EClassSelector` documentation for behaviour when row is not specified.
+ It also check that then entity type is not a strict subobject (e.g. may only
+ be used as a composed of another entity).
+
+ See :class:`~cubicweb.selectors.EClassSelector` documentation for entity
+ class lookup / score rules according to the input context when `etype` is
+ not specified.
"""
- def score(self, cls, req, etype):
- eschema = cls.schema.eschema(etype)
- if not (eschema.final or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
+ def __init__(self, etype=None, **kwargs):
+ super(has_add_permission, self).__init__(**kwargs)
+ self.etype = etype
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ if self.etype is None:
+ return super(has_add_permission, self).__call__(cls, req, **kwargs)
+ return self.score(cls, req, self.etype)
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ if eschema.final or eschema.is_subobject(strict=True) \
+ or not eschema.has_perm(req, 'add'):
+ return 0
+ return 1
class rql_condition(EntitySelector):
- """accept if an arbitrary rql return some results for an eid found in the
- result set. Returned score is the number of items returned by the rql
+ """Return non-zero score if arbitrary rql specified in `expression`
+ initializer argument return some results for entity found in the input
+ context. Returned score is the number of items returned by the rql
condition.
- See `EntitySelector` documentation for behaviour when row is not specified.
+ `expression` is expected to be a string containing an rql expression, which
+ must use 'X' variable to represent the context entity and may use 'U' to
+ represent the request's user.
- :param expression: basestring containing an rql expression, which should use
- X variable to represent the context entity and may use U
- to represent the logged user
-
- return the sum of the number of items returned by the rql condition as score
- or 0 at the first entity scoring to zero.
+ See :class:`~cubicweb.selectors.EntitySelector` documentation for entity
+ lookup / score rules according to the input context.
"""
def __init__(self, expression, once_is_enough=False):
super(rql_condition, self).__init__(once_is_enough)
@@ -932,6 +859,9 @@
rql = 'Any X WHERE X eid %%(x)s, %s' % expression
self.rql = rql
+ def __repr__(self):
+ return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+
def score(self, req, rset, row, col):
try:
return len(req.execute(self.rql, {'x': rset[row][col],
@@ -939,11 +869,216 @@
except Unauthorized:
return 0
- def __repr__(self):
- return u'<rql_condition "%s" at %x>' % (self.rql, id(self))
+# logged user selectors ########################################################
+
+@objectify_selector
+@lltrace
+def authenticated_user(cls, req, **kwargs):
+ """Return 1 if the user is authenticated (e.g. not the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ if req.cnx.anonymous_connection:
+ return 0
+ return 1
+
+
+# XXX == ~ authenticated_user()
+def anonymous_user():
+ """Return 1 if the user is not authenticated (e.g. is the anonymous user).
+
+ May only be used on the web side, not on the data repository side.
+ """
+ return ~ authenticated_user()
+
+
+class match_user_groups(ExpectedValueSelector):
+ """Return a non-zero score if request's user is in at least one of the
+ groups given as initializer argument. Returned score is the number of groups
+ in which the user is.
+
+ If the special 'owners' group is given and `rset` is specified in the input
+ context:
+
+ * if `row` is specified check the entity at the given `row`/`col` (default
+ to 0) is owned by the user
+
+ * else check all entities in `col` (default to 0) are owned by the user
+ """
+
+ @lltrace
+ def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs):
+ user = req.user
+ if user is None:
+ return int('guests' in self.expected)
+ score = user.matching_groups(self.expected)
+ if not score and 'owners' in self.expected and rset:
+ if row is not None:
+ if not user.owns(rset[row][col]):
+ return 0
+ score = 1
+ else:
+ score = all(user.owns(r[col]) for r in rset)
+ return score
+
+
+# Web request selectors ########################################################
+
+@objectify_selector
+@lltrace
+def primary_view(cls, req, view=None, **kwargs):
+ """Return 1 if:
+
+ * *no view is specified* in the input context
+
+ * a view is specified and its `.is_primary()` method return True
+
+ This selector is usually used by contextual components that only want to
+ appears for the primary view of an entity.
+ """
+ if view is not None and not view.is_primary():
+ return 0
+ return 1
+
+
+class match_view(ExpectedValueSelector):
+ """Return 1 if a view is specified an as its registry id is in one of the
+ expected view id given to the initializer.
+ """
+ @lltrace
+ def __call__(self, cls, req, view=None, **kwargs):
+ if view is None or not view.__regid__ in self.expected:
+ return 0
+ return 1
-class but_etype(EntitySelector):
+@objectify_selector
+@lltrace
+def match_context_prop(cls, req, context=None, **kwargs):
+ """Return 1 if:
+
+ * no `context` is specified in input context (take care to confusion, here
+ `context` refers to a string given as an argument to the input context...)
+
+ * specified `context` is matching the context property value for the
+ appobject using this selector
+
+ * the appobject's context property value is None
+
+ This selector is usually used by contextual components that want to appears
+ in a configurable place.
+ """
+ if context is None:
+ return 0
+ propval = req.property_value('%s.%s.context' % (cls.__registry__,
+ cls.__regid__))
+ if not propval:
+ propval = cls.context
+ if propval and context != propval:
+ return 0
+ return 1
+
+
+class match_search_state(ExpectedValueSelector):
+ """Return 1 if the current request search state is in one of the expected
+ states given to the initializer.
+
+ Known search states are either 'normal' or 'linksearch' (eg searching for an
+ object to create a relation with another).
+
+ This selector is usually used by action that want to appears or not according
+ to the ui search state.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ try:
+ if not req.search_state[0] in self.expected:
+ return 0
+ except AttributeError:
+ return 1 # class doesn't care about search state, accept it
+ return 1
+
+
+class match_form_params(ExpectedValueSelector):
+ """Return non-zero score if parameter names specified as initializer
+ arguments are specified in request's form parameters. When multiple
+ parameters are specified, all of them should be found in req.form. Return a
+ score corresponding to the number of expected parameters.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ for param in self.expected:
+ if not param in req.form:
+ return 0
+ return len(self.expected)
+
+
+class specified_etype_implements(implements):
+ """Return non-zero score if the entity type specified by an 'etype' key
+ searched in (by priority) input context kwargs and request form parameters
+ match a known entity type (case insensitivly), and it's associated entity
+ class is of one of the type(s) given to the initializer or implements at
+ least one of the given interfaces. If multiple arguments are given, matching
+ one of them is enough.
+
+ Entity types should be given as string, the corresponding class will be
+ fetched from the entity types registry at selection time.
+
+ .. note:: when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
+
+ This selector is usually used by views holding entity creation forms (since
+ we've no result set to work on).
+ """
+
+ @lltrace
+ def __call__(self, cls, req, **kwargs):
+ try:
+ etype = kwargs['etype']
+ except KeyError:
+ try:
+ etype = req.form['etype']
+ except KeyError:
+ return 0
+ else:
+ # only check this is a known type if etype comes from req.form,
+ # else we want the error to propagate
+ try:
+ etype = req.vreg.case_insensitive_etypes[etype.lower()]
+ req.form['etype'] = etype
+ except KeyError:
+ return 0
+ score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
+ if score:
+ eschema = req.vreg.schema.eschema(etype)
+ if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+ return score
+ return 0
+
+
+# Other selectors ##############################################################
+
+
+class match_transition(ExpectedValueSelector):
+ """Return 1 if a `transition` argument is found in the input context which
+ has a `.name` attribute matching one of the expected names given to the
+ initializer.
+ """
+ @lltrace
+ def __call__(self, cls, req, transition=None, **kwargs):
+ # XXX check this is a transition that apply to the object?
+ if transition is not None and getattr(transition, 'name', None) in self.expected:
+ return 1
+ return 0
+
+
+## deprecated stuff ############################################################
+
+entity_implements = class_renamed('entity_implements', implements)
+
+class _but_etype(EntitySelector):
"""accept if the given entity types are not found in the result set.
See `EntitySelector` documentation for behaviour when row is not specified.
@@ -951,7 +1086,7 @@
:param *etypes: entity types (`basestring`) which should be refused
"""
def __init__(self, *etypes):
- super(but_etype, self).__init__()
+ super(_but_etype, self).__init__()
self.but_etypes = etypes
def score(self, req, rset, row, col):
@@ -959,225 +1094,12 @@
return 0
return 1
-
-class score_entity(EntitySelector):
- """accept if some arbitrary function return a positive score for an entity
- found in the result set.
-
- See `EntitySelector` documentation for behaviour when row is not specified.
-
- :param scorefunc: callable expected to take an entity as argument and to
- return a score >= 0
- """
- def __init__(self, scorefunc, once_is_enough=False):
- super(score_entity, self).__init__(once_is_enough)
- def intscore(*args, **kwargs):
- score = scorefunc(*args, **kwargs)
- if not score:
- return 0
- if isinstance(score, (int, long)):
- return score
- return 1
- self.score_entity = intscore
-
-
-# XXX DEPRECATED ##############################################################
-# XXX remove when deprecated functions are removed
-filterwarnings('ignore',
- category=DeprecationWarning,
- module='cubicweb.selectors')
-from cubicweb.vregistry import chainall
-
-yes_selector = deprecated()(yes)
-norset_selector = deprecated()(none_rset)
-rset_selector = deprecated()(any_rset)
-anyrset_selector = deprecated()(nonempty_rset)
-emptyrset_selector = deprecated()(empty_rset)
-onelinerset_selector = deprecated()(one_line_rset)
-twolinerset_selector = deprecated()(two_lines_rset)
-twocolrset_selector = deprecated()(two_cols_rset)
-largerset_selector = deprecated()(paginated_rset)
-sortedrset_selector = deprecated()(sorted_rset)
-oneetyperset_selector = deprecated()(one_etype_rset)
-multitype_selector = deprecated()(two_etypes_rset)
-anonymous_selector = deprecated()(anonymous_user)
-not_anonymous_selector = deprecated()(authenticated_user)
-primaryview_selector = deprecated()(primary_view)
-contextprop_selector = deprecated()(match_context_prop)
-
-@deprecated('use non_final_entity instead of %s')
-def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return non_final_entity()(cls, req, rset, row, col)
-
-@deprecated('use implements instead of %s')
-def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
- return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
-_interface_selector = deprecated()(implement_interface)
-interface_selector = deprecated()(implement_interface)
-
-@deprecated('use specified_etype_implements instead of %s')
-def accept_etype(cls, req, *args, **kwargs):
- """check etype presence in request form *and* accepts conformance"""
- return specified_etype_implements(*cls.accepts)(cls, req, *args)
-etype_form_selector = accept_etype
-
-@deprecated('use match_search_state instead of %s')
-def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return match_search_state(cls.search_states)(cls, req, rset, row, col)
-
-@deprecated('use match_user_groups instead of %s')
-def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
- return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
-in_group_selector = match_user_group
-
-@deprecated('use relation_possible instead of %s')
-def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
- return relation_possible(cls.rtype, role(cls), cls.etype,
- getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use relation_possible instead of %s')
-def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
- return relation_possible(cls.rtype, role(cls), cls.etype,
- getattr(cls, 'require_permission', 'read',
- once_is_enough=True))(cls, req, rset, row, col, **kwargs)
-
-@deprecated('use implements instead of %s')
-def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
- """simply delegate to cls.accept_rset method"""
- return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
-accept_rset_selector = accept_rset
-
-accept = chainall(non_final_entity(), accept_rset, name='accept')
-accept = deprecated('use implements selector')(accept)
-accept_selector = deprecated()(accept)
-
-accept_one = deprecated()(chainall(one_line_rset, accept,
- name='accept_one'))
-accept_one_selector = deprecated()(accept_one)
+but_etype = class_renamed('but_etype', _but_etype, 'use ~implements(*etypes) instead')
-def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
- if cls.condition:
- return rql_condition(cls.condition)(cls, req, rset, row, col)
- return 1
-_rqlcondition_selector = deprecated()(_rql_condition)
-
-rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
- name='rql_condition'))
-
-@deprecated('use but_etype instead of %s')
-def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- return but_etype(cls.etype)(cls, req, rset, row, col)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
- schema = cls.schema
- perm = getattr(cls, 'require_permission', 'read')
- if hasattr(cls, 'etype'):
- eschema = schema.eschema(cls.etype)
- if not (eschema.has_perm(req, perm) or eschema.has_local_role(perm)):
- return 0
- if hasattr(cls, 'rtype'):
- rschema = schema.rschema(cls.rtype)
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- return 1
-etype_rtype_selector = deprecated()(etype_rtype_selector)
-
-#req_form_params_selector = deprecated()(match_form_params) # form_params
-#kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
-
-# compound selectors ##########################################################
-
-searchstate_accept = chainall(nonempty_rset(), accept,
- name='searchstate_accept')
-searchstate_accept_selector = deprecated()(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
- name='searchstate_accept_one')
-searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
-
-searchstate_accept = deprecated()(searchstate_accept)
-searchstate_accept_one = deprecated()(searchstate_accept_one)
-
-# end of deprecation section ##################################################
-
-def unbind_method(selector):
- def new_selector(registered):
- # get the unbound method
- if hasattr(registered, 'im_func'):
- registered = registered.im_func
- # don't rebind since it will be done automatically during
- # the assignment, inside the destination class body
- return selector(registered)
- new_selector.__name__ = selector.__name__
- return new_selector
-
-
-def deprecate(registered, msg):
- # get the unbound method
- if hasattr(registered, 'im_func'):
- registered = registered.im_func
- def _deprecate(cls, vreg):
- warn(msg, DeprecationWarning)
- return registered(cls, vreg)
- return _deprecate
-
-@unbind_method
-def require_group_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'require_groups', None):
- warn('use "match_user_groups(group1, group2)" instead of using require_groups',
- DeprecationWarning)
- cls.__select__ &= match_user_groups(cls.require_groups)
- return cls
- return plug_selector
-
-@unbind_method
-def accepts_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'accepts', None):
- warn('use "implements("EntityType", IFace)" instead of using accepts on %s'
- % cls,
- DeprecationWarning)
- cls.__select__ &= implements(*cls.accepts)
- return cls
- return plug_selector
-
-@unbind_method
-def accepts_etype_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'accepts', None):
- warn('use "specified_etype_implements("EntityType", IFace)" instead of using accepts',
- DeprecationWarning)
- cls.__select__ &= specified_etype_implements(*cls.accepts)
- return cls
- return plug_selector
-
-@unbind_method
-def condition_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'condition', None):
- warn('use "use rql_condition(expression)" instead of using condition',
- DeprecationWarning)
- cls.__select__ &= rql_condition(cls.condition)
- return cls
- return plug_selector
-
-@unbind_method
-def has_relation_compat(registered):
- def plug_selector(cls, vreg):
- cls = registered(cls, vreg)
- if getattr(cls, 'etype', None):
- warn('use relation_possible selector instead of using etype_rtype',
- DeprecationWarning)
- cls.__select__ &= relation_possible(cls.rtype, role(cls),
- getattr(cls, 'etype', None),
- action=getattr(cls, 'require_permission', 'read'))
- return cls
- return plug_selector
-
+# XXX deprecated the one_* variants of selectors below w/ multi_xxx(nb=1)?
+# take care at the implementation though (looking for the 'row' argument's
+# value)
+two_lines_rset = class_renamed('two_lines_rset', multi_lines_rset)
+two_cols_rset = class_renamed('two_cols_rset', multi_columns_rset)
+two_etypes_rset = class_renamed('two_etypes_rset', multi_etypes_rset)
--- a/server/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -30,7 +30,7 @@
DBG_REPO = 4 # repository events
DBG_MS = 8 # multi-sources
DBG_MORE = 16 # more verbosity
-
+DBG_ALL = 1 + 2 + 4 + 8 + 16
# current debug mode
DEBUG = 0
@@ -167,7 +167,7 @@
session.commit()
# reloging using the admin user
config._cubes = None # avoid assertion error
- repo, cnx = in_memory_cnx(config, login, pwd)
+ repo, cnx = in_memory_cnx(config, login, password=pwd)
# trigger vreg initialisation of entity classes
config.cubicweb_appobject_path = set(('entities',))
config.cube_appobject_path = set(('entities',))
@@ -203,6 +203,10 @@
def initialize_schema(config, schema, mhandler, event='create'):
from cubicweb.server.schemaserial import serialize_schema
+ # deactivate every hooks but those responsible to set metadata
+ # so, NO INTEGRITY CHECKS are done, to have quicker db creation
+ oldmode = config.set_hooks_mode(config.DENY_ALL)
+ changes = config.enable_hook_category('metadata')
paths = [p for p in config.cubes_path() + [config.apphome]
if exists(join(p, 'migration'))]
# execute cubicweb's pre<event> script
@@ -218,6 +222,10 @@
# execute cubes'post<event> script if any
for path in reversed(paths):
mhandler.exec_event_script('post%s' % event, path)
+ # restore hooks config
+ if changes:
+ config.disable_hook_category(changes)
+ config.set_hooks_mode(oldmode)
# sqlite'stored procedures have to be registered at connexion opening time
--- a/server/checkintegrity.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/checkintegrity.py Mon Feb 08 13:35:07 2010 +0100
@@ -68,8 +68,6 @@
"""reindex all entities in the repository"""
# deactivate modification_date hook since we don't want them
# to be updated due to the reindexation
- from cubicweb.server.hooks import (setmtime_before_update_entity,
- uniquecstrcheck_before_modification)
from cubicweb.server.repository import FTIndexEntityOp
repo = session.repo
cursor = session.pool['system']
@@ -80,10 +78,8 @@
# XXX indexer.init_fti(cursor) once index 0.7 is out
indexer.init_extensions(cursor)
cursor.execute(indexer.sql_init_fti())
- repo.hm.unregister_hook(setmtime_before_update_entity,
- 'before_update_entity', '')
- repo.hm.unregister_hook(uniquecstrcheck_before_modification,
- 'before_update_entity', '')
+ repo.config.disabled_hooks_categories.add('metadata')
+ repo.config.disabled_hooks_categories.add('integrity')
repo.do_fti = True # ensure full-text indexation is activated
etypes = set()
for eschema in schema.entities():
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/hook.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,506 @@
+"""Hooks management
+
+This module defined the `Hook` class and registry and a set of abstract classes
+for operations.
+
+
+Hooks are called before / after any individual update of entities / relations
+in the repository and on special events such as server startup or shutdown.
+
+
+Operations may be registered by hooks during a transaction, which will be
+fired when the pool is commited or rollbacked.
+
+
+Entity hooks (eg before_add_entity, after_add_entity, before_update_entity,
+after_update_entity, before_delete_entity, after_delete_entity) all have an
+`entity` attribute
+
+Relation (eg before_add_relation, after_add_relation, before_delete_relation,
+after_delete_relation) all have `eidfrom`, `rtype`, `eidto` attributes.
+
+Server start/stop hooks (eg server_startup, server_shutdown) have a `repo`
+attribute, but *their `_cw` attribute is None*.
+
+Backup/restore hooks (eg server_backup, server_restore) have a `repo` and a
+`timestamp` attributes, but *their `_cw` attribute is None*.
+
+Session hooks (eg session_open, session_close) have no special attribute.
+
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+from logging import getLogger
+from itertools import chain
+
+from logilab.common.decorators import classproperty
+from logilab.common.deprecation import deprecated
+from logilab.common.logging_ext import set_log_methods
+
+from cubicweb.cwvreg import CWRegistry, VRegistry
+from cubicweb.selectors import (objectify_selector, lltrace, match_search_state,
+ implements)
+from cubicweb.appobject import AppObject
+
+
+ENTITIES_HOOKS = set(('before_add_entity', 'after_add_entity',
+ 'before_update_entity', 'after_update_entity',
+ 'before_delete_entity', 'after_delete_entity'))
+RELATIONS_HOOKS = set(('before_add_relation', 'after_add_relation' ,
+ 'before_delete_relation','after_delete_relation'))
+SYSTEM_HOOKS = set(('server_backup', 'server_restore',
+ 'server_startup', 'server_shutdown',
+ 'session_open', 'session_close'))
+ALL_HOOKS = ENTITIES_HOOKS | RELATIONS_HOOKS | SYSTEM_HOOKS
+
+
+class HooksRegistry(CWRegistry):
+
+ def register(self, obj, **kwargs):
+ try:
+ iter(obj.events)
+ except AttributeError:
+ raise
+ except:
+ raise Exception('bad .events attribute %s on %s.%s' % (
+ obj.events, obj.__module__, obj.__name__))
+ for event in obj.events:
+ if event not in ALL_HOOKS:
+ raise Exception('bad event %s on %s.%s' % (
+ event, obj.__module__, obj.__name__))
+ super(HooksRegistry, self).register(obj, **kwargs)
+
+ def call_hooks(self, event, req=None, **kwargs):
+ kwargs['event'] = event
+ for hook in sorted(self.possible_objects(req, **kwargs), key=lambda x: x.order):
+ if hook.enabled:
+ hook()
+ else:
+ warn('[3.6] %s: enabled is deprecated' % hook.__class__)
+
+VRegistry.REGISTRY_FACTORY['hooks'] = HooksRegistry
+
+
+def entity_oldnewvalue(entity, attr):
+ """returns the couple (old attr value, new attr value)
+ NOTE: will only work in a before_update_entity hook
+ """
+ # get new value and remove from local dict to force a db query to
+ # fetch old value
+ newvalue = entity.pop(attr, None)
+ oldvalue = getattr(entity, attr)
+ if newvalue is not None:
+ entity[attr] = newvalue
+ return oldvalue, newvalue
+
+
+# some hook specific selectors #################################################
+
+@objectify_selector
+@lltrace
+def match_event(cls, req, **kwargs):
+ if kwargs.get('event') in cls.events:
+ return 1
+ return 0
+
+@objectify_selector
+@lltrace
+def enabled_category(cls, req, **kwargs):
+ if req is None:
+ # server startup / shutdown event
+ config = kwargs['repo'].config
+ else:
+ config = req.vreg.config
+ return config.is_hook_activated(cls)
+
+@objectify_selector
+@lltrace
+def regular_session(cls, req, **kwargs):
+ if req is None or req.is_super_session:
+ return 0
+ return 1
+
+
+class rechain(object):
+ def __init__(self, *iterators):
+ self.iterators = iterators
+ def __iter__(self):
+ return iter(chain(*self.iterators))
+
+
+class match_rtype(match_search_state):
+ """accept if parameters specified as initializer arguments are specified
+ in named arguments given to the selector
+
+ :param *expected: parameters (eg `basestring`) which are expected to be
+ found in named arguments (kwargs)
+ """
+ def __init__(self, *expected, **more):
+ self.expected = expected
+ self.frometypes = more.pop('frometypes', None)
+ self.toetypes = more.pop('toetypes', None)
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ if kwargs.get('rtype') not in self.expected:
+ return 0
+ if self.frometypes is not None and \
+ req.describe(kwargs['eidfrom'])[0] not in self.frometypes:
+ return 0
+ if self.toetypes is not None and \
+ req.describe(kwargs['eidto'])[0] not in self.toetypes:
+ return 0
+ return 1
+
+
+class match_rtype_sets(match_search_state):
+ """accept if parameters specified as initializer arguments are specified
+ in named arguments given to the selector
+ """
+
+ def __init__(self, *expected):
+ self.expected = expected
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ for rel_set in self.expected:
+ if kwargs.get('rtype') in rel_set:
+ return 1
+ return 0
+
+# base class for hook ##########################################################
+
+class Hook(AppObject):
+ __registry__ = 'hooks'
+ __select__ = match_event() & enabled_category()
+ # set this in derivated classes
+ events = None
+ category = None
+ order = 0
+ # XXX deprecated
+ enabled = True
+
+ @classproperty
+ def __regid__(cls):
+ warn('[3.6] %s.%s: please specify an id for your hook'
+ % (cls.__module__, cls.__name__), DeprecationWarning)
+ return str(id(cls))
+
+ @classmethod
+ def __registered__(cls, reg):
+ super(Hook, cls).__registered__(reg)
+ if getattr(cls, 'accepts', None):
+ warn('[3.6] %s.%s: accepts is deprecated, define proper __select__'
+ % (cls.__module__, cls.__name__), DeprecationWarning)
+ rtypes = []
+ for ertype in cls.accepts:
+ if ertype.islower():
+ rtypes.append(ertype)
+ else:
+ cls.__select__ = cls.__select__ & implements(ertype)
+ if rtypes:
+ cls.__select__ = cls.__select__ & match_rtype(*rtypes)
+ return cls
+
+ known_args = set(('entity', 'rtype', 'eidfrom', 'eidto', 'repo', 'timestamp'))
+ def __init__(self, req, event, **kwargs):
+ for arg in self.known_args:
+ if arg in kwargs:
+ setattr(self, arg, kwargs.pop(arg))
+ super(Hook, self).__init__(req, **kwargs)
+ self.event = event
+
+ def __call__(self):
+ if hasattr(self, 'call'):
+ cls = self.__class__
+ warn('[3.6] %s.%s: call is deprecated, implements __call__'
+ % (cls.__module__, cls.__name__), DeprecationWarning)
+ if self.event.endswith('_relation'):
+ self.call(self._cw, self.eidfrom, self.rtype, self.eidto)
+ elif 'delete' in self.event:
+ self.call(self._cw, self.entity.eid)
+ elif self.event.startswith('server_'):
+ self.call(self.repo)
+ elif self.event.startswith('session_'):
+ self.call(self._cw)
+ else:
+ self.call(self._cw, self.entity)
+
+set_log_methods(Hook, getLogger('cubicweb.hook'))
+
+
+# base classes for relation propagation ########################################
+
+class PropagateSubjectRelationHook(Hook):
+ """propagate permissions and nosy list when new entity are added"""
+ events = ('after_add_relation',)
+
+ # to set in concrete class
+ main_rtype = None
+ subject_relations = None
+ object_relations = None
+
+ def __call__(self):
+ for eid in (self.eidfrom, self.eidto):
+ etype = self._cw.describe(eid)[0]
+ if not self._cw.vreg.schema.eschema(etype).has_subject_relation(self.main_rtype):
+ return
+ if self.rtype in self.subject_relations:
+ meid, seid = self.eidfrom, self.eidto
+ else:
+ assert self.rtype in self.object_relations
+ meid, seid = self.eidto, self.eidfrom
+ self._cw.unsafe_execute(
+ 'SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\
+ % (self.main_rtype, self.main_rtype, self.main_rtype),
+ {'x': meid, 'e': seid}, ('x', 'e'))
+
+
+class PropagateSubjectRelationAddHook(Hook):
+ """propagate on existing entities when a permission or nosy list is added"""
+ events = ('after_add_relation',)
+
+ # to set in concrete class
+ main_rtype = None
+ subject_relations = None
+ object_relations = None
+
+ def __call__(self):
+ eschema = self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])
+ execute = self._cw.unsafe_execute
+ for rel in self.subject_relations:
+ if rel in eschema.subjrels:
+ execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+ 'X %s R, NOT R %s P' % (self.rtype, rel, self.rtype),
+ {'x': self.eidfrom, 'p': self.eidto}, 'x')
+ for rel in self.object_relations:
+ if rel in eschema.objrels:
+ execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+ 'R %s X, NOT R %s P' % (self.rtype, rel, self.rtype),
+ {'x': self.eidfrom, 'p': self.eidto}, 'x')
+
+
+class PropagateSubjectRelationDelHook(Hook):
+ """propagate on existing entities when a permission is deleted"""
+ events = ('after_delete_relation',)
+
+ # to set in concrete class
+ main_rtype = None
+ subject_relations = None
+ object_relations = None
+
+ def __call__(self):
+ eschema = self._cw.vreg.schema.eschema(self._cw.describe(self.eidfrom)[0])
+ execute = self._cw.unsafe_execute
+ for rel in self.subject_relations:
+ if rel in eschema.subjrels:
+ execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+ 'X %s R' % (self.rtype, rel),
+ {'x': self.eidfrom, 'p': self.eidto}, 'x')
+ for rel in self.object_relations:
+ if rel in eschema.objrels:
+ execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
+ 'R %s X' % (self.rtype, rel),
+ {'x': self.eidfrom, 'p': self.eidto}, 'x')
+
+
+# abstract classes for operation ###############################################
+
+class Operation(object):
+ """an operation is triggered on connections pool events related to
+ commit / rollback transations. Possible events are:
+
+ precommit:
+ the pool is preparing to commit. You shouldn't do anything things which
+ has to be reverted if the commit fail at this point, but you can freely
+ do any heavy computation or raise an exception if the commit can't go.
+ You can add some new operation during this phase but their precommit
+ event won't be triggered
+
+ commit:
+ the pool is preparing to commit. You should avoid to do to expensive
+ stuff or something that may cause an exception in this event
+
+ revertcommit:
+ if an operation failed while commited, this event is triggered for
+ all operations which had their commit event already to let them
+ revert things (including the operation which made fail the commit)
+
+ rollback:
+ the transaction has been either rollbacked either
+ * intentionaly
+ * a precommit event failed, all operations are rollbacked
+ * a commit event failed, all operations which are not been triggered for
+ commit are rollbacked
+
+ order of operations may be important, and is controlled according to:
+ * operation's class
+ """
+
+ def __init__(self, session, **kwargs):
+ self.session = session
+ self.__dict__.update(kwargs)
+ self.register(session)
+ # execution information
+ self.processed = None # 'precommit', 'commit'
+ self.failed = False
+
+ def register(self, session):
+ session.add_operation(self, self.insert_index())
+
+ def insert_index(self):
+ """return the index of the lastest instance which is not a
+ LateOperation instance
+ """
+ # faster by inspecting operation in reverse order for heavy transactions
+ i = None
+ for i, op in enumerate(reversed(self.session.pending_operations)):
+ if isinstance(op, (LateOperation, SingleLastOperation)):
+ continue
+ return -i or None
+ if i is None:
+ return None
+ return -(i + 1)
+
+ def handle_event(self, event):
+ """delegate event handling to the opertaion"""
+ getattr(self, event)()
+
+ def precommit_event(self):
+ """the observed connections pool is preparing a commit"""
+
+ def revertprecommit_event(self):
+ """an error went when pre-commiting this operation or a later one
+
+ should revert pre-commit's changes but take care, they may have not
+ been all considered if it's this operation which failed
+ """
+
+ def commit_event(self):
+ """the observed connections pool is commiting"""
+
+ def revertcommit_event(self):
+ """an error went when commiting this operation or a later one
+
+ should revert commit's changes but take care, they may have not
+ been all considered if it's this operation which failed
+ """
+
+ def rollback_event(self):
+ """the observed connections pool has been rollbacked
+
+ do nothing by default, the operation will just be removed from the pool
+ operation list
+ """
+
+ def postcommit_event(self):
+ """the observed connections pool has committed"""
+
+ @property
+ @deprecated('[3.6] use self.session.user')
+ def user(self):
+ return self.session.user
+
+ @property
+ @deprecated('[3.6] use self.session.repo')
+ def repo(self):
+ return self.session.repo
+
+ @property
+ @deprecated('[3.6] use self.session.vreg.schema')
+ def schema(self):
+ return self.session.repo.schema
+
+ @property
+ @deprecated('[3.6] use self.session.vreg.config')
+ def config(self):
+ return self.session.repo.config
+
+set_log_methods(Operation, getLogger('cubicweb.session'))
+
+
+class LateOperation(Operation):
+ """special operation which should be called after all possible (ie non late)
+ operations
+ """
+ def insert_index(self):
+ """return the index of the lastest instance which is not a
+ SingleLastOperation instance
+ """
+ # faster by inspecting operation in reverse order for heavy transactions
+ i = None
+ for i, op in enumerate(reversed(self.session.pending_operations)):
+ if isinstance(op, SingleLastOperation):
+ continue
+ return -i or None
+ if i is None:
+ return None
+ return -(i + 1)
+
+
+class SingleOperation(Operation):
+ """special operation which should be called once"""
+ def register(self, session):
+ """override register to handle cases where this operation has already
+ been added
+ """
+ operations = session.pending_operations
+ index = self.equivalent_index(operations)
+ if index is not None:
+ equivalent = operations.pop(index)
+ else:
+ equivalent = None
+ session.add_operation(self, self.insert_index())
+ return equivalent
+
+ def equivalent_index(self, operations):
+ """return the index of the equivalent operation if any"""
+ for i, op in enumerate(reversed(operations)):
+ if op.__class__ is self.__class__:
+ return -(i+1)
+ return None
+
+
+class SingleLastOperation(SingleOperation):
+ """special operation which should be called once and after all other
+ operations
+ """
+ def insert_index(self):
+ return None
+
+
+class SendMailOp(SingleLastOperation):
+ def __init__(self, session, msg=None, recipients=None, **kwargs):
+ # may not specify msg yet, as
+ # `cubicweb.sobjects.supervision.SupervisionMailOp`
+ if msg is not None:
+ assert recipients
+ self.to_send = [(msg, recipients)]
+ else:
+ assert recipients is None
+ self.to_send = []
+ super(SendMailOp, self).__init__(session, **kwargs)
+
+ def register(self, session):
+ previous = super(SendMailOp, self).register(session)
+ if previous:
+ self.to_send = previous.to_send + self.to_send
+
+ def commit_event(self):
+ self.session.repo.threaded_task(self.sendmails)
+
+ def sendmails(self):
+ self.session.vreg.config.sendmails(self.to_send)
+
+
+class RQLPrecommitOperation(Operation):
+ def precommit_event(self):
+ execute = self.session.unsafe_execute
+ for rql in self.rqls:
+ execute(*rql)
--- a/server/hookhelper.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/hookhelper.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,87 +7,23 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb import RepositoryError
-from cubicweb.server.pool import SingleLastOperation
-
-def entity_oldnewvalue(entity, attr):
- """returns the couple (old attr value, new attr value)
+from logilab.common.deprecation import deprecated, class_moved
- NOTE: will only work in a before_update_entity hook
- """
- # get new value and remove from local dict to force a db query to
- # fetch old value
- newvalue = entity.pop(attr, None)
- oldvalue = getattr(entity, attr)
- if newvalue is not None:
- entity[attr] = newvalue
- return oldvalue, newvalue
+from cubicweb import RepositoryError
+from cubicweb.server import hook
-def rproperty(session, rtype, eidfrom, eidto, rprop):
- rschema = session.repo.schema[rtype]
- subjtype = session.describe(eidfrom)[0]
- objtype = session.describe(eidto)[0]
- return rschema.rproperty(subjtype, objtype, rprop)
-
-def check_internal_entity(session, eid, internal_names):
- """check that the entity's name is not in the internal_names list.
- raise a RepositoryError if so, else return the entity's name
- """
- name = session.entity_from_eid(eid).name
- if name in internal_names:
- raise RepositoryError('%s entity can\'t be deleted' % name)
- return name
-
-def get_user_sessions(repo, ueid):
- for session in repo._sessions.values():
- if ueid == session.user.eid:
- yield session
-
-
-# mail related ################################################################
+@deprecated('[3.6] entity_oldnewvalue should be imported from cw.server.hook')
+def entity_oldnewvalue(entity, attr):
+ """return the "name" attribute of the entity with the given eid"""
+ return hook.entity_oldnewvalue(entity, attr)
-class SendMailOp(SingleLastOperation):
- def __init__(self, session, msg=None, recipients=None, **kwargs):
- # may not specify msg yet, as
- # `cubicweb.sobjects.supervision.SupervisionMailOp`
- if msg is not None:
- assert recipients
- self.to_send = [(msg, recipients)]
- else:
- assert recipients is None
- self.to_send = []
- super(SendMailOp, self).__init__(session, **kwargs)
-
- def register(self, session):
- previous = super(SendMailOp, self).register(session)
- if previous:
- self.to_send = previous.to_send + self.to_send
-
- def commit_event(self):
- self.repo.threaded_task(self.sendmails)
-
- def sendmails(self):
- self.config.sendmails(self.to_send)
+@deprecated('[3.6] entity_name is deprecated, use entity.name')
+def entity_name(session, eid):
+ """return the "name" attribute of the entity with the given eid"""
+ return session.entity_from_eid(eid).name
-
-# state related ###############################################################
+@deprecated('[3.6] rproperty is deprecated, use session.schema_rproperty')
+def rproperty(session, rtype, eidfrom, eidto, rprop):
+ return session.rproperty(rtype, eidfrom, eidto, rprop)
-def previous_state(session, eid):
- """return the state of the entity with the given eid,
- usually since it's changing in the current transaction. Due to internal
- relation hooks, the relation may has been deleted at this point, so
- we have handle that
- """
- # don't check eid in session.transaction_data.get('neweids', ()), we don't
- # want to miss previous state of entity whose state change in the same
- # transaction as it's being created
- pending = session.transaction_data.get('pendingrelations', ())
- for eidfrom, rtype, eidto in reversed(pending):
- if rtype == 'in_state' and eidfrom == eid:
- rset = session.execute('Any S,N WHERE S eid %(x)s, S name N',
- {'x': eidto}, 'x')
- return rset.get_entity(0, 0)
- rset = session.execute('Any S,N WHERE X eid %(x)s, X in_state S, S name N',
- {'x': eid}, 'x')
- if rset:
- return rset.get_entity(0, 0)
+SendMailOp = class_moved(hook.SendMailOp)
--- a/server/hooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,881 +0,0 @@
-"""Core hooks: check schema validity, unsure we are not deleting necessary
-entities...
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from threading import Lock
-from datetime import datetime
-
-from cubicweb import UnknownProperty, ValidationError, BadConnectionId
-from cubicweb.schema import RQLConstraint, RQLUniqueConstraint
-from cubicweb.server.pool import Operation, LateOperation, PreCommitOperation
-from cubicweb.server.hookhelper import (check_internal_entity,
- get_user_sessions, rproperty)
-from cubicweb.server.repository import FTIndexEntityOp
-
-# special relations that don't have to be checked for integrity, usually
-# because they are handled internally by hooks (so we trust ourselves)
-DONT_CHECK_RTYPES_ON_ADD = set(('owned_by', 'created_by',
- 'is', 'is_instance_of',
- 'wf_info_for', 'from_state', 'to_state'))
-DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
- 'wf_info_for', 'from_state', 'to_state'))
-
-_UNIQUE_CONSTRAINTS_LOCK = Lock()
-_UNIQUE_CONSTRAINTS_HOLDER = None
-
-class _ReleaseUniqueConstraintsHook(Operation):
- def commit_event(self):
- pass
- def postcommit_event(self):
- _release_unique_cstr_lock(self.session)
- def rollback_event(self):
- _release_unique_cstr_lock(self.session)
-
-def _acquire_unique_cstr_lock(session):
- """acquire the _UNIQUE_CONSTRAINTS_LOCK for the session.
-
- This lock used to avoid potential integrity pb when checking
- RQLUniqueConstraint in two different transactions, as explained in
- http://intranet.logilab.fr/jpl/ticket/36564
- """
- global _UNIQUE_CONSTRAINTS_HOLDER
- asession = session.actual_session()
- if _UNIQUE_CONSTRAINTS_HOLDER is asession:
- return
- _UNIQUE_CONSTRAINTS_LOCK.acquire()
- _UNIQUE_CONSTRAINTS_HOLDER = asession
- # register operation responsible to release the lock on commit/rollback
- _ReleaseUniqueConstraintsHook(asession)
-
-def _release_unique_cstr_lock(session):
- global _UNIQUE_CONSTRAINTS_HOLDER
- if _UNIQUE_CONSTRAINTS_HOLDER is session:
- _UNIQUE_CONSTRAINTS_HOLDER = None
- _UNIQUE_CONSTRAINTS_LOCK.release()
-
-
-def relation_deleted(session, eidfrom, rtype, eidto):
- session.transaction_data.setdefault('pendingrelations', []).append(
- (eidfrom, rtype, eidto))
-
-def eschema_type_eid(session, etype):
- """get eid of the CWEType entity for the given yams type"""
- eschema = session.repo.schema.eschema(etype)
- # eschema.eid is None if schema has been readen from the filesystem, not
- # from the database (eg during tests)
- if eschema.eid is None:
- eschema.eid = session.unsafe_execute(
- 'Any X WHERE X is CWEType, X name %(name)s',
- {'name': str(etype)})[0][0]
- return eschema.eid
-
-
-# base meta-data handling ######################################################
-
-def setctime_before_add_entity(session, entity):
- """before create a new entity -> set creation and modification date
-
- this is a conveniency hook, you shouldn't have to disable it
- """
- timestamp = datetime.now()
- entity.setdefault('creation_date', timestamp)
- entity.setdefault('modification_date', timestamp)
- if not session.get_shared_data('do-not-insert-cwuri'):
- entity.setdefault('cwuri', u'%seid/%s' % (session.base_url(), entity.eid))
-
-
-def setmtime_before_update_entity(session, entity):
- """update an entity -> set modification date"""
- entity.setdefault('modification_date', datetime.now())
-
-
-class SetCreatorOp(PreCommitOperation):
-
- def precommit_event(self):
- session = self.session
- if self.entity.eid in session.transaction_data.get('pendingeids', ()):
- # entity have been created and deleted in the same transaction
- return
- if not self.entity.created_by:
- session.add_relation(self.entity.eid, 'created_by', session.user.eid)
-
-
-def setowner_after_add_entity(session, entity):
- """create a new entity -> set owner and creator metadata"""
- asession = session.actual_session()
- if not asession.is_internal_session:
- session.add_relation(entity.eid, 'owned_by', asession.user.eid)
- SetCreatorOp(asession, entity=entity)
-
-
-def setis_after_add_entity(session, entity):
- """create a new entity -> set is relation"""
- if hasattr(entity, '_cw_recreating'):
- return
- try:
- #session.add_relation(entity.eid, 'is',
- # eschema_type_eid(session, entity.id))
- session.system_sql('INSERT INTO is_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_type_eid(session, entity.id)))
- except IndexError:
- # during schema serialization, skip
- return
- for etype in entity.e_schema.ancestors() + [entity.e_schema]:
- #session.add_relation(entity.eid, 'is_instance_of',
- # eschema_type_eid(session, etype))
- session.system_sql('INSERT INTO is_instance_of_relation(eid_from,eid_to) VALUES (%s,%s)'
- % (entity.eid, eschema_type_eid(session, etype)))
-
-
-def setowner_after_add_user(session, entity):
- """when a user has been created, add owned_by relation on itself"""
- session.add_relation(entity.eid, 'owned_by', entity.eid)
-
-
-def fti_update_after_add_relation(session, eidfrom, rtype, eidto):
- """sync fulltext index when relevant relation is added. Reindexing the
- contained entity is enough since it will implicitly reindex the container
- entity.
- """
- ftcontainer = session.repo.schema.rschema(rtype).fulltext_container
- if ftcontainer == 'subject':
- FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
- elif ftcontainer == 'object':
- FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
-
-
-def fti_update_after_delete_relation(session, eidfrom, rtype, eidto):
- """sync fulltext index when relevant relation is deleted. Reindexing both
- entities is necessary.
- """
- if session.repo.schema.rschema(rtype).fulltext_container:
- FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
- FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
-
-
-class SyncOwnersOp(PreCommitOperation):
-
- def precommit_event(self):
- self.session.unsafe_execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
- 'NOT EXISTS(X owned_by U, X eid %(x)s)',
- {'c': self.compositeeid, 'x': self.composedeid},
- ('c', 'x'))
-
-
-def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto):
- """when adding composite relation, the composed should have the same owners
- has the composite
- """
- if rtype == 'wf_info_for':
- # skip this special composite relation # XXX (syt) why?
- return
- composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
- if composite == 'subject':
- SyncOwnersOp(session, compositeeid=eidfrom, composedeid=eidto)
- elif composite == 'object':
- SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom)
-
-
-def _register_metadata_hooks(hm):
- """register meta-data related hooks on the hooks manager"""
- hm.register_hook(setctime_before_add_entity, 'before_add_entity', '')
- hm.register_hook(setmtime_before_update_entity, 'before_update_entity', '')
- hm.register_hook(setowner_after_add_entity, 'after_add_entity', '')
- hm.register_hook(sync_owner_after_add_composite_relation, 'after_add_relation', '')
- hm.register_hook(fti_update_after_add_relation, 'after_add_relation', '')
- hm.register_hook(fti_update_after_delete_relation, 'after_delete_relation', '')
- if 'is' in hm.schema:
- hm.register_hook(setis_after_add_entity, 'after_add_entity', '')
- if 'CWUser' in hm.schema:
- hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
-
-
-# core hooks ##################################################################
-
-class DelayedDeleteOp(PreCommitOperation):
- """delete the object of composite relation except if the relation
- has actually been redirected to another composite
- """
-
- def precommit_event(self):
- session = self.session
- # don't do anything if the entity is being created or deleted
- if not (self.eid in session.transaction_data.get('pendingeids', ()) or
- self.eid in session.transaction_data.get('neweids', ())):
- etype = session.describe(self.eid)[0]
- if self.role == 'subject':
- rql = 'DELETE %s X WHERE X eid %%(x)s, NOT X %s Y'
- else: # self.role == 'object':
- rql = 'DELETE %s X WHERE X eid %%(x)s, NOT Y %s X'
- session.unsafe_execute(rql % (etype, self.rtype), {'x': self.eid}, 'x')
-
-
-def handle_composite_before_del_relation(session, eidfrom, rtype, eidto):
- """delete the object of composite relation"""
- # if the relation is being delete, don't delete composite's components
- # automatically
- pendingrdefs = session.transaction_data.get('pendingrdefs', ())
- if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
- return
- composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
- if composite == 'subject':
- DelayedDeleteOp(session, eid=eidto, rtype=rtype, role='object')
- elif composite == 'object':
- DelayedDeleteOp(session, eid=eidfrom, rtype=rtype, role='subject')
-
-
-def before_del_group(session, eid):
- """check that we don't remove the owners group"""
- check_internal_entity(session, eid, ('owners',))
-
-
-# schema validation hooks #####################################################
-
-class CheckConstraintsOperation(LateOperation):
- """check a new relation satisfy its constraints
- """
- def precommit_event(self):
- eidfrom, rtype, eidto = self.rdef
- # first check related entities have not been deleted in the same
- # transaction
- pending = self.session.transaction_data.get('pendingeids', ())
- if eidfrom in pending:
- return
- if eidto in pending:
- return
- for constraint in self.constraints:
- # XXX
- # * lock RQLConstraint as well?
- # * use a constraint id to use per constraint lock and avoid
- # unnecessary commit serialization ?
- if isinstance(constraint, RQLUniqueConstraint):
- _acquire_unique_cstr_lock(self.session)
- try:
- constraint.repo_check(self.session, eidfrom, rtype, eidto)
- except NotImplementedError:
- self.critical('can\'t check constraint %s, not supported',
- constraint)
-
- def commit_event(self):
- pass
-
-
-def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
- """check the relation satisfy its constraints
-
- this is delayed to a precommit time operation since other relation which
- will make constraint satisfied may be added later.
- """
- if session.is_super_session:
- return
- constraints = rproperty(session, rtype, eidfrom, eidto, 'constraints')
- if constraints:
- # XXX get only RQL[Unique]Constraints?
- CheckConstraintsOperation(session, constraints=constraints,
- rdef=(eidfrom, rtype, eidto))
-
-def uniquecstrcheck_before_modification(session, entity):
- if session.is_super_session:
- return
- eschema = entity.e_schema
- for attr in entity.edited_attributes:
- val = entity[attr]
- if val is None:
- continue
- if eschema.subjrels[attr].final and \
- eschema.has_unique_values(attr):
- rql = '%s X WHERE X %s %%(val)s' % (entity.e_schema, attr)
- rset = session.unsafe_execute(rql, {'val': val})
- if rset and rset[0][0] != entity.eid:
- msg = session._('the value "%s" is already used, use another one')
- raise ValidationError(entity.eid, {attr: msg % val})
-
-
-def cstrcheck_after_update_attributes(session, entity):
- if session.is_super_session:
- return
- eschema = entity.e_schema
- for attr in entity.edited_attributes:
- if eschema.subjrels[attr].final:
- constraints = [c for c in entity.e_schema.constraints(attr)
- if isinstance(c, (RQLConstraint, RQLUniqueConstraint))]
- if constraints:
- CheckConstraintsOperation(session, rdef=(entity.eid, attr, None),
- constraints=constraints)
-
-
-class CheckRequiredRelationOperation(LateOperation):
- """checking relation cardinality has to be done after commit in
- case the relation is being replaced
- """
- eid, rtype = None, None
-
- def precommit_event(self):
- # recheck pending eids
- if self.eid in self.session.transaction_data.get('pendingeids', ()):
- return
- if self.rtype in self.session.transaction_data.get('pendingrtypes', ()):
- return
- if self.session.unsafe_execute(*self._rql()).rowcount < 1:
- etype = self.session.describe(self.eid)[0]
- _ = self.session._
- msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
- msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
- raise ValidationError(self.eid, {self.rtype: msg})
-
- def commit_event(self):
- pass
-
- def _rql(self):
- raise NotImplementedError()
-
-
-class CheckSRelationOp(CheckRequiredRelationOperation):
- """check required subject relation"""
- def _rql(self):
- return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-
-
-class CheckORelationOp(CheckRequiredRelationOperation):
- """check required object relation"""
- def _rql(self):
- return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
-
-
-def checkrel_if_necessary(session, opcls, rtype, eid):
- """check an equivalent operation has not already been added"""
- for op in session.pending_operations:
- if isinstance(op, opcls) and op.rtype == rtype and op.eid == eid:
- break
- else:
- opcls(session, rtype=rtype, eid=eid)
-
-
-def cardinalitycheck_after_add_entity(session, entity):
- """check cardinalities are satisfied"""
- if session.is_super_session:
- return
- eid = entity.eid
- for rschema, targetschemas, x in entity.e_schema.relation_definitions():
- # skip automatically handled relations
- if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
- continue
- if x == 'subject':
- subjtype = entity.e_schema
- objtype = targetschemas[0].type
- cardindex = 0
- opcls = CheckSRelationOp
- else:
- subjtype = targetschemas[0].type
- objtype = entity.e_schema
- cardindex = 1
- opcls = CheckORelationOp
- card = rschema.rproperty(subjtype, objtype, 'cardinality')
- if card[cardindex] in '1+':
- checkrel_if_necessary(session, opcls, rschema.type, eid)
-
-def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto):
- """check cardinalities are satisfied"""
- if session.is_super_session:
- return
- if rtype in DONT_CHECK_RTYPES_ON_DEL:
- return
- card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
- pendingrdefs = session.transaction_data.get('pendingrdefs', ())
- if (session.describe(eidfrom)[0], rtype, session.describe(eidto)[0]) in pendingrdefs:
- return
- pendingeids = session.transaction_data.get('pendingeids', ())
- if card[0] in '1+' and not eidfrom in pendingeids:
- checkrel_if_necessary(session, CheckSRelationOp, rtype, eidfrom)
- if card[1] in '1+' and not eidto in pendingeids:
- checkrel_if_necessary(session, CheckORelationOp, rtype, eidto)
-
-
-def _register_core_hooks(hm):
- hm.register_hook(handle_composite_before_del_relation, 'before_delete_relation', '')
- hm.register_hook(before_del_group, 'before_delete_entity', 'CWGroup')
-
- #hm.register_hook(cstrcheck_before_update_entity, 'before_update_entity', '')
- hm.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
- hm.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
- hm.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
- hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
- hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
- hm.register_hook(cstrcheck_after_update_attributes, 'after_add_entity', '')
- hm.register_hook(cstrcheck_after_update_attributes, 'after_update_entity', '')
-
-# user/groups synchronisation #################################################
-
-class GroupOperation(Operation):
- """base class for group operation"""
- geid = None
- def __init__(self, session, *args, **kwargs):
- """override to get the group name before actual groups manipulation:
-
- we may temporarily loose right access during a commit event, so
- no query should be emitted while comitting
- """
- rql = 'Any N WHERE G eid %(x)s, G name N'
- result = session.execute(rql, {'x': kwargs['geid']}, 'x', build_descr=False)
- Operation.__init__(self, session, *args, **kwargs)
- self.group = result[0][0]
-
-
-class DeleteGroupOp(GroupOperation):
- """synchronize user when a in_group relation has been deleted"""
- def commit_event(self):
- """the observed connections pool has been commited"""
- groups = self.cnxuser.groups
- try:
- groups.remove(self.group)
- except KeyError:
- self.error('user %s not in group %s', self.cnxuser, self.group)
- return
-
-
-def after_del_in_group(session, fromeid, rtype, toeid):
- """modify user permission, need to update users"""
- for session_ in get_user_sessions(session.repo, fromeid):
- DeleteGroupOp(session, cnxuser=session_.user, geid=toeid)
-
-
-class AddGroupOp(GroupOperation):
- """synchronize user when a in_group relation has been added"""
- def commit_event(self):
- """the observed connections pool has been commited"""
- groups = self.cnxuser.groups
- if self.group in groups:
- self.warning('user %s already in group %s', self.cnxuser,
- self.group)
- return
- groups.add(self.group)
-
-
-def after_add_in_group(session, fromeid, rtype, toeid):
- """modify user permission, need to update users"""
- for session_ in get_user_sessions(session.repo, fromeid):
- AddGroupOp(session, cnxuser=session_.user, geid=toeid)
-
-
-class DelUserOp(Operation):
- """synchronize user when a in_group relation has been added"""
- def __init__(self, session, cnxid):
- self.cnxid = cnxid
- Operation.__init__(self, session)
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- self.repo.close(self.cnxid)
- except BadConnectionId:
- pass # already closed
-
-
-def after_del_user(session, eid):
- """modify user permission, need to update users"""
- for session_ in get_user_sessions(session.repo, eid):
- DelUserOp(session, session_.id)
-
-
-def _register_usergroup_hooks(hm):
- """register user/group related hooks on the hooks manager"""
- hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
- hm.register_hook(after_add_in_group, 'after_add_relation', 'in_group')
- hm.register_hook(after_del_in_group, 'after_delete_relation', 'in_group')
-
-
-# workflow handling ###########################################################
-
-from cubicweb.entities.wfobjs import WorkflowTransition, WorkflowException
-
-def _change_state(session, x, oldstate, newstate):
- nocheck = session.transaction_data.setdefault('skip-security', set())
- nocheck.add((x, 'in_state', oldstate))
- nocheck.add((x, 'in_state', newstate))
- # delete previous state first in case we're using a super session
- fromsource = session.describe(x)[1]
- # don't try to remove previous state if in_state isn't stored in the system
- # source
- if fromsource == 'system' or \
- not session.repo.sources_by_uri[fromsource].support_relation('in_state'):
- session.delete_relation(x, 'in_state', oldstate)
- session.add_relation(x, 'in_state', newstate)
-
-
-class FireAutotransitionOp(PreCommitOperation):
- """try to fire auto transition after state changes"""
-
- def precommit_event(self):
- session = self.session
- entity = self.entity
- autotrs = list(entity.possible_transitions('auto'))
- if autotrs:
- assert len(autotrs) == 1
- entity.fire_transition(autotrs[0])
-
-
-def before_add_trinfo(session, entity):
- """check the transition is allowed, add missing information. Expect that:
- * wf_info_for inlined relation is set
- * by_transition or to_state (managers only) inlined relation is set
- """
- # first retreive entity to which the state change apply
- try:
- foreid = entity['wf_info_for']
- except KeyError:
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {'wf_info_for': msg})
- forentity = session.entity_from_eid(foreid)
- # then check it has a workflow set, unless we're in the process of changing
- # entity's workflow
- if session.transaction_data.get((forentity.eid, 'customwf')):
- wfeid = session.transaction_data[(forentity.eid, 'customwf')]
- wf = session.entity_from_eid(wfeid)
- else:
- wf = forentity.current_workflow
- if wf is None:
- msg = session._('related entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
- # then check it has a state set
- fromstate = forentity.current_state
- if fromstate is None:
- msg = session._('related entity has no state')
- raise ValidationError(entity.eid, {None: msg})
- # True if we are coming back from subworkflow
- swtr = session.transaction_data.pop((forentity.eid, 'subwfentrytr'), None)
- cowpowers = session.is_super_session or 'managers' in session.user.groups
- # no investigate the requested state change...
- try:
- treid = entity['by_transition']
- except KeyError:
- # no transition set, check user is a manager and destination state is
- # specified (and valid)
- if not cowpowers:
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {'by_transition': msg})
- deststateeid = entity.get('to_state')
- if not deststateeid:
- msg = session._('mandatory relation')
- raise ValidationError(entity.eid, {'by_transition': msg})
- deststate = wf.state_by_eid(deststateeid)
- if deststate is None:
- msg = entity.req._("state doesn't belong to entity's current workflow")
- raise ValidationError(entity.eid, {'to_state': msg})
- else:
- # check transition is valid and allowed, unless we're coming back from
- # subworkflow
- tr = session.entity_from_eid(treid)
- if swtr is None:
- if tr is None:
- msg = session._("transition doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {'by_transition': msg})
- if not tr.has_input_state(fromstate):
- _ = session._
- msg = _("transition %(tr)s isn't allowed from %(st)s") % {'tr': _(tr.name),
- 'st': _(fromstate.name),
- }
- raise ValidationError(entity.eid, {'by_transition': msg})
- if not tr.may_be_fired(foreid):
- msg = session._("transition may not be fired")
- raise ValidationError(entity.eid, {'by_transition': msg})
- if entity.get('to_state'):
- deststateeid = entity['to_state']
- if not cowpowers and deststateeid != tr.destination().eid:
- msg = session._("transition isn't allowed")
- raise ValidationError(entity.eid, {'by_transition': msg})
- if swtr is None:
- deststate = session.entity_from_eid(deststateeid)
- if not cowpowers and deststate is None:
- msg = entity.req._("state doesn't belong to entity's workflow")
- raise ValidationError(entity.eid, {'to_state': msg})
- else:
- deststateeid = tr.destination().eid
- # everything is ok, add missing information on the trinfo entity
- entity['from_state'] = fromstate.eid
- entity['to_state'] = deststateeid
- nocheck = session.transaction_data.setdefault('skip-security', set())
- nocheck.add((entity.eid, 'from_state', fromstate.eid))
- nocheck.add((entity.eid, 'to_state', deststateeid))
- FireAutotransitionOp(session, entity=forentity)
-
-
-def after_add_trinfo(session, entity):
- """change related entity state"""
- _change_state(session, entity['wf_info_for'],
- entity['from_state'], entity['to_state'])
- forentity = session.entity_from_eid(entity['wf_info_for'])
- assert forentity.current_state.eid == entity['to_state'], (
- forentity.eid, forentity.current_state.name)
- if forentity.main_workflow.eid != forentity.current_workflow.eid:
- SubWorkflowExitOp(session, forentity=forentity, trinfo=entity)
-
-class SubWorkflowExitOp(PreCommitOperation):
- def precommit_event(self):
- session = self.session
- forentity = self.forentity
- trinfo = self.trinfo
- # we're in a subworkflow, check if we've reached an exit point
- wftr = forentity.subworkflow_input_transition()
- if wftr is None:
- # inconsistency detected
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(self.trinfo.eid, {'to_state': msg})
- tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
- if tostate is not None:
- # reached an exit point
- msg = session._('exiting from subworkflow %s')
- msg %= session._(forentity.current_workflow.name)
- session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
- # XXX iirk
- req = forentity.req
- forentity.req = session.super_session
- try:
- trinfo = forentity.change_state(tostate, msg, u'text/plain',
- tr=wftr)
- finally:
- forentity.req = req
-
-
-class SetInitialStateOp(PreCommitOperation):
- """make initial state be a default state"""
-
- def precommit_event(self):
- session = self.session
- entity = self.entity
- # if there is an initial state and the entity's state is not set,
- # use the initial state as a default state
- pendingeids = session.transaction_data.get('pendingeids', ())
- if not entity.eid in pendingeids and not entity.in_state and \
- entity.main_workflow:
- state = entity.main_workflow.initial
- if state:
- # use super session to by-pass security checks
- session.super_session.add_relation(entity.eid, 'in_state',
- state.eid)
-
-
-def set_initial_state_after_add(session, entity):
- SetInitialStateOp(session, entity=entity)
-
-
-def before_add_in_state(session, eidfrom, rtype, eidto):
- """check state apply, in case of direct in_state change using unsafe_execute
- """
- nocheck = session.transaction_data.setdefault('skip-security', set())
- if (eidfrom, 'in_state', eidto) in nocheck:
- # state changed through TrInfo insertion, so we already know it's ok
- return
- entity = session.entity_from_eid(eidfrom)
- mainwf = entity.main_workflow
- if mainwf is None:
- msg = session._('entity has no workflow set')
- raise ValidationError(entity.eid, {None: msg})
- for wf in mainwf.iter_workflows():
- if wf.state_by_eid(eidto):
- break
- else:
- msg = session._("state doesn't belong to entity's workflow. You may "
- "want to set a custom workflow for this entity first.")
- raise ValidationError(eidfrom, {'in_state': msg})
- if entity.current_workflow and wf.eid != entity.current_workflow.eid:
- msg = session._("state doesn't belong to entity's current workflow")
- raise ValidationError(eidfrom, {'in_state': msg})
-
-
-class CheckTrExitPoint(PreCommitOperation):
-
- def precommit_event(self):
- tr = self.session.entity_from_eid(self.treid)
- outputs = set()
- for ep in tr.subworkflow_exit:
- if ep.subwf_state.eid in outputs:
- msg = self.session._("can't have multiple exits on the same state")
- raise ValidationError(self.treid, {'subworkflow_exit': msg})
- outputs.add(ep.subwf_state.eid)
-
-
-def after_add_subworkflow_exit(session, eidfrom, rtype, eidto):
- CheckTrExitPoint(session, treid=eidfrom)
-
-
-class WorkflowChangedOp(PreCommitOperation):
- """fix entity current state when changing its workflow"""
-
- def precommit_event(self):
- # notice that enforcement that new workflow apply to the entity's type is
- # done by schema rule, no need to check it here
- session = self.session
- pendingeids = session.transaction_data.get('pendingeids', ())
- if self.eid in pendingeids:
- return
- entity = session.entity_from_eid(self.eid)
- # check custom workflow has not been rechanged to another one in the same
- # transaction
- mainwf = entity.main_workflow
- if mainwf.eid == self.wfeid:
- deststate = mainwf.initial
- if not deststate:
- msg = session._('workflow has no initial state')
- raise ValidationError(entity.eid, {'custom_workflow': msg})
- if mainwf.state_by_eid(entity.current_state.eid):
- # nothing to do
- return
- # if there are no history, simply go to new workflow's initial state
- if not entity.workflow_history:
- if entity.current_state.eid != deststate.eid:
- _change_state(session, entity.eid,
- entity.current_state.eid, deststate.eid)
- return
- msg = session._('workflow changed to "%s"')
- msg %= session._(mainwf.name)
- session.transaction_data[(entity.eid, 'customwf')] = self.wfeid
- entity.change_state(deststate, msg, u'text/plain')
-
-
-def set_custom_workflow(session, eidfrom, rtype, eidto):
- WorkflowChangedOp(session, eid=eidfrom, wfeid=eidto)
-
-
-def del_custom_workflow(session, eidfrom, rtype, eidto):
- entity = session.entity_from_eid(eidfrom)
- typewf = entity.cwetype_workflow()
- if typewf is not None:
- WorkflowChangedOp(session, eid=eidfrom, wfeid=typewf.eid)
-
-
-def after_del_workflow(session, eid):
- # workflow cleanup
- session.execute('DELETE State X WHERE NOT X state_of Y')
- session.execute('DELETE Transition X WHERE NOT X transition_of Y')
-
-
-def _register_wf_hooks(hm):
- """register workflow related hooks on the hooks manager"""
- if 'in_state' in hm.schema:
- hm.register_hook(before_add_trinfo, 'before_add_entity', 'TrInfo')
- hm.register_hook(after_add_trinfo, 'after_add_entity', 'TrInfo')
- #hm.register_hook(relation_deleted, 'before_delete_relation', 'in_state')
- for eschema in hm.schema.entities():
- if 'in_state' in eschema.subject_relations():
- hm.register_hook(set_initial_state_after_add, 'after_add_entity',
- str(eschema))
- hm.register_hook(set_custom_workflow, 'after_add_relation', 'custom_workflow')
- hm.register_hook(del_custom_workflow, 'after_delete_relation', 'custom_workflow')
- hm.register_hook(after_del_workflow, 'after_delete_entity', 'Workflow')
- hm.register_hook(before_add_in_state, 'before_add_relation', 'in_state')
- hm.register_hook(after_add_subworkflow_exit, 'after_add_relation', 'subworkflow_exit')
-
-
-# CWProperty hooks #############################################################
-
-
-class DelCWPropertyOp(Operation):
- """a user's custom properties has been deleted"""
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- del self.epropdict[self.key]
- except KeyError:
- self.error('%s has no associated value', self.key)
-
-
-class ChangeCWPropertyOp(Operation):
- """a user's custom properties has been added/changed"""
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- self.epropdict[self.key] = self.value
-
-
-class AddCWPropertyOp(Operation):
- """a user's custom properties has been added/changed"""
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- eprop = self.eprop
- if not eprop.for_user:
- self.repo.vreg.eprop_values[eprop.pkey] = eprop.value
- # if for_user is set, update is handled by a ChangeCWPropertyOp operation
-
-
-def after_add_eproperty(session, entity):
- key, value = entity.pkey, entity.value
- try:
- value = session.vreg.typed_value(key, value)
- except UnknownProperty:
- raise ValidationError(entity.eid, {'pkey': session._('unknown property key')})
- except ValueError, ex:
- raise ValidationError(entity.eid, {'value': session._(str(ex))})
- if not session.user.matching_groups('managers'):
- session.add_relation(entity.eid, 'for_user', session.user.eid)
- else:
- AddCWPropertyOp(session, eprop=entity)
-
-
-def after_update_eproperty(session, entity):
- if not ('pkey' in entity.edited_attributes or
- 'value' in entity.edited_attributes):
- return
- key, value = entity.pkey, entity.value
- try:
- value = session.vreg.typed_value(key, value)
- except UnknownProperty:
- return
- except ValueError, ex:
- raise ValidationError(entity.eid, {'value': session._(str(ex))})
- if entity.for_user:
- for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
- ChangeCWPropertyOp(session, epropdict=session_.user.properties,
- key=key, value=value)
- else:
- # site wide properties
- ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values,
- key=key, value=value)
-
-
-def before_del_eproperty(session, eid):
- for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()):
- if rtype == 'for_user' and eidfrom == eid:
- # if for_user was set, delete has already been handled
- break
- else:
- key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
- {'x': eid}, 'x')[0][0]
- DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key)
-
-
-def after_add_for_user(session, fromeid, rtype, toeid):
- if not session.describe(fromeid)[0] == 'CWProperty':
- return
- key, value = session.execute('Any K,V WHERE P eid %(x)s,P pkey K,P value V',
- {'x': fromeid}, 'x')[0]
- if session.vreg.property_info(key)['sitewide']:
- raise ValidationError(fromeid,
- {'for_user': session._("site-wide property can't be set for user")})
- for session_ in get_user_sessions(session.repo, toeid):
- ChangeCWPropertyOp(session, epropdict=session_.user.properties,
- key=key, value=value)
-
-
-def before_del_for_user(session, fromeid, rtype, toeid):
- key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
- {'x': fromeid}, 'x')[0][0]
- relation_deleted(session, fromeid, rtype, toeid)
- for session_ in get_user_sessions(session.repo, toeid):
- DelCWPropertyOp(session, epropdict=session_.user.properties, key=key)
-
-
-def _register_eproperty_hooks(hm):
- """register workflow related hooks on the hooks manager"""
- hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty')
- hm.register_hook(after_update_eproperty, 'after_update_entity', 'CWProperty')
- hm.register_hook(before_del_eproperty, 'before_delete_entity', 'CWProperty')
- hm.register_hook(after_add_for_user, 'after_add_relation', 'for_user')
- hm.register_hook(before_del_for_user, 'before_delete_relation', 'for_user')
--- a/server/hooksmanager.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/hooksmanager.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,347 +1,10 @@
-"""Hooks management
-
-Hooks are called before / after any individual update of entities / relations
-in the repository.
-
-Here is the prototype of the different hooks:
-
-* filtered on the entity's type:
-
- before_add_entity (session, entity)
- after_add_entity (session, entity)
- before_update_entity (session, entity)
- after_update_entity (session, entity)
- before_delete_entity (session, eid)
- after_delete_entity (session, eid)
-
-* filtered on the relation's type:
-
- before_add_relation (session, fromeid, rtype, toeid)
- after_add_relation (session, fromeid, rtype, toeid)
- before_delete_relation (session, fromeid, rtype, toeid)
- after_delete_relation (session, fromeid, rtype, toeid)
-
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-ENTITIES_HOOKS = ('before_add_entity', 'after_add_entity',
- 'before_update_entity', 'after_update_entity',
- 'before_delete_entity', 'after_delete_entity')
-RELATIONS_HOOKS = ('before_add_relation', 'after_add_relation' ,
- 'before_delete_relation','after_delete_relation')
-SYSTEM_HOOKS = ('server_backup', 'server_restore',
- 'server_startup', 'server_shutdown',
- 'session_open', 'session_close')
-
-ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS)
-
-class HooksManager(object):
- """handle hooks registration and calls
- """
- verification_hooks_activated = True
-
- def __init__(self, schema):
- self.set_schema(schema)
-
- def set_schema(self, schema):
- self._hooks = {}
- self.schema = schema
- self._init_hooks(schema)
-
- def register_hooks(self, hooks):
- """register a dictionary of hooks :
-
- {'event': {'entity or relation type': [callbacks list]}}
- """
- for event, subevents in hooks.items():
- for subevent, callbacks in subevents.items():
- for callback in callbacks:
- self.register_hook(callback, event, subevent)
-
- def register_hook(self, function, event, etype=''):
- """register a function to call when <event> occurs
-
- <etype> is an entity/relation type or an empty string.
-
- If etype is the empty string, the function will be called at each event,
- else the function will be called only when event occurs on an entity or
- relation of the given type.
- """
- assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS)
- assert (not event in SYSTEM_HOOKS or not etype), (event, etype)
- etype = etype or ''
- try:
- self._hooks[event][etype].append(function)
- self.debug('registered hook %s on %s (%s)', event, etype or 'any',
- function.func_name)
-
- except KeyError:
- self.error('can\'t register hook %s on %s (%s)',
- event, etype or 'any', function.func_name)
-
- def unregister_hook(self, function_or_cls, event=None, etype=''):
- """unregister a function to call when <event> occurs, or a Hook subclass.
- In the later case, event/type information are extracted from the given
- class.
- """
- if isinstance(function_or_cls, type) and issubclass(function_or_cls, Hook):
- for event, ertype in function_or_cls.register_to():
- for hook in self._hooks[event][ertype]:
- if getattr(hook, 'im_self', None).__class__ is function_or_cls:
- self._hooks[event][ertype].remove(hook)
- self.info('unregister hook %s on %s (%s)', event, etype,
- function_or_cls.__name__)
- break
- else:
- self.warning("can't unregister hook %s on %s (%s), not found",
- event, etype, function_or_cls.__name__)
- else:
- assert event in ALL_HOOKS, event
- etype = etype or ''
- self.info('unregister hook %s on %s (%s)', event, etype,
- function_or_cls.func_name)
- self._hooks[event][etype].remove(function_or_cls)
-
- def call_hooks(self, __event, __type='', *args, **kwargs):
- """call hook matching event and optional type"""
- if __type:
- self.info('calling hooks for event %s (%s)', __event, __type)
- else:
- self.info('calling hooks for event %s', __event)
- # call generic hooks first
- for hook in self._hooks[__event]['']:
- #print '[generic]', hook.__name__
- hook(*args, **kwargs)
- if __type:
- for hook in self._hooks[__event][__type]:
- #print '[%s]'%__type, hook.__name__
- hook(*args, **kwargs)
-
- def _init_hooks(self, schema):
- """initialize the hooks map"""
- for hook_event in ENTITIES_HOOKS:
- self._hooks[hook_event] = {'': []}
- for etype in schema.entities():
- self._hooks[hook_event][etype] = []
- for hook_event in RELATIONS_HOOKS:
- self._hooks[hook_event] = {'': []}
- for r_type in schema.relations():
- self._hooks[hook_event][r_type] = []
- for hook_event in SYSTEM_HOOKS:
- self._hooks[hook_event] = {'': []}
-
- def register_system_hooks(self, config):
- """register system hooks according to the configuration"""
- self.info('register core hooks')
- from cubicweb.server.hooks import _register_metadata_hooks, _register_wf_hooks
- _register_metadata_hooks(self)
- self.info('register workflow hooks')
- _register_wf_hooks(self)
- if config.core_hooks:
- from cubicweb.server.hooks import _register_core_hooks
- _register_core_hooks(self)
- if config.schema_hooks:
- from cubicweb.server.schemahooks import _register_schema_hooks
- self.info('register schema hooks')
- _register_schema_hooks(self)
- if config.usergroup_hooks:
- from cubicweb.server.hooks import _register_usergroup_hooks
- from cubicweb.server.hooks import _register_eproperty_hooks
- self.info('register user/group hooks')
- _register_usergroup_hooks(self)
- _register_eproperty_hooks(self)
- if config.security_hooks:
- from cubicweb.server.securityhooks import register_security_hooks
- self.info('register security hooks')
- register_security_hooks(self)
- if not self.verification_hooks_activated:
- self.deactivate_verification_hooks()
-
- def deactivate_verification_hooks(self):
- from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
- cardinalitycheck_before_del_relation,
- cstrcheck_after_add_relation,
- uniquecstrcheck_before_modification)
- self.warning('deactivating verification hooks')
- self.verification_hooks_activated = False
- self.unregister_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
- self.unregister_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
- self.unregister_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
- self.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
- self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-# self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
-# self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-
- def reactivate_verification_hooks(self):
- from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
- cardinalitycheck_before_del_relation,
- cstrcheck_after_add_relation,
- uniquecstrcheck_before_modification)
- self.warning('reactivating verification hooks')
- self.verification_hooks_activated = True
- self.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
- self.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
- self.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
- self.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
- self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
-# self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
-# self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
-
-from cubicweb.selectors import yes
-from cubicweb.appobject import AppObject
-
-class autoid(type):
- """metaclass to create an unique 'id' attribute on the class using it"""
- # XXX is this metaclass really necessary ?
- def __new__(mcs, name, bases, classdict):
- cls = super(autoid, mcs).__new__(mcs, name, bases, classdict)
- cls.id = str(id(cls))
- return cls
-
-class Hook(AppObject):
- __metaclass__ = autoid
- __registry__ = 'hooks'
- __select__ = yes()
- # set this in derivated classes
- events = None
- accepts = None
- enabled = True
-
- def __init__(self, event=None):
- super(Hook, self).__init__()
- self.event = event
-
- @classmethod
- def registered(cls, vreg):
- super(Hook, cls).registered(vreg)
- return cls()
-
- @classmethod
- def register_to(cls):
- if not cls.enabled:
- cls.warning('%s hook has been disabled', cls)
- return
- done = set()
- assert isinstance(cls.events, (tuple, list)), \
- '%s: events is expected to be a tuple, not %s' % (
- cls, type(cls.events))
- for event in cls.events:
- if event in SYSTEM_HOOKS:
- assert not cls.accepts or cls.accepts == ('Any',), \
- '%s doesnt make sense on %s' % (cls.accepts, event)
- cls.accepts = ('Any',)
- for ertype in cls.accepts:
- if (event, ertype) in done:
- continue
- yield event, ertype
- done.add((event, ertype))
- try:
- eschema = cls.schema.eschema(ertype)
- except KeyError:
- # relation schema
- pass
- else:
- for eetype in eschema.specialized_by():
- if (event, eetype) in done:
- continue
- yield event, str(eetype)
- done.add((event, eetype))
-
-
- def make_callback(self, event):
- if len(self.events) == 1:
- return self.call
- return self.__class__(event=event).call
-
- def call(self):
- raise NotImplementedError
-
-class SystemHook(Hook):
- accepts = ()
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(HooksManager, getLogger('cubicweb.hooksmanager'))
-set_log_methods(Hook, getLogger('cubicweb.hooks'))
-
-# base classes for relation propagation ########################################
-
-from cubicweb.server.pool import PreCommitOperation
-
-
-class PropagateSubjectRelationHook(Hook):
- """propagate permissions and nosy list when new entity are added"""
- events = ('after_add_relation',)
- # to set in concrete class
- rtype = None
- subject_relations = None
- object_relations = None
- accepts = None # subject_relations + object_relations
-
- def call(self, session, fromeid, rtype, toeid):
- for eid in (fromeid, toeid):
- etype = session.describe(eid)[0]
- if self.rtype not in self.schema.eschema(etype).subjrels:
- return
- if rtype in self.subject_relations:
- meid, seid = fromeid, toeid
- else:
- assert rtype in self.object_relations
- meid, seid = toeid, fromeid
- session.unsafe_execute(
- 'SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\
- % (self.rtype, self.rtype, self.rtype),
- {'x': meid, 'e': seid}, ('x', 'e'))
-
-
-class PropagateSubjectRelationAddHook(Hook):
- """propagate on existing entities when a permission or nosy list is added"""
- events = ('after_add_relation',)
- # to set in concrete class
- rtype = None
- subject_relations = None
- object_relations = None
- accepts = None # (self.rtype,)
-
- def call(self, session, fromeid, rtype, toeid):
- eschema = self.schema.eschema(session.describe(fromeid)[0])
- execute = session.unsafe_execute
- for rel in self.subject_relations:
- if rel in eschema.subjrels:
- execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
- 'X %s R, NOT R %s P' % (rtype, rel, rtype),
- {'x': fromeid, 'p': toeid}, 'x')
- for rel in self.object_relations:
- if rel in eschema.objrels:
- execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
- 'R %s X, NOT R %s P' % (rtype, rel, rtype),
- {'x': fromeid, 'p': toeid}, 'x')
-
-
-class PropagateSubjectRelationDelHook(Hook):
- """propagate on existing entities when a permission is deleted"""
- events = ('after_delete_relation',)
- # to set in concrete class
- rtype = None
- subject_relations = None
- object_relations = None
- accepts = None # (self.rtype,)
-
- def call(self, session, fromeid, rtype, toeid):
- eschema = self.schema.eschema(session.describe(fromeid)[0])
- execute = session.unsafe_execute
- for rel in self.subject_relations:
- if rel in eschema.subjrels:
- execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
- 'X %s R' % (rtype, rel),
- {'x': fromeid, 'p': toeid}, 'x')
- for rel in self.object_relations:
- if rel in eschema.objrels:
- execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, '
- 'R %s X' % (rtype, rel),
- {'x': fromeid, 'p': toeid}, 'x')
+from logilab.common.deprecation import class_renamed, class_moved
+from cubicweb.server import hook
+SystemHook = class_renamed('SystemHook', hook.Hook)
+PropagateSubjectRelationHook = class_renamed('PropagateSubjectRelationHook',
+ hook.PropagateSubjectRelationHook)
+PropagateSubjectRelationAddHook = class_renamed('PropagateSubjectRelationAddHook',
+ hook.PropagateSubjectRelationAddHook)
+PropagateSubjectRelationDelHook = class_renamed('PropagateSubjectRelationDelHook',
+ hook.PropagateSubjectRelationDelHook)
+Hook = class_moved(hook.Hook)
--- a/server/migractions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/migractions.py Mon Feb 08 13:35:07 2010 +0100
@@ -38,7 +38,7 @@
from cubicweb.schema import (META_RTYPES, VIRTUAL_RTYPES,
CubicWebRelationSchema, order_eschemas)
from cubicweb.dbapi import get_repository, repo_connect
-from cubicweb.common.migration import MigrationHelper, yes
+from cubicweb.migration import MigrationHelper, yes
try:
from cubicweb.server import SOURCE_TYPES, schemaserial as ss
@@ -148,7 +148,7 @@
try:
source.backup(osp.join(tmpdir, source.uri))
except Exception, exc:
- print '-> error trying to backup [%s]' % exc
+ print '-> error trying to backup %s [%s]' % (source.uri, exc)
if not self.confirm('Continue anyway?', default='n'):
raise SystemExit(1)
else:
@@ -180,7 +180,7 @@
bkup = tarfile.open(backupfile, 'r|gz')
except tarfile.ReadError:
# assume restoring old backup
- shutil.copy(backupfile, osp.join(tmpdir, 'system'))
+ shutil.copy(backupfile, osp.join(tmpdir, 'system'))
else:
for name in bkup.getnames():
if name[0] in '/.':
@@ -198,7 +198,7 @@
try:
source.restore(osp.join(tmpdir, source.uri), self.confirm, drop)
except Exception, exc:
- print '-> error trying to restore [%s]' % exc
+ print '-> error trying to restore %s [%s]' % (source.uri, exc)
if not self.confirm('Continue anyway?', default='n'):
raise SystemExit(1)
shutil.rmtree(tmpdir)
@@ -221,7 +221,7 @@
login, pwd = manager_userpasswd()
while True:
try:
- self._cnx = repo_connect(self.repo, login, pwd)
+ self._cnx = repo_connect(self.repo, login, password=pwd)
if not 'managers' in self._cnx.user(self.session).groups:
print 'migration need an account in the managers group'
else:
@@ -263,7 +263,9 @@
def _create_context(self):
"""return a dictionary to use as migration script execution context"""
context = super(ServerMigrationHelper, self)._create_context()
- context.update({'checkpoint': self.checkpoint,
+ context.update({'commit': self.checkpoint,
+ 'rollback': self.rollback,
+ 'checkpoint': deprecated('[3.6] use commit')(self.checkpoint),
'sql': self.sqlexec,
'rql': self.rqlexec,
'rqliter': self.rqliter,
@@ -272,9 +274,9 @@
'fsschema': self.fs_schema,
'session' : self.session,
'repo' : self.repo,
- 'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms),
- 'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms),
- 'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms),
+ 'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
+ 'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
+ 'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms), # 3.4
})
return context
@@ -294,7 +296,7 @@
from cubicweb.server.hooks import setowner_after_add_entity
self.repo.hm.unregister_hook(setowner_after_add_entity,
'after_add_entity', '')
- self.deactivate_verification_hooks()
+ self.cmd_deactivate_verification_hooks()
self.info('executing %s', apc)
confirm = self.confirm
execscript_confirm = self.execscript_confirm
@@ -308,7 +310,7 @@
if self.config.free_wheel:
self.repo.hm.register_hook(setowner_after_add_entity,
'after_add_entity', '')
- self.reactivate_verification_hooks()
+ self.cmd_reactivate_verification_hooks()
def install_custom_sql_scripts(self, directory, driver):
self.session.set_pool() # ensure pool is set
@@ -327,35 +329,31 @@
# schema synchronization internals ########################################
- def _synchronize_permissions(self, ertype):
+ def _synchronize_permissions(self, erschema, teid):
"""permission synchronization for an entity or relation type"""
- if ertype in VIRTUAL_RTYPES:
- return
- newrschema = self.fs_schema[ertype]
- teid = self.repo.schema[ertype].eid
- if 'update' in newrschema.ACTIONS or newrschema.final:
+ assert teid, erschema
+ if 'update' in erschema.ACTIONS or erschema.final:
# entity type
exprtype = u'ERQLExpression'
else:
# relation type
exprtype = u'RRQLExpression'
- assert teid, ertype
gm = self.group_mapping()
confirm = self.verbosity >= 2
# * remove possibly deprecated permission (eg in the persistent schema
# but not in the new schema)
# * synchronize existing expressions
# * add new groups/expressions
- for action in newrschema.ACTIONS:
+ for action in erschema.ACTIONS:
perm = '%s_permission' % action
# handle groups
- newgroups = list(newrschema.get_groups(action))
+ newgroups = list(erschema.get_groups(action))
for geid, gname in self.rqlexec('Any G, GN WHERE T %s G, G name GN, '
'T eid %%(x)s' % perm, {'x': teid}, 'x',
ask_confirm=False):
if not gname in newgroups:
if not confirm or self.confirm('remove %s permission of %s to %s?'
- % (action, ertype, gname)):
+ % (action, erschema, gname)):
self.rqlexec('DELETE T %s G WHERE G eid %%(x)s, T eid %s'
% (perm, teid),
{'x': geid}, 'x', ask_confirm=False)
@@ -363,18 +361,18 @@
newgroups.remove(gname)
for gname in newgroups:
if not confirm or self.confirm('grant %s permission of %s to %s?'
- % (action, ertype, gname)):
+ % (action, erschema, gname)):
self.rqlexec('SET T %s G WHERE G eid %%(x)s, T eid %s'
% (perm, teid),
{'x': gm[gname]}, 'x', ask_confirm=False)
# handle rql expressions
- newexprs = dict((expr.expression, expr) for expr in newrschema.get_rqlexprs(action))
+ newexprs = dict((expr.expression, expr) for expr in erschema.get_rqlexprs(action))
for expreid, expression in self.rqlexec('Any E, EX WHERE T %s E, E expression EX, '
'T eid %s' % (perm, teid),
ask_confirm=False):
if not expression in newexprs:
if not confirm or self.confirm('remove %s expression for %s permission of %s?'
- % (expression, action, ertype)):
+ % (expression, action, erschema)):
# deleting the relation will delete the expression entity
self.rqlexec('DELETE T %s E WHERE E eid %%(x)s, T eid %s'
% (perm, teid),
@@ -384,7 +382,7 @@
for expression in newexprs.values():
expr = expression.expression
if not confirm or self.confirm('add %s expression for %s permission of %s?'
- % (expr, action, ertype)):
+ % (expr, action, erschema)):
self.rqlexec('INSERT RQLExpression X: X exprtype %%(exprtype)s, '
'X expression %%(expr)s, X mainvars %%(vars)s, T %s X '
'WHERE T eid %%(x)s' % perm,
@@ -392,12 +390,12 @@
'vars': expression.mainvars, 'x': teid}, 'x',
ask_confirm=False)
- def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True):
+ def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True, syncprops=True):
"""synchronize properties of the persistent relation schema against its
current definition:
* description
- * symetric, meta
+ * symmetric, meta
* inlined
* relation definitions if `syncrdefs`
* permissions if `syncperms`
@@ -409,16 +407,17 @@
return
self._synchronized.add(rtype)
rschema = self.fs_schema.rschema(rtype)
- self.rqlexecall(ss.updaterschema2rql(rschema),
- ask_confirm=self.verbosity>=2)
- reporschema = self.repo.schema.rschema(rtype)
+ if syncprops:
+ self.rqlexecall(ss.updaterschema2rql(rschema),
+ ask_confirm=self.verbosity>=2)
if syncrdefs:
- for subj, obj in rschema.iter_rdefs():
- if not reporschema.has_rdef(subj, obj):
+ reporschema = self.repo.schema.rschema(rtype)
+ for subj, obj in rschema.rdefs:
+ if (subj, obj) not in reporschema.rdefs:
continue
- self._synchronize_rdef_schema(subj, rschema, obj)
- if syncperms:
- self._synchronize_permissions(rtype)
+ self._synchronize_rdef_schema(subj, rschema, obj,
+ syncprops=syncprops,
+ syncperms=syncperms)
def _synchronize_eschema(self, etype, syncperms=True):
"""synchronize properties of the persistent entity schema against
@@ -464,13 +463,14 @@
reporschema = self.repo.schema.rschema(rschema)
for subj in subjtypes:
for obj in objtypes:
- if not reporschema.has_rdef(subj, obj):
+ if (subj, obj) not in reporschema.rdefs:
continue
self._synchronize_rdef_schema(subj, rschema, obj)
if syncperms:
- self._synchronize_permissions(etype)
+ self._synchronize_permissions(eschema, repoeschema.eid)
- def _synchronize_rdef_schema(self, subjtype, rtype, objtype):
+ def _synchronize_rdef_schema(self, subjtype, rtype, objtype,
+ syncperms=True, syncprops=True):
"""synchronize properties of the persistent relation definition schema
against its current definition:
* order and other properties
@@ -482,48 +482,53 @@
if (subjtype, rschema, objtype) in self._synchronized:
return
self._synchronized.add((subjtype, rschema, objtype))
- if rschema.symetric:
+ if rschema.symmetric:
self._synchronized.add((objtype, rschema, subjtype))
confirm = self.verbosity >= 2
- # properties
- self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
- ask_confirm=confirm)
- # constraints
- newconstraints = list(rschema.rproperty(subjtype, objtype, 'constraints'))
- # 1. remove old constraints and update constraints of the same type
- # NOTE: don't use rschema.constraint_by_type because it may be
- # out of sync with newconstraints when multiple
- # constraints of the same type are used
- for cstr in reporschema.rproperty(subjtype, objtype, 'constraints'):
+ if syncprops:
+ # properties
+ self.rqlexecall(ss.updaterdef2rql(rschema, subjtype, objtype),
+ ask_confirm=confirm)
+ # constraints
+ rdef = rschema.rdef(subjtype, objtype)
+ repordef = reporschema.rdef(subjtype, objtype)
+ newconstraints = list(rdef.constraints)
+ # 1. remove old constraints and update constraints of the same type
+ # NOTE: don't use rschema.constraint_by_type because it may be
+ # out of sync with newconstraints when multiple
+ # constraints of the same type are used
+ for cstr in repordef.constraints:
+ for newcstr in newconstraints:
+ if newcstr.type() == cstr.type():
+ break
+ else:
+ newcstr = None
+ if newcstr is None:
+ self.rqlexec('DELETE X constrained_by C WHERE C eid %(x)s',
+ {'x': cstr.eid}, 'x',
+ ask_confirm=confirm)
+ self.rqlexec('DELETE CWConstraint C WHERE C eid %(x)s',
+ {'x': cstr.eid}, 'x',
+ ask_confirm=confirm)
+ else:
+ newconstraints.remove(newcstr)
+ values = {'x': cstr.eid,
+ 'v': unicode(newcstr.serialize())}
+ self.rqlexec('SET X value %(v)s WHERE X eid %(x)s',
+ values, 'x', ask_confirm=confirm)
+ # 2. add new constraints
for newcstr in newconstraints:
- if newcstr.type() == cstr.type():
- break
- else:
- newcstr = None
- if newcstr is None:
- self.rqlexec('DELETE X constrained_by C WHERE C eid %(x)s',
- {'x': cstr.eid}, 'x',
- ask_confirm=confirm)
- self.rqlexec('DELETE CWConstraint C WHERE C eid %(x)s',
- {'x': cstr.eid}, 'x',
- ask_confirm=confirm)
- else:
- newconstraints.remove(newcstr)
- values = {'x': cstr.eid,
- 'v': unicode(newcstr.serialize())}
- self.rqlexec('SET X value %(v)s WHERE X eid %(x)s',
- values, 'x', ask_confirm=confirm)
- # 2. add new constraints
- for newcstr in newconstraints:
- self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
- newcstr),
- ask_confirm=confirm)
+ self.rqlexecall(ss.constraint2rql(rschema, subjtype, objtype,
+ newcstr),
+ ask_confirm=confirm)
+ if syncperms and not rschema in VIRTUAL_RTYPES:
+ self._synchronize_permissions(rdef, repordef.eid)
# base actions ############################################################
- def checkpoint(self):
+ def checkpoint(self, ask_confirm=True):
"""checkpoint action"""
- if self.confirm('commit now ?', shell=False):
+ if not ask_confirm or self.confirm('commit now ?', shell=False):
self.commit()
def cmd_add_cube(self, cube, update_database=True):
@@ -579,8 +584,8 @@
# check if attributes has been added to existing entities
for rschema in newcubes_schema.relations():
existingschema = self.repo.schema.rschema(rschema.type)
- for (fromtype, totype) in rschema.iter_rdefs():
- if existingschema.has_rdef(fromtype, totype):
+ for (fromtype, totype) in rschema.rdefs:
+ if (fromtype, totype) in existingschema.rdefs:
continue
# check we should actually add the relation definition
if not (fromtype in new or totype in new or rschema in new):
@@ -616,9 +621,9 @@
if rschema in removedcubes_schema and rschema in reposchema:
# check if attributes/relations has been added to entities from
# other cubes
- for fromtype, totype in rschema.iter_rdefs():
- if not removedcubes_schema[rschema.type].has_rdef(fromtype, totype) and \
- reposchema[rschema.type].has_rdef(fromtype, totype):
+ for fromtype, totype in rschema.rdefs:
+ if (fromtype, totype) not in removedcubes_schema[rschema.type].rdefs and \
+ (fromtype, totype) in reposchema[rschema.type].rdefs:
self.cmd_drop_relation_definition(
str(fromtype), rschema.type, str(totype))
# execute post-remove files
@@ -685,13 +690,11 @@
else:
eschema = self.fs_schema.eschema(etype)
confirm = self.verbosity >= 2
+ groupmap = self.group_mapping()
# register the entity into CWEType
- self.rqlexecall(ss.eschema2rql(eschema), ask_confirm=confirm)
+ self.rqlexecall(ss.eschema2rql(eschema, groupmap), ask_confirm=confirm)
# add specializes relation if needed
self.rqlexecall(ss.eschemaspecialize2rql(eschema), ask_confirm=confirm)
- # register groups / permissions for the entity
- self.rqlexecall(ss.erperms2rql(eschema, self.group_mapping()),
- ask_confirm=confirm)
# register entity's attributes
for rschema, attrschema in eschema.attribute_definitions():
# ignore those meta relations, they will be automatically added
@@ -702,7 +705,8 @@
# actually in the schema
self.cmd_add_relation_type(rschema.type, False, commit=True)
# register relation definition
- self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type),
+ self.rqlexecall(ss.rdef2rql(rschema, etype, attrschema.type,
+ groupmap=groupmap),
ask_confirm=confirm)
# take care to newly introduced base class
# XXX some part of this should probably be under the "if auto" block
@@ -714,8 +718,8 @@
continue
if instspschema.specializes() != eschema:
self.rqlexec('SET D specializes P WHERE D eid %(d)s, P name %(pn)s',
- {'d': instspschema.eid,
- 'pn': eschema.type}, ask_confirm=confirm)
+ {'d': instspschema.eid,
+ 'pn': eschema.type}, ask_confirm=confirm)
for rschema, tschemas, role in spschema.relation_definitions(True):
for tschema in tschemas:
if not tschema in instschema:
@@ -760,10 +764,11 @@
self.cmd_add_relation_type(rschema.type, False, commit=True)
rtypeadded = True
# register relation definition
- # remember this two avoid adding twice non symetric relation
+ # remember this two avoid adding twice non symmetric relation
# such as "Emailthread forked_from Emailthread"
added.append((etype, rschema.type, targettype))
- self.rqlexecall(ss.rdef2rql(rschema, etype, targettype),
+ self.rqlexecall(ss.rdef2rql(rschema, etype, targettype,
+ groupmap=groupmap),
ask_confirm=confirm)
for rschema in eschema.object_relations():
rtypeadded = rschema.type in instschema or rschema.type in added
@@ -783,7 +788,8 @@
elif (targettype, rschema.type, etype) in added:
continue
# register relation definition
- self.rqlexecall(ss.rdef2rql(rschema, targettype, etype),
+ self.rqlexecall(ss.rdef2rql(rschema, targettype, etype,
+ groupmap=groupmap),
ask_confirm=confirm)
if commit:
self.commit()
@@ -828,12 +834,9 @@
# definitions
self.rqlexecall(ss.rschema2rql(rschema, addrdef=False),
ask_confirm=self.verbosity>=2)
- # register groups / permissions for the relation
- self.rqlexecall(ss.erperms2rql(rschema, self.group_mapping()),
- ask_confirm=self.verbosity>=2)
if addrdef:
self.commit()
- self.rqlexecall(ss.rdef2rql(rschema),
+ self.rqlexecall(ss.rdef2rql(rschema, groupmap=self.group_mapping()),
ask_confirm=self.verbosity>=2)
if rtype in META_RTYPES:
# if the relation is in META_RTYPES, ensure we're adding it for
@@ -848,7 +851,8 @@
props = rschema.rproperties(
rschema.subjects(objtype)[0], objtype)
assert props
- self.rqlexecall(ss.rdef2rql(rschema, etype, objtype, props),
+ self.rqlexecall(ss.rdef2rql(rschema, etype, objtype, props,
+ groupmap=self.group_mapping()),
ask_confirm=self.verbosity>=2)
if commit:
@@ -880,7 +884,8 @@
rschema = self.fs_schema.rschema(rtype)
if not rtype in self.repo.schema:
self.cmd_add_relation_type(rtype, addrdef=False, commit=True)
- self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype),
+ self.rqlexecall(ss.rdef2rql(rschema, subjtype, objtype,
+ groupmap=self.group_mapping()),
ask_confirm=self.verbosity>=2)
if commit:
self.commit()
@@ -913,22 +918,25 @@
if isinstance(ertype, (tuple, list)):
assert len(ertype) == 3, 'not a relation definition'
assert syncprops, 'can\'t update permission for a relation definition'
- self._synchronize_rdef_schema(*ertype)
- elif syncprops:
+ self._synchronize_rdef_schema(ertype[0], ertype[1], ertype[2],
+ syncperms=syncperms,
+ syncprops=syncprops)
+ else:
erschema = self.repo.schema[ertype]
if isinstance(erschema, CubicWebRelationSchema):
self._synchronize_rschema(erschema, syncperms=syncperms,
+ syncprops=syncprops,
syncrdefs=syncrdefs)
- else:
+ elif syncprops:
self._synchronize_eschema(erschema, syncperms=syncperms)
- else:
- self._synchronize_permissions(ertype)
+ else:
+ self._synchronize_permissions(self.fs_schema[ertype], erschema.eid)
else:
for etype in self.repo.schema.entities():
if syncprops:
self._synchronize_eschema(etype, syncperms=syncperms)
else:
- self._synchronize_permissions(etype)
+ self._synchronize_permissions(self.fs_schema[etype], erschema.eid)
if commit:
self.commit()
@@ -1045,7 +1053,8 @@
return rset.get_entity(0, 0)
return self.cmd_add_workflow('%s workflow' % ';'.join(etypes), etypes)
- @deprecated('[3.5] use add_workflow and Workflow.add_state method')
+ @deprecated('[3.5] use add_workflow and Workflow.add_state method',
+ stacklevel=3)
def cmd_add_state(self, name, stateof, initial=False, commit=False, **kwargs):
"""method to ease workflow definition: add a state for one or more
entity type(s)
@@ -1056,7 +1065,8 @@
self.commit()
return state.eid
- @deprecated('[3.5] use add_workflow and Workflow.add_transition method')
+ @deprecated('[3.5] use add_workflow and Workflow.add_transition method',
+ stacklevel=3)
def cmd_add_transition(self, name, transitionof, fromstates, tostate,
requiredgroups=(), conditions=(), commit=False, **kwargs):
"""method to ease workflow definition: add a transition for one or more
@@ -1069,7 +1079,8 @@
self.commit()
return tr.eid
- @deprecated('[3.5] use Transition.set_transition_permissions method')
+ @deprecated('[3.5] use Transition.set_transition_permissions method',
+ stacklevel=3)
def cmd_set_transition_permissions(self, treid,
requiredgroups=(), conditions=(),
reset=True, commit=False):
@@ -1081,7 +1092,8 @@
if commit:
self.commit()
- @deprecated('[3.5] use entity.fire_transition("transition") or entity.change_state("state")')
+ @deprecated('[3.5] use entity.fire_transition("transition") or entity.change_state("state")',
+ stacklevel=3)
def cmd_set_state(self, eid, statename, commit=False):
self._cw.entity_from_eid(eid).change_state(statename)
if commit:
@@ -1123,7 +1135,7 @@
self.commit()
return entity
- @deprecated('use create_entity')
+ @deprecated('[3.5] use create_entity', stacklevel=3)
def cmd_add_entity(self, etype, *args, **kwargs):
"""add a new entity of the given type"""
return self.cmd_create_entity(etype, *args, **kwargs).eid
@@ -1171,10 +1183,10 @@
return ForRqlIterator(self, rql, None, ask_confirm)
def cmd_deactivate_verification_hooks(self):
- self.repo.hm.deactivate_verification_hooks()
+ self.config.disabled_hooks_categories.add('integrity')
def cmd_reactivate_verification_hooks(self):
- self.repo.hm.reactivate_verification_hooks()
+ self.config.disabled_hooks_categories.remove('integrity')
# broken db commands ######################################################
--- a/server/pool.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/pool.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,13 +1,7 @@
-"""CubicWeb server connections pool :
-
-* the rql repository has a limited number of connections pools, each of them
- dealing with a set of connections on each source used by the repository
-
-* operation may be registered by hooks during a transaction, which will be
- fired when the pool is commited or rollbacked
-
-This module defined the `ConnectionsPool` class and a set of abstract classes
-for operation.
+"""CubicWeb server connections pool : the repository has a limited number of
+connections pools, each of them dealing with a set of connections on each source
+used by the repository. A connections pools (`ConnectionsPool`) is an
+abstraction for a group of connection to each source.
:organization: Logilab
@@ -130,163 +124,11 @@
self._cursors.pop(source.uri, None)
-class Operation(object):
- """an operation is triggered on connections pool events related to
- commit / rollback transations. Possible events are:
-
- precommit:
- the pool is preparing to commit. You shouldn't do anything things which
- has to be reverted if the commit fail at this point, but you can freely
- do any heavy computation or raise an exception if the commit can't go.
- You can add some new operation during this phase but their precommit
- event won't be triggered
-
- commit:
- the pool is preparing to commit. You should avoid to do to expensive
- stuff or something that may cause an exception in this event
-
- revertcommit:
- if an operation failed while commited, this event is triggered for
- all operations which had their commit event already to let them
- revert things (including the operation which made fail the commit)
-
- rollback:
- the transaction has been either rollbacked either
- * intentionaly
- * a precommit event failed, all operations are rollbacked
- * a commit event failed, all operations which are not been triggered for
- commit are rollbacked
-
- order of operations may be important, and is controlled according to:
- * operation's class
- """
-
- def __init__(self, session, **kwargs):
- self.session = session
- self.user = session.user
- self.repo = session.repo
- self.schema = session.repo.schema
- self.config = session.repo.config
- self.__dict__.update(kwargs)
- self.register(session)
- # execution information
- self.processed = None # 'precommit', 'commit'
- self.failed = False
-
- def register(self, session):
- session.add_operation(self, self.insert_index())
-
- def insert_index(self):
- """return the index of the lastest instance which is not a
- LateOperation instance
- """
- # faster by inspecting operation in reverse order for heavy transactions
- i = None
- for i, op in enumerate(reversed(self.session.pending_operations)):
- if isinstance(op, (LateOperation, SingleLastOperation)):
- continue
- return -i or None
- if i is None:
- return None
- return -(i + 1)
-
- def handle_event(self, event):
- """delegate event handling to the operation"""
- getattr(self, event)()
-
- def precommit_event(self):
- """the observed connections pool is preparing a commit"""
-
- def revertprecommit_event(self):
- """an error went when pre-commiting this operation or a later one
-
- should revert pre-commit's changes but take care, they may have not
- been all considered if it's this operation which failed
- """
-
- def commit_event(self):
- """the observed connections pool is commiting"""
- raise NotImplementedError()
-
- def revertcommit_event(self):
- """an error went when commiting this operation or a later one
-
- should revert commit's changes but take care, they may have not
- been all considered if it's this operation which failed
- """
-
- def rollback_event(self):
- """the observed connections pool has been rollbacked
-
- do nothing by default, the operation will just be removed from the pool
- operation list
- """
-
- def postcommit_event(self):
- """the observed connections pool has committed"""
-
-
-class PreCommitOperation(Operation):
- """base class for operation only defining a precommit operation
- """
-
- def precommit_event(self):
- """the observed connections pool is preparing a commit"""
- raise NotImplementedError()
-
- def commit_event(self):
- """the observed connections pool is commiting"""
-
-
-class LateOperation(Operation):
- """special operation which should be called after all possible (ie non late)
- operations
- """
- def insert_index(self):
- """return the index of the lastest instance which is not a
- SingleLastOperation instance
- """
- # faster by inspecting operation in reverse order for heavy transactions
- i = None
- for i, op in enumerate(reversed(self.session.pending_operations)):
- if isinstance(op, SingleLastOperation):
- continue
- return -i or None
- if i is None:
- return None
- return -(i + 1)
-
-
-class SingleOperation(Operation):
- """special operation which should be called once"""
- def register(self, session):
- """override register to handle cases where this operation has already
- been added
- """
- operations = session.pending_operations
- index = self.equivalent_index(operations)
- if index is not None:
- equivalent = operations.pop(index)
- else:
- equivalent = None
- session.add_operation(self, self.insert_index())
- return equivalent
-
- def equivalent_index(self, operations):
- """return the index of the equivalent operation if any"""
- for i, op in enumerate(reversed(operations)):
- if op.__class__ is self.__class__:
- return -(i+1)
- return None
-
-
-class SingleLastOperation(SingleOperation):
- """special operation which should be called once and after all other
- operations
- """
- def insert_index(self):
- return None
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(Operation, getLogger('cubicweb.session'))
+from cubicweb.server.hook import (Operation, LateOperation, SingleOperation,
+ SingleLastOperation)
+from logilab.common.deprecation import class_moved, class_renamed
+Operation = class_moved(Operation)
+PreCommitOperation = class_renamed('PreCommitOperation', Operation)
+LateOperation = class_moved(LateOperation)
+SingleOperation = class_moved(SingleOperation)
+SingleLastOperation = class_moved(SingleLastOperation)
--- a/server/querier.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/querier.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,7 +14,7 @@
from logilab.common.compat import any
from rql import RQLHelper, RQLSyntaxError
from rql.stmts import Union, Select
-from rql.nodes import (Relation, VariableRef, Constant, SubQuery)
+from rql.nodes import Relation, VariableRef, Constant, SubQuery
from cubicweb import Unauthorized, QueryError, UnknownEid, typed_eid
from cubicweb import server
@@ -71,14 +71,23 @@
# XXX has_text may have specific perm ?
if rel.r_type in READ_ONLY_RTYPES:
continue
- if not schema.rschema(rel.r_type).has_access(user, 'read'):
+ rschema = schema.rschema(rel.r_type)
+ if rschema.final:
+ eschema = schema.eschema(solution[rel.children[0].name])
+ rdef = eschema.rdef(rschema)
+ else:
+ rdef = rschema.rdef(solution[rel.children[0].name],
+ solution[rel.children[1].children[0].name])
+ if not user.matching_groups(rdef.get_groups('read')):
raise Unauthorized('read', rel.r_type)
localchecks = {}
# iterate on defined_vars and not on solutions to ignore column aliases
for varname in rqlst.defined_vars:
etype = solution[varname]
eschema = schema.eschema(etype)
- if not eschema.has_access(user, 'read'):
+ if eschema.final:
+ continue
+ if not user.matching_groups(eschema.get_groups('read')):
erqlexprs = eschema.get_rqlexprs('read')
if not erqlexprs:
ex = Unauthorized('read', etype)
--- a/server/repository.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/repository.py Mon Feb 08 13:35:07 2010 +0100
@@ -34,19 +34,12 @@
ETypeNotSupportedBySources, MultiSourcesError,
BadConnectionId, Unauthorized, ValidationError,
typed_eid)
-from cubicweb.cwvreg import CubicWebVRegistry
-from cubicweb.schema import VIRTUAL_RTYPES, CubicWebSchema
-from cubicweb import server
-from cubicweb.server.utils import RepoThread, LoopTask
-from cubicweb.server.pool import ConnectionsPool, LateOperation, SingleLastOperation
+from cubicweb import cwvreg, schema, server
+from cubicweb.server import utils, hook, pool, querier, sources
from cubicweb.server.session import Session, InternalSession
-from cubicweb.server.querier import QuerierHelper
-from cubicweb.server.sources import get_source
-from cubicweb.server.hooksmanager import HooksManager
-from cubicweb.server.hookhelper import rproperty
-class CleanupEidTypeCacheOp(SingleLastOperation):
+class CleanupEidTypeCacheOp(hook.SingleLastOperation):
"""on rollback of a insert query or commit of delete query, we have to
clear repository's cache from no more valid entries
@@ -62,7 +55,8 @@
remove inserted eid from repository type/source cache
"""
try:
- self.repo.clear_caches(self.session.transaction_data['pendingeids'])
+ self.session.repo.clear_caches(
+ self.session.transaction_data['pendingeids'])
except KeyError:
pass
@@ -71,12 +65,13 @@
remove inserted eid from repository type/source cache
"""
try:
- self.repo.clear_caches(self.session.transaction_data['neweids'])
+ self.session.repo.clear_caches(
+ self.session.transaction_data['neweids'])
except KeyError:
pass
-class FTIndexEntityOp(LateOperation):
+class FTIndexEntityOp(hook.LateOperation):
"""operation to delay entity full text indexation to commit
since fti indexing may trigger discovery of other entities, it should be
@@ -114,7 +109,7 @@
def ensure_card_respected(execute, session, eidfrom, rtype, eidto):
- card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
+ card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
# one may be tented to check for neweids but this may cause more than one
# relation even with '1?' cardinality if thoses relations are added in the
# same transaction where the entity is being created. This never occurs from
@@ -141,7 +136,7 @@
def __init__(self, config, vreg=None, debug=False):
self.config = config
if vreg is None:
- vreg = CubicWebVRegistry(config, debug)
+ vreg = cwvreg.CubicWebVRegistry(config, debug)
self.vreg = vreg
self.pyro_registered = False
self.info('starting repository from %s', self.config.apphome)
@@ -152,10 +147,10 @@
# list of running threads
self._running_threads = []
# initial schema, should be build or replaced latter
- self.schema = CubicWebSchema(config.appid)
+ self.schema = schema.CubicWebSchema(config.appid)
self.vreg.schema = self.schema # until actual schema is loaded...
# querier helper, need to be created after sources initialization
- self.querier = QuerierHelper(self, self.schema)
+ self.querier = querier.QuerierHelper(self, self.schema)
# should we reindex in changes?
self.do_fti = not config['delay-full-text-indexation']
# sources
@@ -178,16 +173,21 @@
self._type_source_cache = {}
# cache (extid, source uri) -> eid
self._extid_cache = {}
- # create the hooks manager
- self.hm = HooksManager(self.schema)
# open some connections pools
if config.open_connections_pools:
self.open_connections_pools()
+ def _boostrap_hook_registry(self):
+ """called during bootstrap since we need the metadata hooks"""
+ hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks')
+ self.vreg.init_registration([hooksdirectory])
+ self.vreg.load_file(join(hooksdirectory, 'metadata.py'),
+ 'cubicweb.hooks.metadata')
+
def open_connections_pools(self):
config = self.config
self._available_pools = Queue.Queue()
- self._available_pools.put_nowait(ConnectionsPool(self.sources))
+ self._available_pools.put_nowait(pool.ConnectionsPool(self.sources))
if config.read_instance_schema:
# normal start: load the instance schema from the database
self.fill_schema()
@@ -195,16 +195,14 @@
# usually during repository creation
self.warning("set fs instance'schema as bootstrap schema")
config.bootstrap_cubes()
- self.set_bootstrap_schema(config.load_schema())
+ self.set_schema(config.load_schema(), resetvreg=False)
# need to load the Any and CWUser entity types
etdirectory = join(CW_SOFTWARE_ROOT, 'entities')
self.vreg.init_registration([etdirectory])
- self.vreg.load_file(join(etdirectory, '__init__.py'),
- 'cubicweb.entities.__init__')
- self.vreg.load_file(join(etdirectory, 'authobjs.py'),
- 'cubicweb.entities.authobjs')
- self.vreg.load_file(join(etdirectory, 'wfobjs.py'),
- 'cubicweb.entities.wfobjs')
+ for modname in ('__init__', 'authobjs', 'wfobjs'):
+ self.vreg.load_file(join(etdirectory, '%s.py' % modname),
+ 'cubicweb.entities.%s' % modname)
+ self._boostrap_hook_registry()
else:
# test start: use the file system schema (quicker)
self.warning("set fs instance'schema")
@@ -230,27 +228,29 @@
# list of available pools (we can't iterated on Queue instance)
self.pools = []
for i in xrange(config['connections-pool-size']):
- self.pools.append(ConnectionsPool(self.sources))
+ self.pools.append(pool.ConnectionsPool(self.sources))
self._available_pools.put_nowait(self.pools[-1])
self._shutting_down = False
+ self.hm = self.vreg['hooks']
if not (config.creating or config.repairing):
# call instance level initialisation hooks
self.hm.call_hooks('server_startup', repo=self)
# register a task to cleanup expired session
self.looping_task(config['session-time']/3., self.clean_sessions)
- CW_EVENT_MANAGER.bind('after-registry-reload', self.reset_hooks)
# internals ###############################################################
def get_source(self, uri, source_config):
source_config['uri'] = uri
- return get_source(source_config, self.schema, self)
+ return sources.get_source(source_config, self.schema, self)
def set_schema(self, schema, resetvreg=True, rebuildinfered=True):
if rebuildinfered:
schema.rebuild_infered_relations()
self.info('set schema %s %#x', schema.name, id(schema))
if resetvreg:
+ if self.config._cubes is None:
+ self.config.init_cubes(self.get_cubes())
# full reload of all appobjects
self.vreg.reset()
self.vreg.set_schema(schema)
@@ -260,22 +260,13 @@
for source in self.sources:
source.set_schema(schema)
self.schema = schema
- self.reset_hooks()
-
- def reset_hooks(self):
- self.hm.set_schema(self.schema)
- self.hm.register_system_hooks(self.config)
- # instance specific hooks
- if self.config.instance_hooks:
- self.info('loading instance hooks')
- self.hm.register_hooks(self.config.load_hooks(self.vreg))
def fill_schema(self):
"""lod schema from the repository"""
from cubicweb.server.schemaserial import deserialize_schema
self.info('loading schema from the repository')
- appschema = CubicWebSchema(self.config.appid)
- self.set_bootstrap_schema(self.config.load_bootstrap_schema())
+ appschema = schema.CubicWebSchema(self.config.appid)
+ self.set_schema(self.config.load_bootstrap_schema(), resetvreg=False)
self.debug('deserializing db schema into %s %#x', appschema.name, id(appschema))
session = self.internal_session()
try:
@@ -289,44 +280,14 @@
raise Exception('Is the database initialised ? (cause: %s)' %
(ex.args and ex.args[0].strip() or 'unknown')), \
None, sys.exc_info()[-1]
- self.info('set the actual schema')
- # XXX have to do this since CWProperty isn't in the bootstrap schema
- # it'll be redone in set_schema
- self.set_bootstrap_schema(appschema)
- # 2.49 migration
- if exists(join(self.config.apphome, 'vc.conf')):
- session.set_pool()
- if not 'template' in file(join(self.config.apphome, 'vc.conf')).read():
- # remaning from cubicweb < 2.38...
- session.execute('DELETE CWProperty X WHERE X pkey "system.version.template"')
- session.commit()
finally:
session.close()
- self.config.init_cubes(self.get_cubes())
self.set_schema(appschema)
- def set_bootstrap_schema(self, schema):
- """disable hooks when setting a bootstrap schema, but restore
- the configuration for the next time
- """
- config = self.config
- # XXX refactor
- config.core_hooks = False
- config.usergroup_hooks = False
- config.schema_hooks = False
- config.notification_hooks = False
- config.instance_hooks = False
- self.set_schema(schema, resetvreg=False)
- config.core_hooks = True
- config.usergroup_hooks = True
- config.schema_hooks = True
- config.notification_hooks = True
- config.instance_hooks = True
-
def start_looping_tasks(self):
assert isinstance(self._looping_tasks, list), 'already started'
for i, (interval, func, args) in enumerate(self._looping_tasks):
- self._looping_tasks[i] = task = LoopTask(interval, func, args)
+ self._looping_tasks[i] = task = utils.LoopTask(interval, func, args)
self.info('starting task %s with interval %.2fs', task.name,
interval)
task.start()
@@ -346,7 +307,7 @@
def threaded_task(self, func):
"""start function in a separated thread"""
- t = RepoThread(func, self._running_threads)
+ t = utils.RepoThread(func, self._running_threads)
t.start()
#@locked
@@ -421,7 +382,7 @@
session.close()
return login
- def authenticate_user(self, session, login, password):
+ def authenticate_user(self, session, login, **kwargs):
"""validate login / password, raise AuthenticationError on failure
return associated CWUser instance on success
"""
@@ -430,7 +391,7 @@
for source in self.sources:
if source.support_entity('CWUser'):
try:
- eid = source.authenticate(session, login, password)
+ eid = source.authenticate(session, login, **kwargs)
break
except AuthenticationError:
continue
@@ -569,7 +530,7 @@
session.close()
return True
- def connect(self, login, password, cnxprops=None):
+ def connect(self, login, **kwargs):
"""open a connection for a given user
base_url may be needed to send mails
@@ -581,16 +542,17 @@
# use an internal connection
session = self.internal_session()
# try to get a user object
+ cnxprops = kwargs.pop('cnxprops', None)
try:
- user = self.authenticate_user(session, login, password)
+ user = self.authenticate_user(session, login, **kwargs)
finally:
session.close()
session = Session(user, self, cnxprops)
- user.req = user.rset.req = session
+ user._cw = user.cw_rset.req = session
user.clear_related_cache()
self._sessions[session.id] = session
self.info('opened %s', session)
- self.hm.call_hooks('session_open', session=session)
+ self.hm.call_hooks('session_open', session)
# commit session at this point in case write operation has been done
# during `session_open` hooks
session.commit()
@@ -681,7 +643,7 @@
checkshuttingdown=checkshuttingdown)
# operation uncommited before close are rollbacked before hook is called
session.rollback()
- self.hm.call_hooks('session_close', session=session)
+ self.hm.call_hooks('session_close', session)
# commit session at this point in case write operation has been done
# during `session_close` hooks
session.commit()
@@ -862,11 +824,11 @@
entity = source.before_entity_insertion(session, extid, etype, eid)
entity._cw_recreating = True
if source.should_call_hooks:
- self.hm.call_hooks('before_add_entity', etype, session, entity)
+ self.hm.call_hooks('before_add_entity', session, entity=entity)
# XXX add fti op ?
source.after_entity_insertion(session, extid, entity)
if source.should_call_hooks:
- self.hm.call_hooks('after_add_entity', etype, session, entity)
+ self.hm.call_hooks('after_add_entity', session, entity=entity)
if reset_pool:
session.reset_pool()
return eid
@@ -889,16 +851,17 @@
if not hasattr(entity, 'edited_attributes'):
entity.edited_attributes = set()
if source.should_call_hooks:
- self.hm.call_hooks('before_add_entity', etype, session, entity)
+ entity.edited_attributes = set(entity)
+ self.hm.call_hooks('before_add_entity', session, entity=entity)
# XXX call add_info with complete=False ?
self.add_info(session, entity, source, extid)
source.after_entity_insertion(session, extid, entity)
if source.should_call_hooks:
- self.hm.call_hooks('after_add_entity', etype, session, entity)
+ self.hm.call_hooks('after_add_entity', session, entity=entity)
else:
# minimal meta-data
session.execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
- {'x': entity.eid, 'name': entity.id}, 'x')
+ {'x': entity.eid, 'name': entity.__regid__}, 'x')
session.commit(reset_pool)
return eid
except:
@@ -953,7 +916,7 @@
pendingrtypes = session.transaction_data.get('pendingrtypes', ())
for rschema, targetschemas, x in eschema.relation_definitions():
rtype = rschema.type
- if rtype in VIRTUAL_RTYPES or rtype in pendingrtypes:
+ if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
continue
var = '%s%s' % (rtype.upper(), x.upper())
if x == 'subject':
@@ -1022,7 +985,7 @@
print 'ADD entity', etype, entity.eid, dict(entity)
relations = []
if source.should_call_hooks:
- self.hm.call_hooks('before_add_entity', etype, session, entity)
+ self.hm.call_hooks('before_add_entity', session, entity=entity)
# XXX use entity.keys here since edited_attributes is not updated for
# inline relations
for attr in entity.keys():
@@ -1043,7 +1006,7 @@
session.set_entity_cache(entity)
for rschema in eschema.subject_relations():
rtype = str(rschema)
- if rtype in VIRTUAL_RTYPES:
+ if rtype in schema.VIRTUAL_RTYPES:
continue
if rschema.final:
entity.setdefault(rtype, None)
@@ -1051,7 +1014,7 @@
entity.set_related_cache(rtype, 'subject', session.empty_rset())
for rschema in eschema.object_relations():
rtype = str(rschema)
- if rtype in VIRTUAL_RTYPES:
+ if rtype in schema.VIRTUAL_RTYPES:
continue
entity.set_related_cache(rtype, 'object', session.empty_rset())
# set inline relation cache before call to after_add_entity
@@ -1059,13 +1022,13 @@
session.update_rel_cache_add(entity.eid, attr, value)
# trigger after_add_entity after after_add_relation
if source.should_call_hooks:
- self.hm.call_hooks('after_add_entity', etype, session, entity)
+ self.hm.call_hooks('after_add_entity', session, entity=entity)
# call hooks for inlined relations
for attr, value in relations:
- self.hm.call_hooks('before_add_relation', attr, session,
- entity.eid, attr, value)
- self.hm.call_hooks('after_add_relation', attr, session,
- entity.eid, attr, value)
+ self.hm.call_hooks('before_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
+ self.hm.call_hooks('after_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
return entity.eid
def glob_update_entity(self, session, entity, edited_attributes):
@@ -1087,7 +1050,7 @@
continue
rschema = eschema.subjrels[attr]
if rschema.final:
- if eschema.rproperty(attr, 'fulltextindexed'):
+ if getattr(eschema.rdef(attr), 'fulltextindexed', False):
need_fti_update = True
only_inline_rels = False
else:
@@ -1098,19 +1061,18 @@
if previous_value == entity[attr]:
previous_value = None
else:
- self.hm.call_hooks('before_delete_relation', attr,
- session, entity.eid, attr,
- previous_value)
+ self.hm.call_hooks('before_delete_relation', session,
+ eidfrom=entity.eid, rtype=attr,
+ eidto=previous_value)
relations.append((attr, entity[attr], previous_value))
source = self.source_from_eid(entity.eid, session)
if source.should_call_hooks:
# call hooks for inlined relations
for attr, value, _ in relations:
- self.hm.call_hooks('before_add_relation', attr, session,
- entity.eid, attr, value)
+ self.hm.call_hooks('before_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
if not only_inline_rels:
- self.hm.call_hooks('before_update_entity', etype, session,
- entity)
+ self.hm.call_hooks('before_update_entity', session, entity=entity)
source.update_entity(session, entity)
if not only_inline_rels:
if need_fti_update and self.do_fti:
@@ -1118,15 +1080,14 @@
# one indexable attribute
FTIndexEntityOp(session, entity=entity)
if source.should_call_hooks:
- self.hm.call_hooks('after_update_entity', etype, session,
- entity)
+ self.hm.call_hooks('after_update_entity', session, entity=entity)
if source.should_call_hooks:
for attr, value, prevvalue in relations:
# if the relation is already cached, update existant cache
relcache = entity.relation_cached(attr, 'subject')
if prevvalue is not None:
- self.hm.call_hooks('after_delete_relation', attr, session,
- entity.eid, attr, prevvalue)
+ self.hm.call_hooks('after_delete_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=prevvalue)
if relcache is not None:
session.update_rel_cache_del(entity.eid, attr, prevvalue)
del_existing_rel_if_needed(session, entity.eid, attr, value)
@@ -1135,8 +1096,8 @@
else:
entity.set_related_cache(attr, 'subject',
session.eid_rset(value))
- self.hm.call_hooks('after_add_relation', attr, session,
- entity.eid, attr, value)
+ self.hm.call_hooks('after_add_relation', session,
+ eidfrom=entity.eid, rtype=attr, eidto=value)
def glob_delete_entity(self, session, eid):
"""delete an entity and all related entities from the repository"""
@@ -1149,11 +1110,12 @@
server.DEBUG |= (server.DBG_SQL | server.DBG_RQL | server.DBG_MORE)
source = self.sources_by_uri[uri]
if source.should_call_hooks:
- self.hm.call_hooks('before_delete_entity', etype, session, eid)
+ entity = session.entity_from_eid(eid)
+ self.hm.call_hooks('before_delete_entity', session, entity=entity)
self._delete_info(session, eid)
source.delete_entity(session, etype, eid)
if source.should_call_hooks:
- self.hm.call_hooks('after_delete_entity', etype, session, eid)
+ self.hm.call_hooks('after_delete_entity', session, entity=entity)
# don't clear cache here this is done in a hook on commit
def glob_add_relation(self, session, subject, rtype, object):
@@ -1163,14 +1125,14 @@
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', rtype, 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.symetric)
+ session.update_rel_cache_add(subject, rtype, object, rschema.symmetric)
if source.should_call_hooks:
- self.hm.call_hooks('after_add_relation', rtype, session,
- subject, rtype, object)
+ self.hm.call_hooks('after_add_relation', session,
+ eidfrom=subject, rtype=rtype, eidto=object)
def glob_delete_relation(self, session, subject, rtype, object):
"""delete a relation from the repository"""
@@ -1178,18 +1140,18 @@
print 'DELETE relation', subject, rtype, object
source = self.locate_relation_source(session, subject, rtype, object)
if source.should_call_hooks:
- self.hm.call_hooks('before_delete_relation', rtype, session,
- subject, rtype, object)
+ self.hm.call_hooks('before_delete_relation', session,
+ eidfrom=subject, rtype=rtype, eidto=object)
source.delete_relation(session, subject, rtype, object)
rschema = self.schema.rschema(rtype)
- session.update_rel_cache_del(subject, rtype, object, rschema.symetric)
- if rschema.symetric:
- # on symetric relation, we can't now in which sense it's
+ session.update_rel_cache_del(subject, rtype, object, rschema.symmetric)
+ if rschema.symmetric:
+ # on symmetric relation, we can't now in which sense it's
# stored so try to delete both
source.delete_relation(session, object, rtype, subject)
if source.should_call_hooks:
- self.hm.call_hooks('after_delete_relation', rtype, session,
- subject, rtype, object)
+ self.hm.call_hooks('after_delete_relation', session,
+ eidfrom=subject, rtype=rtype, eidto=object)
# pyro handling ###########################################################
--- a/server/rqlannotation.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/rqlannotation.py Mon Feb 08 13:35:07 2010 +0100
@@ -23,7 +23,7 @@
has_text_query = False
need_distinct = rqlst.distinct
for rel in rqlst.iget_nodes(Relation):
- if getrschema(rel.r_type).symetric and not rel.neged(strict=True):
+ if getrschema(rel.r_type).symmetric and not rel.neged(strict=True):
for vref in rel.iget_nodes(VariableRef):
stinfo = vref.variable.stinfo
if not stinfo['constnode'] and stinfo['selected']:
@@ -105,7 +105,7 @@
# can use N.ecrit_par as principal
if (stinfo['selected'] or len(stinfo['relations']) > 1):
break
- elif rschema.symetric and stinfo['selected']:
+ elif rschema.symmetric and stinfo['selected']:
break
joins.add(rel)
else:
--- a/server/schemahooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1121 +0,0 @@
-"""schema hooks:
-
-- synchronize the living schema object with the persistent schema
-- perform physical update on the source when necessary
-
-checking for schema consistency is done in hooks.py
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from yams.schema import BASE_TYPES
-from yams.buildobjs import EntityType, RelationType, RelationDefinition
-from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
-
-from logilab.common.decorators import clear_cache
-
-from cubicweb import ValidationError, RepositoryError
-from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS
-from cubicweb.server import schemaserial as ss
-from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
-from cubicweb.server.hookhelper import entity_oldnewvalue, check_internal_entity
-
-
-TYPE_CONVERTER = { # XXX
- 'Boolean': bool,
- 'Int': int,
- 'Float': float,
- 'Password': str,
- 'String': unicode,
- 'Date' : unicode,
- 'Datetime' : unicode,
- 'Time' : unicode,
- }
-
-# core entity and relation types which can't be removed
-CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
- 'CWConstraint', 'CWAttribute', 'CWRelation']
-CORE_RTYPES = ['eid', 'creation_date', 'modification_date', 'cwuri',
- 'login', 'upassword', 'name',
- 'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
- 'relation_type', 'from_entity', 'to_entity',
- 'constrainted_by',
- 'read_permission', 'add_permission',
- 'delete_permission', 'updated_permission',
- ]
-
-def get_constraints(session, entity):
- constraints = []
- for cstreid in session.transaction_data.get(entity.eid, ()):
- cstrent = session.entity_from_eid(cstreid)
- cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
- cstr.eid = cstreid
- constraints.append(cstr)
- return constraints
-
-def add_inline_relation_column(session, etype, rtype):
- """add necessary column and index for an inlined relation"""
- table = SQL_PREFIX + etype
- column = SQL_PREFIX + rtype
- try:
- session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
- % (table, column)), rollback_on_failure=False)
- session.info('added column %s to table %s', column, table)
- except:
- # silent exception here, if this error has not been raised because the
- # column already exists, index creation will fail anyway
- session.exception('error while adding column %s to table %s',
- table, column)
- # create index before alter table which may expectingly fail during test
- # (sqlite) while index creation should never fail (test for index existence
- # is done by the dbhelper)
- session.pool.source('system').create_index(session, table, column)
- session.info('added index on %s(%s)', table, column)
- session.transaction_data.setdefault('createdattrs', []).append(
- '%s.%s' % (etype, rtype))
-
-
-# operations for low-level database alteration ################################
-
-class DropTable(PreCommitOperation):
- """actually remove a database from the instance's schema"""
- table = None # make pylint happy
- def precommit_event(self):
- dropped = self.session.transaction_data.setdefault('droppedtables',
- set())
- if self.table in dropped:
- return # already processed
- dropped.add(self.table)
- self.session.system_sql('DROP TABLE %s' % self.table)
- self.info('dropped table %s', self.table)
-
-
-class DropRelationTable(DropTable):
- def __init__(self, session, rtype):
- super(DropRelationTable, self).__init__(
- session, table='%s_relation' % rtype)
- session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
-
-
-class DropColumn(PreCommitOperation):
- """actually remove the attribut's column from entity table in the system
- database
- """
- table = column = None # make pylint happy
- def precommit_event(self):
- session, table, column = self.session, self.table, self.column
- # drop index if any
- session.pool.source('system').drop_index(session, table, column)
- try:
- session.system_sql('ALTER TABLE %s DROP COLUMN %s'
- % (table, column), rollback_on_failure=False)
- self.info('dropped column %s from table %s', column, table)
- except Exception, ex:
- # not supported by sqlite for instance
- self.error('error while altering table %s: %s', table, ex)
-
-
-# base operations for in-memory schema synchronization ########################
-
-class MemSchemaNotifyChanges(SingleLastOperation):
- """the update schema operation:
-
- special operation which should be called once and after all other schema
- operations. It will trigger internal structures rebuilding to consider
- schema changes
- """
-
- def __init__(self, session):
- self.repo = session.repo
- SingleLastOperation.__init__(self, session)
-
- def precommit_event(self):
- for eschema in self.repo.schema.entities():
- if not eschema.final:
- clear_cache(eschema, 'ordered_relations')
-
- def commit_event(self):
- rebuildinfered = self.session.data.get('rebuild-infered', True)
- self.repo.set_schema(self.repo.schema, rebuildinfered=rebuildinfered)
- # CWUser class might have changed, update current session users
- cwuser_cls = self.session.vreg['etypes'].etype_class('CWUser')
- for session in self.repo._sessions.values():
- session.user.__class__ = cwuser_cls
-
- def rollback_event(self):
- self.precommit_event()
-
-
-class MemSchemaOperation(Operation):
- """base class for schema operations"""
- def __init__(self, session, kobj=None, **kwargs):
- self.schema = session.schema
- self.kobj = kobj
- # once Operation.__init__ has been called, event may be triggered, so
- # do this last !
- Operation.__init__(self, session, **kwargs)
- # every schema operation is triggering a schema update
- MemSchemaNotifyChanges(session)
-
- def prepare_constraints(self, subjtype, rtype, objtype):
- constraints = rtype.rproperty(subjtype, objtype, 'constraints')
- self.constraints = list(constraints)
- rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
-
-
-class MemSchemaEarlyOperation(MemSchemaOperation):
- def insert_index(self):
- """schema operation which are inserted at the begining of the queue
- (typically to add/remove entity or relation types)
- """
- i = -1
- for i, op in enumerate(self.session.pending_operations):
- if not isinstance(op, MemSchemaEarlyOperation):
- return i
- return i + 1
-
-
-class MemSchemaPermissionOperation(MemSchemaOperation):
- """base class to synchronize schema permission definitions"""
- def __init__(self, session, perm, etype_eid):
- self.perm = perm
- try:
- self.name = session.entity_from_eid(etype_eid).name
- except IndexError:
- self.error('changing permission of a no more existant type #%s',
- etype_eid)
- else:
- Operation.__init__(self, session)
-
-
-# operations for high-level source database alteration ########################
-
-class SourceDbCWETypeRename(PreCommitOperation):
- """this operation updates physical storage accordingly"""
- oldname = newname = None # make pylint happy
-
- def precommit_event(self):
- # we need sql to operate physical changes on the system database
- sqlexec = self.session.system_sql
- sqlexec('ALTER TABLE %s%s RENAME TO %s%s' % (SQL_PREFIX, self.oldname,
- SQL_PREFIX, self.newname))
- self.info('renamed table %s to %s', self.oldname, self.newname)
- sqlexec('UPDATE entities SET type=%s WHERE type=%s',
- (self.newname, self.oldname))
- sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
- (self.newname, self.oldname))
-
-
-class SourceDbCWRTypeUpdate(PreCommitOperation):
- """actually update some properties of a relation definition"""
- rschema = values = entity = None # make pylint happy
-
- def precommit_event(self):
- session = self.session
- rschema = self.rschema
- if rschema.final or not 'inlined' in self.values:
- return # nothing to do
- inlined = self.values['inlined']
- entity = self.entity
- # check in-lining is necessary / possible
- if not entity.inlined_changed(inlined):
- return # nothing to do
- # inlined changed, make necessary physical changes!
- sqlexec = self.session.system_sql
- rtype = rschema.type
- eidcolumn = SQL_PREFIX + 'eid'
- if not inlined:
- # need to create the relation if it has not been already done by
- # another event of the same transaction
- if not rschema.type in session.transaction_data.get('createdtables', ()):
- tablesql = rschema2sql(rschema)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- sqlexec(sql)
- session.transaction_data.setdefault('createdtables', []).append(
- rschema.type)
- # copy existant data
- column = SQL_PREFIX + rtype
- for etype in rschema.subjects():
- table = SQL_PREFIX + str(etype)
- sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
- % (rtype, eidcolumn, column, table, column))
- # drop existant columns
- for etype in rschema.subjects():
- DropColumn(session, table=SQL_PREFIX + str(etype),
- column=SQL_PREFIX + rtype)
- else:
- for etype in rschema.subjects():
- try:
- add_inline_relation_column(session, str(etype), rtype)
- except Exception, ex:
- # the column probably already exists. this occurs when the
- # entity's type has just been added or if the column has not
- # been previously dropped
- self.error('error while altering table %s: %s', etype, ex)
- # copy existant data.
- # XXX don't use, it's not supported by sqlite (at least at when i tried it)
- #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
- # 'FROM %(rtype)s_relation '
- # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
- # % locals())
- table = SQL_PREFIX + str(etype)
- cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
- '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
- '%(rtype)s_relation.eid_from' % locals())
- args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
- if args:
- column = SQL_PREFIX + rtype
- cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
- % (table, column, eidcolumn), args)
- # drop existant table
- DropRelationTable(session, rtype)
-
-
-class SourceDbCWAttributeAdd(PreCommitOperation):
- """an attribute relation (CWAttribute) has been added:
- * add the necessary column
- * set default on this column if any and possible
- * register an operation to add the relation definition to the
- instance's schema on commit
-
- constraints are handled by specific hooks
- """
- entity = None # make pylint happy
-
- def init_rdef(self, **kwargs):
- entity = self.entity
- fromentity = entity.stype
- self.session.execute('SET X ordernum Y+1 '
- 'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
- 'X ordernum >= %(order)s, NOT X eid %(x)s',
- {'x': entity.eid, 'se': fromentity.eid,
- 'order': entity.ordernum or 0})
- subj = str(fromentity.name)
- rtype = entity.rtype.name
- obj = str(entity.otype.name)
- constraints = get_constraints(self.session, entity)
- rdef = RelationDefinition(subj, rtype, obj,
- description=entity.description,
- cardinality=entity.cardinality,
- constraints=constraints,
- order=entity.ordernum,
- eid=entity.eid,
- **kwargs)
- MemSchemaRDefAdd(self.session, rdef)
- return rdef
-
- def precommit_event(self):
- session = self.session
- entity = self.entity
- # entity.defaultval is a string or None, but we need a correctly typed
- # value
- default = entity.defaultval
- if default is not None:
- default = TYPE_CONVERTER[entity.otype.name](default)
- props = {'default': default,
- 'indexed': entity.indexed,
- 'fulltextindexed': entity.fulltextindexed,
- 'internationalizable': entity.internationalizable}
- rdef = self.init_rdef(**props)
- sysource = session.pool.source('system')
- attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
- rdef.constraints)
- # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
- # add a new column with UNIQUE, it should be added after the ALTER TABLE
- # using ADD INDEX
- if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
- extra_unique_index = True
- attrtype = attrtype.replace(' UNIQUE', '')
- else:
- extra_unique_index = False
- # added some str() wrapping query since some backend (eg psycopg) don't
- # allow unicode queries
- table = SQL_PREFIX + rdef.subject
- column = SQL_PREFIX + rdef.name
- try:
- session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
- % (table, column, attrtype)),
- rollback_on_failure=False)
- self.info('added column %s to table %s', table, column)
- except Exception, ex:
- # the column probably already exists. this occurs when
- # the entity's type has just been added or if the column
- # has not been previously dropped
- self.error('error while altering table %s: %s', table, ex)
- if extra_unique_index or entity.indexed:
- try:
- sysource.create_index(session, table, column,
- unique=extra_unique_index)
- except Exception, ex:
- self.error('error while creating index for %s.%s: %s',
- table, column, ex)
- # final relations are not infered, propagate
- try:
- eschema = self.schema.eschema(rdef.subject)
- except KeyError:
- return # entity type currently being added
- # propagate attribute to children classes
- rschema = self.schema.rschema(rdef.name)
- # if relation type has been inserted in the same transaction, its final
- # attribute is still set to False, so we've to ensure it's False
- rschema.final = True
- # XXX 'infered': True/False, not clear actually
- props.update({'constraints': rdef.constraints,
- 'description': rdef.description,
- 'cardinality': rdef.cardinality,
- 'constraints': rdef.constraints,
- 'order': rdef.order})
- for specialization in eschema.specialized_by(False):
- if rschema.has_rdef(specialization, rdef.object):
- continue
- for rql, args in ss.frdef2rql(rschema, str(specialization),
- rdef.object, props):
- session.execute(rql, args)
- # set default value, using sql for performance and to avoid
- # modification_date update
- if default:
- session.system_sql('UPDATE %s SET %s=%%(default)s' % (table, column),
- {'default': default})
-
-
-class SourceDbCWRelationAdd(SourceDbCWAttributeAdd):
- """an actual relation has been added:
- * if this is an inlined relation, add the necessary column
- else if it's the first instance of this relation type, add the
- necessary table and set default permissions
- * register an operation to add the relation definition to the
- instance's schema on commit
-
- constraints are handled by specific hooks
- """
- entity = None # make pylint happy
-
- def precommit_event(self):
- session = self.session
- entity = self.entity
- rdef = self.init_rdef(composite=entity.composite)
- schema = session.schema
- rtype = rdef.name
- rschema = session.schema.rschema(rtype)
- # this have to be done before permissions setting
- if rschema.inlined:
- # need to add a column if the relation is inlined and if this is the
- # first occurence of "Subject relation Something" whatever Something
- # and if it has not been added during other event of the same
- # transaction
- key = '%s.%s' % (rdef.subject, rtype)
- try:
- alreadythere = bool(rschema.objects(rdef.subject))
- except KeyError:
- alreadythere = False
- if not (alreadythere or
- key in session.transaction_data.get('createdattrs', ())):
- add_inline_relation_column(session, rdef.subject, rtype)
- else:
- # need to create the relation if no relation definition in the
- # schema and if it has not been added during other event of the same
- # transaction
- if not (rschema.subjects() or
- rtype in session.transaction_data.get('createdtables', ())):
- try:
- rschema = session.schema.rschema(rtype)
- tablesql = rschema2sql(rschema)
- except KeyError:
- # fake we add it to the schema now to get a correctly
- # initialized schema but remove it before doing anything
- # more dangerous...
- rschema = session.schema.add_relation_type(rdef)
- tablesql = rschema2sql(rschema)
- session.schema.del_relation_type(rtype)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- session.system_sql(sql)
- session.transaction_data.setdefault('createdtables', []).append(
- rtype)
-
-
-class SourceDbRDefUpdate(PreCommitOperation):
- """actually update some properties of a relation definition"""
- rschema = values = None # make pylint happy
-
- def precommit_event(self):
- etype = self.kobj[0]
- table = SQL_PREFIX + etype
- column = SQL_PREFIX + self.rschema.type
- if 'indexed' in self.values:
- sysource = self.session.pool.source('system')
- if self.values['indexed']:
- sysource.create_index(self.session, table, column)
- else:
- sysource.drop_index(self.session, table, column)
- if 'cardinality' in self.values and self.rschema.final:
- adbh = self.session.pool.source('system').dbhelper
- if not adbh.alter_column_support:
- # not supported (and NOT NULL not set by yams in that case, so
- # no worry)
- return
- atype = self.rschema.objects(etype)[0]
- constraints = self.rschema.rproperty(etype, atype, 'constraints')
- coltype = type_from_constraints(adbh, atype, constraints,
- creating=False)
- # XXX check self.values['cardinality'][0] actually changed?
- sql = adbh.sql_set_null_allowed(table, column, coltype,
- self.values['cardinality'][0] != '1')
- self.session.system_sql(sql)
-
-
-class SourceDbCWConstraintAdd(PreCommitOperation):
- """actually update constraint of a relation definition"""
- entity = None # make pylint happy
- cancelled = False
-
- def precommit_event(self):
- rdef = self.entity.reverse_constrained_by[0]
- session = self.session
- # when the relation is added in the same transaction, the constraint
- # object is created by the operation adding the attribute or relation,
- # so there is nothing to do here
- if rdef.eid in session.transaction_data.get('neweids', ()):
- return
- subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid)
- cstrtype = self.entity.type
- oldcstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
- newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
- table = SQL_PREFIX + str(subjtype)
- column = SQL_PREFIX + str(rtype)
- # alter the physical schema on size constraint changes
- if newcstr.type() == 'SizeConstraint' and (
- oldcstr is None or oldcstr.max != newcstr.max):
- adbh = self.session.pool.source('system').dbhelper
- card = rtype.rproperty(subjtype, objtype, 'cardinality')
- coltype = type_from_constraints(adbh, objtype, [newcstr],
- creating=False)
- sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
- try:
- session.system_sql(sql, rollback_on_failure=False)
- self.info('altered column %s of table %s: now VARCHAR(%s)',
- column, table, newcstr.max)
- except Exception, ex:
- # not supported by sqlite for instance
- self.error('error while altering table %s: %s', table, ex)
- elif cstrtype == 'UniqueConstraint' and oldcstr is None:
- session.pool.source('system').create_index(
- self.session, table, column, unique=True)
-
-
-class SourceDbCWConstraintDel(PreCommitOperation):
- """actually remove a constraint of a relation definition"""
- rtype = subjtype = objtype = None # make pylint happy
-
- def precommit_event(self):
- cstrtype = self.cstr.type()
- table = SQL_PREFIX + str(self.subjtype)
- column = SQL_PREFIX + str(self.rtype)
- # alter the physical schema on size/unique constraint changes
- if cstrtype == 'SizeConstraint':
- try:
- self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
- % (table, column),
- rollback_on_failure=False)
- self.info('altered column %s of table %s: now TEXT',
- column, table)
- except Exception, ex:
- # not supported by sqlite for instance
- self.error('error while altering table %s: %s', table, ex)
- elif cstrtype == 'UniqueConstraint':
- self.session.pool.source('system').drop_index(
- self.session, table, column, unique=True)
-
-
-# operations for in-memory schema synchronization #############################
-
-class MemSchemaCWETypeAdd(MemSchemaEarlyOperation):
- """actually add the entity type to the instance's schema"""
- eid = None # make pylint happy
- def commit_event(self):
- self.schema.add_entity_type(self.kobj)
-
-
-class MemSchemaCWETypeRename(MemSchemaOperation):
- """this operation updates physical storage accordingly"""
- oldname = newname = None # make pylint happy
-
- def commit_event(self):
- self.session.schema.rename_entity_type(self.oldname, self.newname)
-
-
-class MemSchemaCWETypeDel(MemSchemaOperation):
- """actually remove the entity type from the instance's schema"""
- def commit_event(self):
- try:
- # del_entity_type also removes entity's relations
- self.schema.del_entity_type(self.kobj)
- except KeyError:
- # s/o entity type have already been deleted
- pass
-
-
-class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation):
- """actually add the relation type to the instance's schema"""
- eid = None # make pylint happy
- def commit_event(self):
- rschema = self.schema.add_relation_type(self.kobj)
- rschema.set_default_groups()
-
-
-class MemSchemaCWRTypeUpdate(MemSchemaOperation):
- """actually update some properties of a relation definition"""
- rschema = values = None # make pylint happy
-
- def commit_event(self):
- # structure should be clean, not need to remove entity's relations
- # at this point
- self.rschema.__dict__.update(self.values)
-
-
-class MemSchemaCWRTypeDel(MemSchemaOperation):
- """actually remove the relation type from the instance's schema"""
- def commit_event(self):
- try:
- self.schema.del_relation_type(self.kobj)
- except KeyError:
- # s/o entity type have already been deleted
- pass
-
-
-class MemSchemaRDefAdd(MemSchemaEarlyOperation):
- """actually add the attribute relation definition to the instance's
- schema
- """
- def commit_event(self):
- self.schema.add_relation_def(self.kobj)
-
-
-class MemSchemaRDefUpdate(MemSchemaOperation):
- """actually update some properties of a relation definition"""
- rschema = values = None # make pylint happy
-
- def commit_event(self):
- # structure should be clean, not need to remove entity's relations
- # at this point
- self.rschema._rproperties[self.kobj].update(self.values)
-
-
-class MemSchemaRDefDel(MemSchemaOperation):
- """actually remove the relation definition from the instance's schema"""
- def commit_event(self):
- subjtype, rtype, objtype = self.kobj
- try:
- self.schema.del_relation_def(subjtype, rtype, objtype)
- except KeyError:
- # relation type may have been already deleted
- pass
-
-
-class MemSchemaCWConstraintAdd(MemSchemaOperation):
- """actually update constraint of a relation definition
-
- has to be called before SourceDbCWConstraintAdd
- """
- cancelled = False
-
- def precommit_event(self):
- rdef = self.entity.reverse_constrained_by[0]
- # when the relation is added in the same transaction, the constraint
- # object is created by the operation adding the attribute or relation,
- # so there is nothing to do here
- if rdef.eid in self.session.transaction_data.get('neweids', ()):
- self.cancelled = True
- return
- subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid)
- self.prepare_constraints(subjtype, rtype, objtype)
- cstrtype = self.entity.type
- self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
- self.newcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
- self.newcstr.eid = self.entity.eid
-
- def commit_event(self):
- if self.cancelled:
- return
- # in-place modification
- if not self.cstr is None:
- self.constraints.remove(self.cstr)
- self.constraints.append(self.newcstr)
-
-
-class MemSchemaCWConstraintDel(MemSchemaOperation):
- """actually remove a constraint of a relation definition
-
- has to be called before SourceDbCWConstraintDel
- """
- rtype = subjtype = objtype = None # make pylint happy
- def precommit_event(self):
- self.prepare_constraints(self.subjtype, self.rtype, self.objtype)
-
- def commit_event(self):
- self.constraints.remove(self.cstr)
-
-
-class MemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation):
- """synchronize schema when a *_permission relation has been added on a group
- """
- def __init__(self, session, perm, etype_eid, group_eid):
- self.group = session.entity_from_eid(group_eid).name
- super(MemSchemaPermissionCWGroupAdd, self).__init__(
- session, perm, etype_eid)
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- groups = list(erschema.get_groups(self.perm))
- try:
- groups.index(self.group)
- self.warning('group %s already have permission %s on %s',
- self.group, self.perm, erschema.type)
- except ValueError:
- groups.append(self.group)
- erschema.set_groups(self.perm, groups)
-
-
-class MemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd):
- """synchronize schema when a *_permission relation has been deleted from a
- group
- """
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- groups = list(erschema.get_groups(self.perm))
- try:
- groups.remove(self.group)
- erschema.set_groups(self.perm, groups)
- except ValueError:
- self.error('can\'t remove permission %s on %s to group %s',
- self.perm, erschema.type, self.group)
-
-
-class MemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation):
- """synchronize schema when a *_permission relation has been added on a rql
- expression
- """
- def __init__(self, session, perm, etype_eid, expression):
- self.expr = expression
- super(MemSchemaPermissionRQLExpressionAdd, self).__init__(
- session, perm, etype_eid)
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- exprs = list(erschema.get_rqlexprs(self.perm))
- exprs.append(erschema.rql_expression(self.expr))
- erschema.set_rqlexprs(self.perm, exprs)
-
-
-class MemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd):
- """synchronize schema when a *_permission relation has been deleted from an
- rql expression
- """
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- rqlexprs = list(erschema.get_rqlexprs(self.perm))
- for i, rqlexpr in enumerate(rqlexprs):
- if rqlexpr.expression == self.expr:
- rqlexprs.pop(i)
- break
- else:
- self.error('can\'t remove permission %s on %s for expression %s',
- self.perm, erschema.type, self.expr)
- return
- erschema.set_rqlexprs(self.perm, rqlexprs)
-
-
-class MemSchemaSpecializesAdd(MemSchemaOperation):
-
- def commit_event(self):
- eschema = self.session.schema.schema_by_eid(self.etypeeid)
- parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
- eschema._specialized_type = parenteschema.type
- parenteschema._specialized_by.append(eschema.type)
-
-
-class MemSchemaSpecializesDel(MemSchemaOperation):
-
- def commit_event(self):
- try:
- eschema = self.session.schema.schema_by_eid(self.etypeeid)
- parenteschema = self.session.schema.schema_by_eid(self.parentetypeeid)
- except KeyError:
- # etype removed, nothing to do
- return
- eschema._specialized_type = None
- parenteschema._specialized_by.remove(eschema.type)
-
-
-# deletion hooks ###############################################################
-
-def before_del_eetype(session, eid):
- """before deleting a CWEType entity:
- * check that we don't remove a core entity type
- * cascade to delete related CWAttribute and CWRelation entities
- * instantiate an operation to delete the entity type on commit
- """
- # final entities can't be deleted, don't care about that
- name = check_internal_entity(session, eid, CORE_ETYPES)
- # delete every entities of this type
- session.unsafe_execute('DELETE %s X' % name)
- DropTable(session, table=SQL_PREFIX + name)
- MemSchemaCWETypeDel(session, name)
-
-
-def after_del_eetype(session, eid):
- # workflow cleanup
- session.execute('DELETE Workflow X WHERE NOT X workflow_of Y')
-
-
-def before_del_ertype(session, eid):
- """before deleting a CWRType entity:
- * check that we don't remove a core relation type
- * cascade to delete related CWAttribute and CWRelation entities
- * instantiate an operation to delete the relation type on commit
- """
- name = check_internal_entity(session, eid, CORE_RTYPES)
- # delete relation definitions using this relation type
- session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
- {'x': eid})
- session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
- {'x': eid})
- MemSchemaCWRTypeDel(session, name)
-
-
-def after_del_relation_type(session, rdefeid, rtype, rteid):
- """before deleting a CWAttribute or CWRelation entity:
- * if this is a final or inlined relation definition, instantiate an
- operation to drop necessary column, else if this is the last instance
- of a non final relation, instantiate an operation to drop necessary
- table
- * instantiate an operation to delete the relation definition on commit
- * delete the associated relation type when necessary
- """
- subjschema, rschema, objschema = session.schema.schema_by_eid(rdefeid)
- pendings = session.transaction_data.get('pendingeids', ())
- pendingrdefs = session.transaction_data.setdefault('pendingrdefs', set())
- # first delete existing relation if necessary
- if rschema.final:
- rdeftype = 'CWAttribute'
- pendingrdefs.add((subjschema, rschema))
- else:
- rdeftype = 'CWRelation'
- pendingrdefs.add((subjschema, rschema, objschema))
- if not (subjschema.eid in pendings or objschema.eid in pendings):
- session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
- % (rschema, subjschema, objschema))
- execute = session.unsafe_execute
- rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
- 'R eid %%(x)s' % rdeftype, {'x': rteid})
- lastrel = rset[0][0] == 0
- # we have to update physical schema systematically for final and inlined
- # relations, but only if it's the last instance for this relation type
- # for other relations
-
- if (rschema.final or rschema.inlined):
- rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
- 'R eid %%(x)s, X from_entity E, E name %%(name)s'
- % rdeftype, {'x': rteid, 'name': str(subjschema)})
- if rset[0][0] == 0 and not subjschema.eid in pendings:
- ptypes = session.transaction_data.setdefault('pendingrtypes', set())
- ptypes.add(rschema.type)
- DropColumn(session, table=SQL_PREFIX + subjschema.type,
- column=SQL_PREFIX + rschema.type)
- elif lastrel:
- DropRelationTable(session, rschema.type)
- # if this is the last instance, drop associated relation type
- if lastrel and not rteid in pendings:
- execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
- MemSchemaRDefDel(session, (subjschema, rschema, objschema))
-
-
-# addition hooks ###############################################################
-
-def before_add_eetype(session, entity):
- """before adding a CWEType entity:
- * check that we are not using an existing entity type,
- """
- name = entity['name']
- schema = session.schema
- if name in schema and schema[name].eid is not None:
- raise RepositoryError('an entity type %s already exists' % name)
-
-def after_add_eetype(session, entity):
- """after adding a CWEType entity:
- * create the necessary table
- * set creation_date and modification_date by creating the necessary
- CWAttribute entities
- * add owned_by relation by creating the necessary CWRelation entity
- * register an operation to add the entity type to the instance's
- schema on commit
- """
- if entity.get('final'):
- return
- schema = session.schema
- name = entity['name']
- etype = EntityType(name=name, description=entity.get('description'),
- meta=entity.get('meta')) # don't care about final
- # fake we add it to the schema now to get a correctly initialized schema
- # but remove it before doing anything more dangerous...
- schema = session.schema
- eschema = schema.add_entity_type(etype)
- eschema.set_default_groups()
- # generate table sql and rql to add metadata
- tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
- prefix=SQL_PREFIX)
- relrqls = []
- for rtype in (META_RTYPES - VIRTUAL_RTYPES):
- rschema = schema[rtype]
- sampletype = rschema.subjects()[0]
- desttype = rschema.objects()[0]
- props = rschema.rproperties(sampletype, desttype)
- relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
- # now remove it !
- schema.del_entity_type(name)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- session.system_sql(sql)
- # register operation to modify the schema on commit
- # this have to be done before adding other relations definitions
- # or permission settings
- etype.eid = entity.eid
- MemSchemaCWETypeAdd(session, etype)
- # add meta relations
- for rql, kwargs in relrqls:
- session.execute(rql, kwargs)
-
-
-def before_add_ertype(session, entity):
- """before adding a CWRType entity:
- * check that we are not using an existing relation type,
- * register an operation to add the relation type to the instance's
- schema on commit
-
- We don't know yeat this point if a table is necessary
- """
- name = entity['name']
- if name in session.schema.relations():
- raise RepositoryError('a relation type %s already exists' % name)
-
-
-def after_add_ertype(session, entity):
- """after a CWRType entity has been added:
- * register an operation to add the relation type to the instance's
- schema on commit
- We don't know yeat this point if a table is necessary
- """
- rtype = RelationType(name=entity['name'],
- description=entity.get('description'),
- meta=entity.get('meta', False),
- inlined=entity.get('inlined', False),
- symetric=entity.get('symetric', False))
- rtype.eid = entity.eid
- MemSchemaCWRTypeAdd(session, rtype)
-
-
-def after_add_efrdef(session, entity):
- SourceDbCWAttributeAdd(session, entity=entity)
-
-def after_add_enfrdef(session, entity):
- SourceDbCWRelationAdd(session, entity=entity)
-
-
-# update hooks #################################################################
-
-def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
- errors = {}
- # don't use getattr(entity, attr), we would get the modified value if any
- for attr in ro_attrs:
- if attr in entity.edited_attributes:
- origval, newval = entity_oldnewvalue(entity, attr)
- if newval != origval:
- errors[attr] = session._("can't change the %s attribute") % \
- display_name(session, attr)
- if errors:
- raise ValidationError(entity.eid, errors)
-
-def before_update_eetype(session, entity):
- """check name change, handle final"""
- check_valid_changes(session, entity, ro_attrs=('final',))
- # don't use getattr(entity, attr), we would get the modified value if any
- if 'name' in entity.edited_attributes:
- oldname, newname = entity_oldnewvalue(entity, 'name')
- if newname.lower() != oldname.lower():
- SourceDbCWETypeRename(session, oldname=oldname, newname=newname)
- MemSchemaCWETypeRename(session, oldname=oldname, newname=newname)
-
-def before_update_ertype(session, entity):
- """check name change, handle final"""
- check_valid_changes(session, entity)
-
-
-def after_update_erdef(session, entity):
- if entity.eid in session.transaction_data.get('pendingeids', ()):
- return
- desttype = entity.otype.name
- rschema = session.schema[entity.rtype.name]
- newvalues = {}
- for prop in rschema.rproperty_defs(desttype):
- if prop == 'constraints':
- continue
- if prop == 'order':
- prop = 'ordernum'
- if prop in entity.edited_attributes:
- newvalues[prop] = entity[prop]
- if newvalues:
- subjtype = entity.stype.name
- MemSchemaRDefUpdate(session, kobj=(subjtype, desttype),
- rschema=rschema, values=newvalues)
- SourceDbRDefUpdate(session, kobj=(subjtype, desttype),
- rschema=rschema, values=newvalues)
-
-def after_update_ertype(session, entity):
- rschema = session.schema.rschema(entity.name)
- newvalues = {}
- for prop in ('meta', 'symetric', 'inlined'):
- if prop in entity:
- newvalues[prop] = entity[prop]
- if newvalues:
- MemSchemaCWRTypeUpdate(session, rschema=rschema, values=newvalues)
- SourceDbCWRTypeUpdate(session, rschema=rschema, values=newvalues,
- entity=entity)
-
-# constraints synchronization hooks ############################################
-
-def after_add_econstraint(session, entity):
- MemSchemaCWConstraintAdd(session, entity=entity)
- SourceDbCWConstraintAdd(session, entity=entity)
-
-
-def after_update_econstraint(session, entity):
- MemSchemaCWConstraintAdd(session, entity=entity)
- SourceDbCWConstraintAdd(session, entity=entity)
-
-
-def before_delete_constrained_by(session, fromeid, rtype, toeid):
- if not fromeid in session.transaction_data.get('pendingeids', ()):
- schema = session.schema
- entity = session.entity_from_eid(toeid)
- subjtype, rtype, objtype = schema.schema_by_eid(fromeid)
- try:
- cstr = rtype.constraint_by_type(subjtype, objtype,
- entity.cstrtype[0].name)
- except IndexError:
- session.critical('constraint type no more accessible')
- else:
- SourceDbCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
- objtype=objtype, cstr=cstr)
- MemSchemaCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
- objtype=objtype, cstr=cstr)
-
-
-def after_add_constrained_by(session, fromeid, rtype, toeid):
- if fromeid in session.transaction_data.get('neweids', ()):
- session.transaction_data.setdefault(fromeid, []).append(toeid)
-
-
-# permissions synchronization hooks ############################################
-
-def after_add_permission(session, subject, rtype, object):
- """added entity/relation *_permission, need to update schema"""
- perm = rtype.split('_', 1)[0]
- if session.describe(object)[0] == 'CWGroup':
- MemSchemaPermissionCWGroupAdd(session, perm, subject, object)
- else: # RQLExpression
- expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
- {'x': object}, 'x')[0][0]
- MemSchemaPermissionRQLExpressionAdd(session, perm, subject, expr)
-
-
-def before_del_permission(session, subject, rtype, object):
- """delete entity/relation *_permission, need to update schema
-
- skip the operation if the related type is being deleted
- """
- if subject in session.transaction_data.get('pendingeids', ()):
- return
- perm = rtype.split('_', 1)[0]
- if session.describe(object)[0] == 'CWGroup':
- MemSchemaPermissionCWGroupDel(session, perm, subject, object)
- else: # RQLExpression
- expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
- {'x': object}, 'x')[0][0]
- MemSchemaPermissionRQLExpressionDel(session, perm, subject, expr)
-
-
-def after_add_specializes(session, subject, rtype, object):
- MemSchemaSpecializesAdd(session, etypeeid=subject, parentetypeeid=object)
-
-def after_del_specializes(session, subject, rtype, object):
- MemSchemaSpecializesDel(session, etypeeid=subject, parentetypeeid=object)
-
-
-def _register_schema_hooks(hm):
- """register schema related hooks on the hooks manager"""
- # schema synchronisation #####################
- # before/after add
- hm.register_hook(before_add_eetype, 'before_add_entity', 'CWEType')
- hm.register_hook(before_add_ertype, 'before_add_entity', 'CWRType')
- hm.register_hook(after_add_eetype, 'after_add_entity', 'CWEType')
- hm.register_hook(after_add_ertype, 'after_add_entity', 'CWRType')
- hm.register_hook(after_add_efrdef, 'after_add_entity', 'CWAttribute')
- hm.register_hook(after_add_enfrdef, 'after_add_entity', 'CWRelation')
- # before/after update
- hm.register_hook(before_update_eetype, 'before_update_entity', 'CWEType')
- hm.register_hook(before_update_ertype, 'before_update_entity', 'CWRType')
- hm.register_hook(after_update_ertype, 'after_update_entity', 'CWRType')
- hm.register_hook(after_update_erdef, 'after_update_entity', 'CWAttribute')
- hm.register_hook(after_update_erdef, 'after_update_entity', 'CWRelation')
- # before/after delete
- hm.register_hook(before_del_eetype, 'before_delete_entity', 'CWEType')
- hm.register_hook(after_del_eetype, 'after_delete_entity', 'CWEType')
- hm.register_hook(before_del_ertype, 'before_delete_entity', 'CWRType')
- hm.register_hook(after_del_relation_type, 'after_delete_relation', 'relation_type')
- hm.register_hook(after_add_specializes, 'after_add_relation', 'specializes')
- hm.register_hook(after_del_specializes, 'after_delete_relation', 'specializes')
- # constraints synchronization hooks
- hm.register_hook(after_add_econstraint, 'after_add_entity', 'CWConstraint')
- hm.register_hook(after_update_econstraint, 'after_update_entity', 'CWConstraint')
- hm.register_hook(before_delete_constrained_by, 'before_delete_relation', 'constrained_by')
- hm.register_hook(after_add_constrained_by, 'after_add_relation', 'constrained_by')
- # permissions synchronisation ################
- for perm in ('read_permission', 'add_permission',
- 'delete_permission', 'update_permission'):
- hm.register_hook(after_add_permission, 'after_add_relation', perm)
- hm.register_hook(before_del_permission, 'before_delete_relation', perm)
--- a/server/schemaserial.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/schemaserial.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,6 +9,7 @@
import os
import sys
+import os
from itertools import chain
from logilab.common.shellutils import ProgressBar
@@ -27,7 +28,8 @@
from the user.
"""
res = {}
- for eid, name in cursor.execute('Any G, N WHERE G is CWGroup, G name N'):
+ for eid, name in cursor.execute('Any G, N WHERE G is CWGroup, G name N',
+ build_descr=False):
res[name] = eid
if not interactive:
return res
@@ -50,39 +52,6 @@
continue
return res
-def _set_sql_prefix(prefix):
- """3.2.0 migration function: allow to unset/reset SQL_PREFIX"""
- for module in ('checkintegrity', 'migractions', 'schemahooks',
- 'sources.rql2sql', 'sources.native', 'sqlutils'):
- try:
- sys.modules['cubicweb.server.%s' % module].SQL_PREFIX = prefix
- print 'changed SQL_PREFIX for %s' % module
- except KeyError:
- pass
-
-def _update_database(schema, sqlcu):
- """3.2.0 migration function: update database schema by adding SQL_PREFIX to
- entity type tables and columns
- """
- for etype in schema.entities():
- if etype.final:
- continue
- try:
- sql = 'ALTER TABLE %s RENAME TO cw_%s' % (
- etype, ETYPE_NAME_MAP.get(etype, etype))
- print sql
- sqlcu.execute(sql)
- except:
- pass
- for rschema in etype.subject_relations():
- if rschema == 'has_text':
- continue
- if rschema.final or rschema.inlined:
- sql = 'ALTER TABLE cw_%s RENAME %s TO cw_%s' % (
- etype, rschema, rschema)
- print sql
- sqlcu.execute(sql)
-
# schema / perms deserialization ##############################################
OLD_SCHEMA_TYPES = frozenset(('EFRDef', 'ENFRDef', 'ERType', 'EEType',
'EConstraintType', 'EConstraint', 'EGroup',
@@ -92,42 +61,31 @@
"""return a schema according to information stored in an rql database
as CWRType and CWEType entities
"""
- #
repo = session.repo
- sqlcu = session.pool['system']
- _3_2_migration = False
dbhelper = repo.system_source.dbhelper
- tables = set(t.lower() for t in dbhelper.list_tables(sqlcu))
- if 'eetype' in tables:
- _3_2_migration = True
- # 3.2 migration
- _set_sql_prefix('')
- # first rename entity types whose name changed in 3.2 without adding the
- # cw_ prefix
- for etype in OLD_SCHEMA_TYPES:
- if etype.lower() in tables:
- sql = 'ALTER TABLE %s RENAME TO %s' % (etype,
- ETYPE_NAME_MAP[etype])
- print sql
- sqlcu.execute(sql)
- # other table renaming done once schema has been read
- index = {}
+ # 3.6 migration
+ sqlcu = session.pool['system']
+ sqlcu.execute("SELECT * FROM cw_CWRType WHERE cw_name='symetric'")
+ if sqlcu.fetchall():
+ sql = dbhelper.sql_rename_col('cw_CWRType', 'cw_symetric', 'cw_symmetric',
+ dbhelper.TYPE_MAPPING['Boolean'], True)
+ sqlcu.execute(sql)
+ sqlcu.execute("UPDATE cw_CWRType SET cw_name='symmetric' WHERE cw_name='symetric'")
+ sidx = {}
permsdict = deserialize_ertype_permissions(session)
schema.reading_from_database = True
- for eid, etype, desc in session.execute('Any X, N, D WHERE '
- 'X is CWEType, X name N, '
- 'X description D',
- build_descr=False):
+ for eid, etype, desc in session.execute(
+ 'Any X, N, D WHERE X is CWEType, X name N, X description D',
+ build_descr=False):
# base types are already in the schema, skip them
if etype in schemamod.BASE_TYPES:
# just set the eid
eschema = schema.eschema(etype)
eschema.eid = eid
- index[eid] = eschema
+ sidx[eid] = eschema
continue
if etype in ETYPE_NAME_MAP:
netype = ETYPE_NAME_MAP[etype]
- print 'fixing etype name from %s to %s' % (etype, netype)
# can't use write rql queries at this point, use raw sql
session.system_sql('UPDATE %(p)sCWEType SET %(p)sname=%%(n)s WHERE %(p)seid=%%(x)s'
% {'p': sqlutils.SQL_PREFIX},
@@ -154,35 +112,21 @@
etype = netype
etype = ybo.EntityType(name=etype, description=desc, eid=eid)
eschema = schema.add_entity_type(etype)
- index[eid] = eschema
- set_perms(eschema, permsdict.get(eid, {}))
- try:
- rset = session.execute('Any XN, ETN WHERE X is CWEType, X name XN, '
- 'X specializes ET, ET name ETN')
- except: # `specializes` relation not available for versions prior to 2.50
- session.rollback(False)
- else:
- for etype, stype in rset:
- eschema = schema.eschema(etype)
- seschema = schema.eschema(stype)
- eschema._specialized_type = stype
- seschema._specialized_by.append(etype)
- for eid, rtype, desc, sym, il in session.execute(
- 'Any X,N,D,S,I WHERE X is CWRType, X name N, X description D, '
- 'X symetric S, X inlined I', build_descr=False):
- try:
- # bw compat: fulltext_container added in 2.47
- ft_container = session.execute('Any FTC WHERE X eid %(x)s, X fulltext_container FTC',
- {'x': eid}).rows[0][0]
- except:
- ft_container = None
- session.rollback(False)
+ sidx[eid] = eschema
+ set_perms(eschema, permsdict)
+ for etype, stype in session.execute(
+ 'Any XN, ETN WHERE X is CWEType, X name XN, X specializes ET, ET name ETN',
+ build_descr=False):
+ schema.eschema(etype)._specialized_type = stype
+ schema.eschema(stype)._specialized_by.append(etype)
+ for eid, rtype, desc, sym, il, ftc in session.execute(
+ 'Any X,N,D,S,I,FTC WHERE X is CWRType, X name N, X description D, '
+ 'X symmetric S, X inlined I, X fulltext_container FTC', build_descr=False):
rtype = ybo.RelationType(name=rtype, description=desc,
- symetric=bool(sym), inlined=bool(il),
- fulltext_container=ft_container, eid=eid)
+ symmetric=bool(sym), inlined=bool(il),
+ fulltext_container=ftc, eid=eid)
rschema = schema.add_relation_type(rtype)
- index[eid] = rschema
- set_perms(rschema, permsdict.get(eid, {}))
+ sidx[eid] = rschema
cstrsdict = deserialize_rdef_constraints(session)
for values in session.execute(
'Any X,SE,RT,OE,CARD,ORD,DESC,IDX,FTIDX,I18N,DFLT WHERE X is CWAttribute,'
@@ -191,35 +135,31 @@
'X fulltextindexed FTIDX, X from_entity SE, X to_entity OE',
build_descr=False):
rdefeid, seid, reid, teid, card, ord, desc, idx, ftidx, i18n, default = values
- constraints = cstrsdict.get(rdefeid, ())
- frometype = index[seid].type
- rtype = index[reid].type
- toetype = index[teid].type
- rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card,
- order=ord, description=desc,
- constraints=constraints,
- indexed=idx, fulltextindexed=ftidx,
- internationalizable=i18n,
- default=default, eid=rdefeid)
- schema.add_relation_def(rdef)
+ rdef = ybo.RelationDefinition(sidx[seid].type, sidx[reid].type, sidx[teid].type,
+ cardinality=card,
+ constraints=cstrsdict.get(rdefeid, ()),
+ order=ord, description=desc,
+ indexed=idx, fulltextindexed=ftidx,
+ internationalizable=i18n,
+ default=default, eid=rdefeid)
+ rdefs = schema.add_relation_def(rdef)
+ # rdefs can be None on duplicated relation definitions (e.g. symmetrics)
+ if rdefs is not None:
+ set_perms(rdefs, permsdict)
for values in session.execute(
'Any X,SE,RT,OE,CARD,ORD,DESC,C WHERE X is CWRelation, X relation_type RT,'
'X cardinality CARD, X ordernum ORD, X description DESC, '
'X from_entity SE, X to_entity OE, X composite C', build_descr=False):
rdefeid, seid, reid, teid, card, ord, desc, c = values
- frometype = index[seid].type
- rtype = index[reid].type
- toetype = index[teid].type
- constraints = cstrsdict.get(rdefeid, ())
- rdef = ybo.RelationDefinition(frometype, rtype, toetype, cardinality=card,
- order=ord, description=desc,
- composite=c, constraints=constraints,
- eid=rdefeid)
- schema.add_relation_def(rdef)
+ rdef = ybo.RelationDefinition(sidx[seid].type, sidx[reid].type, sidx[teid].type,
+ constraints=cstrsdict.get(rdefeid, ()),
+ cardinality=card, order=ord, description=desc,
+ composite=c, eid=rdefeid)
+ rdefs = schema.add_relation_def(rdef)
+ # rdefs can be None on duplicated relation definitions (e.g. symmetrics)
+ if rdefs is not None:
+ set_perms(rdefs, permsdict)
schema.infer_specialization_rules()
- if _3_2_migration:
- _update_database(schema, sqlcu)
- _set_sql_prefix('cw_')
session.commit()
schema.reading_from_database = False
@@ -249,14 +189,15 @@
definition dictionary as built by deserialize_ertype_permissions for a
given erschema's eid
"""
- for action in erschema.ACTIONS:
- actperms = []
- for something in permsdict.get(action, ()):
- if isinstance(something, tuple):
- actperms.append(erschema.rql_expression(*something))
- else: # group name
- actperms.append(something)
- erschema.set_permissions(action, actperms)
+ try:
+ thispermsdict = permsdict[erschema.eid]
+ except KeyError:
+ return
+ permissions = erschema.permissions
+ for action, somethings in thispermsdict.iteritems():
+ permissions[action] = tuple(
+ isinstance(p, tuple) and erschema.rql_expression(*p) or p
+ for p in somethings)
def deserialize_rdef_constraints(session):
@@ -277,11 +218,14 @@
"""synchronize schema and permissions in the database according to
current schema
"""
- _title = '-> storing the schema in the database '
- print _title,
+ quiet = os.environ.get('APYCOT_ROOT')
+ if not quiet:
+ _title = '-> storing the schema in the database '
+ print _title,
+ execute = cursor.execute
eschemas = schema.entities()
aller = eschemas + schema.relations()
- if not verbose and not os.environ.get('APYCOT_ROOT'):
+ if not verbose and not quiet:
pb_size = len(aller) + len(CONSTRAINTS) + len([x for x in eschemas if x.specializes()])
pb = ProgressBar(pb_size, title=_title)
else:
@@ -290,7 +234,7 @@
for cstrtype in CONSTRAINTS:
if verbose:
print rql
- cursor.execute(rql, {'ct': unicode(cstrtype)})
+ execute(rql, {'ct': unicode(cstrtype)}, build_descr=False)
if pb is not None:
pb.update()
groupmap = group_mapping(cursor, interactive=False)
@@ -300,23 +244,20 @@
if pb is not None:
pb.update()
continue
- for rql, kwargs in erschema2rql(schema[ertype]):
+ for rql, kwargs in erschema2rql(schema[ertype], groupmap):
if verbose:
print rql % kwargs
- cursor.execute(rql, kwargs)
- for rql, kwargs in erperms2rql(schema[ertype], groupmap):
- if verbose:
- print rql
- cursor.execute(rql, kwargs)
+ execute(rql, kwargs, build_descr=False)
if pb is not None:
pb.update()
for rql, kwargs in specialize2rql(schema):
if verbose:
print rql % kwargs
- cursor.execute(rql, kwargs)
+ execute(rql, kwargs, build_descr=False)
if pb is not None:
pb.update()
- print
+ if not quiet:
+ print
def _ervalues(erschema):
@@ -345,7 +286,7 @@
def rschema_relations_values(rschema):
values = _ervalues(rschema)
values['final'] = rschema.final
- values['symetric'] = rschema.symetric
+ values['symmetric'] = rschema.symmetric
values['inlined'] = rschema.inlined
if HAS_FULLTEXT_CONTAINER:
if isinstance(rschema.fulltext_container, str):
@@ -358,8 +299,8 @@
def _rdef_values(rschema, objtype, props):
amap = {'order': 'ordernum'}
values = {}
- for prop, default in rschema.rproperty_defs(objtype).iteritems():
- if prop in ('eid', 'constraints', 'uid', 'infered'):
+ for prop, default in schemamod.RelationDefinitionSchema.rproperty_defs(objtype).iteritems():
+ if prop in ('eid', 'constraints', 'uid', 'infered', 'permissions'):
continue
value = props.get(prop, default)
if prop in ('indexed', 'fulltextindexed', 'internationalizable'):
@@ -390,17 +331,23 @@
return relations, values
-def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None):
+def __rdef2rql(genmap, rschema, subjtype=None, objtype=None, props=None,
+ groupmap=None):
if subjtype is None:
assert objtype is None
assert props is None
- targets = rschema.iter_rdefs()
+ targets = sorted(rschema.rdefs)
else:
assert not objtype is None
targets = [(subjtype, objtype)]
+ # relation schema
+ if rschema.final:
+ etype = 'CWAttribute'
+ else:
+ etype = 'CWRelation'
for subjtype, objtype in targets:
if props is None:
- _props = rschema.rproperties(subjtype, objtype)
+ _props = rschema.rdef(subjtype, objtype)
else:
_props = props
# don't serialize infered relations
@@ -409,6 +356,15 @@
gen = genmap[rschema.final]
for rql, values in gen(rschema, subjtype, objtype, _props):
yield rql, values
+ # no groupmap means "no security insertion"
+ if groupmap:
+ for rql, args in _erperms2rql(_props, groupmap):
+ args['st'] = str(subjtype)
+ args['rt'] = str(rschema)
+ args['ot'] = str(objtype)
+ yield rql + 'X is %s, X from_entity ST, X to_entity OT, '\
+ 'X relation_type RT, RT name %%(rt)s, ST name %%(st)s, '\
+ 'OT name %%(ot)s' % etype, args
def schema2rql(schema, skip=None, allow=None):
@@ -424,12 +380,12 @@
return chain(*[erschema2rql(schema[t]) for t in all if t in allow])
return chain(*[erschema2rql(schema[t]) for t in all])
-def erschema2rql(erschema):
+def erschema2rql(erschema, groupmap):
if isinstance(erschema, schemamod.EntitySchema):
- return eschema2rql(erschema)
- return rschema2rql(erschema)
+ return eschema2rql(erschema, groupmap=groupmap)
+ return rschema2rql(erschema, groupmap=groupmap)
-def eschema2rql(eschema):
+def eschema2rql(eschema, groupmap=None):
"""return a list of rql insert statements to enter an entity schema
in the database as an CWEType entity
"""
@@ -437,6 +393,11 @@
# NOTE: 'specializes' relation can't be inserted here since there's no
# way to make sure the parent type is inserted before the child type
yield 'INSERT CWEType X: %s' % ','.join(relations) , values
+ # entity permissions
+ if groupmap is not None:
+ for rql, args in _erperms2rql(eschema, groupmap):
+ args['name'] = str(eschema)
+ yield rql + 'X is CWEType, X name %(name)s', args
def specialize2rql(schema):
for eschema in schema.entities():
@@ -449,7 +410,7 @@
values = {'x': eschema.type, 'et': specialized_type.type}
yield 'SET X specializes ET WHERE X name %(x)s, ET name %(et)s', values
-def rschema2rql(rschema, addrdef=True):
+def rschema2rql(rschema, addrdef=True, groupmap=None):
"""return a list of rql insert statements to enter a relation schema
in the database as an CWRType entity
"""
@@ -458,12 +419,12 @@
relations, values = rschema_relations_values(rschema)
yield 'INSERT CWRType X: %s' % ','.join(relations), values
if addrdef:
- for rql, values in rdef2rql(rschema):
+ for rql, values in rdef2rql(rschema, groupmap=groupmap):
yield rql, values
-def rdef2rql(rschema, subjtype=None, objtype=None, props=None):
+def rdef2rql(rschema, subjtype=None, objtype=None, props=None, groupmap=None):
genmap = {True: frdef2rql, False: nfrdef2rql}
- return __rdef2rql(genmap, rschema, subjtype, objtype, props)
+ return __rdef2rql(genmap, rschema, subjtype, objtype, props, groupmap)
_LOCATE_RDEF_RQL0 = 'X relation_type ER,X from_entity SE,X to_entity OE'
@@ -487,7 +448,7 @@
def rdefrelations2rql(rschema, subjtype, objtype, props):
iterators = []
- for constraint in props['constraints']:
+ for constraint in props.constraints:
iterators.append(constraint2rql(rschema, subjtype, objtype, constraint))
return chain(*iterators)
@@ -499,43 +460,30 @@
CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, \
ER name %(rt)s, SE name %(se)s, OE name %(oe)s', values
-def perms2rql(schema, groupmapping):
- """return rql insert statements to enter the schema's permissions in
- the database as [read|add|delete|update]_permission relations between
- CWEType/CWRType and CWGroup entities
- groupmapping is a dictionnary mapping standard group names to
- eids
- """
- for etype in sorted(schema.entities()):
- yield erperms2rql(schema[etype], groupmapping)
- for rtype in sorted(schema.relations()):
- yield erperms2rql(schema[rtype], groupmapping)
-
-def erperms2rql(erschema, groupmapping):
+def _erperms2rql(erschema, groupmap):
"""return rql insert statements to enter the entity or relation
schema's permissions in the database as
[read|add|delete|update]_permission relations between CWEType/CWRType
and CWGroup entities
"""
- etype = isinstance(erschema, schemamod.EntitySchema) and 'CWEType' or 'CWRType'
for action in erschema.ACTIONS:
- for group in sorted(erschema.get_groups(action)):
- try:
- yield ('SET X %s_permission Y WHERE X is %s, X name %%(name)s, Y eid %%(g)s'
- % (action, etype), {'name': str(erschema),
- 'g': groupmapping[group]})
- except KeyError:
- continue
- for rqlexpr in sorted(erschema.get_rqlexprs(action)):
- yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
- 'E mainvars %%(v)s, X %s_permission E '
- 'WHERE X is %s, X name %%(name)s' % (action, etype),
- {'e': unicode(rqlexpr.expression),
- 'v': unicode(rqlexpr.mainvars),
- 't': unicode(rqlexpr.__class__.__name__),
- 'name': unicode(erschema)
- })
+ for group_or_rqlexpr in erschema.action_permissions(action):
+ if isinstance(group_or_rqlexpr, basestring):
+ # group
+ try:
+ yield ('SET X %s_permission Y WHERE Y eid %%(g)s, ' % action,
+ {'g': groupmap[group_or_rqlexpr]})
+ except KeyError:
+ continue
+ else:
+ # rqlexpr
+ rqlexpr = group_or_rqlexpr
+ yield ('INSERT RQLExpression E: E expression %%(e)s, E exprtype %%(t)s, '
+ 'E mainvars %%(v)s, X %s_permission E WHERE ' % action,
+ {'e': unicode(rqlexpr.expression),
+ 'v': unicode(rqlexpr.mainvars),
+ 't': unicode(rqlexpr.__class__.__name__)})
def updateeschema2rql(eschema):
--- a/server/securityhooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-"""Security hooks: check permissions to add/delete/update entities according to
-the user connected to a session
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb import Unauthorized
-from cubicweb.server.pool import LateOperation
-from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS
-
-def check_entity_attributes(session, entity):
- eid = entity.eid
- eschema = entity.e_schema
- # ._default_set is only there on entity creation to indicate unspecified
- # attributes which has been set to a default value defined in the schema
- defaults = getattr(entity, '_default_set', ())
- try:
- editedattrs = entity.edited_attributes
- except AttributeError:
- editedattrs = entity.keys()
- for attr in editedattrs:
- if attr in defaults:
- continue
- rschema = eschema.subjrels[attr]
- if rschema.final: # non final relation are checked by other hooks
- # add/delete should be equivalent (XXX: unify them into 'update' ?)
- rschema.check_perm(session, 'add', eid)
-
-
-class CheckEntityPermissionOp(LateOperation):
- def precommit_event(self):
- #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
- self.entity.check_perm(self.action)
- check_entity_attributes(self.session, self.entity)
-
- def commit_event(self):
- pass
-
-
-class CheckRelationPermissionOp(LateOperation):
- def precommit_event(self):
- self.rschema.check_perm(self.session, self.action, self.fromeid, self.toeid)
-
- def commit_event(self):
- pass
-
-def after_add_entity(session, entity):
- if not session.is_super_session:
- CheckEntityPermissionOp(session, entity=entity, action='add')
-
-def after_update_entity(session, entity):
- if not session.is_super_session:
- try:
- # check user has permission right now, if not retry at commit time
- entity.check_perm('update')
- check_entity_attributes(session, entity)
- except Unauthorized:
- entity.clear_local_perm_cache('update')
- CheckEntityPermissionOp(session, entity=entity, action='update')
-
-def before_del_entity(session, eid):
- if not session.is_super_session:
- eschema = session.repo.schema[session.describe(eid)[0]]
- eschema.check_perm(session, 'delete', eid)
-
-
-def before_add_relation(session, fromeid, rtype, toeid):
- if rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
- nocheck = session.transaction_data.get('skip-security', ())
- if (fromeid, rtype, toeid) in nocheck:
- return
- rschema = session.repo.schema[rtype]
- rschema.check_perm(session, 'add', fromeid, toeid)
-
-def after_add_relation(session, fromeid, rtype, toeid):
- if not rtype in BEFORE_ADD_RELATIONS and not session.is_super_session:
- nocheck = session.transaction_data.get('skip-security', ())
- if (fromeid, rtype, toeid) in nocheck:
- return
- rschema = session.repo.schema[rtype]
- if rtype in ON_COMMIT_ADD_RELATIONS:
- CheckRelationPermissionOp(session, action='add', rschema=rschema,
- fromeid=fromeid, toeid=toeid)
- else:
- rschema.check_perm(session, 'add', fromeid, toeid)
-
-def before_del_relation(session, fromeid, rtype, toeid):
- if not session.is_super_session:
- nocheck = session.transaction_data.get('skip-security', ())
- if (fromeid, rtype, toeid) in nocheck:
- return
- session.repo.schema[rtype].check_perm(session, 'delete', fromeid, toeid)
-
-def register_security_hooks(hm):
- """register meta-data related hooks on the hooks manager"""
- hm.register_hook(after_add_entity, 'after_add_entity', '')
- hm.register_hook(after_update_entity, 'after_update_entity', '')
- hm.register_hook(before_del_entity, 'before_delete_entity', '')
- hm.register_hook(before_add_relation, 'before_add_relation', '')
- hm.register_hook(after_add_relation, 'after_add_relation', '')
- hm.register_hook(before_del_relation, 'before_delete_relation', '')
-
--- a/server/serverconfig.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/serverconfig.py Mon Feb 08 13:35:07 2010 +0100
@@ -86,7 +86,7 @@
"""standalone RQL server"""
name = 'repository'
- cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects'])
+ cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects', 'hooks'])
cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['sobjects', 'hooks'])
options = merge_options((
@@ -187,14 +187,56 @@
# check user's state at login time
consider_user_state = True
- # hooks registration configuration
+ # hooks activation configuration
# all hooks should be activated during normal execution
- core_hooks = True
- usergroup_hooks = True
- schema_hooks = True
- notification_hooks = True
- security_hooks = True
- instance_hooks = True
+ disabled_hooks_categories = set()
+ enabled_hooks_categories = set()
+ ALLOW_ALL = object()
+ DENY_ALL = object()
+ hooks_mode = ALLOW_ALL
+
+ @classmethod
+ def set_hooks_mode(cls, mode):
+ assert mode is cls.ALLOW_ALL or mode is cls.DENY_ALL
+ oldmode = cls.hooks_mode
+ cls.hooks_mode = mode
+ return oldmode
+
+ @classmethod
+ def disable_hook_category(cls, *categories):
+ changes = set()
+ if cls.hooks_mode is cls.DENY_ALL:
+ for category in categories:
+ if category in cls.enabled_hooks_categories:
+ cls.enabled_hooks_categories.remove(category)
+ changes.add(category)
+ else:
+ for category in categories:
+ if category not in cls.disabled_hooks_categories:
+ cls.disabled_hooks_categories.add(category)
+ changes.add(category)
+ return changes
+
+ @classmethod
+ def enable_hook_category(cls, *categories):
+ changes = set()
+ if cls.hooks_mode is cls.DENY_ALL:
+ for category in categories:
+ if category not in cls.enabled_hooks_categories:
+ cls.enabled_hooks_categories.add(category)
+ changes.add(category)
+ else:
+ for category in categories:
+ if category in cls.disabled_hooks_categories:
+ cls.disabled_hooks_categories.remove(category)
+ changes.add(category)
+ return changes
+
+ @classmethod
+ def is_hook_activated(cls, hook):
+ if cls.hooks_mode is cls.DENY_ALL:
+ return hook.category in cls.enabled_hooks_categories
+ return hook.category not in cls.disabled_hooks_categories
# should some hooks be deactivated during [pre|post]create script execution
free_wheel = False
@@ -259,20 +301,6 @@
"""pyro is always enabled in standalone repository configuration"""
return True
- def load_hooks(self, vreg):
- hooks = {}
- try:
- apphookdefs = vreg['hooks'].all_objects()
- except RegistryNotFound:
- return hooks
- for hookdef in apphookdefs:
- for event, ertype in hookdef.register_to():
- if ertype == 'Any':
- ertype = ''
- cb = hookdef.make_callback(event)
- hooks.setdefault(event, {}).setdefault(ertype, []).append(cb)
- return hooks
-
def load_schema(self, expand_cubes=False, **kwargs):
from cubicweb.schema import CubicWebSchemaLoader
if expand_cubes:
--- a/server/serverctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/serverctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,9 +14,8 @@
from logilab.common.clcommands import register_commands, cmd_run, pop_arg
from logilab.common.shellutils import ASK
-from cubicweb import (AuthenticationError, ExecutionError, ConfigurationError,
- underline_title)
-from cubicweb.toolsutils import Command, CommandHandler
+from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
+from cubicweb.toolsutils import Command, CommandHandler, underline_title
from cubicweb.server import SOURCE_TYPES
from cubicweb.server.utils import ask_source_config
from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
@@ -115,7 +114,7 @@
login, pwd = manager_userpasswd()
while True:
try:
- return in_memory_cnx(config, login, pwd)
+ return in_memory_cnx(config, login, password=pwd)
except AuthenticationError:
print '-> Error: wrong user/password.'
# reset cubes else we'll have an assertion error on next retry
--- a/server/session.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/session.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,7 +15,8 @@
from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
-from cubicweb import RequestSessionMixIn, Binary, UnknownEid
+from cubicweb import Binary, UnknownEid
+from cubicweb.req import RequestSessionBase
from cubicweb.dbapi import ConnectionProperties
from cubicweb.utils import make_uid
from cubicweb.rqlrewrite import RQLRewriter
@@ -41,7 +42,7 @@
return description
-class Session(RequestSessionMixIn):
+class Session(RequestSessionBase):
"""tie session id, user, connections pool and other session data all
together
"""
@@ -74,10 +75,6 @@
return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
self.id, id(self))
- @property
- def schema(self):
- return self.repo.schema
-
def hijack_user(self, user):
"""return a fake request/session using specified user"""
session = Session(user, self.repo)
@@ -136,16 +133,16 @@
# relations cache handling #################################################
- def update_rel_cache_add(self, subject, rtype, object, symetric=False):
+ def update_rel_cache_add(self, subject, rtype, object, symmetric=False):
self._update_entity_rel_cache_add(subject, rtype, 'subject', object)
- if symetric:
+ if symmetric:
self._update_entity_rel_cache_add(object, rtype, 'subject', subject)
else:
self._update_entity_rel_cache_add(object, rtype, 'object', subject)
- def update_rel_cache_del(self, subject, rtype, object, symetric=False):
+ def update_rel_cache_del(self, subject, rtype, object, symmetric=False):
self._update_entity_rel_cache_del(subject, rtype, 'subject', object)
- if symetric:
+ if symmetric:
self._update_entity_rel_cache_del(object, rtype, 'object', object)
else:
self._update_entity_rel_cache_del(object, rtype, 'object', subject)
@@ -165,10 +162,10 @@
rset.description = list(rset.description)
rset.description.append([self.describe(targeteid)[0]])
targetentity = self.entity_from_eid(targeteid)
- if targetentity.rset is None:
- targetentity.rset = rset
- targetentity.row = rset.rowcount
- targetentity.col = 0
+ if targetentity.cw_rset is None:
+ targetentity.cw_rset = rset
+ targetentity.cw_row = rset.rowcount
+ targetentity.cw_col = 0
rset.rowcount += 1
entities.append(targetentity)
entity._related_cache['%s_%s' % (rtype, role)] = (rset, tuple(entities))
@@ -235,6 +232,19 @@
assert prop == 'lang' # this is the only one changeable property for now
self.set_language(value)
+ def deleted_in_transaction(self, eid):
+ return eid in self.transaction_data.get('pendingeids', ())
+
+ def added_in_transaction(self, eid):
+ return eid in self.transaction_data.get('neweids', ())
+
+ def schema_rproperty(self, rtype, eidfrom, eidto, rprop):
+ rschema = self.repo.schema[rtype]
+ subjtype = self.describe(eidfrom)[0]
+ objtype = self.describe(eidto)[0]
+ rdef = rschema.rdef(subjtype, objtype)
+ return rdef.get(rprop)
+
# connection management ###################################################
def keep_pool_mode(self, mode):
@@ -332,6 +342,11 @@
# request interface #######################################################
+ @property
+ def cursor(self):
+ """return a rql cursor"""
+ return self
+
def set_entity_cache(self, entity):
# XXX session level caching may be a pb with multiple repository
# instances, but 1. this is probably not the only one :$ and 2. it
@@ -418,11 +433,6 @@
return self.super_session.execute(rql, kwargs, eid_key, build_descr,
propagate)
- @property
- def cursor(self):
- """return a rql cursor"""
- return self
-
def execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
propagate=False):
"""db-api like method directly linked to the querier execute method
@@ -561,7 +571,6 @@
self._threaddata.pending_operations = []
return self._threaddata.pending_operations
-
def add_operation(self, operation, index=None):
"""add an observer"""
assert self.commit_state != 'commit'
@@ -641,12 +650,19 @@
description.append(tuple(row_descr))
return description
- @deprecated("use vreg['etypes'].etype_class(etype)")
+ # deprecated ###############################################################
+
+ @property
+ @deprecated("[3.6] use session.vreg.schema")
+ def schema(self):
+ return self.repo.schema
+
+ @deprecated("[3.4] use vreg['etypes'].etype_class(etype)")
def etype_class(self, etype):
"""return an entity class for the given entity type"""
return self.vreg['etypes'].etype_class(etype)
- @deprecated('use direct access to session.transaction_data')
+ @deprecated('[3.4] use direct access to session.transaction_data')
def query_data(self, key, default=None, setdefault=False, pop=False):
if setdefault:
assert not pop
@@ -656,7 +672,7 @@
else:
return self.transaction_data.get(key, default)
- @deprecated('use entity_from_eid(eid, etype=None)')
+ @deprecated('[3.4] use entity_from_eid(eid, etype=None)')
def entity(self, eid):
"""return a result set for the given eid"""
return self.entity_from_eid(eid)
@@ -674,6 +690,7 @@
# session which has created this one
self.parent_session = parent_session
self.user = InternalManager()
+ self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
self.repo = parent_session.repo
self.vreg = parent_session.vreg
self.data = parent_session.data
@@ -743,8 +760,9 @@
"""special session created internaly by the repository"""
def __init__(self, repo, cnxprops=None):
- super(InternalSession, self).__init__(_IMANAGER, repo, cnxprops,
+ super(InternalSession, self).__init__(InternalManager(), repo, cnxprops,
_id='internal')
+ self.user.req = self # XXX remove when "vreg = user.req.vreg" hack in entity.py is gone
self.cnxtype = 'inmemory'
self.is_internal_session = True
self.is_super_session = True
@@ -781,7 +799,6 @@
return 'en'
return None
-_IMANAGER = InternalManager()
from logging import getLogger
from cubicweb import set_log_methods
--- a/server/sources/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/sources/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -291,7 +291,7 @@
"""
pass
- def authenticate(self, session, login, password):
+ def authenticate(self, session, login, **kwargs):
"""if the source support CWUser entity type, it should implements
this method which should return CWUser eid for the given login/password
if this account is defined in this source and valid login / password is
--- a/server/sources/ldapuser.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/sources/ldapuser.py Mon Feb 08 13:35:07 2010 +0100
@@ -237,14 +237,15 @@
self._connect()
return ConnectionWrapper(self._conn)
- def authenticate(self, session, login, password):
+ def authenticate(self, session, login, password=None, **kwargs):
"""return CWUser eid for the given login/password if this account is
defined in this source, else raise `AuthenticationError`
two queries are needed since passwords are stored crypted, so we have
to fetch the salt first
"""
- assert login, 'no login!'
+ if password is None:
+ raise AuthenticationError()
searchfilter = [filter_format('(%s=%s)', (self.user_login_attr, login))]
searchfilter.extend([filter_format('(%s=%s)', ('objectClass', o))
for o in self.user_classes])
--- a/server/sources/native.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/sources/native.py Mon Feb 08 13:35:07 2010 +0100
@@ -96,11 +96,6 @@
"""adapter for source using the native cubicweb schema (see below)
"""
sqlgen_class = SQLGenerator
-
- passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
- auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
- _sols = ({'X': 'CWUser', 'P': 'Password'},)
-
options = (
('db-driver',
{'type' : 'string',
@@ -148,6 +143,7 @@
def __init__(self, repo, appschema, source_config, *args, **kwargs):
SQLAdapterMixIn.__init__(self, source_config)
+ self.authentifiers = [LoginPasswordAuthentifier(self)]
AbstractSource.__init__(self, repo, appschema, source_config,
*args, **kwargs)
# sql generator
@@ -182,6 +178,11 @@
# consuming, find another way
return SQLAdapterMixIn.get_connection(self)
+ def add_authentifier(self, authentifier):
+ self.authentifiers.append(authentifier)
+ authentifier.source = self
+ authentifier.set_schema(self.schema)
+
def reset_caches(self):
"""method called during test to reset potential source caches"""
self._cache = Cache(self.repo.config['rql-cache-size'])
@@ -232,10 +233,10 @@
# ISource interface #######################################################
- def compile_rql(self, rql):
+ def compile_rql(self, rql, sols):
rqlst = self.repo.vreg.rqlhelper.parse(rql)
rqlst.restricted_vars = ()
- rqlst.children[0].solutions = self._sols
+ rqlst.children[0].solutions = sols
self.repo.querier.sqlgen_annotate(rqlst)
set_qdata(self.schema.rschema, rqlst, ())
return rqlst
@@ -249,10 +250,8 @@
self._rql_sqlgen.schema = schema
except AttributeError:
pass # __init__
- if 'CWUser' in schema: # probably an empty schema if not true...
- # rql syntax trees used to authenticate users
- self._passwd_rqlst = self.compile_rql(self.passwd_rql)
- self._auth_rqlst = self.compile_rql(self.auth_rql)
+ for authentifier in self.authentifiers:
+ authentifier.set_schema(self.schema)
def support_entity(self, etype, write=False):
"""return true if the given entity's type is handled by this adapter
@@ -273,30 +272,16 @@
def may_cross_relation(self, rtype):
return True
- def authenticate(self, session, login, password):
- """return CWUser eid for the given login/password if this account is
- defined in this source, else raise `AuthenticationError`
-
- two queries are needed since passwords are stored crypted, so we have
- to fetch the salt first
+ def authenticate(self, session, login, **kwargs):
+ """return CWUser eid for the given login and other authentication
+ information found in kwargs, else raise `AuthenticationError`
"""
- args = {'login': login, 'pwd' : password}
- if password is not None:
- rset = self.syntax_tree_search(session, self._passwd_rqlst, args)
+ for authentifier in self.authentifiers:
try:
- pwd = rset[0][0]
- except IndexError:
- raise AuthenticationError('bad login')
- # passwords are stored using the Bytes type, so we get a StringIO
- if pwd is not None:
- args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2]))
- # get eid from login and (crypted) password
- # XXX why not simply compare password?
- rset = self.syntax_tree_search(session, self._auth_rqlst, args)
- try:
- return rset[0][0]
- except IndexError:
- raise AuthenticationError('bad password')
+ return authentifier.authenticate(session, login, **kwargs)
+ except AuthenticationError:
+ continue
+ raise AuthenticationError()
def syntax_tree_search(self, session, union, args=None, cachekey=None,
varmap=None):
@@ -534,7 +519,7 @@
if extid is not None:
assert isinstance(extid, str)
extid = b64encode(extid)
- attrs = {'type': entity.id, 'eid': entity.eid, 'extid': extid,
+ attrs = {'type': entity.__regid__, 'eid': entity.eid, 'extid': extid,
'source': source.uri, 'mtime': datetime.now()}
session.system_sql(self.sqlgen.insert('entities', attrs), attrs)
@@ -644,3 +629,49 @@
result += 'GRANT ALL ON deleted_entities TO %s;\n' % user
result += 'GRANT ALL ON entities_id_seq TO %s;\n' % user
return result
+
+
+class BaseAuthentifier(object):
+
+ def __init__(self, source=None):
+ self.source = source
+
+ def set_schema(self, schema):
+ """set the instance'schema"""
+ pass
+
+class LoginPasswordAuthentifier(BaseAuthentifier):
+ passwd_rql = "Any P WHERE X is CWUser, X login %(login)s, X upassword P"
+ auth_rql = "Any X WHERE X is CWUser, X login %(login)s, X upassword %(pwd)s"
+ _sols = ({'X': 'CWUser', 'P': 'Password'},)
+
+ def set_schema(self, schema):
+ """set the instance'schema"""
+ if 'CWUser' in schema: # probably an empty schema if not true...
+ # rql syntax trees used to authenticate users
+ self._passwd_rqlst = self.source.compile_rql(self.passwd_rql, self._sols)
+ self._auth_rqlst = self.source.compile_rql(self.auth_rql, self._sols)
+
+ def authenticate(self, session, login, password=None, **kwargs):
+ """return CWUser eid for the given login/password if this account is
+ defined in this source, else raise `AuthenticationError`
+
+ two queries are needed since passwords are stored crypted, so we have
+ to fetch the salt first
+ """
+ args = {'login': login, 'pwd' : password}
+ if password is not None:
+ rset = self.source.syntax_tree_search(session, self._passwd_rqlst, args)
+ try:
+ pwd = rset[0][0]
+ except IndexError:
+ raise AuthenticationError('bad login')
+ # passwords are stored using the Bytes type, so we get a StringIO
+ if pwd is not None:
+ args['pwd'] = Binary(crypt_password(password, pwd.getvalue()[:2]))
+ # get eid from login and (crypted) password
+ rset = self.source.syntax_tree_search(session, self._auth_rqlst, args)
+ try:
+ return rset[0][0]
+ except IndexError:
+ raise AuthenticationError('bad password')
--- a/server/sources/rql2sql.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/sources/rql2sql.py Mon Feb 08 13:35:07 2010 +0100
@@ -742,7 +742,7 @@
sqls += self._process_relation_term(relation, rid, lhsvar, lhsconst, 'eid_from')
sqls += self._process_relation_term(relation, rid, rhsvar, rhsconst, 'eid_to')
sql = ' AND '.join(sqls)
- if rschema.symetric:
+ if rschema.symmetric:
sql = '(%s OR %s)' % (sql, switch_relation_field(sql))
return sql
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/sources/storages.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,115 @@
+"""custom storages for the system source"""
+from os import unlink, path as osp
+
+from cubicweb import Binary
+from cubicweb.server.hook import Operation
+
+
+ETYPE_ATTR_STORAGE = {}
+def set_attribute_storage(repo, etype, attr, storage):
+ ETYPE_ATTR_STORAGE.setdefault(etype, {})[attr] = storage
+ repo.system_source.map_attribute(etype, attr, storage.sqlgen_callback)
+
+
+class Storage(object):
+ """abstract storage"""
+ def sqlgen_callback(self, generator, relation, linkedvar):
+ """sql generator callback when some attribute with a custom storage is
+ accessed
+ """
+ raise NotImplementedError()
+
+ def entity_added(self, entity, attr):
+ """an entity using this storage for attr has been added"""
+ raise NotImplementedError()
+ def entity_updated(self, entity, attr):
+ """an entity using this storage for attr has been updatded"""
+ raise NotImplementedError()
+ def entity_deleted(self, entity, attr):
+ """an entity using this storage for attr has been deleted"""
+ raise NotImplementedError()
+
+# TODO
+# * make it configurable without code
+# * better file path attribution
+# * handle backup/restore
+
+class BytesFileSystemStorage(Storage):
+ """store Bytes attribute value on the file system"""
+ def __init__(self, defaultdir):
+ self.default_directory = defaultdir
+
+ def sqlgen_callback(self, generator, linkedvar, relation):
+ """sql generator callback when some attribute with a custom storage is
+ accessed
+ """
+ linkedvar.accept(generator)
+ return '_fsopen(%s.cw_%s)' % (
+ linkedvar._q_sql.split('.', 1)[0], # table name
+ relation.r_type) # attribute name
+
+ def entity_added(self, entity, attr):
+ """an entity using this storage for attr has been added"""
+ if not entity._cw.transaction_data.get('fs_importing'):
+ try:
+ value = entity.pop(attr)
+ except KeyError:
+ pass
+ else:
+ fpath = self.new_fs_path(entity, attr)
+ # bytes storage used to store file's path
+ entity[attr]= Binary(fpath)
+ file(fpath, 'w').write(value.getvalue())
+ AddFileOp(entity._cw, filepath=fpath)
+ # else entity[attr] is expected to be an already existant file path
+
+ def entity_updated(self, entity, attr):
+ """an entity using this storage for attr has been updatded"""
+ try:
+ value = entity.pop(attr)
+ except KeyError:
+ pass
+ else:
+ fpath = self.current_fs_path(entity, attr)
+ UpdateFileOp(entity._cw, filepath=fpath, filedata=value.getvalue())
+
+ def entity_deleted(self, entity, attr):
+ """an entity using this storage for attr has been deleted"""
+ DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
+
+ def new_fs_path(self, entity, attr):
+ fspath = osp.join(self.default_directory, '%s_%s' % (entity.eid, attr))
+ while osp.exists(fspath):
+ fspath = '_' + fspath
+ return fspath
+
+ def current_fs_path(self, entity, attr):
+ sysource = entity._cw.pool.source('system')
+ cu = sysource.doexec(entity._cw,
+ 'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
+ attr, entity.__regid__, entity.eid))
+ dbmod = sysource.dbapi_module
+ return dbmod.process_value(cu.fetchone()[0], [None, dbmod.BINARY],
+ binarywrap=str)
+
+
+class AddFileOp(Operation):
+ def rollback_event(self):
+ try:
+ unlink(self.filepath)
+ except:
+ pass
+
+class DeleteFileOp(Operation):
+ def commit_event(self):
+ try:
+ unlink(self.filepath)
+ except:
+ pass
+
+class UpdateFileOp(Operation):
+ def precommit_event(self):
+ try:
+ file(self.filepath, 'w').write(self.filedata)
+ except Exception, ex:
+ self.exception(str(ex))
--- a/server/sqlutils.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/sqlutils.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,16 +14,16 @@
from datetime import datetime, date, timedelta
import logilab.common as lgc
+from logilab.common import db
from logilab.common.shellutils import ProgressBar
-from logilab.common import db
from logilab.common.adbh import get_adv_func_helper
from logilab.common.sqlgen import SQLGenerator
+from logilab.common.date import todate, todatetime
from indexer import get_indexer
from cubicweb import Binary, ConfigurationError
-from cubicweb.utils import todate, todatetime
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.uilib import remove_html_tags
from cubicweb.toolsutils import restrict_perms_to_user
from cubicweb.schema import PURE_VIRTUAL_RTYPES
from cubicweb.server import SQL_CONNECT_HOOKS
@@ -33,6 +33,15 @@
lgc.USE_MX_DATETIME = False
SQL_PREFIX = 'cw_'
+def _run_command(cmd):
+ """backup/restore command are string w/ lgc < 0.47, lists with earlier versions
+ """
+ if isinstance(cmd, basestring):
+ print '->', cmd
+ return subprocess.call(cmd, shell=True)
+ print ' '.join(cmd)
+ return subprocess.call(cmd)
+
def sqlexec(sqlstmts, cursor_or_execute, withpb=not os.environ.get('APYCOT_ROOT'),
pbtitle='', delimiter=';'):
@@ -122,10 +131,6 @@
skip_relations=skip_relations))
return '\n'.join(output)
-try:
- from mx.DateTime import DateTimeType, DateTimeDeltaType
-except ImportError:
- DateTimeType = DateTimeDeltaType = None
class SQLAdapterMixIn(object):
"""Mixin for SQL data sources, getting a connection from a configuration
@@ -171,11 +176,12 @@
return cnx
def backup_to_file(self, backupfile):
- cmd = self.dbhelper.backup_command(self.dbname, self.dbhost,
- self.dbuser, backupfile,
- keepownership=False)
- if subprocess.call(cmd, shell=isinstance(cmd, str)):
- raise Exception('Failed command: %s' % cmd)
+ for cmd in self.dbhelper.backup_commands(self.dbname, self.dbhost,
+ self.dbuser, backupfile,
+ keepownership=False):
+ if _run_command(cmd):
+ if not confirm(' [Failed] Continue anyway?', default='n'):
+ raise Exception('Failed command: %s' % cmd)
def restore_from_file(self, backupfile, confirm, drop=True):
for cmd in self.dbhelper.restore_commands(self.dbname, self.dbhost,
@@ -183,32 +189,21 @@
self.encoding,
keepownership=False,
drop=drop):
- if subprocess.call(cmd, shell=isinstance(cmd, str)):
- print '-> Failed command: %s' % cmd
- if not confirm('Continue anyway?', default='n'):
+ if _run_command(cmd):
+ if not confirm(' [Failed] Continue anyway?', default='n'):
raise Exception('Failed command: %s' % cmd)
def merge_args(self, args, query_args):
if args is not None:
- args = dict(args)
- for key, val in args.items():
+ newargs = {}
+ for key, val in args.iteritems():
# convert cubicweb binary into db binary
if isinstance(val, Binary):
val = self.binary(val.getvalue())
- # XXX <3.2 bw compat
- elif type(val) is DateTimeType:
- warn('found mx date time instance, please update to use datetime',
- DeprecationWarning)
- val = datetime(val.year, val.month, val.day,
- val.hour, val.minute, int(val.second))
- elif type(val) is DateTimeDeltaType:
- warn('found mx date time instance, please update to use datetime',
- DeprecationWarning)
- val = timedelta(0, int(val.seconds), 0)
- args[key] = val
+ newargs[key] = val
# should not collide
- args.update(query_args)
- return args
+ newargs.update(query_args)
+ return newargs
return query_args
def process_result(self, cursor):
@@ -229,7 +224,6 @@
results[i] = result
return results
-
def preprocess_entity(self, entity):
"""return a dictionary to use as extra argument to cursor.execute
to insert/update an entity into a SQL database
@@ -257,16 +251,6 @@
value = todate(value)
elif isinstance(value, Binary):
value = self.binary(value.getvalue())
- # XXX <3.2 bw compat
- elif type(value) is DateTimeType:
- warn('found mx date time instance, please update to use datetime',
- DeprecationWarning)
- value = datetime(value.year, value.month, value.day,
- value.hour, value.minute, int(value.second))
- elif type(value) is DateTimeDeltaType:
- warn('found mx date time instance, please update to use datetime',
- DeprecationWarning)
- value = timedelta(0, int(value.seconds), 0)
attrs[SQL_PREFIX+str(attr)] = value
return attrs
@@ -314,6 +298,29 @@
if hasattr(yams.constraints, 'patch_sqlite_decimal'):
yams.constraints.patch_sqlite_decimal()
+ def fspath(eid, etype, attr):
+ try:
+ cu = cnx.cursor()
+ cu.execute('SELECT X.cw_%s FROM cw_%s as X '
+ 'WHERE X.cw_eid=%%(eid)s' % (attr, etype),
+ {'eid': eid})
+ return cu.fetchone()[0]
+ except:
+ import traceback
+ traceback.print_exc()
+ raise
+ cnx.create_function('fspath', 3, fspath)
+
+ def _fsopen(fspath):
+ if fspath:
+ try:
+ return buffer(file(fspath).read())
+ except:
+ import traceback
+ traceback.print_exc()
+ raise
+ cnx.create_function('_fsopen', 1, _fsopen)
+
sqlite_hooks = SQL_CONNECT_HOOKS.setdefault('sqlite', [])
sqlite_hooks.append(init_sqlite_connexion)
--- a/server/ssplanner.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/ssplanner.py Mon Feb 08 13:35:07 2010 +0100
@@ -376,6 +376,7 @@
def execute(self):
"""execute this step"""
+ results = self.execute_child()
todelete = frozenset(typed_eid(eid) for eid, in self.execute_child())
session = self.plan.session
delete = session.repo.glob_delete_entity
@@ -385,7 +386,7 @@
pending |= actual
for eid in actual:
delete(session, eid)
-
+ return results
class DeleteRelationsStep(Step):
"""step consisting in deleting relations"""
--- a/server/test/data/hooks.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/data/hooks.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,27 +5,31 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.server.hooksmanager import SystemHook
+from cubicweb.server.hook import Hook
CALLED_EVENTS = {}
-class StartupHook(SystemHook):
+class StartupHook(Hook):
+ __regid__ = 'mystartup'
events = ('server_startup',)
- def call(self, repo):
+ def __call__(self):
CALLED_EVENTS['server_startup'] = True
-class ShutdownHook(SystemHook):
+class ShutdownHook(Hook):
+ __regid__ = 'myshutdown'
events = ('server_shutdown',)
- def call(self, repo):
+ def __call__(self):
CALLED_EVENTS['server_shutdown'] = True
-class LoginHook(SystemHook):
+class LoginHook(Hook):
+ __regid__ = 'mylogin'
events = ('session_open',)
- def call(self, session):
- CALLED_EVENTS['session_open'] = session.user.login
+ def __call__(self):
+ CALLED_EVENTS['session_open'] = self._cw.user.login
-class LogoutHook(SystemHook):
+class LogoutHook(Hook):
+ __regid__ = 'mylogout'
events = ('session_close',)
- def call(self, session):
- CALLED_EVENTS['session_close'] = session.user.login
+ def __call__(self):
+ CALLED_EVENTS['session_close'] = self._cw.user.login
--- a/server/test/data/migratedapp/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/data/migratedapp/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
ERQLExpression, RRQLExpression)
class Affaire(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
@@ -27,7 +27,7 @@
concerne = SubjectRelation('Societe')
class concerne(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
@@ -42,7 +42,7 @@
class Note(Para):
__specializes_schema__ = True
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
'update': ('managers', 'owners',),
'delete': ('managers', ),
'add': ('managers',
@@ -63,7 +63,7 @@
summary = String(maxsize=512)
class ecrit_par(RelationType):
- permissions = {'read': ('managers', 'users', 'guests',),
+ __permissions__ = {'read': ('managers', 'users', 'guests',),
'delete': ('managers', ),
'add': ('managers',
RRQLExpression('O require_permission P, P name "add_note", '
@@ -102,10 +102,10 @@
travaille = SubjectRelation('Societe')
concerne = SubjectRelation('Affaire')
concerne2 = SubjectRelation(('Affaire', 'Note'), cardinality='1*')
- connait = SubjectRelation('Personne', symetric=True)
+ connait = SubjectRelation('Personne', symmetric=True)
class Societe(WorkflowableEntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners'),
'delete': ('managers', 'owners'),
--- a/server/test/data/migration/postcreate.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/data/migration/postcreate.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
done = wf.add_state(u'done')
wf.add_transition(u'redoit', done, todo)
wf.add_transition(u'markasdone', todo, done)
-checkpoint()
+commit()
wf = add_workflow(u'affaire workflow', 'Affaire')
pitetre = wf.add_state(u'pitetre', initial=True)
@@ -21,5 +21,5 @@
wf.add_transition(u'abort', pitetre, bennon)
wf.add_transition(u'start', pitetre, encours)
wf.add_transition(u'end', encours, finie)
-checkpoint()
+commit()
--- a/server/test/data/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/data/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
ERQLExpression, RRQLExpression)
class Affaire(WorkflowableEntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -39,7 +39,7 @@
class Societe(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
@@ -118,31 +118,31 @@
inlined = True
class connait(RelationType):
- symetric = True
+ symmetric = True
class concerne(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
}
class travaille(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('O owned_by U')),
}
class para(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'add': ('managers', ERQLExpression('X in_state S, S name "todo"')),
'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
}
class test(RelationType):
- permissions = {'read': ('managers', 'users', 'guests'),
+ __permissions__ = {'read': ('managers', 'users', 'guests'),
'delete': ('managers',),
'add': ('managers',)}
--- a/server/test/unittest_checkintegrity.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_checkintegrity.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
from cubicweb.server.checkintegrity import check
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
class CheckIntegrityTC(TestCase):
def test(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_hook.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+"""unit/functional tests for cubicweb.server.hook
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from logilab.common.testlib import TestCase, unittest_main, mock_object
+
+
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.selectors import implements
+from cubicweb.server import hook
+from cubicweb.hooks import integrity, syncschema
+
+
+def clean_session_ops(func):
+ def wrapper(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ self.session.pending_operations[:] = []
+ return wrapper
+
+class OperationsTC(CubicWebTC):
+
+ def setUp(self):
+ CubicWebTC.setUp(self)
+ self.hm = self.repo.hm
+
+ @clean_session_ops
+ def test_late_operation(self):
+ session = self.session
+ l1 = hook.LateOperation(session)
+ l2 = hook.LateOperation(session)
+ l3 = hook.Operation(session)
+ self.assertEquals(session.pending_operations, [l3, l1, l2])
+
+ @clean_session_ops
+ def test_single_last_operation(self):
+ session = self.session
+ l0 = hook.SingleLastOperation(session)
+ l1 = hook.LateOperation(session)
+ l2 = hook.LateOperation(session)
+ l3 = hook.Operation(session)
+ self.assertEquals(session.pending_operations, [l3, l1, l2, l0])
+ l4 = hook.SingleLastOperation(session)
+ self.assertEquals(session.pending_operations, [l3, l1, l2, l4])
+
+ @clean_session_ops
+ def test_global_operation_order(self):
+ session = self.session
+ op1 = integrity._DelayedDeleteOp(session)
+ op2 = syncschema.MemSchemaRDefDel(session)
+ # equivalent operation generated by op2 but replace it here by op3 so we
+ # can check the result...
+ op3 = syncschema.MemSchemaNotifyChanges(session)
+ op4 = integrity._DelayedDeleteOp(session)
+ op5 = integrity._CheckORelationOp(session)
+ self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
+
+
+class HookCalled(Exception): pass
+
+config = TestServerConfiguration('data')
+config.bootstrap_cubes()
+schema = config.load_schema()
+
+class AddAnyHook(hook.Hook):
+ __regid__ = 'addany'
+ category = 'cat1'
+ events = ('before_add_entity',)
+ def __call__(self):
+ raise HookCalled()
+
+
+class HooksManagerTC(TestCase):
+
+ def setUp(self):
+ """ called before each test from this class """
+ self.vreg = mock_object(config=config, schema=schema)
+ self.o = hook.HooksRegistry(self.vreg)
+
+ def test_register_bad_hook1(self):
+ class _Hook(hook.Hook):
+ events = ('before_add_entiti',)
+ ex = self.assertRaises(Exception, self.o.register, _Hook)
+ self.assertEquals(str(ex), 'bad event before_add_entiti on unittest_hook._Hook')
+
+ def test_register_bad_hook2(self):
+ class _Hook(hook.Hook):
+ events = None
+ ex = self.assertRaises(Exception, self.o.register, _Hook)
+ self.assertEquals(str(ex), 'bad .events attribute None on unittest_hook._Hook')
+
+ def test_register_bad_hook3(self):
+ class _Hook(hook.Hook):
+ events = 'before_add_entity'
+ ex = self.assertRaises(Exception, self.o.register, _Hook)
+ self.assertEquals(str(ex), 'bad event b on unittest_hook._Hook')
+
+ def test_call_hook(self):
+ self.o.register(AddAnyHook)
+ cw = mock_object(vreg=self.vreg)
+ self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+ self.o.call_hooks('before_delete_entity', cw) # nothing to call
+ config.disabled_hooks_categories.add('cat1')
+ self.o.call_hooks('before_add_entity', cw) # disabled hooks category, not called
+ config.disabled_hooks_categories.remove('cat1')
+ self.assertRaises(HookCalled, self.o.call_hooks, 'before_add_entity', cw)
+ self.o.unregister(AddAnyHook)
+ self.o.call_hooks('before_add_entity', cw) # nothing to call
+
+
+class SystemHooksTC(CubicWebTC):
+
+ def test_startup_shutdown(self):
+ import hooks # cubicweb/server/test/data/hooks.py
+ self.assertEquals(hooks.CALLED_EVENTS['server_startup'], True)
+ # don't actually call repository.shutdown !
+ self.repo.hm.call_hooks('server_shutdown', repo=self.repo)
+ self.assertEquals(hooks.CALLED_EVENTS['server_shutdown'], True)
+
+ def test_session_open_close(self):
+ import hooks # cubicweb/server/test/data/hooks.py
+ cnx = self.login('anon')
+ self.assertEquals(hooks.CALLED_EVENTS['session_open'], 'anon')
+ cnx.close()
+ self.assertEquals(hooks.CALLED_EVENTS['session_close'], 'anon')
+
+
+# class RelationHookTC(TestCase):
+# """testcase for relation hooks grouping"""
+# def setUp(self):
+# """ called before each test from this class """
+# self.o = HooksManager(schema)
+# self.called = []
+
+# def test_before_add_relation(self):
+# """make sure before_xxx_relation hooks are called directly"""
+# self.o.register(self._before_relation_hook,
+# 'before_add_relation', 'concerne')
+# self.assertEquals(self.called, [])
+# self.o.call_hooks('before_add_relation', 'concerne', 'USER',
+# 1, 'concerne', 2)
+# self.assertEquals(self.called, [(1, 'concerne', 2)])
+
+# def test_after_add_relation(self):
+# """make sure after_xxx_relation hooks are deferred"""
+# self.o.register(self._after_relation_hook,
+# 'after_add_relation', 'concerne')
+# self.assertEquals(self.called, [])
+# self.o.call_hooks('after_add_relation', 'concerne', 'USER',
+# 1, 'concerne', 2)
+# self.o.call_hooks('after_add_relation', 'concerne', 'USER',
+# 3, 'concerne', 4)
+# self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
+
+# def test_before_delete_relation(self):
+# """make sure before_xxx_relation hooks are called directly"""
+# self.o.register(self._before_relation_hook,
+# 'before_delete_relation', 'concerne')
+# self.assertEquals(self.called, [])
+# self.o.call_hooks('before_delete_relation', 'concerne', 'USER',
+# 1, 'concerne', 2)
+# self.assertEquals(self.called, [(1, 'concerne', 2)])
+
+# def test_after_delete_relation(self):
+# """make sure after_xxx_relation hooks are deferred"""
+# self.o.register(self._after_relation_hook,
+# 'after_delete_relation', 'concerne')
+# self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
+# 1, 'concerne', 2)
+# self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
+# 3, 'concerne', 4)
+# self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
+
+
+# def _before_relation_hook(self, pool, subject, r_type, object):
+# self.called.append((subject, r_type, object))
+
+# def _after_relation_hook(self, pool, subject, r_type, object):
+# self.called.append((subject, r_type, object))
+
+
+if __name__ == '__main__':
+ unittest_main()
--- a/server/test/unittest_hookhelper.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit/functional tests for cubicweb.server.hookhelper
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-from cubicweb.server.pool import LateOperation, Operation, SingleLastOperation
-from cubicweb.server.hookhelper import *
-
-
-class HookHelpersTC(RepositoryBasedTC):
-
- def setUp(self):
- RepositoryBasedTC.setUp(self)
- self.hm = self.repo.hm
-
- def test_late_operation(self):
- session = self.session
- l1 = LateOperation(session)
- l2 = LateOperation(session)
- l3 = Operation(session)
- self.assertEquals(session.pending_operations, [l3, l1, l2])
-
- def test_single_last_operation(self):
- session = self.session
- l0 = SingleLastOperation(session)
- l1 = LateOperation(session)
- l2 = LateOperation(session)
- l3 = Operation(session)
- self.assertEquals(session.pending_operations, [l3, l1, l2, l0])
- l4 = SingleLastOperation(session)
- self.assertEquals(session.pending_operations, [l3, l1, l2, l4])
-
- def test_global_operation_order(self):
- from cubicweb.server import hooks, schemahooks
- session = self.session
- op1 = hooks.DelayedDeleteOp(session)
- op2 = schemahooks.MemSchemaRDefDel(session)
- # equivalent operation generated by op2 but replace it here by op3 so we
- # can check the result...
- op3 = schemahooks.MemSchemaNotifyChanges(session)
- op4 = hooks.DelayedDeleteOp(session)
- op5 = hooks.CheckORelationOp(session)
- self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
-
-if __name__ == '__main__':
- unittest_main()
--- a/server/test/unittest_hooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-# -*- coding: utf-8 -*-
-"""functional tests for core hooks
-
-note: most schemahooks.py hooks are actually tested in unittest_migrations.py
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from datetime import datetime
-
-from cubicweb import (ConnectionError, RepositoryError, ValidationError,
- AuthenticationError, BadConnectionId)
-from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
-
-from cubicweb.server.sqlutils import SQL_PREFIX
-from cubicweb.server.repository import Repository
-
-orig_get_versions = Repository.get_versions
-
-def setup_module(*args):
- Repository.get_versions = get_versions
-
-def teardown_module(*args):
- Repository.get_versions = orig_get_versions
-
-
-
-class CoreHooksTC(RepositoryBasedTC):
-
- def test_delete_internal_entities(self):
- self.assertRaises(RepositoryError, self.execute,
- 'DELETE CWEType X WHERE X name "CWEType"')
- self.assertRaises(RepositoryError, self.execute,
- 'DELETE CWRType X WHERE X name "relation_type"')
- self.assertRaises(RepositoryError, self.execute,
- 'DELETE CWGroup X WHERE X name "owners"')
-
- def test_delete_required_relations_subject(self):
- self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y '
- 'WHERE Y name "users"')
- self.commit()
- self.execute('DELETE X in_group Y WHERE X login "toto", Y name "users"')
- self.assertRaises(ValidationError, self.commit)
- self.execute('DELETE X in_group Y WHERE X login "toto"')
- self.execute('SET X in_group Y WHERE X login "toto", Y name "guests"')
- self.commit()
-
- def test_delete_required_relations_object(self):
- self.skip('no sample in the schema ! YAGNI ? Kermaat ?')
-
- def test_static_vocabulary_check(self):
- self.assertRaises(ValidationError,
- self.execute,
- 'SET X composite "whatever" WHERE X from_entity FE, FE name "CWUser", X relation_type RT, RT name "in_group"')
-
- def test_missing_required_relations_subject_inline(self):
- # missing in_group relation
- self.execute('INSERT CWUser X: X login "toto", X upassword "hop"')
- self.assertRaises(ValidationError,
- self.commit)
-
- def test_inlined(self):
- self.assertEquals(self.repo.schema['sender'].inlined, True)
- self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
- self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
- eeid = self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
- 'WHERE Y is EmailAddress, P is EmailPart')[0][0]
- self.execute('SET X sender Y WHERE X is Email, Y is EmailAddress')
- rset = self.execute('Any S WHERE X sender S, X eid %s' % eeid)
- self.assertEquals(len(rset), 1)
-
- def test_composite_1(self):
- self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
- self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
- self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
- 'WHERE Y is EmailAddress, P is EmailPart')
- self.failUnless(self.execute('Email X WHERE X sender Y'))
- self.commit()
- self.execute('DELETE Email X')
- rset = self.execute('Any X WHERE X is EmailPart')
- self.assertEquals(len(rset), 1)
- self.commit()
- rset = self.execute('Any X WHERE X is EmailPart')
- self.assertEquals(len(rset), 0)
-
- def test_composite_2(self):
- self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
- self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
- self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
- 'WHERE Y is EmailAddress, P is EmailPart')
- self.commit()
- self.execute('DELETE Email X')
- self.execute('DELETE EmailPart X')
- self.commit()
- rset = self.execute('Any X WHERE X is EmailPart')
- self.assertEquals(len(rset), 0)
-
- def test_composite_redirection(self):
- self.execute('INSERT EmailAddress X: X address "toto@logilab.fr", X alias "hop"')
- self.execute('INSERT EmailPart X: X content_format "text/plain", X ordernum 1, X content "this is a test"')
- self.execute('INSERT Email X: X messageid "<1234>", X subject "test", X sender Y, X recipients Y, X parts P '
- 'WHERE Y is EmailAddress, P is EmailPart')
- self.execute('INSERT Email X: X messageid "<2345>", X subject "test2", X sender Y, X recipients Y '
- 'WHERE Y is EmailAddress')
- self.commit()
- self.execute('DELETE X parts Y WHERE X messageid "<1234>"')
- self.execute('SET X parts Y WHERE X messageid "<2345>"')
- self.commit()
- rset = self.execute('Any X WHERE X is EmailPart')
- self.assertEquals(len(rset), 1)
- self.assertEquals(rset.get_entity(0, 0).reverse_parts[0].messageid, '<2345>')
-
- def test_unsatisfied_constraints(self):
- self.execute('INSERT CWRelation X: X from_entity FE, X relation_type RT, X to_entity TE '
- 'WHERE FE name "Affaire", RT name "concerne", TE name "String"')
- self.assertRaises(ValidationError,
- self.commit)
-
-
- def test_html_tidy_hook(self):
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "yo"').get_entity(0, 0)
- self.assertEquals(entity.descr, u'yo')
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>yo"').get_entity(0, 0)
- self.assertEquals(entity.descr, u'<b>yo</b>')
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>yo</b>"').get_entity(0, 0)
- self.assertEquals(entity.descr, u'<b>yo</b>')
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "<b>R&D</b>"').get_entity(0, 0)
- self.assertEquals(entity.descr, u'<b>R&D</b>')
- xml = u"<div>c'est <b>l'été"
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr %(d)s',
- {'d': xml}).get_entity(0, 0)
- self.assertEquals(entity.descr, u"<div>c'est <b>l'été</b></div>")
-
- def test_nonregr_html_tidy_hook_no_update(self):
- entity = self.execute('INSERT Affaire A: A descr_format "text/html", A descr "yo"').get_entity(0, 0)
- self.assertEquals(entity.descr, u'yo')
- self.execute('SET A ref "REF" WHERE A eid %s' % entity.eid)
- entity = self.execute('Any A WHERE A eid %s' % entity.eid).get_entity(0, 0)
- self.assertEquals(entity.descr, u'yo')
- self.execute('SET A descr "R&D<p>yo" WHERE A eid %s' % entity.eid)
- entity = self.execute('Any A WHERE A eid %s' % entity.eid).get_entity(0, 0)
- self.assertEquals(entity.descr, u'R&D<p>yo</p>')
-
-
- def test_metadata_cwuri(self):
- eid = self.execute('INSERT Note X')[0][0]
- cwuri = self.execute('Any U WHERE X eid %s, X cwuri U' % eid)[0][0]
- self.assertEquals(cwuri, self.repo.config['base-url'] + 'eid/%s' % eid)
-
- def test_metadata_creation_modification_date(self):
- _now = datetime.now()
- eid = self.execute('INSERT Note X')[0][0]
- creation_date, modification_date = self.execute('Any CD, MD WHERE X eid %s, '
- 'X creation_date CD, '
- 'X modification_date MD' % eid)[0]
- self.assertEquals((creation_date - _now).seconds, 0)
- self.assertEquals((modification_date - _now).seconds, 0)
-
- def test_metadata__date(self):
- _now = datetime.now()
- eid = self.execute('INSERT Note X')[0][0]
- creation_date = self.execute('Any D WHERE X eid %s, X creation_date D' % eid)[0][0]
- self.assertEquals((creation_date - _now).seconds, 0)
-
- def test_metadata_created_by(self):
- eid = self.execute('INSERT Note X')[0][0]
- self.commit() # fire operations
- rset = self.execute('Any U WHERE X eid %s, X created_by U' % eid)
- self.assertEquals(len(rset), 1) # make sure we have only one creator
- self.assertEquals(rset[0][0], self.session.user.eid)
-
- def test_metadata_owned_by(self):
- eid = self.execute('INSERT Note X')[0][0]
- self.commit() # fire operations
- rset = self.execute('Any U WHERE X eid %s, X owned_by U' % eid)
- self.assertEquals(len(rset), 1) # make sure we have only one owner
- self.assertEquals(rset[0][0], self.session.user.eid)
-
-
-class UserGroupHooksTC(RepositoryBasedTC):
-
- def test_user_synchronization(self):
- self.create_user('toto', password='hop', commit=False)
- self.assertRaises(AuthenticationError,
- self.repo.connect, u'toto', 'hop')
- self.commit()
- cnxid = self.repo.connect(u'toto', 'hop')
- self.failIfEqual(cnxid, self.cnxid)
- self.execute('DELETE CWUser X WHERE X login "toto"')
- self.repo.execute(cnxid, 'State X')
- self.commit()
- self.assertRaises(BadConnectionId,
- self.repo.execute, cnxid, 'State X')
-
- def test_user_group_synchronization(self):
- user = self.session.user
- self.assertEquals(user.groups, set(('managers',)))
- self.execute('SET X in_group G WHERE X eid %s, G name "guests"' % user.eid)
- self.assertEquals(user.groups, set(('managers',)))
- self.commit()
- self.assertEquals(user.groups, set(('managers', 'guests')))
- self.execute('DELETE X in_group G WHERE X eid %s, G name "guests"' % user.eid)
- self.assertEquals(user.groups, set(('managers', 'guests')))
- self.commit()
- self.assertEquals(user.groups, set(('managers',)))
-
- def test_user_composite_owner(self):
- ueid = self.create_user('toto')
- # 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"')
- self.commit()
- self.assertEquals(self.execute('Any A WHERE X owned_by U, U use_email X,'
- 'U login "toto", X address A')[0][0],
- 'toto@logilab.fr')
-
- def test_no_created_by_on_deleted_entity(self):
- eid = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"')[0][0]
- self.execute('DELETE EmailAddress X WHERE X eid %s' % eid)
- self.commit()
- self.failIf(self.execute('Any X WHERE X created_by Y, X eid >= %(x)s', {'x': eid}))
-
-
-class CWPropertyHooksTC(RepositoryBasedTC):
-
- def test_unexistant_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "bla.bla", X value "hop"')
- self.assertEquals(ex.errors, {'pkey': 'unknown property key'})
-
- def test_site_wide_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.site-title", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'for_user': "site-wide property can't be set for user"})
-
- def test_bad_type_eproperty(self):
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop", X for_user U')
- self.assertEquals(ex.errors, {'value': u'unauthorized value'})
- ex = self.assertRaises(ValidationError,
- self.execute, 'INSERT CWProperty X: X pkey "ui.language", X value "hop"')
- self.assertEquals(ex.errors, {'value': u'unauthorized value'})
-
-
-class SchemaHooksTC(RepositoryBasedTC):
-
- def test_duplicate_etype_error(self):
- # check we can't add a CWEType or CWRType entity if it already exists one
- # with the same name
- #
- # according to hook order, we'll get a repository or validation error
- self.assertRaises((ValidationError, RepositoryError),
- self.execute, 'INSERT CWEType X: X name "Societe"')
- self.assertRaises((ValidationError, RepositoryError),
- self.execute, 'INSERT CWRType X: X name "in_group"')
-
- def test_validation_unique_constraint(self):
- self.assertRaises(ValidationError,
- self.execute, 'INSERT CWUser X: X login "admin"')
- try:
- self.execute('INSERT CWUser X: X login "admin"')
- except ValidationError, ex:
- self.assertIsInstance(ex.entity, int)
- self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
-
-
-class SchemaModificationHooksTC(RepositoryBasedTC):
-
- def setUp(self):
- if not hasattr(self, '_repo'):
- # first initialization
- repo = self.repo # set by the RepositoryBasedTC metaclass
- # force to read schema from the database to get proper eid set on schema instances
- repo.config._cubes = None
- repo.fill_schema()
- RepositoryBasedTC.setUp(self)
-
- def index_exists(self, etype, attr, unique=False):
- dbhelper = self.session.pool.source('system').dbhelper
- sqlcursor = self.session.pool['system']
- return dbhelper.index_exists(sqlcursor, SQL_PREFIX + etype, SQL_PREFIX + attr, unique=unique)
-
- def test_base(self):
- schema = self.repo.schema
- dbhelper = self.session.pool.source('system').dbhelper
- sqlcursor = self.session.pool['system']
- self.failIf(schema.has_entity('Societe2'))
- self.failIf(schema.has_entity('concerne2'))
- # schema should be update on insertion (after commit)
- self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')
- self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symetric FALSE')
- self.failIf(schema.has_entity('Societe2'))
- self.failIf(schema.has_entity('concerne2'))
- self.execute('SET X read_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup')
- self.execute('SET X read_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup')
- self.execute('SET X add_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "managers"')
- self.execute('SET X add_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "managers"')
- self.execute('SET X delete_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup, G name "owners"')
- self.execute('SET X delete_permission G WHERE X is CWRType, X name "concerne2", G is CWGroup, G name "owners"')
- # have to commit before adding definition relations
- self.commit()
- self.failUnless(schema.has_entity('Societe2'))
- self.failUnless(schema.has_relation('concerne2'))
- self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
- 'WHERE RT name "nom", E name "Societe2", F name "String"')
- concerne2_rdef_eid = self.execute(
- 'INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
- 'WHERE RT name "concerne2", E name "Societe2"')[0][0]
- self.execute('INSERT CWRelation X: X cardinality "?*", X relation_type RT, X from_entity E, X to_entity C '
- 'WHERE RT name "comments", E name "Societe2", C name "Comment"')
- self.failIf('nom' in schema['Societe2'].subject_relations())
- self.failIf('concerne2' in schema['Societe2'].subject_relations())
- self.failIf(self.index_exists('Societe2', 'nom'))
- self.commit()
- self.failUnless('nom' in schema['Societe2'].subject_relations())
- self.failUnless('concerne2' in schema['Societe2'].subject_relations())
- self.failUnless(self.index_exists('Societe2', 'nom'))
- # now we should be able to insert and query Societe2
- s2eid = self.execute('INSERT Societe2 X: X nom "logilab"')[0][0]
- self.execute('Societe2 X WHERE X nom "logilab"')
- self.execute('SET X concerne2 X WHERE X nom "logilab"')
- rset = self.execute('Any X WHERE X concerne2 Y')
- self.assertEquals(rset.rows, [[s2eid]])
- # check that when a relation definition is deleted, existing relations are deleted
- self.execute('INSERT CWRelation X: X cardinality "**", X relation_type RT, X from_entity E, X to_entity E '
- 'WHERE RT name "concerne2", E name "Societe"')
- self.commit()
- self.execute('DELETE CWRelation X WHERE X eid %(x)s', {'x': concerne2_rdef_eid}, 'x')
- self.commit()
- self.failUnless('concerne2' in schema['Societe'].subject_relations())
- self.failIf('concerne2' in schema['Societe2'].subject_relations())
- self.failIf(self.execute('Any X WHERE X concerne2 Y'))
- # schema should be cleaned on delete (after commit)
- self.execute('DELETE CWEType X WHERE X name "Societe2"')
- self.execute('DELETE CWRType X WHERE X name "concerne2"')
- self.failUnless(self.index_exists('Societe2', 'nom'))
- self.failUnless(schema.has_entity('Societe2'))
- self.failUnless(schema.has_relation('concerne2'))
- self.commit()
- self.failIf(self.index_exists('Societe2', 'nom'))
- self.failIf(schema.has_entity('Societe2'))
- self.failIf(schema.has_entity('concerne2'))
-
- def test_is_instance_of_insertions(self):
- seid = self.execute('INSERT SubDivision S: S nom "subdiv"')[0][0]
- is_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is ET, ET name ETN' % seid)]
- self.assertEquals(is_etypes, ['SubDivision'])
- instanceof_etypes = [etype for etype, in self.execute('Any ETN WHERE X eid %s, X is_instance_of ET, ET name ETN' % seid)]
- self.assertEquals(sorted(instanceof_etypes), ['Division', 'Societe', 'SubDivision'])
- snames = [name for name, in self.execute('Any N WHERE S is Societe, S nom N')]
- self.failIf('subdiv' in snames)
- snames = [name for name, in self.execute('Any N WHERE S is Division, S nom N')]
- self.failIf('subdiv' in snames)
- snames = [name for name, in self.execute('Any N WHERE S is_instance_of Societe, S nom N')]
- self.failUnless('subdiv' in snames)
- snames = [name for name, in self.execute('Any N WHERE S is_instance_of Division, S nom N')]
- self.failUnless('subdiv' in snames)
-
-
- def test_perms_synchronization_1(self):
- schema = self.repo.schema
- self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users')))
- self.failUnless(self.execute('Any X, Y WHERE X is CWEType, X name "CWUser", Y is CWGroup, Y name "users"')[0])
- self.execute('DELETE X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
- self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users', )))
- self.commit()
- self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', )))
- self.execute('SET X read_permission Y WHERE X is CWEType, X name "CWUser", Y name "users"')
- self.commit()
- self.assertEquals(schema['CWUser'].get_groups('read'), set(('managers', 'users',)))
-
- def test_perms_synchronization_2(self):
- schema = self.repo.schema['in_group']
- self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
- self.execute('DELETE X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
- self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
- self.commit()
- self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
- self.execute('SET X read_permission Y WHERE X is CWRType, X name "in_group", Y name "guests"')
- self.assertEquals(schema.get_groups('read'), set(('managers', 'users')))
- self.commit()
- self.assertEquals(schema.get_groups('read'), set(('managers', 'users', 'guests')))
-
- def test_nonregr_user_edit_itself(self):
- ueid = self.session.user.eid
- groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
- self.execute('DELETE X in_group Y WHERE X eid %s' % ueid)
- self.execute('SET X surname "toto" WHERE X eid %s' % ueid)
- self.execute('SET X in_group Y WHERE X eid %s, Y name "managers"' % ueid)
- self.commit()
- eeid = self.execute('Any X WHERE X is CWEType, X name "CWEType"')[0][0]
- self.execute('DELETE X read_permission Y WHERE X eid %s' % eeid)
- self.execute('SET X final FALSE WHERE X eid %s' % eeid)
- self.execute('SET X read_permission Y WHERE X eid %s, Y eid in (%s, %s)'
- % (eeid, groupeids[0], groupeids[1]))
- self.commit()
- self.execute('Any X WHERE X is CWEType, X name "CWEType"')
-
- # schema modification hooks tests #########################################
-
- def test_uninline_relation(self):
- dbhelper = self.session.pool.source('system').dbhelper
- sqlcursor = self.session.pool['system']
- # Personne inline2 Affaire inline
- # insert a person without inline2 relation (not mandatory)
- self.execute('INSERT Personne X: X nom "toto"')
- peid = self.execute('INSERT Personne X: X nom "tutu"')[0][0]
- aeid = self.execute('INSERT Affaire X: X ref "tata"')[0][0]
- self.execute('SET X inline2 Y WHERE X eid %(x)s, Y eid %(y)s', {'x': peid, 'y': aeid})
- self.failUnless(self.schema['inline2'].inlined)
- try:
- try:
- self.execute('SET X inlined FALSE WHERE X name "inline2"')
- self.failUnless(self.schema['inline2'].inlined)
- self.commit()
- self.failIf(self.schema['inline2'].inlined)
- self.failIf(self.index_exists('Personne', 'inline2'))
- rset = self.execute('Any X, Y WHERE X inline2 Y')
- self.assertEquals(len(rset), 1)
- self.assertEquals(rset.rows[0], [peid, aeid])
- except:
- import traceback
- traceback.print_exc()
- raise
- finally:
- self.execute('SET X inlined TRUE WHERE X name "inline2"')
- self.failIf(self.schema['inline2'].inlined)
- self.commit()
- self.failUnless(self.schema['inline2'].inlined)
- self.failUnless(self.index_exists('Personne', 'inline2'))
- rset = self.execute('Any X, Y WHERE X inline2 Y')
- self.assertEquals(len(rset), 1)
- self.assertEquals(rset.rows[0], [peid, aeid])
-
- def test_indexed_change(self):
- dbhelper = self.session.pool.source('system').dbhelper
- sqlcursor = self.session.pool['system']
- try:
- self.execute('SET X indexed TRUE WHERE X relation_type R, R name "sujet"')
- self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
- self.failIf(self.index_exists('Affaire', 'sujet'))
- self.commit()
- self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
- self.failUnless(self.index_exists('Affaire', 'sujet'))
- finally:
- self.execute('SET X indexed FALSE WHERE X relation_type R, R name "sujet"')
- self.failUnless(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
- self.failUnless(self.index_exists('Affaire', 'sujet'))
- self.commit()
- self.failIf(self.schema['sujet'].rproperty('Affaire', 'String', 'indexed'))
- self.failIf(self.index_exists('Affaire', 'sujet'))
-
- def test_unique_change(self):
- dbhelper = self.session.pool.source('system').dbhelper
- sqlcursor = self.session.pool['system']
- try:
- try:
- self.execute('INSERT CWConstraint X: X cstrtype CT, DEF constrained_by X '
- 'WHERE CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
- 'RT name "sujet", E name "Affaire"')
- self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
- self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
- self.commit()
- self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
- self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
- except:
- import traceback
- traceback.print_exc()
- raise
- finally:
- self.execute('DELETE DEF constrained_by X WHERE X cstrtype CT, '
- 'CT name "UniqueConstraint", DEF relation_type RT, DEF from_entity E,'
- 'RT name "sujet", E name "Affaire"')
- self.failUnless(self.schema['Affaire'].has_unique_values('sujet'))
- self.failUnless(self.index_exists('Affaire', 'sujet', unique=True))
- self.commit()
- self.failIf(self.schema['Affaire'].has_unique_values('sujet'))
- self.failIf(self.index_exists('Affaire', 'sujet', unique=True))
-
- def test_required_change_1(self):
- self.execute('SET DEF cardinality "?1" '
- 'WHERE DEF relation_type RT, DEF from_entity E,'
- 'RT name "nom", E name "Personne"')
- self.commit()
- # should now be able to add personne without nom
- self.execute('INSERT Personne X')
- self.commit()
-
- def test_required_change_2(self):
- self.execute('SET DEF cardinality "11" '
- 'WHERE DEF relation_type RT, DEF from_entity E,'
- 'RT name "prenom", E name "Personne"')
- self.commit()
- # should not be able anymore to add personne without prenom
- self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"')
- self.execute('SET DEF cardinality "?1" '
- 'WHERE DEF relation_type RT, DEF from_entity E,'
- 'RT name "prenom", E name "Personne"')
- self.commit()
-
-
- def test_add_attribute_to_base_class(self):
- self.execute('INSERT CWAttribute X: X cardinality "11", X defaultval "noname", X indexed TRUE, X relation_type RT, X from_entity E, X to_entity F '
- 'WHERE RT name "nom", E name "BaseTransition", F name "String"')
- self.commit()
- self.schema.rebuild_infered_relations()
- self.failUnless('Transition' in self.schema['nom'].subjects())
- self.failUnless('WorkflowTransition' in self.schema['nom'].subjects())
- self.execute('Any X WHERE X is_instance_of BaseTransition, X nom "hop"')
-
-if __name__ == '__main__':
- unittest_main()
--- a/server/test/unittest_hooksmanager.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-"""unit tests for the hooks manager
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.server.hooksmanager import HooksManager, Hook
-from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.apptest import RepositoryBasedTC
-
-class HookCalled(Exception): pass
-
-config = TestServerConfiguration('data')
-config.bootstrap_cubes()
-schema = config.load_schema()
-
-class HooksManagerTC(TestCase):
- args = (None,)
- kwargs = {'a': 1}
-
- def setUp(self):
- """ called before each test from this class """
- self.o = HooksManager(schema)
-
- def test_register_hook_raise_keyerror(self):
- self.assertRaises(AssertionError,
- self.o.register_hook, self._hook, 'before_add_entiti')
- self.assertRaises(AssertionError,
- self.o.register_hook, self._hook, 'session_login', 'CWEType')
- self.assertRaises(AssertionError,
- self.o.register_hook, self._hook, 'session_logout', 'CWEType')
- self.assertRaises(AssertionError,
- self.o.register_hook, self._hook, 'server_startup', 'CWEType')
- self.assertRaises(AssertionError,
- self.o.register_hook, self._hook, 'server_shutdown', 'CWEType')
-
- def test_register_hook1(self):
- self.o.register_hook(self._hook, 'before_add_entity')
- self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
- self._test_called_hooks()
-
- def test_register_hook2(self):
- self.o.register_hook(self._hook, 'before_add_entity', '')
- self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
- self._test_called_hooks()
-
- def test_register_hook3(self):
- self.o.register_hook(self._hook, 'before_add_entity', None)
- self.o.register_hook(self._hook, 'before_delete_entity', 'Personne')
- self._test_called_hooks()
-
- def test_register_hooks(self):
- self.o.register_hooks({'before_add_entity' : {'': [self._hook]},
- 'before_delete_entity' : {'Personne': [self._hook]},
- })
- self._test_called_hooks()
-
- def test_unregister_hook(self):
- self.o.register_hook(self._hook, 'after_delete_entity', 'Personne')
- self.assertRaises(HookCalled,
- self.o.call_hooks, 'after_delete_entity', 'Personne',
- *self.args, **self.kwargs)
- self.o.unregister_hook(self._hook, 'after_delete_entity', 'Personne')
- # no hook should be called there
- self.o.call_hooks('after_delete_entity', 'Personne')
-
-
- def _test_called_hooks(self):
- self.assertRaises(HookCalled,
- self.o.call_hooks, 'before_add_entity', '',
- *self.args, **self.kwargs)
- self.assertRaises(HookCalled,
- self.o.call_hooks, 'before_add_entity', None,
- *self.args, **self.kwargs)
- self.assertRaises(HookCalled,
- self.o.call_hooks, 'before_add_entity', 'Personne',
- *self.args, **self.kwargs)
- self.assertRaises(HookCalled,
- self.o.call_hooks, 'before_delete_entity', 'Personne',
- *self.args, **self.kwargs)
- # no hook should be called there
- self.o.call_hooks('before_delete_entity', None)
- self.o.call_hooks('before_delete_entity', 'Societe')
-
-
- def _hook(self, *args, **kwargs):
- # check arguments
- self.assertEqual(args, self.args)
- self.assertEqual(kwargs, self.kwargs)
- raise HookCalled()
-
-
-class RelationHookTC(TestCase):
- """testcase for relation hooks grouping"""
- def setUp(self):
- """ called before each test from this class """
- self.o = HooksManager(schema)
- self.called = []
-
- def test_before_add_relation(self):
- """make sure before_xxx_relation hooks are called directly"""
- self.o.register_hook(self._before_relation_hook,
- 'before_add_relation', 'concerne')
- self.assertEquals(self.called, [])
- self.o.call_hooks('before_add_relation', 'concerne', 'USER',
- 1, 'concerne', 2)
- self.assertEquals(self.called, [(1, 'concerne', 2)])
-
- def test_after_add_relation(self):
- """make sure after_xxx_relation hooks are deferred"""
- self.o.register_hook(self._after_relation_hook,
- 'after_add_relation', 'concerne')
- self.assertEquals(self.called, [])
- self.o.call_hooks('after_add_relation', 'concerne', 'USER',
- 1, 'concerne', 2)
- self.o.call_hooks('after_add_relation', 'concerne', 'USER',
- 3, 'concerne', 4)
- self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
-
- def test_before_delete_relation(self):
- """make sure before_xxx_relation hooks are called directly"""
- self.o.register_hook(self._before_relation_hook,
- 'before_delete_relation', 'concerne')
- self.assertEquals(self.called, [])
- self.o.call_hooks('before_delete_relation', 'concerne', 'USER',
- 1, 'concerne', 2)
- self.assertEquals(self.called, [(1, 'concerne', 2)])
-
- def test_after_delete_relation(self):
- """make sure after_xxx_relation hooks are deferred"""
- self.o.register_hook(self._after_relation_hook,
- 'after_delete_relation', 'concerne')
- self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
- 1, 'concerne', 2)
- self.o.call_hooks('after_delete_relation', 'concerne', 'USER',
- 3, 'concerne', 4)
- self.assertEquals(self.called, [(1, 'concerne', 2), (3, 'concerne', 4)])
-
-
- def _before_relation_hook(self, pool, subject, r_type, object):
- self.called.append((subject, r_type, object))
-
- def _after_relation_hook(self, pool, subject, r_type, object):
- self.called.append((subject, r_type, object))
-
-
-class SystemHooksTC(RepositoryBasedTC):
-
- def test_startup_shutdown(self):
- import hooks # cubicweb/server/test/data/hooks.py
- self.assertEquals(hooks.CALLED_EVENTS['server_startup'], True)
- # don't actually call repository.shutdown !
- self.repo.hm.call_hooks('server_shutdown', repo=None)
- self.assertEquals(hooks.CALLED_EVENTS['server_shutdown'], True)
-
- def test_session_open_close(self):
- import hooks # cubicweb/server/test/data/hooks.py
- cnx = self.login('anon')
- self.assertEquals(hooks.CALLED_EVENTS['session_open'], 'anon')
- cnx.close()
- self.assertEquals(hooks.CALLED_EVENTS['session_close'], 'anon')
-
-
-from itertools import repeat
-
-class MyHook(Hook):
- schema = schema # set for actual hooks at registration time
- events = ('whatever', 'another')
- accepts = ('Societe', 'Division')
-
-class HookTC(RepositoryBasedTC):
- def test_inheritance(self):
- self.assertEquals(list(MyHook.register_to()),
- zip(repeat('whatever'), ('Societe', 'Division', 'SubDivision'))
- + zip(repeat('another'), ('Societe', 'Division', 'SubDivision')))
-
-
-if __name__ == '__main__':
- unittest_main()
--- a/server/test/unittest_ldapuser.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_ldapuser.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,8 +9,8 @@
import socket
from logilab.common.testlib import TestCase, unittest_main, mock_object
-from cubicweb.devtools import init_test_database, TestServerConfiguration
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import RQLGeneratorTC
from cubicweb.server.sources.ldapuser import *
@@ -23,7 +23,7 @@
ADIM = 'adimascio'
-def nopwd_authenticate(self, session, login, upassword):
+def nopwd_authenticate(self, session, login, password):
"""used to monkey patch the source to get successful authentication without
upassword checking
"""
@@ -44,36 +44,31 @@
-config = TestServerConfiguration('data')
-config.sources_file = lambda : 'data/sourcesldap'
-repo, cnx = init_test_database('sqlite', config=config)
-
-class LDAPUserSourceTC(RepositoryBasedTC):
- repo, cnx = repo, cnx
+class LDAPUserSourceTC(CubicWebTC):
+ config = TestServerConfiguration('data')
+ config.sources_file = lambda : 'data/sourcesldap'
def patch_authenticate(self):
self._orig_authenticate = LDAPUserSource.authenticate
LDAPUserSource.authenticate = nopwd_authenticate
- def setUp(self):
- self._prepare()
+ def setup_database(self):
# XXX: need this first query else we get 'database is locked' from
# sqlite since it doesn't support multiple connections on the same
# database
# so doing, ldap inserted users don't get removed between each test
- rset = self.execute('CWUser X')
- self.commit()
+ rset = self.sexecute('CWUser X')
# check we get some users from ldap
self.assert_(len(rset) > 1)
- self.maxeid = self.execute('Any MAX(X)')[0][0]
def tearDown(self):
if hasattr(self, '_orig_authenticate'):
LDAPUserSource.authenticate = self._orig_authenticate
- RepositoryBasedTC.tearDown(self)
+ CubicWebTC.tearDown(self)
def test_authenticate(self):
source = self.repo.sources_by_uri['ldapuser']
+ self.session.set_pool()
self.assertRaises(AuthenticationError,
source.authenticate, self.session, 'toto', 'toto')
@@ -83,7 +78,7 @@
def test_base(self):
# check a known one
- e = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
+ e = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
self.assertEquals(e.login, SYT)
e.complete()
self.assertEquals(e.creation_date, None)
@@ -95,79 +90,79 @@
self.assertEquals(e.created_by, ())
self.assertEquals(e.primary_email[0].address, 'Sylvain Thenault')
# email content should be indexed on the user
- rset = self.execute('CWUser X WHERE X has_text "thenault"')
+ rset = self.sexecute('CWUser X WHERE X has_text "thenault"')
self.assertEquals(rset.rows, [[e.eid]])
def test_not(self):
- eid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- rset = self.execute('CWUser X WHERE NOT X eid %s' % eid)
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ rset = self.sexecute('CWUser X WHERE NOT X eid %s' % eid)
self.assert_(rset)
self.assert_(not eid in (r[0] for r in rset))
def test_multiple(self):
- seid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- aeid = self.execute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
- rset = self.execute('CWUser X, Y WHERE X login %(syt)s, Y login %(adim)s',
+ seid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ aeid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
+ rset = self.sexecute('CWUser X, Y WHERE X login %(syt)s, Y login %(adim)s',
{'syt': SYT, 'adim': ADIM})
self.assertEquals(rset.rows, [[seid, aeid]])
- rset = self.execute('Any X,Y,L WHERE X login L, X login %(syt)s, Y login %(adim)s',
+ rset = self.sexecute('Any X,Y,L WHERE X login L, X login %(syt)s, Y login %(adim)s',
{'syt': SYT, 'adim': ADIM})
self.assertEquals(rset.rows, [[seid, aeid, SYT]])
def test_in(self):
- seid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- aeid = self.execute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
- rset = self.execute('Any X,L ORDERBY L WHERE X login IN("%s", "%s"), X login L' % (SYT, ADIM))
+ seid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ aeid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM})[0][0]
+ rset = self.sexecute('Any X,L ORDERBY L WHERE X login IN("%s", "%s"), X login L' % (SYT, ADIM))
self.assertEquals(rset.rows, [[aeid, ADIM], [seid, SYT]])
def test_relations(self):
- eid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- rset = self.execute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ rset = self.sexecute('Any X,E WHERE X is CWUser, X login L, X primary_email E')
self.assert_(eid in (r[0] for r in rset))
- rset = self.execute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
+ rset = self.sexecute('Any X,L,E WHERE X is CWUser, X login L, X primary_email E')
self.assert_(SYT in (r[1] for r in rset))
def test_count(self):
- nbusers = self.execute('Any COUNT(X) WHERE X is CWUser')[0][0]
+ nbusers = self.sexecute('Any COUNT(X) WHERE X is CWUser')[0][0]
# just check this is a possible number
self.assert_(nbusers > 1, nbusers)
self.assert_(nbusers < 30, nbusers)
def test_upper(self):
- eid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- rset = self.execute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ rset = self.sexecute('Any UPPER(L) WHERE X eid %s, X login L' % eid)
self.assertEquals(rset[0][0], SYT.upper())
def test_unknown_attr(self):
- eid = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
- rset = self.execute('Any L,C,M WHERE X eid %s, X login L, '
+ eid = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT})[0][0]
+ rset = self.sexecute('Any L,C,M WHERE X eid %s, X login L, '
'X creation_date C, X modification_date M' % eid)
self.assertEquals(rset[0][0], SYT)
self.assertEquals(rset[0][1], None)
self.assertEquals(rset[0][2], None)
def test_sort(self):
- logins = [l for l, in self.execute('Any L ORDERBY L WHERE X login L')]
+ logins = [l for l, in self.sexecute('Any L ORDERBY L WHERE X login L')]
self.assertEquals(logins, sorted(logins))
def test_lower_sort(self):
- logins = [l for l, in self.execute('Any L ORDERBY lower(L) WHERE X login L')]
+ logins = [l for l, in self.sexecute('Any L ORDERBY lower(L) WHERE X login L')]
self.assertEquals(logins, sorted(logins))
def test_or(self):
- rset = self.execute('DISTINCT Any X WHERE X login %(login)s OR (X in_group G, G name "managers")',
+ rset = self.sexecute('DISTINCT Any X WHERE X login %(login)s OR (X in_group G, G name "managers")',
{'login': SYT})
self.assertEquals(len(rset), 2, rset.rows) # syt + admin
def test_nonregr_set_owned_by(self):
# test that when a user coming from ldap is triggering a transition
# the related TrInfo has correct owner information
- self.execute('SET X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
+ self.sexecute('SET X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
self.commit()
- syt = self.execute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
+ syt = self.sexecute('CWUser X WHERE X login %(login)s', {'login': SYT}).get_entity(0, 0)
self.assertEquals([g.name for g in syt.in_group], ['managers', 'users'])
self.patch_authenticate()
- cnx = self.login(SYT, 'dummypassword')
+ cnx = self.login(SYT, password='dummypassword')
cu = cnx.cursor()
adim = cu.execute('CWUser X WHERE X login %(login)s', {'login': ADIM}).get_entity(0, 0)
adim.fire_transition('deactivate')
@@ -178,7 +173,7 @@
trinfo = adim.latest_trinfo()
self.assertEquals(trinfo.owned_by[0].login, SYT)
# select from_state to skip the user's creation TrInfo
- rset = self.execute('Any U ORDERBY D DESC WHERE WF wf_info_for X,'
+ rset = self.sexecute('Any U ORDERBY D DESC WHERE WF wf_info_for X,'
'WF creation_date D, WF from_state FS,'
'WF owned_by U?, X eid %(x)s',
{'x': adim.eid}, 'x')
@@ -186,77 +181,78 @@
finally:
# restore db state
self.restore_connection()
- adim = self.execute('CWUser X WHERE X login %(login)s', {'login': ADIM}).get_entity(0, 0)
+ adim = self.sexecute('CWUser X WHERE X login %(login)s', {'login': ADIM}).get_entity(0, 0)
adim.fire_transition('activate')
- self.execute('DELETE X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
+ self.sexecute('DELETE X in_group G WHERE X login %(syt)s, G name "managers"', {'syt': SYT})
def test_same_column_names(self):
- self.execute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"')
+ 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')
- self.failUnless(self.execute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}))
+ self.create_user('cochon', req=self.session)
+ self.failUnless(self.sexecute('Any X,Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT}))
def test_exists1(self):
- self.add_entity('CWGroup', name=u'bougloup1')
- self.add_entity('CWGroup', name=u'bougloup2')
- self.execute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
- self.execute('SET U in_group G WHERE G name = "bougloup1", U login %(syt)s', {'syt': SYT})
- rset = self.execute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
+ self.session.set_pool()
+ self.session.create_entity('CWGroup', name=u'bougloup1')
+ self.session.create_entity('CWGroup', name=u'bougloup2')
+ self.sexecute('SET U in_group G WHERE G name ~= "bougloup%", U login "admin"')
+ self.sexecute('SET U in_group G WHERE G name = "bougloup1", U login %(syt)s', {'syt': SYT})
+ rset = self.sexecute('Any L,SN ORDERBY L WHERE X in_state S, S name SN, X login L, EXISTS(X in_group G, G name ~= "bougloup%")')
self.assertEquals(rset.rows, [['admin', 'activated'], [SYT, 'activated']])
def test_exists2(self):
- self.create_user('comme')
- self.create_user('cochon')
- self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
- rset = self.execute('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.create_user('comme', req=self.session)
+ self.create_user('cochon', req=self.session)
+ 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.assertEquals(rset.rows, [['managers'], ['users']])
def test_exists3(self):
- self.create_user('comme')
- self.create_user('cochon')
- self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
- self.failUnless(self.execute('Any X, Y WHERE X copain Y, X login "comme", Y login "cochon"'))
- self.execute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
- self.failUnless(self.execute('Any X, Y WHERE X copain Y, X login %(syt)s, Y login "cochon"', {'syt': SYT}))
- rset = self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon"))')
+ self.create_user('comme', req=self.session)
+ self.create_user('cochon', req=self.session)
+ 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})
+ self.failUnless(self.sexecute('Any X, Y WHERE X copain Y, X login %(syt)s, Y login "cochon"', {'syt': SYT}))
+ rset = self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, G name "managers" OR EXISTS(X copain T, T login in ("comme", "cochon"))')
self.assertEquals(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')
- self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
- self.execute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
- self.execute('SET X copain Y WHERE X login "comme", Y login "billy"')
- self.execute('SET X copain Y WHERE X login %(syt)s, Y login "billy"', {'syt': SYT})
+ self.create_user('comme', req=self.session)
+ self.create_user('cochon', groups=('users', 'guests'), req=self.session)
+ self.create_user('billy', req=self.session)
+ 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"')
+ self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "billy"', {'syt': SYT})
# search for group name, login where
# CWUser copain with "comme" or "cochon" AND same login as the copain
# OR
# CWUser in_state activated AND not copain with billy
#
# SO we expect everybody but "comme" and "syt"
- rset= self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, '
+ rset= self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, '
'EXISTS(X copain T, T login L, T login in ("comme", "cochon")) OR '
'EXISTS(X in_state S, S name "activated", NOT X copain T2, T2 login "billy")')
- all = self.execute('Any GN, L WHERE X in_group G, X login L, G name GN')
+ all = self.sexecute('Any GN, L WHERE X in_group G, X login L, G name GN')
all.rows.remove(['users', 'comme'])
all.rows.remove(['users', SYT])
self.assertEquals(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')
- self.execute('SET X copain Y WHERE X login "comme", Y login "cochon"')
- self.execute('SET X copain Y WHERE X login "cochon", Y login "cochon"')
- self.execute('SET X copain Y WHERE X login "comme", Y login "billy"')
- self.execute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
- rset= self.execute('Any L WHERE X login L, '
+ self.create_user('comme', req=self.session)
+ self.create_user('cochon', groups=('users', 'guests'), req=self.session)
+ self.create_user('billy', req=self.session)
+ 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"')
+ self.sexecute('SET X copain Y WHERE X login %(syt)s, Y login "cochon"', {'syt': SYT})
+ rset= self.sexecute('Any L WHERE X login L, '
'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
'NOT EXISTS(X copain T2, T2 login "billy")')
self.assertEquals(sorted(rset.rows), [['cochon'], [SYT]])
- rset= self.execute('Any GN,L WHERE X in_group G, X login L, G name GN, '
+ rset= self.sexecute('Any GN,L WHERE X in_group G, X login L, G name GN, '
'EXISTS(X copain T, T login in ("comme", "cochon")) AND '
'NOT EXISTS(X copain T2, T2 login "billy")')
self.assertEquals(sorted(rset.rows), [['guests', 'cochon'],
@@ -264,18 +260,20 @@
['users', SYT]])
def test_cd_restriction(self):
- rset = self.execute('CWUser X WHERE X creation_date > "2009-02-01"')
- self.assertEquals(len(rset), 2) # admin/anon but no ldap user since it doesn't support creation_date
+ rset = self.sexecute('CWUser X WHERE X creation_date > "2009-02-01"')
+ # admin/anon but no ldap user since it doesn't support creation_date
+ self.assertEquals(sorted(e.login for e in rset.entities()),
+ ['admin', 'anon'])
def test_union(self):
- afeids = self.execute('State X')
- ueids = self.execute('CWUser X')
- rset = self.execute('(Any X WHERE X is State) UNION (Any X WHERE X is CWUser)')
+ afeids = self.sexecute('State X')
+ ueids = self.sexecute('CWUser X')
+ rset = self.sexecute('(Any X WHERE X is State) UNION (Any X WHERE X is CWUser)')
self.assertEquals(sorted(r[0] for r in rset.rows),
sorted(r[0] for r in afeids + ueids))
def _init_security_test(self):
- self.create_user('iaminguestsgrouponly', groups=('guests',))
+ self.create_user('iaminguestsgrouponly', groups=('guests',), req=self.session)
cnx = self.login('iaminguestsgrouponly')
return cnx.cursor()
@@ -301,33 +299,33 @@
self.assertEquals(rset.rows, [[None]])
def test_nonregr1(self):
- self.execute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E owned_by X, '
+ self.sexecute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E owned_by X, '
'X modification_date AA',
- {'x': cnx.user(self.session).eid})
+ {'x': self.session.user.eid})
def test_nonregr2(self):
- self.execute('Any X,L,AA WHERE E eid %(x)s, E owned_by X, '
+ self.sexecute('Any X,L,AA WHERE E eid %(x)s, E owned_by X, '
'X login L, X modification_date AA',
- {'x': cnx.user(self.session).eid})
+ {'x': self.session.user.eid})
def test_nonregr3(self):
- self.execute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, '
+ self.sexecute('Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, '
'X modification_date AA',
- {'x': cnx.user(self.session).eid})
+ {'x': self.session.user.eid})
def test_nonregr4(self):
- emaileid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org"')[0][0]
- self.execute('Any X,AA WHERE X use_email Y, Y eid %(x)s, X modification_date AA',
+ emaileid = self.sexecute('INSERT EmailAddress X: X address "toto@logilab.org"')[0][0]
+ self.sexecute('Any X,AA WHERE X use_email Y, Y eid %(x)s, X modification_date AA',
{'x': emaileid})
def test_nonregr5(self):
# original jpl query:
# Any X, NOW - CD, P WHERE P is Project, U interested_in P, U is CWUser, U login "sthenault", X concerns P, X creation_date CD ORDERBY CD DESC LIMIT 5
rql = 'Any X, NOW - CD, P ORDERBY CD DESC LIMIT 5 WHERE P bookmarked_by U, U login "%s", P is X, X creation_date CD' % self.session.user.login
- self.execute(rql, )#{'x': })
+ self.sexecute(rql, )#{'x': })
def test_nonregr6(self):
- self.execute('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
+ self.sexecute('Any B,U,UL GROUPBY B,U,UL WHERE B created_by U?, B is File '
'WITH U,UL BEING (Any U,UL WHERE ME eid %(x)s, (EXISTS(U identity ME) '
'OR (EXISTS(U in_group G, G name IN("managers", "staff")))) '
'OR (EXISTS(U in_group H, ME in_group H, NOT H name "users")), U login UL, U is CWUser)',
@@ -368,6 +366,9 @@
res = trfunc.apply([[1, 2], [2, 4], [3, 6], [1, 5]])
self.assertEquals(res, [[1, 5], [2, 4], [3, 6]])
+# XXX
+LDAPUserSourceTC._init_repo()
+repo = LDAPUserSourceTC.repo
class RQL2LDAPFilterTC(RQLGeneratorTC):
schema = repo.schema
--- a/server/test/unittest_migractions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_migractions.py Mon Feb 08 13:35:07 2010 +0100
@@ -2,13 +2,14 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from copy import deepcopy
from datetime import date
from os.path import join
from logilab.common.testlib import TestCase, unittest_main
from cubicweb import ConfigurationError
-from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
+from cubicweb.devtools.testlib import CubicWebTC, get_versions
from cubicweb.schema import CubicWebSchemaLoader
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.repository import Repository
@@ -23,22 +24,28 @@
Repository.get_versions = orig_get_versions
-class MigrationCommandsTC(RepositoryBasedTC):
+class MigrationCommandsTC(CubicWebTC):
+
+ @classmethod
+ def init_config(cls, config):
+ super(MigrationCommandsTC, cls).init_config(config)
+ config._cubes = None
+ cls.repo.fill_schema()
+ cls.origschema = deepcopy(cls.repo.schema)
+ # hack to read the schema from data/migrschema
+ config.appid = join('data', 'migratedapp')
+ global migrschema
+ migrschema = config.load_schema()
+ config.appid = 'data'
+ assert 'Folder' in migrschema
+
+ @classmethod
+ def _refresh_repo(cls):
+ super(MigrationCommandsTC, cls)._refresh_repo()
+ cls.repo.schema = cls.vreg.schema = deepcopy(cls.origschema)
def setUp(self):
- if not hasattr(self, '_repo'):
- # first initialization
- repo = self.repo # set by the RepositoryBasedTC metaclass
- # force to read schema from the database
- repo.config._cubes = None
- repo.fill_schema()
- # hack to read the schema from data/migrschema
- self.repo.config.appid = join('data', 'migratedapp')
- global migrschema
- migrschema = self.repo.config.load_schema()
- self.repo.config.appid = 'data'
- assert 'Folder' in migrschema
- RepositoryBasedTC.setUp(self)
+ CubicWebTC.setUp(self)
self.mh = ServerMigrationHelper(self.repo.config, migrschema,
repo=self.repo, cnx=self.cnx,
interactive=False)
@@ -48,7 +55,7 @@
def test_add_attribute_int(self):
self.failIf('whatever' in self.schema)
- self.add_entity('Note')
+ self.request().create_entity('Note')
self.commit()
orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, '
'RDEF relation_type RT, RDEF ordernum O, RT name RTN'))
@@ -61,7 +68,7 @@
self.assertEquals(note.whatever, 2)
orderdict2 = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, '
'RDEF relation_type RT, RDEF ordernum O, RT name RTN'))
- whateverorder = migrschema['whatever'].rproperty('Note', 'Int', 'order')
+ whateverorder = migrschema['whatever'].rdef('Note', 'Int').order
for k, v in orderdict.iteritems():
if v >= whateverorder:
orderdict[k] = v+1
@@ -187,7 +194,7 @@
('Personne',))
self.assertEquals(self.schema['concerne2'].objects(),
('Affaire', ))
- self.assertEquals(self.schema['concerne2'].rproperty('Personne', 'Affaire', 'cardinality'),
+ self.assertEquals(self.schema['concerne2'].rdef('Personne', 'Affaire').cardinality,
'1*')
self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Note')
self.assertEquals(sorted(self.schema['concerne2'].objects()), ['Affaire', 'Note'])
@@ -247,12 +254,12 @@
def test_change_relation_props_non_final(self):
rschema = self.schema['concerne']
- card = rschema.rproperty('Affaire', 'Societe', 'cardinality')
+ card = rschema.rdef('Affaire', 'Societe').cardinality
self.assertEquals(card, '**')
try:
self.mh.cmd_change_relation_props('Affaire', 'concerne', 'Societe',
cardinality='?*')
- card = rschema.rproperty('Affaire', 'Societe', 'cardinality')
+ card = rschema.rdef('Affaire', 'Societe').cardinality
self.assertEquals(card, '?*')
finally:
self.mh.cmd_change_relation_props('Affaire', 'concerne', 'Societe',
@@ -260,12 +267,12 @@
def test_change_relation_props_final(self):
rschema = self.schema['adel']
- card = rschema.rproperty('Personne', 'String', 'fulltextindexed')
+ card = rschema.rdef('Personne', 'String').fulltextindexed
self.assertEquals(card, False)
try:
self.mh.cmd_change_relation_props('Personne', 'adel', 'String',
fulltextindexed=True)
- card = rschema.rproperty('Personne', 'String', 'fulltextindexed')
+ card = rschema.rdef('Personne', 'String').fulltextindexed
self.assertEquals(card, True)
finally:
self.mh.cmd_change_relation_props('Personne', 'adel', 'String',
@@ -273,13 +280,16 @@
def test_sync_schema_props_perms(self):
cursor = self.mh.session
+ cursor.set_pool()
nbrqlexpr_start = len(cursor.execute('RQLExpression X'))
- migrschema['titre']._rproperties[('Personne', 'String')]['order'] = 7
- migrschema['adel']._rproperties[('Personne', 'String')]['order'] = 6
- migrschema['ass']._rproperties[('Personne', 'String')]['order'] = 5
+ migrschema['titre'].rdefs[('Personne', 'String')].order = 7
+ migrschema['adel'].rdefs[('Personne', 'String')].order = 6
+ migrschema['ass'].rdefs[('Personne', 'String')].order = 5
migrschema['Personne'].description = 'blabla bla'
migrschema['titre'].description = 'usually a title'
- migrschema['titre']._rproperties[('Personne', 'String')]['description'] = 'title for this person'
+ migrschema['titre'].rdefs[('Personne', 'String')].description = 'title for this person'
+ delete_concerne_rqlexpr = self._rrqlexpr_rset('delete', 'concerne')
+ add_concerne_rqlexpr = self._rrqlexpr_rset('add', 'concerne')
self.mh.cmd_sync_schema_props_perms(commit=False)
self.assertEquals(cursor.execute('Any D WHERE X name "Personne", X description D')[0][0],
@@ -317,7 +327,7 @@
self.assertEquals(rexpr.expression,
'O require_permission P, P name "add_note", '
'U in_group G, P require_group G')
- self.assertEquals([rt.name for rt in rexpr.reverse_add_permission], ['ecrit_par'])
+ self.assertEquals([rdef.rtype.name for rdef in rexpr.reverse_add_permission], ['ecrit_par'])
self.assertEquals(rexpr.reverse_read_permission, ())
self.assertEquals(rexpr.reverse_delete_permission, ())
# no more rqlexpr to delete and add travaille relation
@@ -335,8 +345,10 @@
self.assertEquals(len(self._erqlexpr_rset('delete', 'Affaire')), 1)
self.assertEquals(len(self._erqlexpr_rset('add', 'Affaire')), 1)
# no change for rqlexpr to add and delete concerne relation
- self.assertEquals(len(self._rrqlexpr_rset('delete', 'concerne')), 1)
- self.assertEquals(len(self._rrqlexpr_rset('add', 'concerne')), 1)
+ for rdef in self.schema['concerne'].rdefs.values():
+ print rdef, rdef.permissions
+ self.assertEquals(len(self._rrqlexpr_rset('delete', 'concerne')), len(delete_concerne_rqlexpr))
+ self.assertEquals(len(self._rrqlexpr_rset('add', 'concerne')), len(add_concerne_rqlexpr))
# * migrschema involve:
# * 8 deletion (2 in Affaire read + Societe + travaille + para rqlexprs)
# * 1 update (Affaire update)
@@ -357,7 +369,7 @@
self.assertEquals(len(rset), 1)
return rset.get_entity(0, 0)
def _rrqlexpr_rset(self, action, ertype):
- rql = 'RQLExpression X WHERE ET is CWRType, ET %s_permission X, ET name %%(name)s' % action
+ rql = 'RQLExpression X WHERE RT is CWRType, RDEF %s_permission X, RT name %%(name)s, RDEF relation_type RT' % action
return self.mh.session.execute(rql, {'name': ertype})
def _rrqlexpr_entity(self, action, ertype):
rset = self._rrqlexpr_rset(action, ertype)
@@ -379,7 +391,7 @@
def test_add_remove_cube_and_deps(self):
cubes = set(self.config.cubes())
schema = self.repo.schema
- self.assertEquals(sorted((str(s), str(o)) for s, o in schema['see_also']._rproperties.keys()),
+ self.assertEquals(sorted((str(s), str(o)) for s, o in schema['see_also'].rdefs.keys()),
sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'),
('Bookmark', 'Bookmark'), ('Bookmark', 'Note'),
('Note', 'Note'), ('Note', 'Bookmark')]))
@@ -394,7 +406,7 @@
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failIf(ertype in schema, ertype)
- self.assertEquals(sorted(schema['see_also']._rproperties.keys()),
+ self.assertEquals(sorted(schema['see_also'].rdefs.keys()),
sorted([('Folder', 'Folder'),
('Bookmark', 'Bookmark'),
('Bookmark', 'Note'),
@@ -417,7 +429,7 @@
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failUnless(ertype in schema, ertype)
- self.assertEquals(sorted(schema['see_also']._rproperties.keys()),
+ self.assertEquals(sorted(schema['see_also'].rdefs.keys()),
sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'),
('Bookmark', 'Bookmark'),
('Bookmark', 'Note'),
--- a/server/test/unittest_msplanner.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_msplanner.py Mon Feb 08 13:35:07 2010 +0100
@@ -59,7 +59,7 @@
# keep cnx so it's not garbage collected and the associated session is closed
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
class BaseMSPlannerTC(BasePlannerTC):
"""test planner related feature on a 3-sources repository:
@@ -74,16 +74,16 @@
#_QuerierTC.setUp(self)
self.setup()
# hijack Affaire security
- affreadperms = list(self.schema['Affaire']._groups['read'])
+ affreadperms = list(self.schema['Affaire'].permissions['read'])
self.prevrqlexpr_affaire = affreadperms[-1]
# add access to type attribute so S can't be invariant
affreadperms[-1] = ERQLExpression('X concerne S?, S owned_by U, S type "X"')
- self.schema['Affaire']._groups['read'] = tuple(affreadperms)
+ self.schema['Affaire'].permissions['read'] = tuple(affreadperms)
# hijack CWUser security
- userreadperms = list(self.schema['CWUser']._groups['read'])
+ userreadperms = list(self.schema['CWUser'].permissions['read'])
self.prevrqlexpr_user = userreadperms[-1]
userreadperms[-1] = ERQLExpression('X owned_by U')
- self.schema['CWUser']._groups['read'] = tuple(userreadperms)
+ self.schema['CWUser'].permissions['read'] = tuple(userreadperms)
self.add_source(FakeUserROSource, 'ldap')
self.add_source(FakeCardSource, 'cards')
@@ -91,19 +91,24 @@
super(BaseMSPlannerTC, self).tearDown()
# restore hijacked security
self.restore_orig_affaire_security()
- self.restore_orig_euser_security()
+ self.restore_orig_cwuser_security()
def restore_orig_affaire_security(self):
- affreadperms = list(self.schema['Affaire']._groups['read'])
+ affreadperms = list(self.schema['Affaire'].permissions['read'])
affreadperms[-1] = self.prevrqlexpr_affaire
- self.schema['Affaire']._groups['read'] = tuple(affreadperms)
- clear_cache(self.schema['Affaire'], 'ERSchema_get_rqlexprs')
+ self.schema['Affaire'].permissions['read'] = tuple(affreadperms)
+ clear_cache(self.schema['Affaire'], 'get_rqlexprs')
+ #clear_cache(self.schema['Affaire'], 'get_groups')
- def restore_orig_euser_security(self):
- userreadperms = list(self.schema['CWUser']._groups['read'])
+ def restore_orig_cwuser_security(self):
+ if hasattr(self, '_orig_cwuser_security_restored'):
+ return
+ self._orig_cwuser_security_restored = True
+ userreadperms = list(self.schema['CWUser'].permissions['read'])
userreadperms[-1] = self.prevrqlexpr_user
- self.schema['CWUser']._groups['read'] = tuple(userreadperms)
- clear_cache(self.schema['CWUser'], 'ERSchema_get_rqlexprs')
+ self.schema['CWUser'].permissions['read'] = tuple(userreadperms)
+ clear_cache(self.schema['CWUser'], 'get_rqlexprs')
+ #clear_cache(self.schema['CWUser'], 'get_groups')
class PartPlanInformationTC(BaseMSPlannerTC):
@@ -989,9 +994,10 @@
])
def test_security_3sources_identity(self):
- self.restore_orig_euser_security()
+ self.restore_orig_cwuser_security()
# use a guest user
self.session = self._user_session()[1]
+ print self.session
self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
@@ -1003,7 +1009,7 @@
])
def test_security_3sources_identity_optional_var(self):
- self.restore_orig_euser_security()
+ self.restore_orig_cwuser_security()
# use a guest user
self.session = self._user_session()[1]
self._test('Any X,XT,U WHERE X is Card, X owned_by U?, X title XT, U login L',
--- a/server/test/unittest_multisources.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_multisources.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
from logilab.common.decorators import cached
from cubicweb.devtools import TestServerConfiguration, init_test_database
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC, refresh_repo
from cubicweb.devtools.repotest import do_monkey_patch, undo_monkey_patch
TestServerConfiguration.no_sqlite_wrap = True
@@ -26,16 +26,9 @@
class ExternalSource2Configuration(TestServerConfiguration):
sourcefile = 'sources_multi2'
-repo2, cnx2 = init_test_database('sqlite', config=ExternalSource1Configuration('data'))
-cu = cnx2.cursor()
-ec1 = cu.execute('INSERT Card X: X title "C3: An external card", X wikiid "aaa"')[0][0]
-cu.execute('INSERT Card X: X title "C4: Ze external card", X wikiid "zzz"')
-aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF"')[0][0]
-cnx2.commit()
-
MTIME = datetime.now() - timedelta(0, 10)
-
-repo3, cnx3 = init_test_database('sqlite', config=ExternalSource2Configuration('data'))
+repo2, cnx2 = init_test_database(config=ExternalSource1Configuration('data'))
+repo3, cnx3 = init_test_database(config=ExternalSource2Configuration('data'))
# XXX, access existing connection, no pyro connection
from cubicweb.server.sources.pyrorql import PyroRQLSource
@@ -45,38 +38,47 @@
from cubicweb.dbapi import Connection
Connection.close = lambda x: None
-class TwoSourcesTC(RepositoryBasedTC):
- repo_config = TwoSourcesConfiguration('data')
+class TwoSourcesTC(CubicWebTC):
+ config = TwoSourcesConfiguration('data')
+
+ @classmethod
+ def _refresh_repo(cls):
+ super(TwoSourcesTC, cls)._refresh_repo()
+ cnx2.rollback()
+ refresh_repo(repo2)
+ cnx3.rollback()
+ refresh_repo(repo3)
def setUp(self):
- RepositoryBasedTC.setUp(self)
- self.repo.sources[-1]._query_cache.clear()
- self.repo.sources[-2]._query_cache.clear()
- # trigger discovery
- self.execute('Card X')
- self.execute('Affaire X')
- self.execute('State X')
- self.commit()
- # don't delete external entities!
- self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
- # add some entities
- self.ic1 = self.execute('INSERT Card X: X title "C1: An internal card", X wikiid "aaai"')[0][0]
- self.ic2 = self.execute('INSERT Card X: X title "C2: Ze internal card", X wikiid "zzzi"')[0][0]
- self.commit()
+ CubicWebTC.setUp(self)
do_monkey_patch()
def tearDown(self):
- RepositoryBasedTC.tearDown(self)
+ CubicWebTC.tearDown(self)
undo_monkey_patch()
+ def setup_database(self):
+ cu = cnx2.cursor()
+ self.ec1 = cu.execute('INSERT Card X: X title "C3: An external card", X wikiid "aaa"')[0][0]
+ cu.execute('INSERT Card X: X title "C4: Ze external card", X wikiid "zzz"')
+ self.aff1 = cu.execute('INSERT Affaire X: X ref "AFFREF"')[0][0]
+ cnx2.commit()
+ # trigger discovery
+ self.sexecute('Card X')
+ self.sexecute('Affaire X')
+ self.sexecute('State X')
+ # add some entities
+ self.ic1 = self.sexecute('INSERT Card X: X title "C1: An internal card", X wikiid "aaai"')[0][0]
+ self.ic2 = self.sexecute('INSERT Card X: X title "C2: Ze internal card", X wikiid "zzzi"')[0][0]
+
def test_eid_comp(self):
- rset = self.execute('Card X WHERE X eid > 1')
+ rset = self.sexecute('Card X WHERE X eid > 1')
self.assertEquals(len(rset), 4)
- rset = self.execute('Any X,T WHERE X title T, X eid > 1')
+ rset = self.sexecute('Any X,T WHERE X title T, X eid > 1')
self.assertEquals(len(rset), 4)
def test_metainformation(self):
- rset = self.execute('Card X ORDERBY T WHERE X title T')
+ rset = self.sexecute('Card X ORDERBY T WHERE X title T')
# 2 added to the system source, 2 added to the external source
self.assertEquals(len(rset), 4)
# since they are orderd by eid, we know the 3 first one is coming from the system source
@@ -89,28 +91,28 @@
self.assertEquals(metainf['source'], {'adapter': 'pyrorql', 'base-url': 'http://extern.org/', 'uri': 'extern'})
self.assertEquals(metainf['type'], 'Card')
self.assert_(metainf['extid'])
- etype = self.execute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
+ etype = self.sexecute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s',
{'x': externent.eid}, 'x')[0][0]
self.assertEquals(etype, 'Card')
def test_order_limit_offset(self):
- rsetbase = self.execute('Any W,X ORDERBY W,X WHERE X wikiid W')
+ rsetbase = self.sexecute('Any W,X ORDERBY W,X WHERE X wikiid W')
self.assertEquals(len(rsetbase), 4)
self.assertEquals(sorted(rsetbase.rows), rsetbase.rows)
- rset = self.execute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W')
+ rset = self.sexecute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W')
self.assertEquals(rset.rows, rsetbase.rows[2:4])
def test_has_text(self):
self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before
- self.failUnless(self.execute('Any X WHERE X has_text "affref"'))
- self.failUnless(self.execute('Affaire X WHERE X has_text "affref"'))
+ self.failUnless(self.sexecute('Any X WHERE X has_text "affref"'))
+ self.failUnless(self.sexecute('Affaire X WHERE X has_text "affref"'))
def test_anon_has_text(self):
self.repo.sources_by_uri['extern'].synchronize(MTIME) # in case fti_update has been run before
- self.execute('INSERT Affaire X: X ref "no readable card"')[0][0]
- aff1 = self.execute('INSERT Affaire X: X ref "card"')[0][0]
+ self.sexecute('INSERT Affaire X: X ref "no readable card"')[0][0]
+ aff1 = self.sexecute('INSERT Affaire X: X ref "card"')[0][0]
# grant read access
- self.execute('SET X owned_by U WHERE X eid %(x)s, U login "anon"', {'x': aff1}, 'x')
+ self.sexecute('SET X owned_by U WHERE X eid %(x)s, U login "anon"', {'x': aff1}, 'x')
self.commit()
cnx = self.login('anon')
cu = cnx.cursor()
@@ -120,79 +122,81 @@
def test_synchronization(self):
cu = cnx2.cursor()
- assert cu.execute('Any X WHERE X eid %(x)s', {'x': aff1}, 'x')
- cu.execute('SET X ref "BLAH" WHERE X eid %(x)s', {'x': aff1}, 'x')
+ assert cu.execute('Any X WHERE X eid %(x)s', {'x': self.aff1}, 'x')
+ cu.execute('SET X ref "BLAH" WHERE X eid %(x)s', {'x': self.aff1}, 'x')
aff2 = cu.execute('INSERT Affaire X: X ref "AFFREUX"')[0][0]
cnx2.commit()
try:
# force sync
self.repo.sources_by_uri['extern'].synchronize(MTIME)
- self.failUnless(self.execute('Any X WHERE X has_text "blah"'))
- self.failUnless(self.execute('Any X WHERE X has_text "affreux"'))
+ self.failUnless(self.sexecute('Any X WHERE X has_text "blah"'))
+ self.failUnless(self.sexecute('Any X WHERE X has_text "affreux"'))
cu.execute('DELETE Affaire X WHERE X eid %(x)s', {'x': aff2})
cnx2.commit()
self.repo.sources_by_uri['extern'].synchronize(MTIME)
- rset = self.execute('Any X WHERE X has_text "affreux"')
+ rset = self.sexecute('Any X WHERE X has_text "affreux"')
self.failIf(rset)
finally:
# restore state
- cu.execute('SET X ref "AFFREF" WHERE X eid %(x)s', {'x': aff1}, 'x')
+ cu.execute('SET X ref "AFFREF" WHERE X eid %(x)s', {'x': self.aff1}, 'x')
cnx2.commit()
def test_simplifiable_var(self):
- affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
- rset = self.execute('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
+ affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+ rset = self.sexecute('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
{'x': affeid}, 'x')
self.assertEquals(len(rset), 1)
self.assertEquals(rset[0][1], "pitetre")
def test_simplifiable_var_2(self):
- affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
- rset = self.execute('Any E WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
+ affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+ rset = self.sexecute('Any E WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
{'x': affeid, 'u': self.session.user.eid}, 'x')
self.assertEquals(len(rset), 1)
def test_sort_func(self):
- self.execute('Affaire X ORDERBY DUMB_SORT(RF) WHERE X ref RF')
+ self.sexecute('Affaire X ORDERBY DUMB_SORT(RF) WHERE X ref RF')
def test_sort_func_ambigous(self):
- self.execute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF')
+ self.sexecute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF')
def test_in_eid(self):
- iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], str(ec1),
+ iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], str(self.ec1),
'Card', self.session)
- rset = self.execute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1))
+ rset = self.sexecute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1))
self.assertEquals(sorted(r[0] for r in rset.rows), sorted([iec1, self.ic1]))
def test_greater_eid(self):
- rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+ rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
self.assertEquals(len(rset.rows), 2) # self.ic1 and self.ic2
+ cu = cnx2.cursor()
ec2 = cu.execute('INSERT Card X: X title "glup"')[0][0]
cnx2.commit()
# 'X eid > something' should not trigger discovery
- rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+ rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
self.assertEquals(len(rset.rows), 2)
# trigger discovery using another query
- crset = self.execute('Card X WHERE X title "glup"')
+ crset = self.sexecute('Card X WHERE X title "glup"')
self.assertEquals(len(crset.rows), 1)
- rset = self.execute('Any X WHERE X eid > %s' % self.maxeid)
+ rset = self.sexecute('Any X WHERE X eid > %s' % (self.ic1 - 1))
self.assertEquals(len(rset.rows), 3)
- rset = self.execute('Any MAX(X)')
+ rset = self.sexecute('Any MAX(X)')
self.assertEquals(len(rset.rows), 1)
self.assertEquals(rset.rows[0][0], crset[0][0])
def test_attr_unification_1(self):
- n1 = self.execute('INSERT Note X: X type "AFFREF"')[0][0]
- n2 = self.execute('INSERT Note X: X type "AFFREU"')[0][0]
- rset = self.execute('Any X,Y WHERE X is Note, Y is Affaire, X type T, Y ref T')
+ n1 = self.sexecute('INSERT Note X: X type "AFFREF"')[0][0]
+ n2 = self.sexecute('INSERT Note X: X type "AFFREU"')[0][0]
+ rset = self.sexecute('Any X,Y WHERE X is Note, Y is Affaire, X type T, Y ref T')
self.assertEquals(len(rset), 1, rset.rows)
def test_attr_unification_2(self):
+ cu = cnx2.cursor()
ec2 = cu.execute('INSERT Card X: X title "AFFREF"')[0][0]
cnx2.commit()
try:
- c1 = self.execute('INSERT Card C: C title "AFFREF"')[0][0]
- rset = self.execute('Any X,Y WHERE X is Card, Y is Affaire, X title T, Y ref T')
+ c1 = self.sexecute('INSERT Card C: C title "AFFREF"')[0][0]
+ rset = self.sexecute('Any X,Y WHERE X is Card, Y is Affaire, X title T, Y ref T')
self.assertEquals(len(rset), 2, rset.rows)
finally:
cu.execute('DELETE Card X WHERE X eid %(x)s', {'x': ec2}, 'x')
@@ -200,83 +204,88 @@
def test_attr_unification_neq_1(self):
# XXX complete
- self.execute('Any X,Y WHERE X is Note, Y is Affaire, X creation_date D, Y creation_date > D')
+ self.sexecute('Any X,Y WHERE X is Note, Y is Affaire, X creation_date D, Y creation_date > D')
def test_attr_unification_neq_2(self):
# XXX complete
- self.execute('Any X,Y WHERE X is Card, Y is Affaire, X creation_date D, Y creation_date > D')
+ self.sexecute('Any X,Y WHERE X is Card, Y is Affaire, X creation_date D, Y creation_date > D')
def test_union(self):
- afeids = self.execute('Affaire X')
- ueids = self.execute('CWUser X')
- rset = self.execute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is CWUser)')
+ afeids = self.sexecute('Affaire X')
+ ueids = self.sexecute('CWUser X')
+ rset = self.sexecute('(Any X WHERE X is Affaire) UNION (Any X WHERE X is CWUser)')
self.assertEquals(sorted(r[0] for r in rset.rows),
sorted(r[0] for r in afeids + ueids))
def test_subquery1(self):
- rsetbase = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
+ rsetbase = self.sexecute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
self.assertEquals(len(rsetbase), 4)
self.assertEquals(sorted(rsetbase.rows), rsetbase.rows)
- rset = self.execute('Any W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
+ rset = self.sexecute('Any W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X ORDERBY W,X WHERE X wikiid W)')
self.assertEquals(rset.rows, rsetbase.rows[2:4])
- rset = self.execute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X WHERE X wikiid W)')
+ rset = self.sexecute('Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WITH W,X BEING (Any W,X WHERE X wikiid W)')
self.assertEquals(rset.rows, rsetbase.rows[2:4])
- rset = self.execute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W)')
+ rset = self.sexecute('Any W,X WITH W,X BEING (Any W,X ORDERBY W,X LIMIT 2 OFFSET 2 WHERE X wikiid W)')
self.assertEquals(rset.rows, rsetbase.rows[2:4])
def test_subquery2(self):
- affeid = self.execute('Affaire X WHERE X ref "AFFREF"')[0][0]
- rset = self.execute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
+ affeid = self.sexecute('Affaire X WHERE X ref "AFFREF"')[0][0]
+ rset = self.sexecute('Any X,AA,AB WITH X,AA,AB BEING (Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB)',
{'x': affeid})
self.assertEquals(len(rset), 1)
self.assertEquals(rset[0][1], "pitetre")
def test_not_relation(self):
- states = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN'))
+ states = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN'))
self.session.user.clear_all_caches()
userstate = self.session.user.in_state[0]
states.remove((userstate.eid, userstate.name))
- notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
+ notstates = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
{'x': self.session.user.eid}, 'x'))
self.assertSetEquals(notstates, states)
- aff1 = self.execute('Any X WHERE X is Affaire, X ref "AFFREF"')[0][0]
- aff1stateeid, aff1statename = self.execute('Any S,SN WHERE X eid %(x)s, X in_state S, S name SN', {'x': aff1}, 'x')[0]
+ aff1 = self.sexecute('Any X WHERE X is Affaire, X ref "AFFREF"')[0][0]
+ aff1stateeid, aff1statename = self.sexecute('Any S,SN WHERE X eid %(x)s, X in_state S, S name SN', {'x': aff1}, 'x')[0]
self.assertEquals(aff1statename, 'pitetre')
states.add((userstate.eid, userstate.name))
states.remove((aff1stateeid, aff1statename))
- notstates = set(tuple(x) for x in self.execute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
+ notstates = set(tuple(x) for x in self.sexecute('Any S,SN WHERE S is State, S name SN, NOT X in_state S, X eid %(x)s',
{'x': aff1}, 'x'))
self.assertSetEquals(notstates, states)
def test_absolute_url_base_url(self):
+ cu = cnx2.cursor()
ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
cnx2.commit()
- lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+ lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
self.assertEquals(lc.absolute_url(), 'http://extern.org/card/eid/%s' % ceid)
+ cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
+ cnx2.commit()
def test_absolute_url_no_base_url(self):
cu = cnx3.cursor()
ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
cnx3.commit()
- lc = self.execute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
+ lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
self.assertEquals(lc.absolute_url(), 'http://testing.fr/cubicweb/card/eid/%s' % lc.eid)
+ cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
+ cnx3.commit()
def test_nonregr1(self):
ueid = self.session.user.eid
- affaire = self.execute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
- self.execute('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
+ affaire = self.sexecute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0)
+ self.sexecute('Any U WHERE U in_group G, (G name IN ("managers", "logilab") OR (X require_permission P?, P name "bla", P require_group G)), X eid %(x)s, U eid %(u)s',
{'x': affaire.eid, 'u': ueid})
def test_nonregr2(self):
self.session.user.fire_transition('deactivate')
treid = self.session.user.latest_trinfo().eid
- rset = self.execute('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
+ rset = self.sexecute('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
{'x': treid})
self.assertEquals(len(rset), 1)
self.assertEquals(rset.rows[0], [self.session.user.eid])
def test_nonregr3(self):
- self.execute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
+ self.sexecute('DELETE Card X WHERE X eid %(x)s, NOT X multisource_inlined_rel Y', {'x': self.ic1})
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
--- a/server/test/unittest_querier.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_querier.py Mon Feb 08 13:35:07 2010 +0100
@@ -46,7 +46,7 @@
('C0 text,C1 integer', {'A': 'table0.C0', 'B': 'table0.C1'}))
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
@@ -559,7 +559,7 @@
rset = self.execute('CWGroup X ORDERBY N LIMIT 2 OFFSET 2 WHERE X name N')
self.assertEquals(tuplify(rset.rows), [(3,), (4,)])
- def test_select_symetric(self):
+ def test_select_symmetric(self):
self.execute("INSERT Personne X: X nom 'machin'")
self.execute("INSERT Personne X: X nom 'bidule'")
self.execute("INSERT Personne X: X nom 'chouette'")
@@ -907,7 +907,8 @@
self.execute("INSERT Personne Y: Y nom 'toto'")
rset = self.execute('Personne X WHERE X nom "toto"')
self.assertEqual(len(rset.rows), 1)
- self.execute("DELETE Personne Y WHERE Y nom 'toto'")
+ drset = self.execute("DELETE Personne Y WHERE Y nom 'toto'")
+ self.assertEqual(drset.rows, rset.rows)
rset = self.execute('Personne X WHERE X nom "toto"')
self.assertEqual(len(rset.rows), 0)
@@ -937,7 +938,7 @@
rset = self.execute('Personne P WHERE P travaille S')
self.assertEqual(len(rset.rows), 0)
- def test_delete_symetric(self):
+ def test_delete_symmetric(self):
teid1 = self.execute("INSERT Folder T: T name 'toto'")[0][0]
teid2 = self.execute("INSERT Folder T: T name 'tutu'")[0][0]
self.execute('SET X see_also Y WHERE X eid %s, Y eid %s' % (teid1, teid2))
--- a/server/test/unittest_repository.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_repository.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,12 +18,13 @@
from yams.constraints import UniqueConstraint
-from cubicweb import BadConnectionId, RepositoryError, ValidationError, UnknownEid, AuthenticationError
+from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
+ UnknownEid, AuthenticationError)
from cubicweb.schema import CubicWebSchema, RQLConstraint
from cubicweb.dbapi import connect, repo_connect, multiple_connections_unfix
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.repotest import tuplify
-from cubicweb.server import repository
+from cubicweb.server import repository, hook
from cubicweb.server.sqlutils import SQL_PREFIX
@@ -31,48 +32,35 @@
os.system('pyro-ns >/dev/null 2>/dev/null &')
-class RepositoryTC(RepositoryBasedTC):
+class RepositoryTC(CubicWebTC):
""" singleton providing access to a persistent storage for entities
and relation
"""
-# def setUp(self):
-# pass
-
-# def tearDown(self):
-# self.repo.config.db_perms = True
-# cnxid = self.repo.connect(*self.default_user_password())
-# for etype in ('Affaire', 'Note', 'Societe', 'Personne'):
-# self.repo.execute(cnxid, 'DELETE %s X' % etype)
-# self.repo.commit(cnxid)
-# self.repo.close(cnxid)
-
def test_fill_schema(self):
self.repo.schema = CubicWebSchema(self.repo.config.appid)
self.repo.config._cubes = None # avoid assertion error
+ self.repo.config.repairing = True # avoid versions checking
self.repo.fill_schema()
- pool = self.repo._get_pool()
table = SQL_PREFIX + 'CWEType'
namecol = SQL_PREFIX + 'name'
finalcol = SQL_PREFIX + 'final'
- try:
- cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
- namecol, table, finalcol))
- self.assertEquals(cu.fetchall(), [])
- cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
- % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
- self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
- (u'Date',), (u'Datetime',),
- (u'Decimal',),(u'Float',),
- (u'Int',),
- (u'Interval',), (u'Password',),
- (u'String',), (u'Time',)])
- finally:
- self.repo._free_pool(pool)
+ self.session.set_pool()
+ cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
+ namecol, table, finalcol))
+ self.assertEquals(cu.fetchall(), [])
+ cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+ % (namecol, table, finalcol, namecol), {'final': 'TRUE'})
+ self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
+ (u'Date',), (u'Datetime',),
+ (u'Decimal',),(u'Float',),
+ (u'Int',),
+ (u'Interval',), (u'Password',),
+ (u'String',), (u'Time',)])
def test_schema_has_owner(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
self.failIf(repo.execute(cnxid, 'CWEType X WHERE NOT X owned_by U'))
self.failIf(repo.execute(cnxid, 'CWRType X WHERE NOT X owned_by U'))
self.failIf(repo.execute(cnxid, 'CWAttribute X WHERE NOT X owned_by U'))
@@ -81,18 +69,21 @@
self.failIf(repo.execute(cnxid, 'CWConstraintType X WHERE NOT X owned_by U'))
def test_connect(self):
- login, passwd = self.default_user_password()
- self.assert_(self.repo.connect(login, passwd))
+ self.assert_(self.repo.connect(self.admlogin, password=self.admpassword))
+ self.assertRaises(AuthenticationError,
+ self.repo.connect, self.admlogin, password='nimportnawak')
+ self.assertRaises(AuthenticationError,
+ self.repo.connect, self.admlogin, password=None)
self.assertRaises(AuthenticationError,
- self.repo.connect, login, 'nimportnawak')
+ self.repo.connect, None, password=None)
self.assertRaises(AuthenticationError,
- self.repo.connect, login, None)
+ self.repo.connect, self.admlogin)
self.assertRaises(AuthenticationError,
- self.repo.connect, None, None)
+ self.repo.connect, None)
def test_execute(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
repo.execute(cnxid, 'Any X')
repo.execute(cnxid, 'Any X where X is Personne')
repo.execute(cnxid, 'Any X where X is Personne, X nom ~= "to"')
@@ -101,15 +92,15 @@
def test_login_upassword_accent(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
repo.execute(cnxid, 'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s, X in_group G WHERE G name "users"',
{'login': u"barnabé", 'passwd': u"héhéhé".encode('UTF8')})
repo.commit(cnxid)
repo.close(cnxid)
- self.assert_(repo.connect(u"barnabé", u"héhéhé".encode('UTF8')))
+ self.assert_(repo.connect(u"barnabé", password=u"héhéhé".encode('UTF8')))
def test_invalid_entity_rollback(self):
- cnxid = self.repo.connect(*self.default_user_password())
+ cnxid = self.repo.connect(self.admlogin, password=self.admpassword)
# no group
self.repo.execute(cnxid,
'INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',
@@ -119,7 +110,7 @@
def test_close(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
self.assert_(cnxid)
repo.close(cnxid)
self.assertRaises(BadConnectionId, repo.execute, cnxid, 'Any X')
@@ -130,9 +121,9 @@
def test_shared_data(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
repo.set_shared_data(cnxid, 'data', 4)
- cnxid2 = repo.connect(*self.default_user_password())
+ cnxid2 = repo.connect(self.admlogin, password=self.admpassword)
self.assertEquals(repo.get_shared_data(cnxid, 'data'), 4)
self.assertEquals(repo.get_shared_data(cnxid2, 'data'), None)
repo.set_shared_data(cnxid2, 'data', 5)
@@ -150,14 +141,14 @@
def test_check_session(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
self.assertEquals(repo.check_session(cnxid), None)
repo.close(cnxid)
self.assertRaises(BadConnectionId, repo.check_session, cnxid)
def test_transaction_base(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
# check db state
result = repo.execute(cnxid, 'Personne X')
self.assertEquals(result.rowcount, 0)
@@ -176,7 +167,7 @@
def test_transaction_base2(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
# rollback relation insertion
repo.execute(cnxid, "SET U in_group G WHERE U login 'admin', G name 'guests'")
result = repo.execute(cnxid, "Any U WHERE U in_group G, U login 'admin', G name 'guests'")
@@ -187,7 +178,7 @@
def test_transaction_base3(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
# rollback state change which trigger TrInfo insertion
user = repo._get_session(cnxid).user
user.fire_transition('deactivate')
@@ -202,7 +193,7 @@
def test_close_wait_processing_request(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
repo.execute(cnxid, 'INSERT CWUser X: X login "toto", X upassword "tutu", X in_group G WHERE G name "users"')
repo.commit(cnxid)
# close has to be in the thread due to sqlite limitations
@@ -223,8 +214,11 @@
self.assertListEquals([r.type for r in schema.eschema('CWAttribute').ordered_relations()
if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
'creation_date', 'modification_date', 'cwuri',
- 'owned_by', 'created_by')],
- ['relation_type', 'from_entity', 'to_entity', 'in_basket', 'constrained_by',
+ 'owned_by', 'created_by',
+ 'add_permission', 'delete_permission', 'read_permission')],
+ ['relation_type',
+ 'from_entity', 'to_entity',
+ 'in_basket', 'constrained_by',
'cardinality', 'ordernum',
'indexed', 'fulltextindexed', 'internationalizable',
'defaultval', 'description', 'description_format'])
@@ -232,7 +226,7 @@
self.assertEquals(schema.eschema('CWEType').main_attribute(), 'name')
self.assertEquals(schema.eschema('State').main_attribute(), 'name')
- constraints = schema.rschema('name').rproperty('CWEType', 'String', 'constraints')
+ constraints = schema.rschema('name').rdef('CWEType', 'String').constraints
self.assertEquals(len(constraints), 2)
for cstr in constraints[:]:
if isinstance(cstr, UniqueConstraint):
@@ -244,7 +238,7 @@
self.assertEquals(sizeconstraint.min, None)
self.assertEquals(sizeconstraint.max, 64)
- constraints = schema.rschema('relation_type').rproperty('CWAttribute', 'CWRType', 'constraints')
+ constraints = schema.rschema('relation_type').rdef('CWAttribute', 'CWRType').constraints
self.assertEquals(len(constraints), 1)
cstr = constraints[0]
self.assert_(isinstance(cstr, RQLConstraint))
@@ -271,7 +265,7 @@
repository.pyro_unregister(self.repo.config)
def _pyro_client(self, done):
- cnx = connect(self.repo.config.appid, u'admin', 'gingkow')
+ cnx = connect(self.repo.config.appid, u'admin', password='gingkow')
try:
# check we can get the schema
schema = cnx.get_schema()
@@ -286,7 +280,7 @@
def test_internal_api(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
session = repo._get_session(cnxid, setpool=True)
self.assertEquals(repo.type_and_source_from_eid(1, session),
('CWGroup', 'system', None))
@@ -304,7 +298,7 @@
def test_session_api(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
self.assertEquals(repo.user_info(cnxid), (5, 'admin', set([u'managers']), {}))
self.assertEquals(repo.describe(cnxid, 1), (u'CWGroup', u'system', None))
repo.close(cnxid)
@@ -313,7 +307,7 @@
def test_shared_data_api(self):
repo = self.repo
- cnxid = repo.connect(*self.default_user_password())
+ cnxid = repo.connect(self.admlogin, password=self.admpassword)
self.assertEquals(repo.get_shared_data(cnxid, 'data'), None)
repo.set_shared_data(cnxid, 'data', 4)
self.assertEquals(repo.get_shared_data(cnxid, 'data'), 4)
@@ -339,14 +333,14 @@
# print 'test time: %.3f (time) %.3f (cpu)' % ((time() - t), clock() - c)
def test_delete_if_singlecard1(self):
- note = self.add_entity('Affaire')
- p1 = self.add_entity('Personne', nom=u'toto')
+ note = self.request().create_entity('Affaire')
+ p1 = self.request().create_entity('Personne', nom=u'toto')
self.execute('SET A todo_by P WHERE A eid %(x)s, P eid %(p)s',
{'x': note.eid, 'p': p1.eid})
rset = self.execute('Any P WHERE A todo_by P, A eid %(x)s',
{'x': note.eid})
self.assertEquals(len(rset), 1)
- p2 = self.add_entity('Personne', nom=u'tutu')
+ p2 = self.request().create_entity('Personne', nom=u'tutu')
self.execute('SET A todo_by P WHERE A eid %(x)s, P eid %(p)s',
{'x': note.eid, 'p': p2.eid})
rset = self.execute('Any P WHERE A todo_by P, A eid %(x)s',
@@ -355,37 +349,34 @@
self.assertEquals(rset.rows[0][0], p2.eid)
-class DataHelpersTC(RepositoryBasedTC):
-
- def setUp(self):
- """ called before each test from this class """
- cnxid = self.repo.connect(*self.default_user_password())
- self.session = self.repo._sessions[cnxid]
- self.session.set_pool()
-
- def tearDown(self):
- self.session.rollback()
+class DataHelpersTC(CubicWebTC):
def test_create_eid(self):
+ self.session.set_pool()
self.assert_(self.repo.system_source.create_eid(self.session))
def test_source_from_eid(self):
+ self.session.set_pool()
self.assertEquals(self.repo.source_from_eid(1, self.session),
self.repo.sources_by_uri['system'])
def test_source_from_eid_raise(self):
+ self.session.set_pool()
self.assertRaises(UnknownEid, self.repo.source_from_eid, -2, self.session)
def test_type_from_eid(self):
+ self.session.set_pool()
self.assertEquals(self.repo.type_from_eid(1, self.session), 'CWGroup')
def test_type_from_eid_raise(self):
+ self.session.set_pool()
self.assertRaises(UnknownEid, self.repo.type_from_eid, -2, self.session)
def test_add_delete_info(self):
entity = self.repo.vreg['etypes'].etype_class('Personne')(self.session)
entity.eid = -1
entity.complete = lambda x: None
+ self.session.set_pool()
self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system'])
cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
data = cu.fetchall()
@@ -400,13 +391,14 @@
self.assertEquals(data, [])
-class FTITC(RepositoryBasedTC):
+class FTITC(CubicWebTC):
def test_reindex_and_modified_since(self):
eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
self.commit()
ts = datetime.now()
self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
+ self.session.set_pool()
cu = self.session.system_sql('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
omtime = cu.fetchone()[0]
# our sqlite datetime adapter is ignore seconds fraction, so we have to
@@ -415,6 +407,7 @@
self.execute('SET X nom "tata" WHERE X eid %(x)s', {'x': eidp}, 'x')
self.commit()
self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
+ self.session.set_pool()
cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp)
mtime = cu.fetchone()[0]
self.failUnless(omtime < mtime)
@@ -433,7 +426,7 @@
def test_composite_entity(self):
assert self.schema.rschema('use_email').fulltext_container == 'subject'
- eid = self.add_entity('EmailAddress', address=u'toto@logilab.fr').eid
+ eid = self.request().create_entity('EmailAddress', address=u'toto@logilab.fr').eid
self.commit()
rset = self.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEquals(rset.rows, [[eid]])
@@ -445,14 +438,14 @@
self.commit()
rset = self.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEquals(rset.rows, [])
- eid = self.add_entity('EmailAddress', address=u'tutu@logilab.fr').eid
+ eid = self.request().create_entity('EmailAddress', address=u'tutu@logilab.fr').eid
self.execute('SET X use_email Y WHERE X login "admin", Y eid %(y)s', {'y': eid})
self.commit()
rset = self.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEquals(rset.rows, [[self.session.user.eid]])
-class DBInitTC(RepositoryBasedTC):
+class DBInitTC(CubicWebTC):
def test_versions_inserted(self):
inserted = [r[0] for r in self.execute('Any K ORDERBY K WHERE P pkey K, P pkey ~= "system.version.%"')]
@@ -462,70 +455,42 @@
u'system.version.file', u'system.version.folder',
u'system.version.tag'])
-class InlineRelHooksTC(RepositoryBasedTC):
+CALLED = []
+class EcritParHook(hook.Hook):
+ __regid__ = 'inlinedrelhook'
+ __select__ = hook.Hook.__select__ & hook.match_rtype('ecrit_par')
+ events = ('before_add_relation', 'after_add_relation',
+ 'before_delete_relation', 'after_delete_relation')
+ def __call__(self):
+ CALLED.append((self.event, self.eidfrom, self.rtype, self.eidto))
+
+class InlineRelHooksTC(CubicWebTC):
"""test relation hooks are called for inlined relations
"""
def setUp(self):
- RepositoryBasedTC.setUp(self)
+ CubicWebTC.setUp(self)
self.hm = self.repo.hm
- self.called = []
-
- def _before_relation_hook(self, pool, fromeid, rtype, toeid):
- self.called.append((fromeid, rtype, toeid))
+ CALLED[:] = ()
def _after_relation_hook(self, pool, fromeid, rtype, toeid):
self.called.append((fromeid, rtype, toeid))
- def test_before_add_inline_relation(self):
- """make sure before_<event>_relation hooks are called directly"""
- self.hm.register_hook(self._before_relation_hook,
- 'before_add_relation', 'ecrit_par')
+ def test_inline_relation(self):
+ """make sure <event>_relation hooks are called for inlined relation"""
+ self.hm.register(EcritParHook)
eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
eidn = self.execute('INSERT Note X: X type "T"')[0][0]
self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp)])
-
- def test_after_add_inline_relation(self):
- """make sure after_<event>_relation hooks are deferred"""
- self.hm.register_hook(self._after_relation_hook,
- 'after_add_relation', 'ecrit_par')
- eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
- eidn = self.execute('INSERT Note X: X type "T"')[0][0]
- self.assertEquals(self.called, [])
- self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
-
- def test_after_add_inline(self):
- """make sure after_<event>_relation hooks are deferred"""
- p1 = self.add_entity('Personne', nom=u'toto')
- self.hm.register_hook(self._after_relation_hook,
- 'after_add_relation', 'ecrit_par')
+ self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+ ('after_add_relation', eidn, 'ecrit_par', eidp)])
+ CALLED[:] = ()
+ self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
+ self.assertEquals(CALLED, [('before_delete_relation', eidn, 'ecrit_par', eidp),
+ ('after_delete_relation', eidn, 'ecrit_par', eidp)])
+ CALLED[:] = ()
eidn = self.execute('INSERT Note N: N ecrit_par P WHERE P nom "toto"')[0][0]
- self.assertEquals(self.called, [(eidn, 'ecrit_par', p1.eid,)])
-
- def test_before_delete_inline_relation(self):
- """make sure before_<event>_relation hooks are called directly"""
- self.hm.register_hook(self._before_relation_hook,
- 'before_delete_relation', 'ecrit_par')
- eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
- eidn = self.execute('INSERT Note X: X type "T"')[0][0]
- self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp)])
- rset = self.execute('Any Y where N ecrit_par Y, N type "T", Y nom "toto"')
- # make sure the relation is really deleted
- self.failUnless(len(rset) == 0, "failed to delete inline relation")
-
- def test_after_delete_inline_relation(self):
- """make sure after_<event>_relation hooks are deferred"""
- self.hm.register_hook(self._after_relation_hook,
- 'after_delete_relation', 'ecrit_par')
- eidp = self.execute('INSERT Personne X: X nom "toto"')[0][0]
- eidn = self.execute('INSERT Note X: X type "T"')[0][0]
- self.execute('SET N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(self.called, [])
- self.execute('DELETE N ecrit_par Y WHERE N type "T", Y nom "toto"')
- self.assertEquals(self.called, [(eidn, 'ecrit_par', eidp,)])
+ self.assertEquals(CALLED, [('before_add_relation', eidn, 'ecrit_par', eidp),
+ ('after_add_relation', eidn, 'ecrit_par', eidp)])
if __name__ == '__main__':
--- a/server/test/unittest_rql2sql.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_rql2sql.py Mon Feb 08 13:35:07 2010 +0100
@@ -34,6 +34,7 @@
config.bootstrap_cubes()
schema = config.load_schema()
schema['in_state'].inlined = True
+schema['state_of'].inlined = False
schema['comments'].inlined = False
PARSER = [
@@ -358,10 +359,6 @@
('DISTINCT Any X,Y WHERE X name "CWGroup", Y eid IN(1, 2, 3), EXISTS(X read_permission Y)',
'''SELECT DISTINCT _X.cw_eid, rel_read_permission0.eid_to
FROM cw_CWEType AS _X, read_permission_relation AS rel_read_permission0
-WHERE _X.cw_name=CWGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=_X.cw_eid)
-UNION
-SELECT DISTINCT _X.cw_eid, rel_read_permission0.eid_to
-FROM cw_CWRType AS _X, read_permission_relation AS rel_read_permission0
WHERE _X.cw_name=CWGroup AND rel_read_permission0.eid_to IN(1, 2, 3) AND EXISTS(SELECT 1 WHERE rel_read_permission0.eid_from=_X.cw_eid)'''),
# no distinct, Y can't be invariant
@@ -372,14 +369,6 @@
UNION ALL
SELECT _X.cw_eid, _Y.cw_eid
FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION ALL
-SELECT _X.cw_eid, _Y.cw_eid
-FROM cw_CWGroup AS _Y, cw_CWRType AS _X
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION ALL
-SELECT _X.cw_eid, _Y.cw_eid
-FROM cw_CWRType AS _X, cw_RQLExpression AS _Y
WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
# DISTINCT but NEGED exists, can't be invariant
@@ -390,14 +379,6 @@
UNION
SELECT DISTINCT _X.cw_eid, _Y.cw_eid
FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION
-SELECT DISTINCT _X.cw_eid, _Y.cw_eid
-FROM cw_CWGroup AS _Y, cw_CWRType AS _X
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION
-SELECT DISTINCT _X.cw_eid, _Y.cw_eid
-FROM cw_CWRType AS _X, cw_RQLExpression AS _Y
WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
# should generate the same query as above
@@ -408,14 +389,6 @@
UNION
SELECT DISTINCT _X.cw_eid, _Y.cw_eid
FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION
-SELECT DISTINCT _X.cw_eid, _Y.cw_eid
-FROM cw_CWGroup AS _Y, cw_CWRType AS _X
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION
-SELECT DISTINCT _X.cw_eid, _Y.cw_eid
-FROM cw_CWRType AS _X, cw_RQLExpression AS _Y
WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
# neged relation, can't be inveriant
@@ -426,14 +399,6 @@
UNION ALL
SELECT _X.cw_eid, _Y.cw_eid
FROM cw_CWEType AS _X, cw_RQLExpression AS _Y
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION ALL
-SELECT _X.cw_eid, _Y.cw_eid
-FROM cw_CWGroup AS _Y, cw_CWRType AS _X
-WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)
-UNION ALL
-SELECT _X.cw_eid, _Y.cw_eid
-FROM cw_CWRType AS _X, cw_RQLExpression AS _Y
WHERE _X.cw_name=CWGroup AND _Y.cw_eid IN(1, 2, 3) AND NOT EXISTS(SELECT 1 FROM read_permission_relation AS rel_read_permission0 WHERE rel_read_permission0.eid_from=_X.cw_eid AND rel_read_permission0.eid_to=_Y.cw_eid)'''),
('Any MAX(X)+MIN(X), N GROUPBY N WHERE X name N, X is IN (Basket, Folder, Tag);',
@@ -1360,7 +1325,7 @@
rqlst = self._prepare(rql)
self.assertRaises(BadRQLQuery, self.o.generate, rqlst)
- def test_symetric(self):
+ def test_symmetric(self):
for t in self._parse(SYMETRIC):
yield t
--- a/server/test/unittest_rqlannotation.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_rqlannotation.py Mon Feb 08 13:35:07 2010 +0100
@@ -6,7 +6,7 @@
from cubicweb.devtools import init_test_database
from cubicweb.devtools.repotest import BaseQuerierTC
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
class SQLGenAnnotatorTC(BaseQuerierTC):
repo = repo
@@ -268,11 +268,11 @@
def test_or_exists_1(self):
# query generated by security rewriting
rqlst = self._prepare('DISTINCT Any A,S WHERE A is Affaire, S nom "chouette", S is IN(Division, Societe, SubDivision),'
- '(EXISTS(A owned_by %(D)s)) '
- 'OR ((((EXISTS(E concerne C?, C owned_by %(D)s, A identity E, C is Note, E is Affaire)) '
- 'OR (EXISTS(I concerne H?, H owned_by %(D)s, H is Societe, A identity I, I is Affaire))) '
- 'OR (EXISTS(J concerne G?, G owned_by %(D)s, G is SubDivision, A identity J, J is Affaire))) '
- 'OR (EXISTS(K concerne F?, F owned_by %(D)s, F is Division, A identity K, K is Affaire)))')
+ '(EXISTS(A owned_by D)) '
+ 'OR ((((EXISTS(E concerne C?, C owned_by D, A identity E, C is Note, E is Affaire)) '
+ 'OR (EXISTS(I concerne H?, H owned_by D, H is Societe, A identity I, I is Affaire))) '
+ 'OR (EXISTS(J concerne G?, G owned_by D, G is SubDivision, A identity J, J is Affaire))) '
+ 'OR (EXISTS(K concerne F?, F owned_by D, F is Division, A identity K, K is Affaire)))')
self.assertEquals(rqlst.defined_vars['A']._q_invariant, False)
self.assertEquals(rqlst.defined_vars['S']._q_invariant, False)
@@ -285,11 +285,11 @@
def test_or_exists_3(self):
rqlst = self._prepare('Any COUNT(S),CS GROUPBY CS ORDERBY 1 DESC LIMIT 10 '
'WHERE C is Societe, S concerne C, C nom CS, '
- '(EXISTS(S owned_by 1)) OR (EXISTS(S documented_by N, N title "published"))')
+ '(EXISTS(S owned_by D)) OR (EXISTS(S documented_by N, N title "published"))')
self.assertEquals(rqlst.defined_vars['S']._q_invariant, True)
rqlst = self._prepare('Any COUNT(S),CS GROUPBY CS ORDERBY 1 DESC LIMIT 10 '
'WHERE S is Affaire, C is Societe, S concerne C, C nom CS, '
- '(EXISTS(S owned_by 1)) OR (EXISTS(S documented_by N, N title "published"))')
+ '(EXISTS(S owned_by D)) OR (EXISTS(S documented_by N, N title "published"))')
self.assertEquals(rqlst.defined_vars['S']._q_invariant, True)
def test_nonregr_ambiguity(self):
--- a/server/test/unittest_schemaserial.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_schemaserial.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,6 +16,7 @@
schema = loader.load(config)
from cubicweb.server.schemaserial import *
+from cubicweb.server.schemaserial import _erperms2rql as erperms2rql
class Schema2RQLTC(TestCase):
@@ -48,43 +49,55 @@
def test_rschema2rql1(self):
self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s',
- {'description': u'link a relation definition to its relation type', 'symetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
-
- ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
- {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
- 'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
- ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
- {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u';O;O final FALSE\n'}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
+ {'description': u'link a relation definition to its relation type', 'symmetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
'ordernum': 1, 'cardinality': u'1*', 'se': 'CWAttribute'}),
('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
{'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWAttribute', 'value': u';O;O final TRUE\n'}),
+
+ ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+ {'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
+ 'ordernum': 1, 'cardinality': u'1*', 'se': 'CWRelation'}),
+ ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWRelation',
+ {'rt': 'relation_type', 'oe': 'CWRType', 'ctname': u'RQLConstraint', 'se': 'CWRelation', 'value': u';O;O final FALSE\n'}),
])
def test_rschema2rql2(self):
self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'symetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'symmetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
+
+ ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+ {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWAttribute'}),
+ ('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+ {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWAttribute'}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWEType'}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
- {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWRType'}),
+ {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWEType'}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
- {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWEType'}),
+ {'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWRelation'}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
- {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWRType'}),
+ {'rt': 'add_permission', 'description': u'rql expression allowing to add entities/relations of this type', 'composite': 'subject', 'oe': 'RQLExpression', 'ordernum': 5, 'cardinality': u'*?', 'se': 'CWRelation'}),
])
def test_rschema2rql3(self):
self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s',
- {'description': u'', 'symetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s',
+ {'description': u'', 'symmetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
+
+ ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
+ {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWAttribute'}),
+ ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+ {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
+ ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
+ {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWRelation'}),
@@ -92,13 +105,6 @@
{'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWRelation', 'value': u'max=2'}),
('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
{'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWRelation', 'value': u"u'?*', u'1*', u'+*', u'**', u'?+', u'1+', u'++', u'*+', u'?1', u'11', u'+1', u'*1', u'??', u'1?', u'+?', u'*?'"}),
-
- ('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
- {'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWAttribute'}),
- ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
- {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
- ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
- {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
])
@@ -117,16 +123,16 @@
def test_updaterschema2rql1(self):
self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'))),
[
- ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
- {'rt': 'relation_type', 'symetric': False,
+ ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
+ {'rt': 'relation_type', 'symmetric': False,
'description': u'link a relation definition to its relation type',
'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
])
def test_updaterschema2rql2(self):
expected = [
- ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
- {'rt': 'add_permission', 'symetric': False,
+ ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symmetric %(symmetric)s WHERE X is CWRType, X name %(rt)s',
+ {'rt': 'add_permission', 'symmetric': False,
'description': u'core relation giving to a group the permission to add an entity or relation type',
'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'})
]
@@ -142,36 +148,34 @@
}
def test_eperms2rql1(self):
- self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
- ['SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X add_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X update_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X update_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
- 'SET X delete_permission Y WHERE X is CWEType, X name %(name)s, Y eid %(g)s',
+ self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.eschema('CWEType'), self.GROUP_MAPPING)],
+ [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
+ ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 3}),
+ ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
])
def test_rperms2rql2(self):
- self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('read_permission'), self.GROUP_MAPPING)],
- ['SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+ self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('read_permission').rdef('CWEType', 'CWGroup'), self.GROUP_MAPPING)],
+ [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
+ ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
])
def test_rperms2rql3(self):
- self.assertListEquals([rql for rql, kwargs in erperms2rql(schema.rschema('name'), self.GROUP_MAPPING)],
- ['SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X read_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X add_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
- 'SET X delete_permission Y WHERE X is CWRType, X name %(name)s, Y eid %(g)s',
+ self.assertListEquals([(rql, kwargs) for rql, kwargs in erperms2rql(schema.rschema('name').rdef('CWEType', 'String'), self.GROUP_MAPPING)],
+ [('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
+ ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
+ ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
+ ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
+ ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
])
#def test_perms2rql(self):
--- a/server/test/unittest_security.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_security.py Mon Feb 08 13:35:07 2010 +0100
@@ -4,23 +4,23 @@
import sys
from logilab.common.testlib import unittest_main, TestCase
-from cubicweb.devtools.apptest import RepositoryBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb import Unauthorized, ValidationError
from cubicweb.server.querier import check_read_access
-class BaseSecurityTC(RepositoryBasedTC):
+class BaseSecurityTC(CubicWebTC):
def setUp(self):
- RepositoryBasedTC.setUp(self)
+ CubicWebTC.setUp(self)
self.create_user('iaminusersgrouponly')
- self.readoriggroups = self.schema['Personne'].get_groups('read')
- self.addoriggroups = self.schema['Personne'].get_groups('add')
+ self.readoriggroups = self.schema['Personne'].permissions['read']
+ self.addoriggroups = self.schema['Personne'].permissions['add']
def tearDown(self):
- RepositoryBasedTC.tearDown(self)
- self.schema['Personne'].set_groups('read', self.readoriggroups)
- self.schema['Personne'].set_groups('add', self.addoriggroups)
+ CubicWebTC.tearDown(self)
+ self.schema['Personne'].set_action_permissions('read', self.readoriggroups)
+ self.schema['Personne'].set_action_permissions('add', self.addoriggroups)
class LowLevelSecurityFunctionTC(BaseSecurityTC):
@@ -29,7 +29,7 @@
rql = u'Personne U where U nom "managers"'
rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
origgroups = self.schema['Personne'].get_groups('read')
- self.schema['Personne'].set_groups('read', ('users', 'managers'))
+ self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
self.repo.vreg.rqlhelper.compute_solutions(rqlst)
solution = rqlst.solutions[0]
check_read_access(self.schema, self.session.user, rqlst, solution)
@@ -37,7 +37,7 @@
cu = cnx.cursor()
self.assertRaises(Unauthorized,
check_read_access,
- self.schema, cnx.user(self.current_session()), rqlst, solution)
+ self.schema, cnx.user(self.session), rqlst, solution)
self.assertRaises(Unauthorized, cu.execute, rql)
def test_upassword_not_selectable(self):
@@ -96,8 +96,8 @@
def test_update_security_2(self):
cnx = self.login('anon')
cu = cnx.cursor()
- self.repo.schema['Personne'].set_groups('read', ('users', 'managers'))
- self.repo.schema['Personne'].set_groups('add', ('guests', 'users', 'managers'))
+ self.repo.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
+ self.repo.schema['Personne'].set_action_permissions('add', ('guests', 'users', 'managers'))
self.assertRaises(Unauthorized, cu.execute, "SET X nom 'bidulechouette' WHERE X is Personne")
#self.assertRaises(Unauthorized, cnx.commit)
# test nothing has actually been inserted
@@ -165,7 +165,7 @@
def test_insert_relation_rql_permission(self):
cnx = self.login('iaminusersgrouponly')
- session = self.current_session()
+ session = self.session
cu = cnx.cursor(session)
cu.execute("SET A concerne S WHERE A is Affaire, S is Societe")
# should raise Unauthorized since user don't own S
@@ -177,7 +177,7 @@
ent = rset.get_entity(0, 0)
session.set_pool() # necessary
self.assertRaises(Unauthorized,
- ent.e_schema.check_perm, session, 'update', ent.eid)
+ ent.e_schema.check_perm, session, 'update', eid=ent.eid)
self.assertRaises(Unauthorized,
cu.execute, "SET P travaille S WHERE P is Personne, S is Societe")
# test nothing has actually been inserted:
@@ -210,17 +210,17 @@
def test_user_can_change_its_upassword(self):
- ueid = self.create_user('user')
+ ueid = self.create_user('user').eid
cnx = self.login('user')
cu = cnx.cursor()
cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
{'x': ueid, 'passwd': 'newpwd'}, 'x')
cnx.commit()
cnx.close()
- cnx = self.login('user', 'newpwd')
+ cnx = self.login('user', password='newpwd')
def test_user_cant_change_other_upassword(self):
- ueid = self.create_user('otheruser')
+ ueid = self.create_user('otheruser').eid
cnx = self.login('iaminusersgrouponly')
cu = cnx.cursor()
cu.execute('SET X upassword %(passwd)s WHERE X eid %(x)s',
@@ -230,7 +230,7 @@
# read security test
def test_read_base(self):
- self.schema['Personne'].set_groups('read', ('users', 'managers'))
+ self.schema['Personne'].set_action_permissions('read', ('users', 'managers'))
cnx = self.login('anon')
cu = cnx.cursor()
self.assertRaises(Unauthorized,
@@ -281,7 +281,7 @@
self.execute("INSERT Personne X: X nom 'bidule'")
self.execute("INSERT Societe X: X nom 'bidule'")
self.commit()
- self.schema['Personne'].set_groups('read', ('managers',))
+ self.schema['Personne'].set_action_permissions('read', ('managers',))
cnx = self.login('iaminusersgrouponly')
cu = cnx.cursor()
rset = cu.execute('Any N WHERE N has_text "bidule"')
@@ -293,7 +293,7 @@
self.execute("INSERT Personne X: X nom 'bidule'")
self.execute("INSERT Societe X: X nom 'bidule'")
self.commit()
- self.schema['Personne'].set_groups('read', ('managers',))
+ self.schema['Personne'].set_action_permissions('read', ('managers',))
cnx = self.login('anon')
cu = cnx.cursor()
rset = cu.execute('Any N,U WHERE N has_text "bidule", N owned_by U?')
@@ -371,8 +371,8 @@
def test_attribute_read_security(self):
# anon not allowed to see users'login, but they can see users
- self.repo.schema['CWUser'].set_groups('read', ('guests', 'users', 'managers'))
- self.repo.schema['login'].set_groups('read', ('users', 'managers'))
+ self.repo.schema['CWUser'].set_action_permissions('read', ('guests', 'users', 'managers'))
+ self.repo.schema['CWUser'].rdef('login').set_action_permissions('read', ('users', 'managers'))
cnx = self.login('anon')
cu = cnx.cursor()
rset = cu.execute('CWUser X')
@@ -415,7 +415,7 @@
def test_users_and_groups_non_readable_by_guests(self):
cnx = self.login('anon')
- anon = cnx.user(self.current_session())
+ anon = cnx.user(self.session)
cu = cnx.cursor()
# anonymous user can only read itself
rset = cu.execute('Any L WHERE X owned_by U, U login L')
@@ -425,7 +425,7 @@
# anonymous user can read groups (necessary to check allowed transitions for instance)
self.assert_(cu.execute('CWGroup X'))
# should only be able to read the anonymous user, not another one
- origuser = self.session.user
+ origuser = self.adminsession.user
self.assertRaises(Unauthorized,
cu.execute, 'CWUser X WHERE X eid %(x)s', {'x': origuser.eid}, 'x')
# nothing selected, nothing updated, no exception raised
@@ -461,7 +461,7 @@
self.commit()
cnx = self.login('anon')
cu = cnx.cursor()
- anoneid = self.current_session().user.eid
+ anoneid = self.session.user.eid
self.assertEquals(cu.execute('Any T,P ORDERBY lower(T) WHERE B is Bookmark,B title T,B path P,'
'B bookmarked_by U, U eid %s' % anoneid).rows,
[['index', '?vid=index']])
@@ -490,27 +490,27 @@
eid = self.execute('INSERT Affaire X: X ref "ARCT01"')[0][0]
self.commit()
cnx = self.login('iaminusersgrouponly')
- session = self.current_session()
+ session = self.session
# needed to avoid check_perm error
session.set_pool()
# needed to remove rql expr granting update perm to the user
- self.schema['Affaire'].set_rqlexprs('update', ())
+ self.schema['Affaire'].set_action_permissions('update', self.schema['Affaire'].get_groups('update'))
self.assertRaises(Unauthorized,
- self.schema['Affaire'].check_perm, session, 'update', eid)
+ self.schema['Affaire'].check_perm, session, 'update', eid=eid)
cu = cnx.cursor()
- self.schema['Affaire'].set_groups('read', ('users',))
+ self.schema['Affaire'].set_action_permissions('read', ('users',))
try:
aff = cu.execute('Any X WHERE X ref "ARCT01"').get_entity(0, 0)
aff.fire_transition('abort')
cnx.commit()
# though changing a user state (even logged user) is reserved to managers
- user = cnx.user(self.current_session())
+ user = cnx.user(self.session)
# XXX wether it should raise Unauthorized or ValidationError is not clear
# the best would probably ValidationError if the transition doesn't exist
# from the current state but Unauthorized if it exists but user can't pass it
self.assertRaises(ValidationError, user.fire_transition, 'deactivate')
finally:
- self.schema['Affaire'].set_groups('read', ('managers',))
+ self.schema['Affaire'].set_action_permissions('read', ('managers',))
def test_trinfo_security(self):
aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
--- a/server/test/unittest_ssplanner.py Mon Feb 08 13:33:09 2010 +0100
+++ b/server/test/unittest_ssplanner.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,7 +10,7 @@
from cubicweb.server.ssplanner import SSPlanner
# keep cnx so it's not garbage collected and the associated session closed
-repo, cnx = init_test_database('sqlite')
+repo, cnx = init_test_database()
class SSPlannerTC(BasePlannerTC):
repo = repo
--- a/sobjects/email.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-"""hooks to ensure use_email / primary_email relations consistency
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.pool import PreCommitOperation
-from cubicweb.server.repository import ensure_card_respected
-
-class SetUseEmailRelationOp(PreCommitOperation):
- """delay this operation to commit to avoid conflict with a late rql query
- already setting the relation
- """
- rtype = 'use_email'
- fromeid = toeid = None # make pylint happy
-
- def condition(self):
- """check entity has use_email set for the email address"""
- return not self.session.unsafe_execute(
- 'Any X WHERE X eid %(x)s, X use_email Y, Y eid %(y)s',
- {'x': self.fromeid, 'y': self.toeid}, 'x')
-
- def precommit_event(self):
- session = self.session
- if self.condition():
- # we've to handle cardinaly by ourselves since we're using unsafe_execute
- # but use session.execute and not session.unsafe_execute to check we
- # can change the relation
- ensure_card_respected(session.execute, session,
- self.fromeid, self.rtype, self.toeid)
- session.unsafe_execute(
- 'SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % self.rtype,
- {'x': self.fromeid, 'y': self.toeid}, 'x')
-
-class SetPrimaryEmailRelationOp(SetUseEmailRelationOp):
- rtype = 'primary_email'
-
- def condition(self):
- """check entity has no primary_email set"""
- return not self.session.unsafe_execute(
- 'Any X WHERE X eid %(x)s, X primary_email Y',
- {'x': self.fromeid}, 'x')
-
-
-class SetPrimaryEmailHook(Hook):
- """notify when a bug or story or version has its state modified"""
- events = ('after_add_relation',)
- accepts = ('use_email',)
-
- def call(self, session, fromeid, rtype, toeid):
- subjtype = session.describe(fromeid)[0]
- eschema = self.vreg.schema[subjtype]
- if 'primary_email' in eschema.subject_relations():
- SetPrimaryEmailRelationOp(session, vreg=self.vreg,
- fromeid=fromeid, toeid=toeid)
-
-class SetUseEmailHook(Hook):
- """notify when a bug or story or version has its state modified"""
- events = ('after_add_relation',)
- accepts = ('primary_email',)
-
- def call(self, session, fromeid, rtype, toeid):
- subjtype = session.describe(fromeid)[0]
- eschema = self.vreg.schema[subjtype]
- if 'use_email' in eschema.subject_relations():
- SetUseEmailRelationOp(session, vreg=self.vreg,
- fromeid=fromeid, toeid=toeid)
--- a/sobjects/hooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-"""various library content hooks
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from datetime import datetime
-
-from cubicweb import RepositoryError
-from cubicweb.common.uilib import soup2xhtml
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.pool import PreCommitOperation
-
-
-class SetModificationDateOnStateChange(Hook):
- """update entity's modification date after changing its state"""
- events = ('after_add_relation',)
- accepts = ('in_state',)
-
- def call(self, session, fromeid, rtype, toeid):
- if fromeid in session.transaction_data.get('neweids', ()):
- # new entity, not needed
- return
- entity = session.entity_from_eid(fromeid)
- try:
- entity.set_attributes(modification_date=datetime.now(),
- _cw_unsafe=True)
- except RepositoryError, ex:
- # usually occurs if entity is coming from a read-only source
- # (eg ldap user)
- self.warning('cant change modification date for %s: %s', entity, ex)
-
-
-class AddUpdateCWUserHook(Hook):
- """ensure user logins are stripped"""
- events = ('before_add_entity', 'before_update_entity',)
- accepts = ('CWUser',)
-
- def call(self, session, entity):
- if 'login' in entity and entity['login']:
- entity['login'] = entity['login'].strip()
-
-
-class AutoDeleteBookmark(PreCommitOperation):
- beid = None # make pylint happy
- def precommit_event(self):
- session = self.session
- if not self.beid in session.transaction_data.get('pendingeids', ()):
- if not session.unsafe_execute('Any X WHERE X bookmarked_by U, X eid %(x)s',
- {'x': self.beid}, 'x'):
- session.unsafe_execute('DELETE Bookmark X WHERE X eid %(x)s',
- {'x': self.beid}, 'x')
-
-class DelBookmarkedByHook(Hook):
- """ensure user logins are stripped"""
- events = ('after_delete_relation',)
- accepts = ('bookmarked_by',)
-
- def call(self, session, subj, rtype, obj):
- AutoDeleteBookmark(session, beid=subj)
-
-
-class TidyHtmlFields(Hook):
- """tidy HTML in rich text strings"""
- events = ('before_add_entity', 'before_update_entity')
- accepts = ('Any',)
-
- def call(self, session, entity):
- if session.is_super_session:
- return
- metaattrs = entity.e_schema.meta_attributes()
- for metaattr, (metadata, attr) in metaattrs.iteritems():
- if metadata == 'format':
- try:
- value = entity[attr]
- except KeyError:
- continue # no text to tidy
- if isinstance(value, unicode): # filter out None and Binary
- if self.event == 'before_add_entity':
- fmt = entity.get(metaattr)
- else:
- fmt = entity.get_value(metaattr)
- if fmt == 'text/html':
- entity[attr] = soup2xhtml(value, session.encoding)
--- a/sobjects/notification.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/notification.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,4 @@
-"""some hooks and views to handle notification on entity's changes
+"""some views to handle notification on data changes
:organization: Logilab
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -13,15 +13,10 @@
from logilab.common.textutils import normalize_text
from logilab.common.deprecation import class_renamed, deprecated
-from cubicweb import RegistryException
-from cubicweb.selectors import implements, yes
+from cubicweb.selectors import yes
from cubicweb.view import Component
-from cubicweb.common.mail import NotificationView, parse_message_id, SkipEmail
-from cubicweb.server.pool import PreCommitOperation, SingleLastOperation
-from cubicweb.server.hookhelper import SendMailOp
-from cubicweb.server.hooksmanager import Hook
-
-parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id)
+from cubicweb.mail import NotificationView, SkipEmail
+from cubicweb.server.hook import SendMailOp
class RecipientsFinder(Component):
@@ -30,155 +25,40 @@
by default user's with their email set are notified if any, else the default
email addresses specified in the configuration are used
"""
- id = 'recipients_finder'
+ __regid__ = 'recipients_finder'
__select__ = yes()
user_rql = ('Any X,E,A WHERE X is CWUser, X in_state S, S name "activated",'
'X primary_email E, E address A')
def recipients(self):
- mode = self.config['default-recipients-mode']
+ mode = self._cw.vreg.config['default-recipients-mode']
if mode == 'users':
# use unsafe execute else we may don't have the right to see users
# to notify...
- execute = self.req.unsafe_execute
+ execute = self._cw.unsafe_execute
dests = [(u.get_email(), u.property_value('ui.language'))
for u in execute(self.user_rql, build_descr=True, propagate=True).entities()]
elif mode == 'default-dest-addrs':
- lang = self.vreg.property_value('ui.language')
- dests = zip(self.config['default-dest-addrs'], repeat(lang))
+ lang = self._cw.vreg.property_value('ui.language')
+ dests = zip(self._cw.vreg.config['default-dest-addrs'], repeat(lang))
else: # mode == 'none'
dests = []
return dests
-# hooks #######################################################################
-
-class EntityUpdatedNotificationOp(SingleLastOperation):
-
- def precommit_event(self):
- session = self.session
- for eid in session.transaction_data['changes']:
- view = session.vreg['views'].select('notif_entity_updated', session,
- rset=session.eid_rset(eid),
- row=0)
- RenderAndSendNotificationView(session, view=view)
-
- def commit_event(self):
- pass
-
-
-class RenderAndSendNotificationView(PreCommitOperation):
- """delay rendering of notification view until precommit"""
- def precommit_event(self):
- view = self.view
- if view.rset is not None and not view.rset:
- return # entity added and deleted in the same transaction (cache effect)
- if view.rset and view.rset[0][0] in self.session.transaction_data.get('pendingeids', ()):
- return # entity added and deleted in the same transaction
- self.view.render_and_send(**getattr(self, 'viewargs', {}))
-
-
-class StatusChangeHook(Hook):
- """notify when a workflowable entity has its state modified"""
- events = ('after_add_entity',)
- accepts = ('TrInfo',)
-
- def call(self, session, entity):
- if not entity.from_state: # not a transition
- return
- rset = entity.related('wf_info_for')
- try:
- view = session.vreg['views'].select('notif_status_change', session,
- rset=rset, row=0)
- except RegistryException:
- return
- comment = entity.printable_value('comment', format='text/plain')
- # XXX don't try to wrap rest until we've a proper transformation (see
- # #103822)
- if comment and entity.comment_format != 'text/rest':
- comment = normalize_text(comment, 80)
- RenderAndSendNotificationView(session, view=view, viewargs={
- 'comment': comment, 'previous_state': entity.previous_state.name,
- 'current_state': entity.new_state.name})
-
-
-class RelationChangeHook(Hook):
- events = ('before_add_relation', 'after_add_relation',
- 'before_delete_relation', 'after_delete_relation')
- accepts = ('Any',)
- def call(self, session, fromeid, rtype, toeid):
- """if a notification view is defined for the event, send notification
- email defined by the view
- """
- rset = session.eid_rset(fromeid)
- vid = 'notif_%s_%s' % (self.event, rtype)
- try:
- view = session.vreg['views'].select(vid, session, rset=rset, row=0)
- except RegistryException:
- return
- RenderAndSendNotificationView(session, view=view)
-
-
-class EntityChangeHook(Hook):
- events = ('after_add_entity',
- 'after_update_entity')
- accepts = ('Any',)
- def call(self, session, entity):
- """if a notification view is defined for the event, send notification
- email defined by the view
- """
- rset = entity.as_rset()
- vid = 'notif_%s' % self.event
- try:
- view = session.vreg['views'].select(vid, session, rset=rset, row=0)
- except RegistryException:
- return
- RenderAndSendNotificationView(session, view=view)
-
-class EntityUpdateHook(Hook):
- events = ('before_update_entity',)
- accepts = ()
- skip_attrs = set()
-
- def call(self, session, entity):
- if entity.eid in session.transaction_data.get('neweids', ()):
- return # entity is being created
- if session.is_super_session:
- return # ignore changes triggered by hooks
- # then compute changes
- changes = session.transaction_data.setdefault('changes', {})
- thisentitychanges = changes.setdefault(entity.eid, set())
- attrs = [k for k in entity.edited_attributes if not k in self.skip_attrs]
- if not attrs:
- return
- rqlsel, rqlrestr = [], ['X eid %(x)s']
- for i, attr in enumerate(attrs):
- var = chr(65+i)
- rqlsel.append(var)
- rqlrestr.append('X %s %s' % (attr, var))
- rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
- rset = session.execute(rql, {'x': entity.eid}, 'x')
- for i, attr in enumerate(attrs):
- oldvalue = rset[0][i]
- newvalue = entity[attr]
- if oldvalue != newvalue:
- thisentitychanges.add((attr, oldvalue, newvalue))
- if thisentitychanges:
- EntityUpdatedNotificationOp(session)
-
# abstract or deactivated notification views and mixin ########################
class NotificationView(NotificationView):
"""overriden to delay actual sending of mails to a commit operation by
default
"""
-
def send_on_commit(self, recipients, msg):
- SendMailOp(self.req, recipients=recipients, msg=msg)
+ SendMailOp(self._cw, recipients=recipients, msg=msg)
send = send_on_commit
+
class StatusChangeMixIn(object):
- id = 'notif_status_change'
+ __regid__ = 'notif_status_change'
msgid_timestamp = True
message = _('status changed')
content = _("""
@@ -208,7 +88,7 @@
override call)
"""
__abstract__ = True
- id = 'notif_after_add_entity'
+ __regid__ = 'notif_after_add_entity'
msgid_timestamp = False
message = _('new')
content = """
@@ -220,7 +100,7 @@
"""
def context(self, **kwargs):
- entity = self.entity(self.row or 0, self.col or 0)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
content = entity.printable_value(self.content_attr, format='text/plain')
if content:
contentformat = getattr(entity, self.content_attr + '_format',
@@ -232,17 +112,17 @@
return super(ContentAddedView, self).context(content=content, **kwargs)
def subject(self):
- entity = self.entity(self.row or 0, self.col or 0)
- return u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema),
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ return u'%s #%s (%s)' % (self._cw.__('New %s' % entity.e_schema),
entity.eid, self.user_data['login'])
-NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
def format_value(value):
if isinstance(value, unicode):
return u'"%s"' % value
return value
+
class EntityUpdatedNotificationView(NotificationView):
"""abstract class for notification on entity/relation
@@ -266,17 +146,19 @@
def context(self, **kwargs):
context = super(EntityUpdatedNotificationView, self).context(**kwargs)
- changes = self.req.transaction_data['changes'][self.rset[0][0]]
- _ = self.req._
+ changes = self._cw.transaction_data['changes'][self.cw_rset[0][0]]
+ _ = self._cw._
formatted_changes = []
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
for attr, oldvalue, newvalue in sorted(changes):
# check current user has permission to see the attribute
- rschema = self.vreg.schema[attr]
+ rschema = self._cw.vreg.schema[attr]
if rschema.final:
- if not rschema.has_perm(self.req, 'read', eid=self.rset[0][0]):
+ rdef = entity.e_schema.rdef(rschema)
+ if not rdef.has_perm(self._cw, 'read', eid=self.cw_rset[0][0]):
continue
# XXX suppose it's a subject relation...
- elif not rschema.has_perm(self.req, 'read', fromeid=self.rset[0][0]):
+ elif not rschema.has_perm(self._cw, 'read', fromeid=self.cw_rset[0][0]): # XXX toeid
continue
if attr in self.no_detailed_change_attrs:
msg = _('%s updated') % _(attr)
@@ -296,6 +178,16 @@
return context
def subject(self):
- entity = self.entity(self.row or 0, self.col or 0)
- return u'%s #%s (%s)' % (self.req.__('Updated %s' % entity.e_schema),
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ return u'%s #%s (%s)' % (self._cw.__('Updated %s' % entity.e_schema),
entity.eid, self.user_data['login'])
+
+
+from logilab.common.deprecation import class_renamed, class_moved, deprecated
+from cubicweb.hooks.notification import RenderAndSendNotificationView
+from cubicweb.mail import parse_message_id
+
+NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
+RenderAndSendNotificationView = class_moved(RenderAndSendNotificationView)
+parse_message_id = deprecated('parse_message_id is now defined in cubicweb.mail')(parse_message_id)
+
--- a/sobjects/supervising.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/supervising.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,59 +12,8 @@
from cubicweb.selectors import none_rset
from cubicweb.schema import display_name
from cubicweb.view import Component
-from cubicweb.common.mail import format_mail
-from cubicweb.server.hooksmanager import Hook
-from cubicweb.server.hookhelper import SendMailOp
-
-
-class SomethingChangedHook(Hook):
- events = ('before_add_relation', 'before_delete_relation',
- 'after_add_entity', 'before_update_entity')
- accepts = ('Any',)
-
- def call(self, session, *args):
- if session.is_super_session or session.repo.config.repairing:
- return # ignore changes triggered by hooks or maintainance shell
- dest = self.config['supervising-addrs']
- if not dest: # no supervisors, don't do this for nothing...
- return
- self.session = session
- if self._call(*args):
- SupervisionMailOp(session)
-
- def _call(self, *args):
- if self._event() == 'update_entity':
- if args[0].eid in self.session.transaction_data.get('neweids', ()):
- return False
- if args[0].e_schema == 'CWUser':
- updated = set(args[0].iterkeys())
- if not (updated - frozenset(('eid', 'modification_date',
- 'last_login_time'))):
- # don't record last_login_time update which are done
- # automatically at login time
- return False
- self.session.transaction_data.setdefault('pendingchanges', []).append(
- (self._event(), args))
- return True
-
- def _event(self):
- return self.event.split('_', 1)[1]
-
-
-class EntityDeleteHook(SomethingChangedHook):
- events = ('before_delete_entity',)
-
- def _call(self, eid):
- entity = self.session.entity_from_eid(eid)
- try:
- title = entity.dc_title()
- except:
- # may raise an error during deletion process, for instance due to
- # missing required relation
- title = '#%s' % eid
- self.session.transaction_data.setdefault('pendingchanges', []).append(
- ('delete_entity', (eid, str(entity.e_schema), title)))
- return True
+from cubicweb.mail import format_mail
+from cubicweb.server.hook import SendMailOp
def filter_changes(changes):
@@ -81,7 +30,7 @@
for change in changes[:]:
event, changedescr = change
if event == 'add_entity':
- entity = changedescr[0]
+ entity = changedescr.entity
added.add(entity.eid)
if entity.e_schema == 'TrInfo':
changes.remove(change)
@@ -95,7 +44,7 @@
index.setdefault(event, set()).add(change)
for key in ('delete_relation', 'add_relation'):
for change in index.get(key, {}).copy():
- if change[1][1] == 'in_state':
+ if change[1].rtype == 'in_state':
index[key].remove(change)
# filter changes
for eid in added:
@@ -104,26 +53,24 @@
changedescr = change[1]
# skip meta-relations which are set automatically
# XXX generate list below using rtags (category = 'generated')
- if changedescr[1] in ('created_by', 'owned_by', 'is', 'is_instance_of',
+ if changedescr.rtype in ('created_by', 'owned_by', 'is', 'is_instance_of',
'from_state', 'to_state', 'by_transition',
'wf_info_for') \
- and changedescr[0] == eid:
+ and changedescr.eidfrom == eid:
index['add_relation'].remove(change)
-
except KeyError:
break
for eid in deleted:
try:
for change in index['delete_relation'].copy():
- fromeid, rtype, toeid = change[1]
- if fromeid == eid:
+ if change[1].eidfrom == eid:
index['delete_relation'].remove(change)
- elif toeid == eid:
+ elif change[1].eidto == eid:
index['delete_relation'].remove(change)
- if rtype == 'wf_info_for':
- for change in index['delete_entity'].copy():
- if change[1][0] == fromeid:
- index['delete_entity'].remove(change)
+ if change[1].rtype == 'wf_info_for':
+ for change_ in index['delete_entity'].copy():
+ if change_[1].eidfrom == change[1].eidfrom:
+ index['delete_entity'].remove(change_)
except KeyError:
break
for change in changes:
@@ -135,22 +82,22 @@
class SupervisionEmailView(Component):
"""view implementing the email API for data changes supervision notification
"""
+ __regid__ = 'supervision_notif'
__select__ = none_rset()
- id = 'supervision_notif'
def recipients(self):
- return self.config['supervising-addrs']
+ return self._cw.vreg.config['supervising-addrs']
def subject(self):
- return self.req._('[%s supervision] changes summary') % self.config.appid
+ return self._cw._('[%s supervision] changes summary') % self._cw.vreg.config.appid
def call(self, changes):
- user = self.req.actual_session().user
- self.w(self.req._('user %s has made the following change(s):\n\n')
+ user = self._cw.actual_session().user
+ self.w(self._cw._('user %s has made the following change(s):\n\n')
% user.login)
for event, changedescr in filter_changes(changes):
self.w(u'* ')
- getattr(self, event)(*changedescr)
+ getattr(self, event)(changedescr)
self.w(u'\n\n')
def _entity_context(self, entity):
@@ -158,32 +105,32 @@
'etype': entity.dc_type().lower(),
'title': entity.dc_title()}
- def add_entity(self, entity):
- msg = self.req._('added %(etype)s #%(eid)s (%(title)s)')
- self.w(u'%s\n' % (msg % self._entity_context(entity)))
- self.w(u' %s' % entity.absolute_url())
+ def add_entity(self, changedescr):
+ msg = self._cw._('added %(etype)s #%(eid)s (%(title)s)')
+ self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity)))
+ self.w(u' %s' % changedescr.entity.absolute_url())
- def update_entity(self, entity):
- msg = self.req._('updated %(etype)s #%(eid)s (%(title)s)')
- self.w(u'%s\n' % (msg % self._entity_context(entity)))
+ def update_entity(self, changedescr):
+ msg = self._cw._('updated %(etype)s #%(eid)s (%(title)s)')
+ self.w(u'%s\n' % (msg % self._entity_context(changedescr.entity)))
# XXX print changes
- self.w(u' %s' % entity.absolute_url())
+ self.w(u' %s' % changedescr.entity.absolute_url())
- def delete_entity(self, eid, etype, title):
- msg = self.req._('deleted %(etype)s #%(eid)s (%(title)s)')
- etype = display_name(self.req, etype).lower()
+ def delete_entity(self, (eid, etype, title)):
+ msg = self._cw._('deleted %(etype)s #%(eid)s (%(title)s)')
+ etype = display_name(self._cw, etype).lower()
self.w(msg % locals())
- def change_state(self, entity, fromstate, tostate):
- msg = self.req._('changed state of %(etype)s #%(eid)s (%(title)s)')
+ def change_state(self, (entity, fromstate, tostate)):
+ msg = self._cw._('changed state of %(etype)s #%(eid)s (%(title)s)')
self.w(u'%s\n' % (msg % self._entity_context(entity)))
self.w(_(' from state %(fromstate)s to state %(tostate)s\n' %
{'fromstate': _(fromstate.name), 'tostate': _(tostate.name)}))
self.w(u' %s' % entity.absolute_url())
- def _relation_context(self, fromeid, rtype, toeid):
- _ = self.req._
- session = self.req.actual_session()
+ def _relation_context(self, changedescr):
+ _ = self._cw._
+ session = self._cw.actual_session()
def describe(eid):
try:
return _(session.describe(eid)[0]).lower()
@@ -191,19 +138,20 @@
# may occurs when an entity has been deleted from an external
# source and we're cleaning its relation
return _('unknown external entity')
+ eidfrom, rtype, eidto = changedescr.eidfrom, changedescr.rtype, changedescr.eidto
return {'rtype': _(rtype),
- 'fromeid': fromeid,
- 'frometype': describe(fromeid),
- 'toeid': toeid,
- 'toetype': describe(toeid)}
+ 'eidfrom': eidfrom,
+ 'frometype': describe(eidfrom),
+ 'eidto': eidto,
+ 'toetype': describe(eidto)}
- def add_relation(self, fromeid, rtype, toeid):
- msg = self.req._('added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
- self.w(msg % self._relation_context(fromeid, rtype, toeid))
+ def add_relation(self, changedescr):
+ msg = self._cw._('added relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s')
+ self.w(msg % self._relation_context(changedescr))
- def delete_relation(self, fromeid, rtype, toeid):
- msg = self.req._('deleted relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%(toeid)s')
- self.w(msg % self._relation_context(fromeid, rtype, toeid))
+ def delete_relation(self, changedescr):
+ msg = self._cw._('deleted relation %(rtype)s from %(frometype)s #%(eidfrom)s to %(toetype)s #%(eidto)s')
+ self.w(msg % self._relation_context(changedescr))
class SupervisionMailOp(SendMailOp):
--- a/sobjects/test/unittest_email.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/test/unittest_email.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,10 +5,11 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+
from cubicweb import Unauthorized
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
-class EmailAddressHooksTC(EnvBasedTC):
+class EmailAddressHooksTC(CubicWebTC):
def test_use_email_set_primary_email(self):
self.execute('INSERT EmailAddress X: X address "admin@logilab.fr", U use_email X WHERE U login "admin"')
--- a/sobjects/test/unittest_hooks.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-"""
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
-
-class HooksTC(EnvBasedTC):
-
- def test_euser_login_stripped(self):
- u = self.create_user(' joe ')
- tname = self.execute('Any L WHERE E login L, E eid %(e)s',
- {'e': u.eid})[0][0]
- self.assertEquals(tname, 'joe')
- self.execute('SET X login " jijoe " WHERE X eid %(x)s', {'x': u.eid})
- tname = self.execute('Any L WHERE E login L, E eid %(e)s',
- {'e': u.eid})[0][0]
- self.assertEquals(tname, 'jijoe')
-
-
- def test_auto_delete_bookmarks(self):
- beid = self.execute('INSERT Bookmark X: X title "hop", X path "view", X bookmarked_by U '
- 'WHERE U login "admin"')[0][0]
- self.execute('SET X bookmarked_by U WHERE U login "anon"')
- self.commit()
- self.execute('DELETE X bookmarked_by U WHERE U login "admin"')
- self.commit()
- self.failUnless(self.execute('Any X WHERE X eid %(x)s', {'x': beid}, 'x'))
- self.execute('DELETE X bookmarked_by U WHERE U login "anon"')
- self.commit()
- self.failIf(self.execute('Any X WHERE X eid %(x)s', {'x': beid}, 'x'))
-
-if __name__ == '__main__':
- unittest_main()
--- a/sobjects/test/unittest_notification.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/test/unittest_notification.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,9 +9,9 @@
from socket import gethostname
from logilab.common.testlib import unittest_main, TestCase
-from cubicweb.devtools.apptest import EnvBasedTC, MAILBOX
+from cubicweb.devtools.testlib import CubicWebTC, MAILBOX
-from cubicweb.common.mail import construct_message_id, parse_message_id
+from cubicweb.mail import construct_message_id, parse_message_id
class MessageIdTC(TestCase):
def test_base(self):
@@ -48,7 +48,7 @@
self.assertNotEquals(msgid1, '<@testapp.%s>' % gethostname())
-class RecipientsFinderTC(EnvBasedTC):
+class RecipientsFinderTC(CubicWebTC):
def test(self):
urset = self.execute('CWUser X WHERE X login "admin"')
self.execute('INSERT EmailAddress X: X address "admin@logilab.fr", U primary_email X '
@@ -67,12 +67,11 @@
self.assertEquals(finder.recipients(), [('abcd@logilab.fr', 'en'), ('efgh@logilab.fr', 'en')])
-class StatusChangeViewsTC(EnvBasedTC):
+class StatusChangeViewsTC(CubicWebTC):
def test_status_change_view(self):
- req = self.session()
- u = self.create_user('toto', req=req)#, commit=False) XXX in cw 3.6, and remove set_pool
- req.set_pool()
+ req = self.request()
+ u = self.create_user('toto', req=req)
u.fire_transition('deactivate', comment=u'yeah')
self.failIf(MAILBOX)
self.commit()
--- a/sobjects/test/unittest_supervising.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/test/unittest_supervising.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,24 +9,25 @@
import re
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.sobjects.supervising import SendMailOp, SupervisionMailOp
-class SupervisingTC(EnvBasedTC):
+class SupervisingTC(CubicWebTC):
def setup_database(self):
- self.add_entity('Card', title=u"une news !", content=u"cubicweb c'est beau")
- self.add_entity('Card', title=u"une autre news !", content=u"cubicweb c'est beau")
- self.add_entity('Bookmark', title=u"un signet !", path=u"view?vid=index")
- self.add_entity('Comment', content=u"Yo !")
+ req = self.request()
+ req.create_entity('Card', title=u"une news !", content=u"cubicweb c'est beau")
+ req.create_entity('Card', title=u"une autre news !", content=u"cubicweb c'est beau")
+ req.create_entity('Bookmark', title=u"un signet !", path=u"view?vid=index")
+ req.create_entity('Comment', content=u"Yo !")
self.execute('SET C comments B WHERE B title "une autre news !", C content "Yo !"')
self.vreg.config.global_set_option('supervising-addrs', 'test@logilab.fr')
def test_supervision(self):
- session = self.session()
+ session = self.session
# do some modification
user = self.execute('INSERT CWUser X: X login "toto", X upassword "sosafe", X in_group G '
'WHERE G name "users"').get_entity(0, 0)
@@ -88,7 +89,7 @@
data)
def test_nonregr1(self):
- session = self.session()
+ session = self.session
# do some unlogged modification
self.execute('SET X last_login_time NOW WHERE X eid %(x)s', {'x': session.user.eid}, 'x')
self.commit() # no crash
--- a/sobjects/textparsers.py Mon Feb 08 13:33:09 2010 +0100
+++ b/sobjects/textparsers.py Mon Feb 08 13:35:07 2010 +0100
@@ -21,11 +21,11 @@
"""analyze and extract information from plain text by calling registered
text parsers
"""
- id = 'textanalyzer'
+ __regid__ = 'textanalyzer'
def parse(self, caller, text):
- for parsercls in self.req.vreg['components'].get('textparser', ()):
- parsercls(self.req).parse(caller, text)
+ for parsercls in self._cw.vreg['components'].get('textparser', ()):
+ parsercls(self._cw).parse(caller, text)
class TextParser(Component):
@@ -36,7 +36,7 @@
method on the caller.
"""
- id = 'textparser'
+ __regid__ = 'textparser'
__abstract__ = True
def parse(self, caller, text):
@@ -53,7 +53,7 @@
def parse(self, caller, text):
for trname, eid in self.instr_rgx.findall(text):
try:
- entity = self.req.entity_from_eid(typed_eid(eid))
+ entity = self._cw.entity_from_eid(typed_eid(eid))
except UnknownEid:
self.error("can't get entity with eid %s", eid)
continue
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tags.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,49 @@
+"""helper classes to generate simple (X)HTML tags
+
+:organization: Logilab
+:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.uilib import simple_sgml_tag, sgml_attributes
+
+class tag(object):
+ def __init__(self, name, escapecontent=True):
+ self.name = name
+ self.escapecontent = escapecontent
+
+ def __call__(self, __content=None, **attrs):
+ attrs.setdefault('escapecontent', self.escapecontent)
+ return simple_sgml_tag(self.name, __content, **attrs)
+
+button = tag('button')
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+div = tag('div', False)
+img = tag('img')
+label = tag('label')
+option = tag('option')
+h1 = tag('h1')
+h2 = tag('h2')
+h3 = tag('h3')
+h4 = tag('h4')
+h5 = tag('h5')
+tr = tag('tr')
+th = tag('th')
+td = tag('td')
+
+def select(name, id=None, multiple=False, options=[], **attrs):
+ if multiple:
+ attrs['multiple'] = 'multiple'
+ if id:
+ attrs['id'] = id
+ attrs['name'] = name
+ html = [u'<select %s>' % sgml_attributes(attrs)]
+ html += options
+ html.append(u'</select>')
+ return u'\n'.join(html)
+
--- a/test/data/entities.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/entities.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,15 +8,15 @@
from cubicweb.entities import AnyEntity, fetch_config
class Societe(AnyEntity):
- id = 'Societe'
+ __regid__ = 'Societe'
fetch_attrs = ('nom',)
class Personne(Societe):
"""customized class forne Person entities"""
- id = 'Personne'
+ __regid__ = 'Personne'
fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
rest_attr = 'nom'
class Note(AnyEntity):
- id = 'Note'
+ __regid__ = 'Note'
--- a/test/data/erqlexpr_on_ertype.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/erqlexpr_on_ertype.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import ERQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
toto = SubjectRelation('TuTu')
class TuTu(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -26,7 +26,7 @@
}
class toto(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', ),
'add': ('managers', ERQLExpression('S bla Y'),),
'delete': ('managers',),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.0.3_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.0.4_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_common.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,7 @@
+"""common to all configuration
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_repository.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,7 @@
+"""repository specific
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.0_web.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,7 @@
+"""web only
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/0.1.2_Any.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,8 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+coucou
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/migration/depends.map Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,5 @@
+0.0.2: 2.3.0
+0.0.3: 2.4.0
+# missing 0.0.4 entry, that's alright
+0.1.0: 2.6.0
+0.1.2: 2.10.0
--- a/test/data/rewrite/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/rewrite/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -2,7 +2,7 @@
from cubicweb.schema import ERQLExpression
class Affaire(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
@@ -15,7 +15,7 @@
class Societe(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', 'users', 'guests'),
'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
--- a/test/data/rqlexpr_on_ertype_read.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/rqlexpr_on_ertype_read.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
toto = SubjectRelation('TuTu')
class TuTu(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -26,7 +26,7 @@
}
class toto(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', RRQLExpression('S bla Y'), ),
'add': ('managers',),
'delete': ('managers',),
--- a/test/data/rrqlexpr_on_attr.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/rrqlexpr_on_attr.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers',),
'add': ('managers',),
'update': ('managers',),
@@ -18,7 +18,7 @@
attr = String()
class attr(RelationType):
- permissions = {
+ __permissions__ = {
'read': ('managers', ),
'add': ('managers', RRQLExpression('S bla Y'),),
'delete': ('managers',),
--- a/test/data/rrqlexpr_on_eetype.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/rrqlexpr_on_eetype.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,7 +9,7 @@
from cubicweb.schema import RRQLExpression
class ToTo(EntityType):
- permissions = {
+ __permissions__ = {
'read': ('managers', RRQLExpression('S bla Y'),),
'add': ('managers',),
'update': ('managers',),
--- a/test/data/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/data/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,7 +14,7 @@
type = String()
travaille = SubjectRelation('Societe')
evaluee = SubjectRelation(('Note', 'Personne'))
- connait = SubjectRelation('Personne', symetric=True)
+ connait = SubjectRelation('Personne', symmetric=True)
class Societe(EntityType):
nom = String()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/server_migration/bootstrapmigration_repository.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,7 @@
+"""allways executed before all others in server migration
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
--- a/test/unittest_cwconfig.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_cwconfig.py Mon Feb 08 13:35:07 2010 +0100
@@ -72,15 +72,15 @@
# self.assertRaises(KeyError, vcconf.__getitem__, 'CRM')
def test_expand_cubes(self):
- self.assertEquals(self.config.expand_cubes(('email', 'eblog')),
- ['email', 'eblog', 'file'])
+ self.assertEquals(self.config.expand_cubes(('email', 'blog')),
+ ['email', 'blog', 'file'])
def test_vregistry_path(self):
self.assertEquals([unabsolutize(p) for p in self.config.vregistry_path()],
- ['entities', 'web/views', 'sobjects',
+ ['entities', 'web/views', 'sobjects', 'hooks',
'file/entities.py', 'file/views', 'file/hooks.py',
'email/entities.py', 'email/views', 'email/hooks.py',
- 'test/data/entities.py'])
+ 'test/data/entities.py', 'test/data/views.py'])
def test_cubes_path(self):
# make sure we don't import the email cube, but the stdlib email package
--- a/test/unittest_dbapi.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_dbapi.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,24 +7,21 @@
"""
from cubicweb import ConnectionError
from cubicweb.dbapi import ProgrammingError
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
-class DBAPITC(EnvBasedTC):
- @property
- def cnx(self):
- return self.login('anon')
+class DBAPITC(CubicWebTC):
def test_public_repo_api(self):
- cnx = self.cnx
- self.assertEquals(cnx.get_schema(), self.env.repo.schema)
+ cnx = self.login('anon')
+ self.assertEquals(cnx.get_schema(), self.repo.schema)
self.assertEquals(cnx.source_defs(), {'system': {'adapter': 'native', 'uri': 'system'}})
self.restore_connection() # proper way to close cnx
self.assertRaises(ProgrammingError, cnx.get_schema)
self.assertRaises(ProgrammingError, cnx.source_defs)
def test_db_api(self):
- cnx = self.cnx
+ cnx = self.login('anon')
self.assertEquals(cnx.rollback(), None)
self.assertEquals(cnx.commit(), None)
self.restore_connection() # proper way to close cnx
@@ -34,7 +31,7 @@
self.assertRaises(ProgrammingError, cnx.close)
def test_api(self):
- cnx = self.cnx
+ cnx = self.login('anon')
self.assertEquals(cnx.user(None).login, 'anon')
self.assertEquals(cnx.describe(1), (u'CWGroup', u'system', None))
self.restore_connection() # proper way to close cnx
@@ -42,7 +39,7 @@
self.assertRaises(ConnectionError, cnx.describe, 1)
def test_session_data_api(self):
- cnx = self.cnx
+ cnx = self.login('anon')
self.assertEquals(cnx.get_session_data('data'), None)
self.assertEquals(cnx.session_data(), {})
cnx.set_session_data('data', 4)
@@ -57,7 +54,7 @@
self.assertEquals(cnx.session_data(), {'data': 4})
def test_shared_data_api(self):
- cnx = self.cnx
+ cnx = self.login('anon')
self.assertEquals(cnx.get_shared_data('data'), None)
cnx.set_shared_data('data', 4)
self.assertEquals(cnx.get_shared_data('data'), 4)
@@ -71,19 +68,6 @@
self.assertRaises(ConnectionError, cnx.set_shared_data, 'data', 0)
self.assertRaises(ConnectionError, cnx.get_shared_data, 'data')
-
-# class DBAPICursorTC(EnvBasedTC):
-
-# @property
-# def cursor(self):
-# return self.env.cnx.cursor()
-
-# def test_api(self):
-# cu = self.cursor
-# self.assertEquals(cu.describe(1), (u'CWGroup', u'system', None))
-# #cu.close()
-# #self.assertRaises(ConnectionError, cu.describe, 1)
-
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
--- a/test/unittest_entity.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_entity.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,32 +10,25 @@
from datetime import datetime
from cubicweb import Binary, Unauthorized
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.common.mttransforms import HAS_TAL
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.mttransforms import HAS_TAL
from cubicweb.entities import fetch_config
-class EntityTC(EnvBasedTC):
-
-## def setup_database(self):
-## self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
-## self.add_entity('Task', title=u'fait ca !', description=u'et plus vite', start=now())
-## self.add_entity('Tag', name=u'x')
-## self.add_entity('Link', title=u'perdu', url=u'http://www.perdu.com',
-## embed=False)
+class EntityTC(CubicWebTC):
def test_boolean_value(self):
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
self.failUnless(e)
def test_yams_inheritance(self):
from entities import Note
- e = self.etype_instance('SubNote')
+ e = self.vreg['etypes'].etype_class('SubNote')(self.request())
self.assertIsInstance(e, Note)
- e2 = self.etype_instance('SubNote')
+ e2 = self.vreg['etypes'].etype_class('SubNote')(self.request())
self.assertIs(e.__class__, e2.__class__)
def test_has_eid(self):
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
self.assertEquals(e.eid, None)
self.assertEquals(e.has_eid(), False)
e.eid = 'X'
@@ -46,13 +39,14 @@
self.assertEquals(e.has_eid(), True)
def test_copy(self):
- self.add_entity('Tag', name=u'x')
- p = self.add_entity('Personne', nom=u'toto')
- oe = self.add_entity('Note', type=u'x')
+ req = self.request()
+ req.create_entity('Tag', name=u'x')
+ p = req.create_entity('Personne', nom=u'toto')
+ oe = req.create_entity('Note', type=u'x')
self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
{'t': oe.eid, 'u': p.eid}, ('t','u'))
self.execute('SET TAG tags X WHERE X eid %(x)s', {'x': oe.eid}, 'x')
- e = self.add_entity('Note', type=u'z')
+ e = req.create_entity('Note', type=u'z')
e.copy_relations(oe.eid)
self.assertEquals(len(e.ecrit_par), 1)
self.assertEquals(e.ecrit_par[0].eid, p.eid)
@@ -61,12 +55,13 @@
self.assertEquals(len(e.created_by), 0)
def test_copy_with_nonmeta_composite_inlined(self):
- p = self.add_entity('Personne', nom=u'toto')
- oe = self.add_entity('Note', type=u'x')
- self.schema['ecrit_par'].set_rproperty('Note', 'Personne', 'composite', 'subject')
+ req = self.request()
+ p = req.create_entity('Personne', nom=u'toto')
+ oe = req.create_entity('Note', type=u'x')
+ self.schema['ecrit_par'].rdef('Note', 'Personne').composite = 'subject'
self.execute('SET T ecrit_par U WHERE T eid %(t)s, U eid %(u)s',
{'t': oe.eid, 'u': p.eid}, ('t','u'))
- e = self.add_entity('Note', type=u'z')
+ e = req.create_entity('Note', type=u'z')
e.copy_relations(oe.eid)
self.failIf(e.ecrit_par)
self.failUnless(oe.ecrit_par)
@@ -102,7 +97,7 @@
user = self.entity('Any X WHERE X eid %(x)s', {'x':self.user().eid}, 'x')
adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
self.commit()
- self.assertEquals(user._related_cache.keys(), [])
+ self.assertEquals(user._related_cache, {})
email = user.primary_email[0]
self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
@@ -112,9 +107,10 @@
self.failIf('in_group_subject' in group._related_cache, group._related_cache.keys())
def test_related_limit(self):
- p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ req = self.request()
+ p = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
for tag in u'abcd':
- self.add_entity('Tag', name=tag)
+ req.create_entity('Tag', name=tag)
self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
self.assertEquals(len(p.related('tags', 'object', limit=2)), 2)
self.assertEquals(len(p.related('tags', 'object')), 4)
@@ -127,10 +123,10 @@
Note = self.vreg['etypes'].etype_class('Note')
peschema = Personne.e_schema
seschema = Societe.e_schema
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '1*')
- peschema.subjrels['connait'].set_rproperty(peschema, peschema, 'cardinality', '11')
- peschema.subjrels['evaluee'].set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
- seschema.subjrels['evaluee'].set_rproperty(seschema, Note.e_schema, 'cardinality', '1*')
+ 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.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB,AC ORDERBY AA ASC '
@@ -160,18 +156,18 @@
'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, '
'AC evaluee AE?, AE modification_date AF'
)
- # testing symetric relation
+ # testing symmetric relation
Personne.fetch_attrs = ('nom', 'connait')
self.assertEquals(Personne.fetch_rql(user), 'Any X,AA,AB ORDERBY AA ASC '
'WHERE X is Personne, X nom AA, X connait AB?')
# testing optional relation
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '?*')
+ peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '?*'
Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
Societe.fetch_attrs = ('nom',)
self.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD')
# testing relation with cardinality > 1
- peschema.subjrels['travaille'].set_rproperty(peschema, seschema, 'cardinality', '**')
+ peschema.subjrels['travaille'].rdef(peschema, seschema).cardinality = '**'
self.assertEquals(Personne.fetch_rql(user),
'Any X,AA,AB ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB')
# XXX test unauthorized attribute
@@ -185,7 +181,7 @@
self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note))
Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type'))
Note.fetch_attrs, Note.fetch_order = fetch_config(('type',))
- p = self.add_entity('Personne', nom=u'pouet')
+ p = self.request().create_entity('Personne', nom=u'pouet')
self.assertEquals(p.related_rql('evaluee'),
'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, '
'X type AA, X modification_date AB')
@@ -248,8 +244,9 @@
'A eid %(B)s, EXISTS(S identity A, NOT A in_group C, C name "guests", C is CWGroup)')
def test_unrelated_base(self):
- p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
- e = self.add_entity('Tag', name=u'x')
+ req = self.request()
+ p = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ e = req.create_entity('Tag', name=u'x')
related = [r.eid for r in e.tags]
self.failUnlessEqual(related, [])
unrelated = [r[0] for r in e.unrelated('tags', 'Personne', 'subject')]
@@ -260,9 +257,10 @@
self.failIf(p.eid in unrelated)
def test_unrelated_limit(self):
- e = self.add_entity('Tag', name=u'x')
- self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
- self.add_entity('Personne', nom=u'thenault', prenom=u'sylvain')
+ req = self.request()
+ e = req.create_entity('Tag', name=u'x')
+ req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ req.create_entity('Personne', nom=u'thenault', prenom=u'sylvain')
self.assertEquals(len(e.unrelated('tags', 'Personne', 'subject', limit=1)),
1)
@@ -293,13 +291,13 @@
self.assertEquals([x.address for x in rset.entities()], [])
def test_unrelated_new_entity(self):
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
unrelated = [r[0] for r in e.unrelated('in_group', 'CWGroup', 'subject')]
# should be default groups but owners, i.e. managers, users, guests
self.assertEquals(len(unrelated), 3)
def test_printable_value_string(self):
- e = self.add_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
+ e = self.request().create_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
content_format=u'text/rest')
self.assertEquals(e.printable_value('content'),
'<p>du <a class="reference" href="http://testing.fr/cubicweb/cwgroup/guests">*ReST*</a></p>\n')
@@ -312,7 +310,6 @@
self.assertEquals(e.printable_value('content'),
'<p>\ndu *texte*\n</p>')
e['title'] = 'zou'
- #e = self.etype_instance('Task')
e['content'] = '''\
a title
=======
@@ -333,9 +330,10 @@
def test_printable_value_bytes(self):
- e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
+ req = self.request()
+ e = req.create_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
data_encoding=u'ascii', data_name=u'toto.py')
- from cubicweb.common import mttransforms
+ from cubicweb import mttransforms
if mttransforms.HAS_PYGMENTS_TRANSFORMS:
self.assertEquals(e.printable_value('data'),
'''<div class="highlight"><pre><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="mi">1</span>
@@ -348,14 +346,15 @@
</pre>
''')
- e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
+ e = req.create_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
data_encoding=u'utf-8', data_name=u'toto.txt')
self.assertEquals(e.printable_value('data'),
u'<p><em>héhéhé</em></p>\n')
def test_printable_value_bad_html(self):
"""make sure we don't crash if we try to render invalid XHTML strings"""
- e = self.add_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
+ req = self.request()
+ e = req.create_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
content_format=u'text/html')
tidy = lambda x: x.replace('\n', '')
self.assertEquals(tidy(e.printable_value('content')),
@@ -388,7 +387,8 @@
def test_printable_value_bad_html_ms(self):
self.skip('fix soup2xhtml to handle this test')
- e = self.add_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
+ req = self.request()
+ e = req.create_entity('Card', title=u'bad html', content=u'<div>R&D<br>',
content_format=u'text/html')
tidy = lambda x: x.replace('\n', '')
e['content'] = u'<div x:foo="bar">ms orifice produces weird html</div>'
@@ -406,27 +406,28 @@
def test_fulltextindex(self):
- e = self.etype_instance('File')
+ e = self.vreg['etypes'].etype_class('File')(self.request())
e['description'] = 'du <em>html</em>'
e['description_format'] = 'text/html'
e['data'] = Binary('some <em>data</em>')
e['data_name'] = 'an html file'
e['data_format'] = 'text/html'
e['data_encoding'] = 'ascii'
- e.req.transaction_data = {} # XXX req should be a session
+ e._cw.transaction_data = {} # XXX req should be a session
self.assertEquals(set(e.get_words()),
set(['an', 'html', 'file', 'du', 'html', 'some', 'data']))
def test_nonregr_relation_cache(self):
- p1 = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
- p2 = self.add_entity('Personne', nom=u'toto')
+ req = self.request()
+ p1 = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ p2 = req.create_entity('Personne', nom=u'toto')
self.execute('SET X evaluee Y WHERE X nom "di mascio", Y nom "toto"')
self.assertEquals(p1.evaluee[0].nom, "toto")
self.failUnless(not p1.reverse_evaluee)
def test_complete_relation(self):
- session = self.session()
+ session = self.session
eid = session.unsafe_execute(
'INSERT TrInfo X: X comment "zou", X wf_info_for U, X from_state S1, X to_state S2 '
'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
@@ -446,26 +447,29 @@
self.failUnless(state is samestate)
def test_rest_path(self):
- note = self.add_entity('Note', type=u'z')
+ req = self.request()
+ note = req.create_entity('Note', type=u'z')
self.assertEquals(note.rest_path(), 'note/%s' % note.eid)
# unique attr
- tag = self.add_entity('Tag', name=u'x')
+ tag = req.create_entity('Tag', name=u'x')
self.assertEquals(tag.rest_path(), 'tag/x')
# test explicit rest_attr
- person = self.add_entity('Personne', prenom=u'john', nom=u'doe')
+ person = req.create_entity('Personne', prenom=u'john', nom=u'doe')
self.assertEquals(person.rest_path(), 'personne/doe')
# ambiguity test
- person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe')
+ person2 = req.create_entity('Personne', prenom=u'remi', nom=u'doe')
+ person.clear_all_caches()
self.assertEquals(person.rest_path(), 'personne/eid/%s' % person.eid)
self.assertEquals(person2.rest_path(), 'personne/eid/%s' % person2.eid)
# unique attr with None value (wikiid in this case)
- card1 = self.add_entity('Card', title=u'hop')
+ card1 = req.create_entity('Card', title=u'hop')
self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid)
- card2 = self.add_entity('Card', title=u'pod', wikiid=u'zob/i')
+ card2 = req.create_entity('Card', title=u'pod', wikiid=u'zob/i')
self.assertEquals(card2.rest_path(), 'card/zob%2Fi')
def test_set_attributes(self):
- person = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ req = self.request()
+ person = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
self.assertEquals(person.prenom, u'adrien')
self.assertEquals(person.nom, u'di mascio')
person.set_attributes(prenom=u'sylvain', nom=u'thénault')
@@ -474,7 +478,8 @@
self.assertEquals(person.nom, u'thénault')
def test_metainformation_and_external_absolute_url(self):
- note = self.add_entity('Note', type=u'z')
+ req = self.request()
+ note = req.create_entity('Note', type=u'z')
metainf = note.metainformation()
self.assertEquals(metainf, {'source': {'adapter': 'native', 'uri': 'system'}, 'type': u'Note', 'extid': None})
self.assertEquals(note.absolute_url(), 'http://testing.fr/cubicweb/note/%s' % note.eid)
@@ -484,14 +489,16 @@
self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/1234')
def test_absolute_url_empty_field(self):
- card = self.add_entity('Card', wikiid=u'', title=u'test')
+ req = self.request()
+ card = req.create_entity('Card', wikiid=u'', title=u'test')
self.assertEquals(card.absolute_url(),
'http://testing.fr/cubicweb/card/eid/%s' % card.eid)
def test_create_entity(self):
- p1 = self.add_entity('Personne', nom=u'fayolle', prenom=u'alexandre')
- p2 = self.add_entity('Personne', nom=u'campeas', prenom=u'aurelien')
- note = self.add_entity('Note', type=u'z')
+ req = self.request()
+ p1 = req.create_entity('Personne', nom=u'fayolle', prenom=u'alexandre')
+ p2 = req.create_entity('Personne', nom=u'campeas', prenom=u'aurelien')
+ note = req.create_entity('Note', type=u'z')
req = self.request()
p = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien',
connait=p1, evaluee=[p1, p2],
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_mail.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+"""unit tests for module cubicweb.mail
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+import os
+import sys
+
+from logilab.common.testlib import unittest_main
+from logilab.common.umessage import message_from_string
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.mail import format_mail
+
+
+def getlogin():
+ """avoid usinng os.getlogin() because of strange tty / stdin problems
+ (man 3 getlogin)
+ Another solution would be to use $LOGNAME, $USER or $USERNAME
+ """
+ if sys.platform != 'win32':
+ import pwd
+ return pwd.getpwuid(os.getuid())[0]
+ else:
+ return os.environ.get('USERNAME')
+
+
+class EmailTC(CubicWebTC):
+
+ def test_format_mail(self):
+ self.set_option('sender-addr', 'bim@boum.fr')
+ self.set_option('sender-name', 'BimBam')
+
+ mail = format_mail({'name': 'oim', 'email': 'oim@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou', u'bïjour',
+ config=self.config)
+ self.assertLinesEquals(mail.as_string(), """\
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+Subject: =?utf-8?q?b=C3=AFjour?=
+From: =?utf-8?q?oim?= <oim@logilab.fr>
+Reply-to: =?utf-8?q?oim?= <oim@logilab.fr>, =?utf-8?q?BimBam?= <bim@boum.fr>
+X-CW: data
+To: test@logilab.fr
+
+dW4gcGV0aXQgY8O2dWNvdQ==
+""")
+ msg = message_from_string(mail.as_string())
+ self.assertEquals(msg.get('subject'), u'bïjour')
+ self.assertEquals(msg.get('from'), u'oim <oim@logilab.fr>')
+ self.assertEquals(msg.get('to'), u'test@logilab.fr')
+ self.assertEquals(msg.get('reply-to'), u'oim <oim@logilab.fr>, BimBam <bim@boum.fr>')
+ self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou')
+
+
+ def test_format_mail_euro(self):
+ mail = format_mail({'name': u'oîm', 'email': u'oim@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
+ self.assertLinesEquals(mail.as_string(), """\
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: base64
+Subject: =?utf-8?b?YsOvam91ciDigqw=?=
+From: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
+Reply-to: =?utf-8?q?o=C3=AEm?= <oim@logilab.fr>
+To: test@logilab.fr
+
+dW4gcGV0aXQgY8O2dWNvdSDigqw=
+""")
+ msg = message_from_string(mail.as_string())
+ self.assertEquals(msg.get('subject'), u'bïjour €')
+ self.assertEquals(msg.get('from'), u'oîm <oim@logilab.fr>')
+ self.assertEquals(msg.get('to'), u'test@logilab.fr')
+ self.assertEquals(msg.get('reply-to'), u'oîm <oim@logilab.fr>')
+ self.assertEquals(msg.get_payload(decode=True), u'un petit cöucou €')
+
+
+ def test_format_mail_from_reply_to(self):
+ # no sender-name, sender-addr in the configuration
+ self.set_option('sender-name', '')
+ self.set_option('sender-addr', '')
+ msg = format_mail({'name': u'', 'email': u''},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ self.assertEquals(msg.get('from'), u'')
+ self.assertEquals(msg.get('reply-to'), None)
+ msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
+ msg = format_mail({'name': u'tutu', 'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €')
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <tutu@logilab.fr>')
+ # set sender name and address as expected
+ self.set_option('sender-name', 'cubicweb-test')
+ self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
+ # anonymous notification: no name and no email specified
+ msg = format_mail({'name': u'', 'email': u''},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'cubicweb-test <cubicweb-test@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'cubicweb-test <cubicweb-test@logilab.fr>')
+ # anonymous notification: only email specified
+ msg = format_mail({'email': u'tutu@logilab.fr'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'cubicweb-test <tutu@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'cubicweb-test <tutu@logilab.fr>, cubicweb-test <cubicweb-test@logilab.fr>')
+ # anonymous notification: only name specified
+ msg = format_mail({'name': u'tutu'},
+ ['test@logilab.fr'], u'un petit cöucou €', u'bïjour €',
+ config=self.config)
+ msg = message_from_string(msg.as_string())
+ self.assertEquals(msg.get('from'), u'tutu <cubicweb-test@logilab.fr>')
+ self.assertEquals(msg.get('reply-to'), u'tutu <cubicweb-test@logilab.fr>')
+
+
+
+if __name__ == '__main__':
+ unittest_main()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_migration.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,103 @@
+"""cubicweb.migration unit tests
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from os.path import abspath
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.migration import MigrationHelper, filter_scripts
+from cubicweb.server.migractions import ServerMigrationHelper
+
+
+class Schema(dict):
+ def has_entity(self, e_type):
+ return self.has_key(e_type)
+
+SMIGRDIR = abspath('data/server_migration') + '/'
+TMIGRDIR = abspath('data/migration') + '/'
+
+class MigrTestConfig(TestServerConfiguration):
+ verbosity = 0
+ def migration_scripts_dir(cls):
+ return SMIGRDIR
+
+ def cube_migration_scripts_dir(cls, cube):
+ return TMIGRDIR
+
+class MigrationToolsTC(TestCase):
+ def setUp(self):
+ self.config = MigrTestConfig('data')
+ from yams.schema import Schema
+ self.config.load_schema = lambda expand_cubes=False: Schema('test')
+ self.config.__class__.cubicweb_appobject_path = frozenset()
+ self.config.__class__.cube_appobject_path = frozenset()
+
+ def test_filter_scripts_base(self):
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
+ ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
+ ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
+ ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
+
+ def test_filter_scripts_for_mode(self):
+ config = CubicWebConfiguration('data')
+ config.verbosity = 0
+ self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
+ self.assertIsInstance(config.migration_handler(), MigrationHelper)
+ config = self.config
+ config.__class__.name = 'twisted'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+ config.__class__.name = 'repository'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
+ config.__class__.name = 'all-in-one'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+ config.__class__.name = 'repository'
+
+
+from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
+
+class BaseCreationTC(TestCase):
+
+ def test_db_creation(self):
+ """make sure database can be created"""
+ config = ApptestConfiguration('data')
+ source = config.sources()['system']
+ self.assertEquals(source['db-driver'], 'sqlite')
+ cleanup_sqlite(source['db-name'], removetemplate=True)
+ init_test_database(config=config)
+
+
+if __name__ == '__main__':
+ unittest_main()
--- a/test/unittest_rqlrewrite.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_rqlrewrite.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,7 +18,8 @@
config = TestServerConfiguration('data/rewrite')
config.bootstrap_cubes()
schema = config.load_schema()
-schema.add_relation_def(mock_object(subject='Card', name='in_state', object='State', cardinality='1*'))
+from yams.buildobjs import RelationDefinition
+schema.add_relation_def(RelationDefinition(subject='Card', name='in_state', object='State', cardinality='1*'))
rqlhelper = RQLHelper(schema, special_relations={'eid': 'uid',
'has_text': 'fti'})
--- a/test/unittest_rset.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_rset.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,5 +1,5 @@
# coding: utf-8
-"""unit tests for module cubicweb.common.utils
+"""unit tests for module cubicweb.utils
:organization: Logilab
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -10,7 +10,7 @@
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.selectors import traced_selection
from urlparse import urlsplit
@@ -55,7 +55,7 @@
-class ResultSetTC(EnvBasedTC):
+class ResultSetTC(CubicWebTC):
def setUp(self):
super(ResultSetTC, self).setUp()
@@ -100,12 +100,12 @@
'Any U,L where U is CWUser, U login L',
description=[['CWUser', 'String']] * 3)
rs.req = self.request()
- rs.vreg = self.env.vreg
+ rs.vreg = self.vreg
self.assertEquals(rs.limit(2).rows, [[12000, 'adim'], [13000, 'syt']])
rs2 = rs.limit(2, offset=1)
self.assertEquals(rs2.rows, [[13000, 'syt'], [14000, 'nico']])
- self.assertEquals(rs2.get_entity(0, 0).row, 0)
+ self.assertEquals(rs2.get_entity(0, 0).cw_row, 0)
self.assertEquals(rs.limit(2, offset=2).rows, [[14000, 'nico']])
self.assertEquals(rs.limit(2, offset=3).rows, [])
@@ -115,7 +115,7 @@
'Any U,L where U is CWUser, U login L',
description=[['CWUser', 'String']] * 3)
rs.req = self.request()
- rs.vreg = self.env.vreg
+ rs.vreg = self.vreg
def test_filter(entity):
return entity.login != 'nico'
@@ -140,7 +140,7 @@
'Any U,L where U is CWUser, U login L',
description=[['CWUser', 'String']] * 3)
rs.req = self.request()
- rs.vreg = self.env.vreg
+ rs.vreg = self.vreg
rs2 = rs.sorted_rset(lambda e:e['login'])
self.assertEquals(len(rs2), 3)
@@ -170,7 +170,7 @@
'D created_by U, D title T',
description=[['CWUser', 'String', 'String']] * 5)
rs.req = self.request()
- rs.vreg = self.env.vreg
+ rs.vreg = self.vreg
rsets = rs.split_rset(lambda e:e['login'])
self.assertEquals(len(rsets), 3)
@@ -212,7 +212,7 @@
self.assert_(rqlst1 is rqlst2)
def test_get_entity_simple(self):
- self.add_entity('CWUser', login=u'adim', upassword='adim',
+ self.request().create_entity('CWUser', login=u'adim', upassword='adim',
surname=u'di mascio', firstname=u'adrien')
e = self.entity('Any X,T WHERE X login "adim", X surname T')
self.assertEquals(e['surname'], 'di mascio')
@@ -224,21 +224,21 @@
self.assertEquals(pprelcachedict(e._related_cache), [])
def test_get_entity_advanced(self):
- self.add_entity('Bookmark', title=u'zou', path=u'/view')
+ self.request().create_entity('Bookmark', title=u'zou', path=u'/view')
self.execute('SET X bookmarked_by Y WHERE X is Bookmark, Y login "anon"')
rset = self.execute('Any X,Y,XT,YN WHERE X bookmarked_by Y, X title XT, Y login YN')
e = rset.get_entity(0, 0)
- self.assertEquals(e.row, 0)
- self.assertEquals(e.col, 0)
+ self.assertEquals(e.cw_row, 0)
+ self.assertEquals(e.cw_col, 0)
self.assertEquals(e['title'], 'zou')
self.assertRaises(KeyError, e.__getitem__, 'path')
self.assertEquals(e.view('text'), 'zou')
self.assertEquals(pprelcachedict(e._related_cache), [])
e = rset.get_entity(0, 1)
- self.assertEquals(e.row, 0)
- self.assertEquals(e.col, 1)
+ self.assertEquals(e.cw_row, 0)
+ self.assertEquals(e.cw_col, 1)
self.assertEquals(e['login'], 'anon')
self.assertRaises(KeyError, e.__getitem__, 'firstname')
self.assertEquals(pprelcachedict(e._related_cache),
@@ -262,7 +262,7 @@
[('in_state_subject', [seid])])
def test_get_entity_advanced_prefilled_cache(self):
- e = self.add_entity('Bookmark', title=u'zou', path=u'path')
+ e = self.request().create_entity('Bookmark', title=u'zou', path=u'path')
self.commit()
rset = self.execute('Any X,U,S,XT,UL,SN WHERE X created_by U, U in_state S, '
'X title XT, S name SN, U login UL, X eid %s' % e.eid)
@@ -295,7 +295,7 @@
def test_get_entity_union(self):
- e = self.add_entity('Bookmark', title=u'manger', path=u'path')
+ e = self.request().create_entity('Bookmark', title=u'manger', path=u'path')
rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
'((Any X,N WHERE X is Bookmark, X title N)'
' UNION '
@@ -304,20 +304,20 @@
('Bookmark', 'manger'), ('CWGroup', 'owners'),
('CWGroup', 'users'))
for entity in rset.entities(): # test get_entity for each row actually
- etype, n = expected[entity.row]
- self.assertEquals(entity.id, etype)
+ etype, n = expected[entity.cw_row]
+ self.assertEquals(entity.__regid__, etype)
attr = etype == 'Bookmark' and 'title' or 'name'
self.assertEquals(entity[attr], n)
def test_related_entity_optional(self):
- e = self.add_entity('Bookmark', title=u'aaaa', path=u'path')
+ e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
rset = self.execute('Any B,U,L WHERE B bookmarked_by U?, U login L')
entity, rtype = rset.related_entity(0, 2)
self.assertEquals(entity, None)
self.assertEquals(rtype, None)
def test_related_entity_union_subquery(self):
- e = self.add_entity('Bookmark', title=u'aaaa', path=u'path')
+ e = self.request().create_entity('Bookmark', title=u'aaaa', path=u'path')
rset = self.execute('Any X,N ORDERBY N WITH X,N BEING '
'((Any X,N WHERE X is CWGroup, X name N)'
' UNION '
@@ -326,7 +326,7 @@
self.assertEquals(entity.eid, e.eid)
self.assertEquals(rtype, 'title')
entity, rtype = rset.related_entity(1, 1)
- self.assertEquals(entity.id, 'CWGroup')
+ self.assertEquals(entity.__regid__, 'CWGroup')
self.assertEquals(rtype, 'name')
#
rset = self.execute('Any X,N ORDERBY N WHERE X is Bookmark WITH X,N BEING '
@@ -345,6 +345,14 @@
self.assertEquals(entity.eid, e.eid)
self.assertEquals(rtype, 'title')
+ def test_related_entity_trap_subquery(self):
+ req = self.request()
+ req.create_entity('Bookmark', title=u'test bookmark', path=u'')
+ self.execute('SET B bookmarked_by U WHERE U login "admin"')
+ rset = self.execute('Any B,T,L WHERE B bookmarked_by U, U login L '
+ 'WITH B,T BEING (Any B,T WHERE B is Bookmark, B title T)')
+ rset.related_entity(0, 2)
+
def test_entities(self):
rset = self.execute('Any U,G WHERE U in_group G')
# make sure we have at least one element
--- a/test/unittest_rtags.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_rtags.py Mon Feb 08 13:35:07 2010 +0100
@@ -39,7 +39,7 @@
# __rtags__ = {
# ('evaluee', 'Note', 'subject') : set(('inlineview',)),
# }
-# self.vreg.register_appobject_class(Personne2)
+# self.vreg.register(Personne2)
# rtags = Personne2.rtags
# self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
# self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
--- a/test/unittest_schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -21,7 +21,7 @@
from cubicweb.schema import (
CubicWebSchema, CubicWebEntitySchema, CubicWebSchemaLoader,
RQLConstraint, RQLUniqueConstraint, RQLVocabularyConstraint,
- ERQLExpression, RRQLExpression,
+ RQLExpression, ERQLExpression, RRQLExpression,
normalize_expression, order_eschemas, guess_rrqlexpr_mainvars)
from cubicweb.devtools import TestServerConfiguration as TestConfiguration
@@ -46,7 +46,7 @@
schema = CubicWebSchema('Test Schema')
enote = schema.add_entity_type(EntityType('Note'))
eaffaire = schema.add_entity_type(EntityType('Affaire'))
-eperson = schema.add_entity_type(EntityType('Personne', permissions=PERSONNE_PERMISSIONS))
+eperson = schema.add_entity_type(EntityType('Personne', __permissions__=PERSONNE_PERMISSIONS))
esociete = schema.add_entity_type(EntityType('Societe'))
RELS = (
@@ -75,12 +75,13 @@
for rel in RELS:
_from, _type, _to = rel.split()
if not _type.lower() in done:
- if _type == 'concerne':
- schema.add_relation_type(RelationType(_type, permissions=CONCERNE_PERMISSIONS))
- else:
- schema.add_relation_type(RelationType(_type))
+ schema.add_relation_type(RelationType(_type))
done[_type.lower()] = True
- schema.add_relation_def(RelationDefinition(_from, _type, _to))
+ if _type == 'concerne':
+ schema.add_relation_def(RelationDefinition(_from, _type, _to,
+ __permissions__=CONCERNE_PERMISSIONS))
+ else:
+ schema.add_relation_def(RelationDefinition(_from, _type, _to))
class CubicWebSchemaTC(TestCase):
@@ -108,23 +109,21 @@
self.assertEqual(schema.rschema('concerne').type, 'concerne')
def test_entity_perms(self):
- eperson.set_default_groups()
self.assertEqual(eperson.get_groups('read'), set(('managers', 'users', 'guests')))
self.assertEqual(eperson.get_groups('update'), set(('managers', 'owners',)))
self.assertEqual(eperson.get_groups('delete'), set(('managers', 'owners')))
self.assertEqual(eperson.get_groups('add'), set(('managers',)))
self.assertEqual([str(e) for e in eperson.get_rqlexprs('add')],
['Any X WHERE X travaille S, S owned_by U, X eid %(x)s, U eid %(u)s'])
- eperson.set_groups('read', ('managers',))
+ eperson.set_action_permissions('read', ('managers',))
self.assertEqual(eperson.get_groups('read'), set(('managers',)))
def test_relation_perms(self):
- rconcerne = schema.rschema('concerne')
- rconcerne.set_default_groups()
+ rconcerne = schema.rschema('concerne').rdef('Personne', 'Societe')
self.assertEqual(rconcerne.get_groups('read'), set(('managers', 'users', 'guests')))
self.assertEqual(rconcerne.get_groups('delete'), set(('managers',)))
self.assertEqual(rconcerne.get_groups('add'), set(('managers', )))
- rconcerne.set_groups('read', ('managers',))
+ rconcerne.set_action_permissions('read', ('managers',))
self.assertEqual(rconcerne.get_groups('read'), set(('managers',)))
self.assertEqual([str(e) for e in rconcerne.get_rqlexprs('add')],
['Any S,U WHERE U has_update_permission S, S eid %(s)s, U eid %(u)s'])
@@ -204,7 +203,7 @@
'read_permission', 'relation_type', 'require_group',
- 'specializes', 'state_of', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symetric', 'synopsis',
+ 'specializes', 'state_of', 'subworkflow', 'subworkflow_exit', 'subworkflow_state', 'surname', 'symmetric', 'synopsis',
'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type',
@@ -229,9 +228,9 @@
self.assertListEquals(rels, ['bookmarked_by', 'created_by', 'for_user',
'identity', 'owned_by', 'wf_info_for'])
rschema = schema.rschema('relation_type')
- properties = rschema.rproperties('CWAttribute', 'CWRType')
- self.assertEquals(properties['cardinality'], '1*')
- constraints = properties['constraints']
+ properties = rschema.rdef('CWAttribute', 'CWRType')
+ self.assertEquals(properties.cardinality, '1*')
+ constraints = properties.constraints
self.failUnlessEqual(len(constraints), 1, constraints)
constraint = constraints[0]
self.failUnless(isinstance(constraint, RQLConstraint))
@@ -248,6 +247,7 @@
self.loader = CubicWebSchemaLoader()
self.loader.defined = {}
self.loader.loaded_files = []
+ self.loader.post_build_callbacks = []
self.loader._pyreader = PyFileReader(self.loader)
def _test(self, schemafile, msg):
@@ -260,13 +260,13 @@
self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on an entity type, use an ERQLExpression (ToTo)")
def test_erqlexpr_on_rtype(self):
- self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on a relation type, use a RRQLExpression (toto)")
+ self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
def test_rqlexpr_on_rtype_read(self):
- self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of a relation type (toto)")
+ self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu")
def test_rrqlexpr_on_attr(self):
- self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on a final relation type (eg attribute relation), use an ERQLExpression (attr)")
+ self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
class NormalizeExpressionTC(TestCase):
@@ -275,10 +275,16 @@
self.assertEquals(normalize_expression('X bla Y,Y blur Z , Z zigoulou X '),
'X bla Y, Y blur Z, Z zigoulou X')
+class RQLExpressionTC(TestCase):
+ def test_comparison(self):
+ self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
+ self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
+
class GuessRrqlExprMainVarsTC(TestCase):
def test_exists(self):
mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3)'))
self.assertEquals(mainvars, 'O')
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_selectors.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_selectors.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,7 +8,7 @@
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.appobject import Selector, AndSelector, OrSelector
from cubicweb.selectors import implements, match_user_groups
from cubicweb.interfaces import IDownloadable
@@ -88,7 +88,7 @@
self.assertIs(csel.search_selector(implements), sel)
-class ImplementsSelectorTC(EnvBasedTC):
+class ImplementsSelectorTC(CubicWebTC):
def test_etype_priority(self):
req = self.request()
cls = self.vreg['etypes'].etype_class('File')
@@ -103,34 +103,38 @@
self.failIf(implements('Societe').score_class(cls, self.request()))
-class MatchUserGroupsTC(EnvBasedTC):
+class MatchUserGroupsTC(CubicWebTC):
def test_owners_group(self):
"""tests usage of 'owners' group with match_user_group"""
class SomeAction(action.Action):
- id = 'yo'
+ __regid__ = 'yo'
category = 'foo'
__select__ = match_user_groups('owners')
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(SomeAction)
+ self.vreg.register(SomeAction)
+ SomeAction.__registered__(self.vreg['actions'])
self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
try:
# login as a simple user
self.create_user('john')
self.login('john')
# it should not be possible to use SomeAction not owned objects
- rset, req = self.env.get_rset_and_req('Any G WHERE G is CWGroup, G name "managers"')
+ req = self.request()
+ rset = req.execute('Any G WHERE G is CWGroup, G name "managers"')
self.failIf('yo' in dict(self.pactions(req, rset)))
# insert a new card, and check that we can use SomeAction on our object
self.execute('INSERT Card C: C title "zoubidou"')
self.commit()
- rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+ req = self.request()
+ rset = req.execute('Card C WHERE C title "zoubidou"')
self.failUnless('yo' in dict(self.pactions(req, rset)), self.pactions(req, rset))
# make sure even managers can't use the action
self.restore_connection()
- rset, req = self.env.get_rset_and_req('Card C WHERE C title "zoubidou"')
+ req = self.request()
+ rset = req.execute('Card C WHERE C title "zoubidou"')
self.failIf('yo' in dict(self.pactions(req, rset)))
finally:
- del self.vreg[SomeAction.__registry__][SomeAction.id]
+ del self.vreg[SomeAction.__registry__][SomeAction.__regid__]
if __name__ == '__main__':
unittest_main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_uilib.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+"""unittests for cubicweb.uilib
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+__docformat__ = "restructuredtext en"
+
+from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.tree import Node
+
+from cubicweb import uilib
+
+class UILIBTC(TestCase):
+
+ def test_remove_tags(self):
+ """make sure remove_tags remove all tags"""
+ data = [
+ ('<h1>Hello</h1>', 'Hello'),
+ ('<h1>Hello <a href="foo/bar"><b>s</b>pam</a></h1>', 'Hello spam'),
+ ('<br>Hello<img src="doh.png"/>', 'Hello'),
+ ('<p></p>', ''),
+ ]
+ for text, expected in data:
+ got = uilib.remove_html_tags(text)
+ self.assertEquals(got, expected)
+
+ def test_fallback_safe_cut(self):
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 4), u'ab c...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">cd</a>', 5), u'ab <a href="hello">cd</a>')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a>', 4), u'ab &...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">&d</a> ef', 5), u'ab &d...')
+ self.assertEquals(uilib.fallback_safe_cut(u'ab <a href="hello">ìd</a>', 4), u'ab ì...')
+ self.assertEquals(uilib.fallback_safe_cut(u'& <a href="hello">&d</a> ef', 4), u'& &d...')
+
+ def test_lxml_safe_cut(self):
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 4), u'<p>aaa</p><div>a...</div>')
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div> ef', 7), u'<p>aaa</p><div>aaad</div>...')
+ self.assertEquals(uilib.safe_cut(u'aaa<div>aaad</div>', 7), u'<p>aaa</p><div>aaad</div>')
+ # Missing ellipsis due to space management but we don't care
+ self.assertEquals(uilib.safe_cut(u'ab <a href="hello">&d</a>', 4), u'<p>ab <a href="hello">&...</a></p>')
+
+ def test_cut(self):
+ """tests uilib.cut() behaviour"""
+ data = [
+ ('hello', 'hello'),
+ ('hello world', 'hello wo...'),
+ ("hell<b>O'</b> world", "hell<b>O..."),
+ ]
+ for text, expected in data:
+ got = uilib.cut(text, 8)
+ self.assertEquals(got, expected)
+
+ def test_text_cut(self):
+ """tests uilib.text_cut() behaviour with no text"""
+ data = [('',''),
+ ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur.""",
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, \
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo \
+consequat."),
+ ("""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum
+""",
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \
+tempor incididunt ut labore et dolore magna aliqua Ut enim ad minim veniam, \
+quis nostrud exercitation ullamco laboris nisi"),
+ ]
+ for text, expected in data:
+ got = uilib.text_cut(text, 30)
+ self.assertEquals(got, expected)
+
+if __name__ == '__main__':
+ unittest_main()
+
--- a/test/unittest_utils.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_utils.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,4 @@
-"""unit tests for module cubicweb.common.utils
+"""unit tests for module cubicweb.utils
:organization: Logilab
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
--- a/test/unittest_vregistry.py Mon Feb 08 13:33:09 2010 +0100
+++ b/test/unittest_vregistry.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
from cubicweb.appobject import AppObject
from cubicweb.cwvreg import CubicWebVRegistry, UnknownProperty
from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.interfaces import IMileStone
from cubes.card.entities import Card
@@ -32,14 +32,6 @@
config.bootstrap_cubes()
self.vreg.schema = config.load_schema()
- def test___selectors__compat(self):
- myselector1 = lambda *args: 1
- myselector2 = lambda *args: 1
- class AnAppObject(AppObject):
- __selectors__ = (myselector1, myselector2)
- AnAppObject.build___select__()
- self.assertEquals(AnAppObject.__select__(AnAppObject), 2)
-
def test_load_interface_based_vojects(self):
self.vreg.init_registration([WEBVIEWSDIR])
self.vreg.load_file(join(BASE, 'entities', '__init__.py'), 'cubicweb.entities.__init__')
@@ -49,6 +41,7 @@
self.vreg.initialization_completed()
self.assertEquals(len(self.vreg['views']['primary']), 1)
+
def test_load_subinterface_based_appobjects(self):
self.vreg.reset()
self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')])
@@ -58,7 +51,7 @@
__implements__ = (IMileStone,)
self.vreg.reset()
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(MyCard)
+ self.vreg.register(MyCard)
self.vreg.register_objects([join(BASE, 'entities', '__init__.py'),
join(BASE, 'web', 'views', 'iprogress.py')])
# check progressbar isn't kicked
@@ -69,12 +62,13 @@
self.failUnless(self.vreg.property_info('system.version.cubicweb'))
self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key')
-class CWVregTC(EnvBasedTC):
+
+class CWVregTC(CubicWebTC):
def test_property_default_overriding(self):
# see data/views.py
from cubicweb.web.views.xmlrss import RSSIconBox
- self.assertEquals(self.vreg.property_info(RSSIconBox.propkey('visible'))['default'], True)
+ self.assertEquals(self.vreg.property_info(RSSIconBox._cwpropkey('visible'))['default'], True)
if __name__ == '__main__':
unittest_main()
--- a/toolsutils.py Mon Feb 08 13:33:09 2010 +0100
+++ b/toolsutils.py Mon Feb 08 13:35:07 2010 +0100
@@ -27,6 +27,9 @@
from cubicweb import warning
from cubicweb import ConfigurationError, ExecutionError
+def underline_title(title, car='-'):
+ return title+'\n'+(car*len(title))
+
def iter_dir(directory, condition_file=None, ignore=()):
"""iterate on a directory"""
for sub in listdir(directory):
@@ -247,7 +250,7 @@
def main_run(args, doc):
"""command line tool"""
try:
- base_main_run(args, doc)
+ base_main_run(args, doc, copyright=None)
except ConfigurationError, err:
print 'ERROR: ', err
sys.exit(1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/uilib.py Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,383 @@
+# -*- coding: utf-8 -*-
+"""user interface libraries
+
+contains some functions designed to help implementation of cubicweb user interface
+
+:organization: Logilab
+:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+import csv
+import re
+from StringIO import StringIO
+
+from logilab.mtconverter import xml_escape, html_unescape
+from logilab.common.date import ustrftime
+
+
+def rql_for_eid(eid):
+ """return the rql query necessary to fetch entity with the given eid. This
+ function should only be used to generate link with rql inside, not to give
+ to cursor.execute (in which case you won't benefit from rql cache).
+
+ :Parameters:
+ - `eid`: the eid of the entity we should search
+ :rtype: str
+ :return: the rql query
+ """
+ return 'Any X WHERE X eid %s' % eid
+
+
+def printable_value(req, attrtype, value, props=None, displaytime=True):
+ """return a displayable value (i.e. unicode string)"""
+ if value is None or attrtype == 'Bytes':
+ return u''
+ if attrtype == 'String':
+ # don't translate empty value if you don't want strange results
+ if props is not None and value and props.get('internationalizable'):
+ return req._(value)
+ return value
+ if attrtype == 'Date':
+ return ustrftime(value, req.property_value('ui.date-format'))
+ if attrtype == 'Time':
+ return ustrftime(value, req.property_value('ui.time-format'))
+ if attrtype == 'Datetime':
+ if displaytime:
+ return ustrftime(value, req.property_value('ui.datetime-format'))
+ return ustrftime(value, req.property_value('ui.date-format'))
+ if attrtype == 'Boolean':
+ if value:
+ return req._('yes')
+ return req._('no')
+ if attrtype == 'Float':
+ value = req.property_value('ui.float-format') % value
+ return unicode(value)
+
+
+# text publishing #############################################################
+
+try:
+ from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
+except ImportError:
+ def rest_publish(entity, data):
+ """default behaviour if docutils was not found"""
+ return xml_escape(data)
+
+TAG_PROG = re.compile(r'</?.*?>', re.U)
+def remove_html_tags(text):
+ """Removes HTML tags from text
+
+ >>> remove_html_tags('<td>hi <a href="http://www.google.fr">world</a></td>')
+ 'hi world'
+ >>>
+ """
+ return TAG_PROG.sub('', text)
+
+
+REF_PROG = re.compile(r"<ref\s+rql=([\'\"])([^\1]*?)\1\s*>([^<]*)</ref>", re.U)
+def _subst_rql(view, obj):
+ delim, rql, descr = obj.groups()
+ return u'<a href="%s">%s</a>' % (view._cw.build_url(rql=rql), descr)
+
+def html_publish(view, text):
+ """replace <ref rql=''> links by <a href="...">"""
+ if not text:
+ return u''
+ return REF_PROG.sub(lambda obj, view=view:_subst_rql(view, obj), text)
+
+# fallback implementation, nicer one defined below if lxml is available
+def soup2xhtml(data, encoding):
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ return u'\n'.join(data.splitlines())
+
+# fallback implementation, nicer one defined below if lxml> 2.0 is available
+def safe_cut(text, length):
+ """returns a string of length <length> based on <text>, removing any html
+ tags from given text if cut is necessary."""
+ if text is None:
+ return u''
+ noenttext = html_unescape(text)
+ text_nohtml = remove_html_tags(noenttext)
+ # try to keep html tags if text is short enough
+ if len(text_nohtml) <= length:
+ return text
+ # else if un-tagged text is too long, cut it
+ return xml_escape(text_nohtml[:length] + u'...')
+
+fallback_safe_cut = safe_cut
+
+
+try:
+ from lxml import etree
+except (ImportError, AttributeError):
+ # gae environment: lxml not available
+ pass
+else:
+
+ def soup2xhtml(data, encoding):
+ """tidy (at least try) html soup and return the result
+ Note: the function considers a string with no surrounding tag as valid
+ if <div>`data`</div> can be parsed by an XML parser
+ """
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ data = u'\n'.join(data.splitlines())
+ # XXX lxml 1.1 support still needed ?
+ xmltree = etree.HTML('<div>%s</div>' % data)
+ # NOTE: lxml 1.1 (etch platforms) doesn't recognize
+ # the encoding=unicode parameter (lxml 2.0 does), this is
+ # why we specify an encoding and re-decode to unicode later
+ body = etree.tostring(xmltree[0], encoding=encoding)
+ # remove <body> and </body> and decode to unicode
+ return body[11:-13].decode(encoding)
+
+ if hasattr(etree.HTML('<div>test</div>'), 'iter'):
+
+ def safe_cut(text, length):
+ """returns an html document of length <length> based on <text>,
+ and cut is necessary.
+ """
+ if text is None:
+ return u''
+ dom = etree.HTML(text)
+ curlength = 0
+ add_ellipsis = False
+ for element in dom.iter():
+ if curlength >= length:
+ parent = element.getparent()
+ parent.remove(element)
+ if curlength == length and (element.text or element.tail):
+ add_ellipsis = True
+ else:
+ if element.text is not None:
+ element.text = cut(element.text, length - curlength)
+ curlength += len(element.text)
+ if element.tail is not None:
+ if curlength < length:
+ element.tail = cut(element.tail, length - curlength)
+ curlength += len(element.tail)
+ elif curlength == length:
+ element.tail = '...'
+ else:
+ element.tail = ''
+ text = etree.tounicode(dom[0])[6:-7] # remove wrapping <body></body>
+ if add_ellipsis:
+ return text + u'...'
+ return text
+
+def text_cut(text, nbwords=30, gotoperiod=True):
+ """from the given plain text, return a text with at least <nbwords> words,
+ trying to go to the end of the current sentence.
+
+ :param nbwords: the minimum number of words required
+ :param gotoperiod: specifies if the function should try to go to
+ the first period after the cut (i.e. finish
+ the sentence if possible)
+
+ Note that spaces are normalized.
+ """
+ if text is None:
+ return u''
+ words = text.split()
+ text = u' '.join(words) # normalize spaces
+ textlength = minlength = len(' '.join(words[:nbwords]))
+ if gotoperiod:
+ textlength = text.find('.', minlength) + 1
+ if textlength == 0: # no period found
+ textlength = minlength
+ return text[:textlength]
+
+def cut(text, length):
+ """returns a string of a maximum length <length> based on <text>
+ (approximatively, since if text has been cut, '...' is added to the end of the string,
+ resulting in a string of len <length> + 3)
+ """
+ if text is None:
+ return u''
+ if len(text) <= length:
+ return text
+ # else if un-tagged text is too long, cut it
+ return text[:length] + u'...'
+
+
+
+# HTML generation helper functions ############################################
+
+HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
+ 'img', 'area', 'input', 'col'))
+
+def sgml_attributes(attrs):
+ return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
+ for attr, value in sorted(attrs.items())
+ if value is not None)
+
+def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
+ """generation of a simple sgml tag (eg without children tags) easier
+
+ content and attri butes will be escaped
+ """
+ value = u'<%s' % tag
+ if attrs:
+ try:
+ attrs['class'] = attrs.pop('klass')
+ except KeyError:
+ pass
+ value += u' ' + sgml_attributes(attrs)
+ if content:
+ if escapecontent:
+ content = xml_escape(unicode(content))
+ value += u'>%s</%s>' % (content, tag)
+ else:
+ if tag in HTML4_EMPTY_TAGS:
+ value += u' />'
+ else:
+ value += u'></%s>' % tag
+ return value
+
+def tooltipize(text, tooltip, url=None):
+ """make an HTML tooltip"""
+ url = url or '#'
+ return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text)
+
+def toggle_action(nodeid):
+ """builds a HTML link that uses the js toggleVisibility function"""
+ return u"javascript: toggleVisibility('%s')" % nodeid
+
+def toggle_link(nodeid, label):
+ """builds a HTML link that uses the js toggleVisibility function"""
+ return u'<a href="%s">%s</a>' % (toggle_action(nodeid), label)
+
+
+def ureport_as_html(layout):
+ from logilab.common.ureports import HTMLWriter
+ formater = HTMLWriter(True)
+ stream = StringIO() #UStringIO() don't want unicode assertion
+ formater.format(layout, stream)
+ res = stream.getvalue()
+ if isinstance(res, str):
+ res = unicode(res, 'UTF8')
+ return res
+
+# traceback formatting ########################################################
+
+import traceback
+
+def rest_traceback(info, exception):
+ """return a ReST formated traceback"""
+ res = [u'Traceback\n---------\n::\n']
+ for stackentry in traceback.extract_tb(info[2]):
+ res.append(u'\tFile %s, line %s, function %s' % tuple(stackentry[:3]))
+ if stackentry[3]:
+ res.append(u'\t %s' % stackentry[3].decode('utf-8', 'replace'))
+ res.append(u'\n')
+ try:
+ res.append(u'\t Error: %s\n' % exception)
+ except:
+ pass
+ return u'\n'.join(res)
+
+
+def html_traceback(info, exception, title='',
+ encoding='ISO-8859-1', body=''):
+ """ return an html formatted traceback from python exception infos.
+ """
+ tcbk = info[2]
+ stacktb = traceback.extract_tb(tcbk)
+ strings = []
+ if body:
+ strings.append(u'<div class="error_body">')
+ # FIXME
+ strings.append(body)
+ strings.append(u'</div>')
+ if title:
+ strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
+ try:
+ strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
+ except UnicodeError:
+ pass
+ strings.append(u'<div class="error_traceback">')
+ for index, stackentry in enumerate(stacktb):
+ strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
+ u'<b class="line">%s</b>, <b>function</b> '
+ u'<b class="function">%s</b>:<br/>'%(
+ xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
+ if stackentry[3]:
+ string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
+ strings.append(u'  %s<br/>\n' % (string))
+ # add locals info for each entry
+ try:
+ local_context = tcbk.tb_frame.f_locals
+ html_info = []
+ chars = 0
+ for name, value in local_context.iteritems():
+ value = xml_escape(repr(value))
+ info = u'<span class="name">%s</span>=%s, ' % (name, value)
+ line_length = len(name) + len(value)
+ chars += line_length
+ # 150 is the result of *years* of research ;-) (CSS might be helpful here)
+ if chars > 150:
+ info = u'<br/>' + info
+ chars = line_length
+ html_info.append(info)
+ boxid = 'ctxlevel%d' % index
+ strings.append(u'[%s]' % toggle_link(boxid, '+'))
+ strings.append(u'<div id="%s" class="pycontext hidden">%s</div>' %
+ (boxid, ''.join(html_info)))
+ tcbk = tcbk.tb_next
+ except Exception:
+ pass # doesn't really matter if we have no context info
+ strings.append(u'</div>')
+ return '\n'.join(strings)
+
+# csv files / unicode support #################################################
+
+class UnicodeCSVWriter:
+ """proxies calls to csv.writer.writerow to be able to deal with unicode"""
+
+ def __init__(self, wfunc, encoding, **kwargs):
+ self.writer = csv.writer(self, **kwargs)
+ self.wfunc = wfunc
+ self.encoding = encoding
+
+ def write(self, data):
+ self.wfunc(data)
+
+ def writerow(self, row):
+ csvrow = []
+ for elt in row:
+ if isinstance(elt, unicode):
+ csvrow.append(elt.encode(self.encoding))
+ else:
+ csvrow.append(str(elt))
+ self.writer.writerow(csvrow)
+
+ def writerows(self, rows):
+ for row in rows:
+ self.writerow(row)
+
+
+# some decorators #############################################################
+
+class limitsize(object):
+ def __init__(self, maxsize):
+ self.maxsize = maxsize
+
+ def __call__(self, function):
+ def newfunc(*args, **kwargs):
+ ret = function(*args, **kwargs)
+ if isinstance(ret, basestring):
+ return ret[:self.maxsize]
+ return ret
+ return newfunc
+
+
+def htmlescape(function):
+ def newfunc(*args, **kwargs):
+ ret = function(*args, **kwargs)
+ assert isinstance(ret, basestring)
+ return xml_escape(ret)
+ return newfunc
--- a/utils.py Mon Feb 08 13:33:09 2010 +0100
+++ b/utils.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,106 +12,15 @@
import locale
import sys
import decimal
-import datetime as pydatetime
+import datetime
from md5 import md5
-from datetime import datetime, timedelta, date
-from time import time, mktime
+from time import time
from random import randint, seed
-from calendar import monthrange
+import decimal
# initialize random seed from current time
seed()
-try:
- strptime = datetime.strptime
-except AttributeError: # py < 2.5
- from time import strptime as time_strptime
- def strptime(value, format):
- return datetime(*time_strptime(value, format)[:6])
-def todate(somedate):
- """return a date from a date (leaving unchanged) or a datetime"""
- if isinstance(somedate, datetime):
- return date(somedate.year, somedate.month, somedate.day)
- assert isinstance(somedate, date), repr(somedate)
- return somedate
-
-def todatetime(somedate):
- """return a date from a date (leaving unchanged) or a datetime"""
- # take care, datetime is a subclass of date
- if isinstance(somedate, datetime):
- return somedate
- assert isinstance(somedate, date), repr(somedate)
- return datetime(somedate.year, somedate.month, somedate.day)
-
-def datetime2ticks(date):
- return mktime(date.timetuple()) * 1000
-
-ONEDAY = timedelta(days=1)
-ONEWEEK = timedelta(days=7)
-
-def days_in_month(date_):
- return monthrange(date_.year, date_.month)[1]
-
-def days_in_year(date_):
- feb = pydatetime.date(date_.year, 2, 1)
- if days_in_month(feb) == 29:
- return 366
- else:
- return 365
-
-def previous_month(date_, nbmonth=1):
- while nbmonth:
- date_ = first_day(date_) - ONEDAY
- nbmonth -= 1
- return date_
-
-def next_month(date_, nbmonth=1):
- while nbmonth:
- date_ = last_day(date_) + ONEDAY
- nbmonth -= 1
- return date_
-
-def first_day(date_):
- return date(date_.year, date_.month, 1)
-
-def last_day(date_):
- return date(date_.year, date_.month, days_in_month(date_))
-
-def date_range(begin, end, incday=None, incmonth=None):
- """yields each date between begin and end
- :param begin: the start date
- :param end: the end date
- :param incr: the step to use to iterate over dates. Default is
- one day.
- :param include: None (means no exclusion) or a function taking a
- date as parameter, and returning True if the date
- should be included.
- """
- assert not (incday and incmonth)
- begin = todate(begin)
- end = todate(end)
- if incmonth:
- while begin < end:
- begin = next_month(begin, incmonth)
- yield begin
- else:
- if not incday:
- incr = ONEDAY
- else:
- incr = timedelta(incday)
- while begin <= end:
- yield begin
- begin += incr
-
-def ustrftime(date, fmt='%Y-%m-%d'):
- """like strftime, but returns a unicode string instead of an encoded
- string which' may be problematic with localized date.
-
- encoding is guessed by locale.getpreferredencoding()
- """
- # date format may depend on the locale
- encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
- return unicode(date.strftime(str(fmt)), encoding)
if sys.version_info[:2] < (2, 5):
@@ -406,13 +315,13 @@
class CubicWebJsonEncoder(JSONEncoder):
"""define a simplejson encoder to be able to encode yams std types"""
def default(self, obj):
- if isinstance(obj, pydatetime.datetime):
+ if isinstance(obj, datetime.datetime):
return obj.strftime('%Y/%m/%d %H:%M:%S')
- elif isinstance(obj, pydatetime.date):
+ elif isinstance(obj, datetime.date):
return obj.strftime('%Y/%m/%d')
- elif isinstance(obj, pydatetime.time):
+ elif isinstance(obj, datetime.time):
return obj.strftime('%H:%M:%S')
- elif isinstance(obj, pydatetime.timedelta):
+ elif isinstance(obj, datetime.timedelta):
return (obj.days * 24 * 60 * 60) + obj.seconds
elif isinstance(obj, decimal.Decimal):
return float(obj)
--- a/view.py Mon Feb 08 13:33:09 2010 +0100
+++ b/view.py Mon Feb 08 13:35:07 2010 +0100
@@ -21,7 +21,6 @@
from cubicweb import NotAnEntity
from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
-from cubicweb.selectors import require_group_compat, accepts_compat
from cubicweb.appobject import AppObject
from cubicweb.utils import UStringIO, HTMLStream
from cubicweb.schema import display_name
@@ -97,7 +96,6 @@
time to a write function to use.
"""
__registry__ = 'views'
- registered = require_group_compat(AppObject.registered)
templatable = True
# content_type = 'application/xhtml+xml' # text/xhtml'
@@ -119,12 +117,12 @@
return True
def __init__(self, req=None, rset=None, **kwargs):
- super(View, self).__init__(req, rset, **kwargs)
+ super(View, self).__init__(req, rset=rset, **kwargs)
self.w = None
@property
def content_type(self):
- return self.req.html_content_type()
+ return self._cw.html_content_type()
def set_stream(self, w=None):
if self.w is not None:
@@ -167,7 +165,20 @@
if stream is not None:
return self._stream.getvalue()
- dispatch = deprecated('.dispatch is deprecated, use .render')(render)
+ def tal_render(self, template, variables):
+ """render a precompiled page template with variables in the given
+ dictionary as context
+ """
+ from cubicweb.ext.tal import CubicWebContext
+ context = CubicWebContext()
+ context.update({'self': self, 'rset': self.cw_rset, '_' : self._cw._,
+ 'req': self._cw, 'user': self._cw.user})
+ context.update(variables)
+ output = UStringIO()
+ template.expand(context, output)
+ return output.getvalue()
+
+ dispatch = deprecated('[3.4] .dispatch is deprecated, use .render')(render)
# should default .call() method add a <div classs="section"> around each
# rset item
@@ -180,15 +191,15 @@
Views applicable on None result sets have to override this method
"""
- rset = self.rset
+ rset = self.cw_rset
if rset is None:
- raise NotImplementedError, self
+ raise NotImplementedError, (self, "an rset is required")
wrap = self.templatable and len(rset) > 1 and self.add_div_section
# XXX propagate self.extra_kwars?
for i in xrange(len(rset)):
if wrap:
self.w(u'<div class="section">')
- self.wview(self.id, rset, row=i, **kwargs)
+ self.wview(self.__regid__, rset, row=i, **kwargs)
if wrap:
self.w(u"</div>")
@@ -206,23 +217,23 @@
return True
def is_primary(self):
- return self.extra_kwargs.get('is_primary', self.id == 'primary')
+ return self.cw_extra_kwargs.get('is_primary', self.__regid__ == 'primary')
def url(self):
"""return the url associated with this view. Should not be
necessary for non linkable views, but a default implementation
is provided anyway.
"""
- rset = self.rset
+ rset = self.cw_rset
if rset is None:
- return self.build_url('view', vid=self.id)
+ return self._cw.build_url('view', vid=self.__regid__)
coltypes = rset.column_types(0)
if len(coltypes) == 1:
etype = iter(coltypes).next()
- if not self.schema.eschema(etype).final:
+ if not self._cw.vreg.schema.eschema(etype).final:
if len(rset) == 1:
entity = rset.get_entity(0, 0)
- return entity.absolute_url(vid=self.id)
+ return entity.absolute_url(vid=self.__regid__)
# don't want to generate /<etype> url if there is some restriction
# on something else than the entity type
restr = rset.syntax_tree().children[0].where
@@ -232,25 +243,25 @@
norestriction = (isinstance(restr, nodes.Relation) and
restr.is_types_restriction())
if norestriction:
- return self.build_url(etype.lower(), vid=self.id)
- return self.build_url('view', rql=rset.printable_rql(), vid=self.id)
+ return self._cw.build_url(etype.lower(), vid=self.__regid__)
+ return self._cw.build_url('view', rql=rset.printable_rql(), vid=self.__regid__)
def set_request_content_type(self):
"""set the content type returned by this view"""
- self.req.set_content_type(self.content_type)
+ self._cw.set_content_type(self.content_type)
# view utilities ##########################################################
def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs):
"""shortcut to self.view method automatically passing self.w as argument
"""
- self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
+ self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
# XXX Template bw compat
- template = deprecated('.template is deprecated, use .view')(wview)
+ template = deprecated('[3.4] .template is deprecated, use .view')(wview)
def whead(self, data):
- self.req.html_headers.write(data)
+ self._cw.html_headers.write(data)
def wdata(self, data):
"""simple helper that escapes `data` and writes into `self.w`"""
@@ -268,34 +279,34 @@
"""returns a title according to the result set - used for the
title in the HTML header
"""
- vtitle = self.req.form.get('vtitle')
+ vtitle = self._cw.form.get('vtitle')
if vtitle:
- return self.req._(vtitle)
+ return self._cw._(vtitle)
# class defined title will only be used if the resulting title doesn't
# seem clear enough
vtitle = getattr(self, 'title', None) or u''
if vtitle:
- vtitle = self.req._(vtitle)
- rset = self.rset
+ vtitle = self._cw._(vtitle)
+ rset = self.cw_rset
if rset and rset.rowcount:
if rset.rowcount == 1:
try:
- entity = self.complete_entity(0)
+ entity = rset.complete_entity(0, 0)
# use long_title to get context information if any
clabel = entity.dc_long_title()
except NotAnEntity:
- clabel = display_name(self.req, rset.description[0][0])
+ clabel = display_name(self._cw, rset.description[0][0])
clabel = u'%s (%s)' % (clabel, vtitle)
else :
etypes = rset.column_types(0)
if len(etypes) == 1:
etype = iter(etypes).next()
- clabel = display_name(self.req, etype, 'plural')
+ clabel = display_name(self._cw, etype, 'plural')
else :
clabel = u'#[*] (%s)' % vtitle
else:
clabel = vtitle
- return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
+ return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title'))
def output_url_builder( self, name, url, args ):
self.w(u'<script language="JavaScript"><!--\n' \
@@ -310,8 +321,8 @@
self.w('}\n-->\n</script>\n')
def create_url(self, etype, **kwargs):
- """return the url of the entity creation form for a given entity type"""
- return self.req.build_url('add/%s' % etype, **kwargs)
+ """ return the url of the entity creation form for a given entity type"""
+ return self._cw.build_url('add/%s' % etype, **kwargs)
def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False):
"""read-only field"""
@@ -323,7 +334,7 @@
w(u'<div class="entityfield">')
if show_label and label:
if tr:
- label = display_name(self.req, label)
+ label = display_name(self._cw, label)
if table:
w(u'<th>%s</th>' % label)
else:
@@ -343,8 +354,6 @@
class EntityView(View):
"""base class for views applying on an entity (i.e. uniform result set)"""
__select__ = non_final_entity()
- registered = accepts_compat(View.registered)
-
category = 'entityview'
@@ -353,7 +362,6 @@
displayed (so they can always be displayed !)
"""
__select__ = none_rset()
- registered = require_group_compat(View.registered)
category = 'startupview'
@@ -375,7 +383,7 @@
default_rql = None
def __init__(self, req, rset=None, **kwargs):
- super(EntityStartupView, self).__init__(req, rset, **kwargs)
+ super(EntityStartupView, self).__init__(req, rset=rset, **kwargs)
if rset is None:
# this instance is not in the "entityview" category
self.category = 'startupview'
@@ -388,11 +396,11 @@
"""override call to execute rql returned by the .startup_rql method if
necessary
"""
- if self.rset is None:
- self.rset = self.req.execute(self.startup_rql())
- rset = self.rset
+ rset = self.cw_rset
+ if rset is None:
+ rset = self.cw_rset = self._cw.execute(self.startup_rql())
for i in xrange(len(rset)):
- self.wview(self.id, rset, row=i, **kwargs)
+ self.wview(self.__regid__, rset, row=i, **kwargs)
class AnyRsetView(View):
@@ -403,18 +411,18 @@
def columns_labels(self, mainindex=0, tr=True):
if tr:
- translate = lambda val, req=self.req: display_name(req, val)
+ translate = lambda val, req=self._cw: display_name(req, val)
else:
translate = lambda val: val
# XXX [0] because of missing Union support
- rqlstdescr = self.rset.syntax_tree().get_description(mainindex,
- translate)[0]
+ rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex,
+ translate)[0]
labels = []
for colindex, label in enumerate(rqlstdescr):
# compute column header
if label == 'Any': # find a better label
label = ','.join(translate(et)
- for et in self.rset.column_types(colindex))
+ for et in self.cw_rset.column_types(colindex))
labels.append(label)
return labels
@@ -426,11 +434,10 @@
There is usually at least a regular main template and a simple fallback
one to display error if the first one failed
"""
- registered = require_group_compat(View.registered)
@property
def doctype(self):
- if self.req.xhtml_browser():
+ if self._cw.xhtml_browser():
return STRICT_DOCTYPE
return STRICT_DOCTYPE_NOEXT
@@ -441,7 +448,7 @@
if self.binary:
self._stream = stream = StringIO()
else:
- self._stream = stream = HTMLStream(self.req)
+ self._stream = stream = HTMLStream(self._cw)
w = stream.write
else:
stream = None
@@ -466,16 +473,16 @@
"""register the given user callback and return an url to call it ready to be
inserted in html
"""
- self.req.add_js('cubicweb.ajax.js')
+ self._cw.add_js('cubicweb.ajax.js')
if nonify:
_cb = cb
def cb(*args):
_cb(*args)
- cbname = self.req.register_onetime_callback(cb, *args)
+ cbname = self._cw.register_onetime_callback(cb, *args)
return self.build_js(cbname, xml_escape(msg or ''))
def build_update_js_call(self, cbname, msg):
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % (
cbname, self.id, dumps(rql), dumps(msg),
self.__registry__, self.div_id())
@@ -493,12 +500,12 @@
"""base class for components"""
__registry__ = 'components'
__select__ = yes()
- property_defs = {}
# XXX huummm, much probably useless
htmlclass = 'mainRelated'
def div_class(self):
- return '%s %s' % (self.htmlclass, self.id)
- # XXX a generic '%s%s' % (self.id, self.__registry__.capitalize()) would probably be nicer
+ return '%s %s' % (self.htmlclass, self.__regid__)
+
+ # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer
def div_id(self):
- return '%sComponent' % self.id
+ return '%sComponent' % self.__regid__
--- a/vregistry.py Mon Feb 08 13:33:09 2010 +0100
+++ b/vregistry.py Mon Feb 08 13:35:07 2010 +0100
@@ -54,6 +54,21 @@
return _toload
+def classid(cls):
+ """returns a unique identifier for an appobject class"""
+ return '%s.%s' % (cls.__module__, cls.__name__)
+
+def class_regid(cls):
+ """returns a unique identifier for an appobject class"""
+ if 'id' in cls.__dict__:
+ warn('[3.6] %s.%s: id is deprecated, use __regid__'
+ % (cls.__module__, cls.__name__), DeprecationWarning)
+ cls.__regid__ = cls.id
+ if hasattr(cls, 'id') and not isinstance(cls.id, property):
+ return cls.id
+ return cls.__regid__
+
+
class Registry(dict):
def __init__(self, config):
@@ -69,32 +84,33 @@
except KeyError:
raise ObjectNotFound(name), None, sys.exc_info()[-1]
+ def initialization_completed(self):
+ for appobjects in self.itervalues():
+ for appobjectcls in appobjects:
+ appobjectcls.__registered__(self)
+
def register(self, obj, oid=None, clear=False):
"""base method to add an object in the registry"""
assert not '__abstract__' in obj.__dict__
- oid = oid or obj.id
+ oid = oid or class_regid(obj)
assert oid
if clear:
appobjects = self[oid] = []
else:
appobjects = self.setdefault(oid, [])
- # registered() is technically a classmethod but is not declared
- # as such because we need to compose registered in some cases
- appobject = obj.registered.im_func(obj, self)
- assert not appobject in appobjects, \
- 'object %s is already registered' % appobject
- assert callable(appobject.__select__), appobject
- appobjects.append(appobject)
+ assert not obj in appobjects, \
+ 'object %s is already registered' % obj
+ appobjects.append(obj)
def register_and_replace(self, obj, replaced):
# XXXFIXME this is a duplication of unregister()
# remove register_and_replace in favor of unregister + register
# or simplify by calling unregister then register here
- if hasattr(replaced, 'classid'):
- replaced = replaced.classid()
- registered_objs = self.get(obj.id, ())
+ if not isinstance(replaced, basestring):
+ replaced = classid(replaced)
+ registered_objs = self.get(class_regid(obj), ())
for index, registered in enumerate(registered_objs):
- if registered.classid() == replaced:
+ if classid(registered) == replaced:
del registered_objs[index]
break
else:
@@ -103,16 +119,17 @@
self.register(obj)
def unregister(self, obj):
- oid = obj.classid()
- for registered in self.get(obj.id, ()):
+ clsid = classid(obj)
+ oid = class_regid(obj)
+ for registered in self.get(oid, ()):
# use classid() to compare classes because vreg will probably
# have its own version of the class, loaded through execfile
- if registered.classid() == oid:
- self[obj.id].remove(registered)
+ if classid(registered) == clsid:
+ self[oid].remove(registered)
break
else:
self.warning('can\'t remove %s, no id %s in the registry',
- oid, obj.id)
+ clsid, oid)
def all_objects(self):
"""return a list containing all objects in this registry.
@@ -142,9 +159,9 @@
raise `ObjectNotFound` if not object with id <oid> in <registry>
raise `NoSelectableObject` if not object apply
"""
- return self.select_best(self[oid], *args, **kwargs)
+ return self._select_best(self[oid], *args, **kwargs)
- def select_object(self, oid, *args, **kwargs):
+ def select_or_none(self, oid, *args, **kwargs):
"""return the most specific object among those with the given oid
according to the given context, or None if no object applies.
"""
@@ -152,6 +169,8 @@
return self.select(oid, *args, **kwargs)
except (NoSelectableObject, ObjectNotFound):
return None
+ select_object = deprecated('[3.6] use select_or_none instead of select_object'
+ )(select_or_none)
def possible_objects(self, *args, **kwargs):
"""return an iterator on possible objects in this registry for the given
@@ -159,11 +178,11 @@
"""
for appobjects in self.itervalues():
try:
- yield self.select_best(appobjects, *args, **kwargs)
+ yield self._select_best(appobjects, *args, **kwargs)
except NoSelectableObject:
continue
- def select_best(self, appobjects, *args, **kwargs):
+ def _select_best(self, appobjects, *args, **kwargs):
"""return an instance of the most specific object according
to parameters
@@ -194,6 +213,8 @@
# return the result of calling the appobject
return winners[0](*args, **kwargs)
+ select_best = deprecated('[3.6] select_best is now private')(_select_best)
+
class VRegistry(dict):
"""class responsible to register, propose and select the various
@@ -222,7 +243,7 @@
# dynamic selection methods ################################################
- @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)')
+ @deprecated('[3.4] use vreg[registry].object_by_id(oid, *args, **kwargs)')
def object_by_id(self, registry, oid, *args, **kwargs):
"""return object in <registry>.<oid>
@@ -231,7 +252,7 @@
"""
return self[registry].object_by_id(oid)
- @deprecated('use vreg[registry].select(oid, *args, **kwargs)')
+ @deprecated('[3.4] use vreg[registry].select(oid, *args, **kwargs)')
def select(self, registry, oid, *args, **kwargs):
"""return the most specific object in <registry>.<oid> according to
the given context
@@ -241,14 +262,14 @@
"""
return self[registry].select(oid, *args, **kwargs)
- @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)')
+ @deprecated('[3.4] use vreg[registry].select_or_none(oid, *args, **kwargs)')
def select_object(self, registry, oid, *args, **kwargs):
"""return the most specific object in <registry>.<oid> according to
the given context, or None if no object apply
"""
- return self[registry].select_object(oid, *args, **kwargs)
+ return self[registry].select_or_none(oid, *args, **kwargs)
- @deprecated('use vreg[registry].possible_objects(*args, **kwargs)')
+ @deprecated('[3.4] use vreg[registry].possible_objects(*args, **kwargs)')
def possible_objects(self, registry, *args, **kwargs):
"""return an iterator on possible objects in <registry> for the given
context
@@ -282,7 +303,7 @@
try:
if obj.__module__ != modname or obj in butclasses:
continue
- oid = obj.id
+ oid = class_regid(obj)
registryname = obj.__registry__
except AttributeError:
continue
@@ -300,8 +321,8 @@
except AttributeError:
vname = obj.__class__.__name__
self.debug('registered appobject %s in registry %s with id %s',
- vname, registryname, oid or obj.id)
- self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
+ vname, registryname, oid or class_regid(obj))
+ self._loadedmods[obj.__module__][classid(obj)] = obj
def unregister(self, obj, registryname=None):
self[registryname or obj.__registry__].unregister(obj)
@@ -338,8 +359,15 @@
for filepath, modname in filemods:
if self.load_file(filepath, modname, force_reload):
change = True
+ if change:
+ self.initialization_completed()
return change
+ def initialization_completed(self):
+ for regname, reg in self.iteritems():
+ self.debug('available in registry %s: %s', regname, sorted(reg))
+ reg.initialization_completed()
+
def load_file(self, filepath, modname, force_reload=False):
"""load app objects from a python file"""
from logilab.common.modutils import load_module_from_name
@@ -380,52 +408,48 @@
self._load_ancestors_then_object(module.__name__, obj)
self.debug('loaded %s', module)
- def _load_ancestors_then_object(self, modname, obj):
+ def _load_ancestors_then_object(self, modname, appobjectcls):
+ """handle automatic appobject class registration:
+
+ - first ensure parent classes are already registered
+
+ - class with __abstract__ == True in their local dictionnary or
+ with a name starting starting by an underscore are not registered
+
+ - appobject class needs to have __registry__ and __regid__ attributes
+ set to a non empty string to be registered.
+ """
# imported classes
- objmodname = getattr(obj, '__module__', None)
+ objmodname = getattr(appobjectcls, '__module__', None)
if objmodname != modname:
if objmodname in self._toloadmods:
self.load_file(self._toloadmods[objmodname], objmodname)
return
# skip non registerable object
try:
- if not issubclass(obj, AppObject):
+ if not issubclass(appobjectcls, AppObject):
return
except TypeError:
return
- objname = '%s.%s' % (modname, obj.__name__)
- if objname in self._loadedmods[modname]:
+ clsid = classid(appobjectcls)
+ if clsid in self._loadedmods[modname]:
return
- self._loadedmods[modname][objname] = obj
- for parent in obj.__bases__:
+ self._loadedmods[modname][clsid] = appobjectcls
+ for parent in appobjectcls.__bases__:
self._load_ancestors_then_object(modname, parent)
- self.load_object(obj)
-
- def load_object(self, obj):
+ if (appobjectcls.__dict__.get('__abstract__')
+ or appobjectcls.__name__[0] == '_'
+ or not appobjectcls.__registry__
+ or not class_regid(appobjectcls)):
+ return
try:
- self.register_appobject_class(obj)
+ self.register(appobjectcls)
except Exception, ex:
if self.config.mode in ('test', 'dev'):
raise
- self.exception('appobject %s registration failed: %s', obj, ex)
-
- # old automatic registration XXX deprecated ###############################
-
- def register_appobject_class(self, cls):
- """handle appobject class registration
+ self.exception('appobject %s registration failed: %s',
+ appobjectcls, ex)
- appobject class with __abstract__ == True in their local dictionnary or
- with a name starting starting by an underscore are not registered.
- Also a appobject class needs to have __registry__ and id attributes set
- to a non empty string to be registered.
- """
- if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
- or not cls.__registry__ or not cls.id):
- return
- regname = cls.__registry__
- if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
- return
- self.register(cls)
# init logging
set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
@@ -436,11 +460,11 @@
from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector
-objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector)
+objectify_selector = deprecated('[3.4] objectify_selector has been moved to appobject module')(objectify_selector)
Selector = class_moved(Selector)
-@deprecated('use & operator (binary and)')
+@deprecated('[3.4] use & operator (binary and)')
def chainall(*selectors, **kwargs):
"""return a selector chaining given selectors. If one of
the selectors fail, selection will fail, else the returned score
@@ -453,7 +477,7 @@
selector.__name__ = kwargs['name']
return selector
-@deprecated('use | operator (binary or)')
+@deprecated('[3.4] use | operator (binary or)')
def chainfirst(*selectors, **kwargs):
"""return a selector chaining given selectors. If all
the selectors fail, selection will fail, else the returned score
--- a/web/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,10 +11,10 @@
_ = unicode
from simplejson import dumps
+from urllib import quote as urlquote
from logilab.common.deprecation import deprecated
-from urllib import quote as urlquote
from cubicweb.web._exceptions import *
from cubicweb.utils import CubicWebJsonEncoder
@@ -57,7 +57,7 @@
return json_dumps(repr(value))
return newfunc
-@deprecated('use req.build_ajax_replace_url() instead')
+@deprecated('[3.4] use req.build_ajax_replace_url() instead')
def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
"""builds a replacePageChunk-like url
>>> ajax_replace_url('foo', 'Person P')
--- a/web/_exceptions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/_exceptions.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,6 +19,10 @@
class NothingToEdit(RequestError):
"""raised when an edit request doesn't specify any eid to edit"""
+class ProcessFormError(RequestError):
+ """raised when posted data can't be processed by the corresponding field
+ """
+
class NotFound(RequestError):
"""raised when a 404 error should be returned"""
--- a/web/action.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/action.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,8 +10,7 @@
from cubicweb import target
from cubicweb.selectors import (partial_relation_possible, match_search_state,
- one_line_rset, partial_may_add_relation, yes,
- accepts_compat, condition_compat, deprecate)
+ one_line_rset, yes)
from cubicweb.appobject import AppObject
@@ -22,7 +21,7 @@
__registry__ = 'actions'
__select__ = match_search_state('normal')
- property_defs = {
+ cw_property_defs = {
'visible': dict(type='Boolean', default=True,
help=_('display the action or not')),
'order': dict(type='Int', default=99,
@@ -50,13 +49,13 @@
raise NotImplementedError
def html_class(self):
- if self.req.selected(self.url()):
+ if self._cw.selected(self.url()):
return 'selected'
if self.category:
return 'box' + self.category.capitalize()
def build_action(self, title, path, **kwargs):
- return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
+ return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
class UnregisteredAction(Action):
@@ -67,7 +66,7 @@
id = None
def __init__(self, req, rset, title, path, **kwargs):
- Action.__init__(self, req, rset)
+ Action.__init__(self, req, rset=rset)
self.title = req._(title)
self._path = path
self.__dict__.update(kwargs)
@@ -77,30 +76,28 @@
class LinkToEntityAction(Action):
- """base class for actions consisting to create a new object
- with an initial relation set to an entity.
- Additionaly to EntityAction behaviour, this class is parametrized
- using .etype, .rtype and .target attributes to check if the
- action apply and if the logged user has access to it
+ """base class for actions consisting to create a new object with an initial
+ relation set to an entity.
+
+ Additionaly to EntityAction behaviour, this class is parametrized using
+ .rtype, .role and .target_etype attributes to check if the action apply and
+ if the logged user has access to it (see
+ :class:`~cubicweb.selectors.partial_relation_possible` selector
+ documentation for more information).
"""
__select__ = (match_search_state('normal') & one_line_rset()
- & partial_relation_possible(action='add')
- & partial_may_add_relation())
- registered = accepts_compat(Action.registered)
+ & partial_relation_possible(action='add', strict=True))
submenu = 'addrelated'
def url(self):
- current_entity = self.rset.get_entity(self.row or 0, self.col or 0)
- linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self))
- return self.build_url('add/%s' % self.etype, __linkto=linkto,
- __redirectpath=current_entity.rest_path(), # should not be url quoted!
- __redirectvid=self.req.form.get('__redirectvid', ''))
+ try:
+ ttype = self.etype # deprecated in 3.6, already warned by the selector
+ except AttributeError:
+ ttype = self.target_etype
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ linkto = '%s:%s:%s' % (self.rtype, entity.eid, target(self))
+ return self._cw.build_url('add/%s' % ttype, __linkto=linkto,
+ __redirectpath=entity.rest_path(),
+ __redirectvid=self._cw.form.get('__redirectvid', ''))
-class EntityAction(Action):
- """DEPRECATED / BACKWARD COMPAT
- """
- registered = deprecate(condition_compat(accepts_compat(Action.registered)),
- msg='EntityAction is deprecated, use Action with '
- 'appropriate selectors')
-
--- a/web/application.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/application.py Mon Feb 08 13:35:07 2010 +0100
@@ -29,19 +29,19 @@
class AbstractSessionManager(component.Component):
"""manage session data associated to a session identifier"""
- id = 'sessionmanager'
+ __regid__ = 'sessionmanager'
- def __init__(self):
- self.session_time = self.vreg.config['http-session-time'] or None
+ def __init__(self, vreg):
+ self.session_time = vreg.config['http-session-time'] or None
assert self.session_time is None or self.session_time > 0
- self.cleanup_session_time = self.vreg.config['cleanup-session-time'] or 43200
+ self.cleanup_session_time = vreg.config['cleanup-session-time'] or 43200
assert self.cleanup_session_time > 0
- self.cleanup_anon_session_time = self.vreg.config['cleanup-anonymous-session-time'] or 120
+ self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 120
assert self.cleanup_anon_session_time > 0
if self.session_time:
assert self.cleanup_session_time < self.session_time
assert self.cleanup_anon_session_time < self.session_time
- self.authmanager = self.vreg['components'].select('authmanager')
+ self.authmanager = vreg['components'].select('authmanager', vreg=vreg)
def clean_sessions(self):
"""cleanup sessions which has not been unused since a given amount of
@@ -92,6 +92,10 @@
class AbstractAuthenticationManager(component.Component):
"""authenticate user associated to a request and check session validity"""
id = 'authmanager'
+ vreg = None # XXX necessary until property for deprecation warning is on appobject
+
+ def __init__(self, vreg):
+ self.vreg = vreg
def authenticate(self, req):
"""authenticate user and return corresponding user object
@@ -113,7 +117,8 @@
def __init__(self, appli):
self.vreg = appli.vreg
- self.session_manager = self.vreg['components'].select('sessionmanager')
+ self.session_manager = self.vreg['components'].select('sessionmanager',
+ vreg=self.vreg)
global SESSION_MANAGER
SESSION_MANAGER = self.session_manager
if not 'last_login_time' in self.vreg.schema:
@@ -122,7 +127,8 @@
def reset_session_manager(self):
data = self.session_manager.dump_data()
- self.session_manager = self.vreg['components'].select('sessionmanager')
+ self.session_manager = self.vreg['components'].select('sessionmanager',
+ vreg=self.vreg)
self.session_manager.restore_data(data)
global SESSION_MANAGER
SESSION_MANAGER = self.session_manager
@@ -229,19 +235,12 @@
self.info('starting web instance from %s', config.apphome)
if vreg is None:
vreg = cwvreg.CubicWebVRegistry(config, debug=debug)
- need_set_schema = True
- else:
- # vreg is specified during test and the vreg is already properly
- # initialized. Even, reinitializing it may cause some unwanted
- # side effect due to unproper reloading of appobjects modules
- need_set_schema = False
self.vreg = vreg
# connect to the repository and get instance's schema
self.repo = config.repository(vreg)
if not vreg.initialized:
self.config.init_cubes(self.repo.get_cubes())
vreg.init_properties(self.repo.properties())
- if need_set_schema:
vreg.set_schema(self.repo.get_schema())
# set the correct publish method
if config['query-log-file']:
@@ -258,7 +257,8 @@
CW_EVENT_MANAGER.bind('after-registry-reload', self.set_urlresolver)
def set_urlresolver(self):
- self.url_resolver = self.vreg['components'].select('urlpublisher')
+ self.url_resolver = self.vreg['components'].select('urlpublisher',
+ vreg=self.vreg)
def connect(self, req):
"""return a connection for a logged user object according to existing
@@ -291,7 +291,7 @@
finally:
self._logfile_lock.release()
- @deprecated("use vreg.select('controllers', ...)")
+ @deprecated("[3.4] use vreg['controllers'].select(...)")
def select_controller(self, oid, req):
try:
return self.vreg['controllers'].select(oid, req=req, appli=self)
@@ -374,7 +374,7 @@
def validation_error_handler(self, req, ex):
ex.errors = dict((k, v) for k, v in ex.errors.items())
if '__errorurl' in req.form:
- forminfo = {'errors': ex,
+ forminfo = {'error': ex,
'values': req.form,
'eidmap': req.data.get('eidmap', {})
}
--- a/web/box.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/box.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,11 +13,10 @@
from cubicweb import Unauthorized, role as get_role, target as get_target
from cubicweb.schema import display_name
from cubicweb.selectors import (one_line_rset, primary_view,
- match_context_prop, partial_has_related_entities,
- accepts_compat, has_relation_compat,
- condition_compat, require_group_compat)
+ match_context_prop, partial_has_related_entities)
from cubicweb.view import View, ReloadableMixIn
+from cubicweb.web import INTERNAL_FIELD_VALUE
from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
RawBoxItem, BoxSeparator)
from cubicweb.web.action import UnregisteredAction
@@ -39,10 +38,9 @@
"""
__registry__ = 'boxes'
__select__ = match_context_prop()
- registered = classmethod(require_group_compat(View.registered))
categories_in_order = ()
- property_defs = {
+ cw_property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the box or not')),
_('order'): dict(type='Int', default=99,
@@ -80,18 +78,18 @@
return self.box_action(self._action(title, path, **kwargs))
def _action(self, title, path, **kwargs):
- return UnregisteredAction(self.req, self.rset, title, path, **kwargs)
+ return UnregisteredAction(self._cw, self.cw_rset, title, path, **kwargs)
# formating callbacks
def boxitem_link_tooltip(self, action):
- if action.id:
- return u'keyword: %s' % action.id
+ if action.__regid__:
+ return u'keyword: %s' % action.__regid__
return u''
def box_action(self, action):
cls = getattr(action, 'html_class', lambda: None)() or self.htmlitemclass
- return BoxLink(action.url(), self.req._(action.title),
+ return BoxLink(action.url(), self._cw._(action.title),
cls, self.boxitem_link_tooltip(action))
@@ -103,23 +101,22 @@
according to application schema and display according to connected
user's rights) and rql attributes
"""
-#XXX __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
rql = None
def to_display_rql(self):
- assert self.rql is not None, self.id
+ assert self.rql is not None, self.__regid__
return (self.rql,)
def call(self, **kwargs):
try:
- rset = self.req.execute(*self.to_display_rql())
+ rset = self._cw.execute(*self.to_display_rql())
except Unauthorized:
# can't access to something in the query, forget this box
return
if len(rset) == 0:
return
- box = BoxWidget(self.req._(self.title), self.id)
+ box = BoxWidget(self._cw._(self.title), self.__regid__)
for i, (teid, tname) in enumerate(rset):
entity = rset.get_entity(i, 0)
box.append(self.mk_action(tname, entity.absolute_url()))
@@ -132,14 +129,13 @@
"""
def to_display_rql(self):
- assert self.rql is not None, self.id
- return (self.rql, {'x': self.req.user.eid}, 'x')
+ assert self.rql is not None, self.__regid__
+ return (self.rql, {'x': self._cw.user.eid}, 'x')
class EntityBoxTemplate(BoxTemplate):
"""base class for boxes related to a single entity"""
__select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
- registered = accepts_compat(has_relation_compat(condition_compat(BoxTemplate.registered)))
context = 'incontext'
def call(self, row=0, col=0, **kwargs):
@@ -151,12 +147,12 @@
__select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
def cell_call(self, row, col, **kwargs):
- entity = self.rset.get_entity(row, col)
- limit = self.req.property_value('navigation.related-limit') + 1
+ entity = self.cw_rset.get_entity(row, col)
+ limit = self._cw.property_value('navigation.related-limit') + 1
role = get_role(self)
self.w(u'<div class="sideBox">')
self.wview('sidebox', entity.related(self.rtype, role, limit=limit),
- title=display_name(self.req, self.rtype, role))
+ title=display_name(self._cw, self.rtype, role))
self.w(u'</div>')
@@ -169,9 +165,9 @@
"""
def cell_call(self, row, col, view=None, **kwargs):
- self.req.add_js('cubicweb.ajax.js')
- entity = self.rset.get_entity(row, col)
- box = SideBoxWidget(display_name(self.req, self.rtype), self.id)
+ self._cw.add_js('cubicweb.ajax.js')
+ entity = self.cw_rset.get_entity(row, col)
+ box = SideBoxWidget(display_name(self._cw, self.rtype), self.__regid__)
related = self.related_boxitems(entity)
unrelated = self.unrelated_boxitems(entity)
box.extend(related)
@@ -181,14 +177,14 @@
box.render(self.w)
def div_id(self):
- return self.id
+ return self.__regid__
def box_item(self, entity, etarget, rql, label):
"""builds HTML link to edit relation between `entity` and `etarget`
"""
role, target = get_role(self), get_target(self)
args = {role[0] : entity.eid, target[0] : etarget.eid}
- url = self.user_rql_callback((rql, args))
+ url = self._cw.user_rql_callback((rql, args))
# for each target, provide a link to edit the relation
label = u'[<a href="%s">%s</a>] %s' % (xml_escape(url), label,
etarget.view('incontext'))
@@ -212,22 +208,23 @@
return entity.related(self.rtype, get_role(self), entities=True)
def unrelated_entities(self, entity):
- """returns the list of unrelated entities
-
- if etype is not defined on the Box's class, the default
- behaviour is to use the entity's appropraite vocabulary function
+ """returns the list of unrelated entities, using the entity's
+ appropriate vocabulary function
"""
- # use entity.unrelated if we've been asked for a particular etype
- if hasattr(self, 'etype'):
- return entity.unrelated(self.rtype, self.etype, get_role(self)).entities()
- # in other cases, use vocabulary functions
+ skip = set(e.eid for e in entity.related(self.rtype, get_role(self),
+ entities=True))
+ skip.add(None)
+ skip.add(INTERNAL_FIELD_VALUE)
+ filteretype = getattr(self, 'etype', None)
entities = []
- form = self.vreg['forms'].select('edition', self.req, rset=self.rset,
- row=self.row or 0)
+ form = self._cw.vreg['forms'].select('edition', self._cw,
+ rset=self.cw_rset,
+ row=self.cw_row or 0)
field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
- for _, eid in form.form_field_vocabulary(field):
- if eid is not None:
- rset = self.req.eid_rset(eid)
- entities.append(rset.get_entity(0, 0))
+ for _, eid in field.vocabulary(form):
+ if eid not in skip:
+ entity = self._cw.entity_from_eid(eid)
+ if filteretype is None or entity.__regid__ == filteretype:
+ entities.append(entity)
return entities
--- a/web/component.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/component.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,11 +15,10 @@
from cubicweb import role
from cubicweb.utils import merge_dicts
-from cubicweb.view import View, Component
+from cubicweb.view import Component
from cubicweb.selectors import (
paginated_rset, one_line_rset, primary_view, match_context_prop,
- partial_has_related_entities, condition_compat, accepts_compat,
- has_relation_compat)
+ partial_has_related_entities)
class EntityVComponent(Component):
@@ -35,9 +34,8 @@
__registry__ = 'contentnavigation'
__select__ = one_line_rset() & primary_view() & match_context_prop()
- registered = accepts_compat(has_relation_compat(condition_compat(View.registered)))
- property_defs = {
+ cw_property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the component or not')),
_('order'): dict(type='Int', default=99,
@@ -60,10 +58,10 @@
class NavigationComponent(Component):
"""abstract base class for navigation components"""
- id = 'navigation'
+ __regid__ = 'navigation'
__select__ = paginated_rset()
- property_defs = {
+ cw_property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the component or not')),
}
@@ -78,7 +76,7 @@
no_next_page_link = u'>>'
def __init__(self, req, rset, **kwargs):
- super(NavigationComponent, self).__init__(req, rset, **kwargs)
+ super(NavigationComponent, self).__init__(req, rset=rset, **kwargs)
self.starting_from = 0
self.total = rset.rowcount
@@ -86,12 +84,12 @@
try:
return self._page_size
except AttributeError:
- page_size = self.extra_kwargs.get('page_size')
+ page_size = self.cw_extra_kwargs.get('page_size')
if page_size is None:
- if 'page_size' in self.req.form:
- page_size = int(self.req.form['page_size'])
+ if 'page_size' in self._cw.form:
+ page_size = int(self._cw.form['page_size'])
else:
- page_size = self.req.property_value(self.page_size_property)
+ page_size = self._cw.property_value(self.page_size_property)
self._page_size = page_size
return page_size
@@ -102,11 +100,11 @@
def page_boundaries(self):
try:
- stop = int(self.req.form[self.stop_param]) + 1
- start = int(self.req.form[self.start_param])
+ stop = int(self._cw.form[self.stop_param]) + 1
+ start = int(self._cw.form[self.start_param])
except KeyError:
start, stop = 0, self.page_size
- if start >= len(self.rset):
+ if start >= len(self.cw_rset):
start, stop = 0, self.page_size
self.starting_from = start
return start, stop
@@ -121,13 +119,13 @@
params = merge_dicts(params, {self.start_param : start,
self.stop_param : stop,})
if path == 'json':
- rql = params.pop('rql', self.rset.printable_rql())
+ rql = params.pop('rql', self.cw_rset.printable_rql())
# latest 'true' used for 'swap' mode
url = 'javascript: replacePageChunk(%s, %s, %s, %s, true)' % (
dumps(params.get('divid', 'paginated-content')),
dumps(rql), dumps(params.pop('vid', None)), dumps(params))
else:
- url = self.build_url(path, **params)
+ url = self._cw.build_url(path, **params)
return url
def page_link(self, path, params, start, stop, content):
@@ -167,15 +165,15 @@
def cell_call(self, row, col, view=None):
rql = self.rql()
if rql is None:
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
rset = entity.related(self.rtype, role(self))
else:
- eid = self.rset[row][col]
- rset = self.req.execute(self.rql(), {'x': eid}, 'x')
+ eid = self.cw_rset[row][col]
+ rset = self._cw.execute(self.rql(), {'x': eid}, 'x')
if not rset.rowcount:
return
self.w(u'<div class="%s">' % self.div_class())
- self.w(u'<h4>%s</h4>\n' % self.req._(self.title).capitalize())
+ self.w(u'<h4>%s</h4>\n' % self._cw._(self.title).capitalize())
self.wview(self.vid, rset)
self.w(u'</div>')
--- a/web/controller.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/controller.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
import datetime
from cubicweb import typed_eid
-from cubicweb.selectors import yes, require_group_compat
+from cubicweb.selectors import yes
from cubicweb.appobject import AppObject
from cubicweb.web import LOGGER, Redirect, RequestError
@@ -35,19 +35,6 @@
params[navparam] = form[redirectparam]
return params
-def parse_relations_descr(rdescr):
- """parse a string describing some relations, in the form
- subjeids:rtype:objeids
- where subjeids and objeids are eids separeted by a underscore
-
- return an iterator on (subject eid, relation type, object eid) found
- """
- for rstr in rdescr:
- subjs, rtype, objs = rstr.split(':')
- for subj in subjs.split('_'):
- for obj in objs.split('_'):
- yield typed_eid(subj), rtype, typed_eid(obj)
-
def append_url_params(url, params):
"""append raw parameters to the url. Given parameters, if any, are expected
to be already url-quoted.
@@ -68,7 +55,6 @@
"""
__registry__ = 'controllers'
__select__ = yes()
- registered = require_group_compat(AppObject.registered)
def __init__(self, *args, **kwargs):
self.appli = kwargs.pop('appli', None)
@@ -88,15 +74,15 @@
def process_rql(self, rql):
"""execute rql if specified"""
# XXX assigning to self really necessary?
- self.rset = None
+ self.cw_rset = None
if rql:
- self.ensure_ro_rql(rql)
+ self._cw.ensure_ro_rql(rql)
if not isinstance(rql, unicode):
- rql = unicode(rql, self.req.encoding)
- pp = self.vreg['components'].select_object('magicsearch', self.req)
+ rql = unicode(rql, self._cw.encoding)
+ pp = self._cw.vreg['components'].select_or_none('magicsearch', self._cw)
if pp is not None:
- self.rset = pp.process_query(rql, self.req)
- return self.rset
+ self.cw_rset = pp.process_query(rql)
+ return self.cw_rset
def check_expected_params(self, params):
"""check that the given list of parameters are specified in the form
@@ -104,7 +90,7 @@
"""
missing = []
for param in params:
- if not self.req.form.get(param):
+ if not self._cw.form.get(param):
missing.append(param)
if missing:
raise RequestError('missing required parameter(s): %s'
@@ -124,7 +110,7 @@
redirect_info = set()
eidtypes = tuple(eidtypes)
for eid, etype in eidtypes:
- entity = self.req.entity_from_eid(eid, etype)
+ entity = self._cw.entity_from_eid(eid, etype)
path, params = entity.after_deletion_path()
redirect_info.add( (path, tuple(params.iteritems())) )
entity.delete()
@@ -134,25 +120,9 @@
else:
self._after_deletion_path = iter(redirect_info).next()
if len(eidtypes) > 1:
- self.req.set_message(self.req._('entities deleted'))
+ self._cw.set_message(self._cw._('entities deleted'))
else:
- self.req.set_message(self.req._('entity deleted'))
-
- def delete_relations(self, rdefs):
- """delete relations from the repository"""
- # FIXME convert to using the syntax subject:relation:eids
- execute = self.req.execute
- for subj, rtype, obj in rdefs:
- rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
- execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
- self.req.set_message(self.req._('relations deleted'))
-
- def insert_relations(self, rdefs):
- """insert relations into the repository"""
- execute = self.req.execute
- for subj, rtype, obj in rdefs:
- rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
- execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+ self._cw.set_message(self._cw._('entity deleted'))
def reset(self):
@@ -161,11 +131,11 @@
"""
newparams = {}
# sets message if needed
- if self.req.message:
- newparams['__message'] = self.req.message
- if self.req.form.has_key('__action_apply'):
+ if self._cw.message:
+ newparams['__message'] = self._cw.message
+ if self._cw.form.has_key('__action_apply'):
self._return_to_edition_view(newparams)
- if self.req.form.has_key('__action_cancel'):
+ if self._cw.form.has_key('__action_cancel'):
self._return_to_lastpage(newparams)
else:
self._return_to_original_view(newparams)
@@ -174,7 +144,7 @@
def _return_to_original_view(self, newparams):
"""validate-button case"""
# transforms __redirect[*] parameters into regular form parameters
- newparams.update(redirect_params(self.req.form))
+ newparams.update(redirect_params(self._cw.form))
# find out if we have some explicit `rql` needs
rql = newparams.pop('rql', None)
# if rql is needed (explicit __redirectrql or multiple deletions for
@@ -182,9 +152,9 @@
if rql:
path = 'view'
newparams['rql'] = rql
- elif '__redirectpath' in self.req.form:
+ elif '__redirectpath' in self._cw.form:
# if redirect path was explicitly specified in the form, use it
- path = self.req.form['__redirectpath']
+ path = self._cw.form['__redirectpath']
if self._edited_entity and path != self._edited_entity.rest_path():
# XXX may be here on modification? if yes the message should be
# modified where __createdpath is detected (cw.web.request)
@@ -199,19 +169,19 @@
path = self._edited_entity.rest_path()
else:
path = 'view'
- url = self.build_url(path, **newparams)
- url = append_url_params(url, self.req.form.get('__redirectparams'))
+ url = self._cw.build_url(path, **newparams)
+ url = append_url_params(url, self._cw.form.get('__redirectparams'))
raise Redirect(url)
def _return_to_edition_view(self, newparams):
"""apply-button case"""
- form = self.req.form
+ form = self._cw.form
if self._edited_entity:
path = self._edited_entity.rest_path()
newparams.pop('rql', None)
# else, fallback on the old `view?rql=...` url form
- elif 'rql' in self.req.form:
+ elif 'rql' in self._cw.form:
path = 'view'
newparams['rql'] = form['rql']
else:
@@ -224,7 +194,7 @@
for redirectparam in NAV_FORM_PARAMETERS:
if redirectparam in form:
newparams[redirectparam] = form[redirectparam]
- raise Redirect(self.build_url(path, **newparams))
+ raise Redirect(self._cw.build_url(path, **newparams))
def _return_to_lastpage(self, newparams):
@@ -233,13 +203,13 @@
__redirectpath is specifying that place if found, else we look in the
request breadcrumbs for the last visited page.
"""
- if '__redirectpath' in self.req.form:
+ if '__redirectpath' in self._cw.form:
# if redirect path was explicitly specified in the form, use it
- path = self.req.form['__redirectpath']
- url = self.build_url(path, **newparams)
- url = append_url_params(url, self.req.form.get('__redirectparams'))
+ path = self._cw.form['__redirectpath']
+ url = self._cw.build_url(path, **newparams)
+ url = append_url_params(url, self._cw.form.get('__redirectparams'))
else:
- url = self.req.last_visited_page()
+ url = self._cw.last_visited_page()
raise Redirect(url)
--- a/web/data/cubicweb.edition.js Mon Feb 08 13:33:09 2010 +0100
+++ b/web/data/cubicweb.edition.js Mon Feb 08 13:35:07 2010 +0100
@@ -1,6 +1,6 @@
/*
* :organization: Logilab
- * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
@@ -230,8 +230,8 @@
}
-function updateInlinedEntitiesCounters(rtype) {
- jQuery('#inline' + rtype + 'slot span.icounter').each(function (i) {
+function updateInlinedEntitiesCounters(rtype, role) {
+ jQuery('div.inline-' + rtype + '-' + role + '-slot span.icounter').each(function (i) {
this.innerHTML = i+1;
});
}
@@ -252,7 +252,7 @@
var form = jQuery(dom);
form.css('display', 'none');
form.insertBefore(insertBefore).slideDown('fast');
- updateInlinedEntitiesCounters(rtype);
+ updateInlinedEntitiesCounters(rtype, role);
reorderTabindex();
jQuery(CubicWeb).trigger('inlinedform-added', form);
// if the inlined form contains a file input, we must force
@@ -273,10 +273,10 @@
/*
* removes the part of the form used to edit an inlined entity
*/
-function removeInlineForm(peid, rtype, eid, showaddnewlink) {
+function removeInlineForm(peid, rtype, role, eid, showaddnewlink) {
jqNode(['div', peid, rtype, eid].join('-')).slideUp('fast', function() {
$(this).remove();
- updateInlinedEntitiesCounters(rtype);
+ updateInlinedEntitiesCounters(rtype, role);
});
if (showaddnewlink) {
toggleVisibility(showaddnewlink);
@@ -331,15 +331,22 @@
for (fieldname in errors) {
var errmsg = errors[fieldname];
var fieldid = fieldname + ':' + eid;
- var field = jqNode(fieldname + ':' + eid);
- if (field && getNodeAttribute(field, 'type') != 'hidden') {
- if ( !firsterrfield ) {
- firsterrfield = 'err-' + fieldid;
+ var suffixes = ['', '-subject', '-object'];
+ var found = false;
+ for (var i=0, length=suffixes.length; i<length;i++) {
+ var field = jqNode(fieldname + suffixes[i] + ':' + eid);
+ if (field && getNodeAttribute(field, 'type') != 'hidden') {
+ if ( !firsterrfield ) {
+ firsterrfield = 'err-' + fieldid;
+ }
+ addElementClass(field, 'error');
+ var span = SPAN({'id': 'err-' + fieldid, 'class': "error"}, errmsg);
+ field.before(span);
+ found = true;
+ break;
}
- addElementClass(field, 'error');
- var span = SPAN({'id': 'err-' + fieldid, 'class': "error"}, errmsg);
- field.before(span);
- } else {
+ }
+ if (!found) {
firsterrfield = formid;
globalerrors.push(_(fieldname) + ' : ' + errmsg);
}
@@ -348,7 +355,7 @@
if (globalerrors.length == 1) {
var innernode = SPAN(null, globalerrors[0]);
} else {
- var innernode = UL(null, map(LI, globalerrors));
+ var innernode = UL(null, map(partial(LI, null), globalerrors));
}
// insert DIV and innernode before the form
var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'});
@@ -407,12 +414,15 @@
function postForm(bname, bvalue, formid) {
var form = getNode(formid);
if (bname) {
- form.appendChild(INPUT({type: 'hidden', name: bname, value: bvalue}));
+ var child = form.appendChild(INPUT({type: 'hidden', name: bname, value: bvalue}));
}
var onsubmit = form.onsubmit;
if (!onsubmit || (onsubmit && onsubmit())) {
form.submit();
}
+ if (bname) {
+ jQuery(child).remove(); /* cleanup */
+ }
}
--- a/web/data/cubicweb.iprogress.css Mon Feb 08 13:33:09 2010 +0100
+++ b/web/data/cubicweb.iprogress.css Mon Feb 08 13:35:07 2010 +0100
@@ -1,6 +1,6 @@
/*
* :organization: Logilab
- * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
@@ -15,9 +15,8 @@
.overpassed{ background: yellow}
-.progressbar {
- height: 10px;
- background: #008000;
+canvas.progressbar {
+ border:1px solid black;
}
.progressbarback {
@@ -40,7 +39,7 @@
border:2px solid #ebe8d9;
}
-table.progress th{
+table.progress th{
text-align:left;
white-space:nowrap;
font-weight : bold;
@@ -49,25 +48,25 @@
}
table.progress th,
-table.progress td{
+table.progress td{
border: 1px solid #dedede;
margin:0px;
}
-table.progress td{
+table.progress td{
text-align:right;
padding:2px 5px 2px 2px;
}
table.progress th.tdleft,
-table.progress td.tdleft{
+table.progress td.tdleft{
text-align:left;
padding:2px 3px 2px 5px;
}
table.progress tr.highlighted{
- background-color: #f4f5ed;
+ background-color: #f4f5ed;
}
table.progress tr.highlighted .progressbarback {
@@ -78,7 +77,7 @@
border: 1px solid #777;
}
-.progress_data{
+.progress_data{
padding-right:3px;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/jquery.timePicker.js Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,269 @@
+/*
+ * A time picker for jQuery
+ * Based on original timePicker by Sam Collet (http://www.texotela.co.uk) -
+ * copyright (c) 2006 Sam Collett (http://www.texotela.co.uk)
+ *
+ * Dual licensed under the MIT and GPL licenses.
+ * Copyright (c) 2009 Anders Fajerson
+ * @name timePicker
+ * @version 0.2
+ * @author Anders Fajerson (http://perifer.se)
+ * @example $("#mytime").timePicker();
+ * @example $("#mytime").timePicker({step:30, startTime:"15:00", endTime:"18:00"});
+ */
+
+(function($){
+ $.fn.timePicker = function(options) {
+ // Build main options before element iteration
+ var settings = $.extend({}, $.fn.timePicker.defaults, options);
+
+ return this.each(function() {
+ $.timePicker(this, settings);
+ });
+ };
+
+ $.timePicker = function (elm, settings) {
+ var e = $(elm)[0];
+ return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings));
+ };
+
+ $._timePicker = function(elm, settings) {
+
+ var tpOver = false;
+ var keyDown = false;
+ var startTime = timeToDate(settings.startTime, settings);
+ var endTime = timeToDate(settings.endTime, settings);
+
+ $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete
+
+ var times = [];
+ var time = new Date(startTime); // Create a new date object.
+ while(time <= endTime) {
+ times[times.length] = formatTime(time, settings);
+ time = new Date(time.setMinutes(time.getMinutes() + settings.step));
+ }
+
+ var $tpDiv = $('<div class="time-picker'+ (settings.show24Hours ? '' : ' time-picker-12hours') +'"></div>');
+ var $tpList = $('<ul></ul>');
+
+ // Build the list.
+ for(var i = 0; i < times.length; i++) {
+ if (times[i] == settings.selectedTime) {
+ $tpList.append('<li class="selected">' + times[i] + "</li>");
+ } else {
+ $tpList.append("<li>" + times[i] + "</li>");
+ }
+ }
+ $tpDiv.append($tpList);
+ // Append the timPicker to the body and position it.
+ var elmOffset = $(elm).offset();
+ $tpDiv.appendTo('body').css({'top':elmOffset.top+20, 'left':elmOffset.left}).hide();
+
+ // Store the mouse state, used by the blur event. Use mouseover instead of
+ // mousedown since Opera fires blur before mousedown.
+ $tpDiv.mouseover(function() {
+ tpOver = true;
+ }).mouseout(function() {
+ tpOver = false;
+ });
+
+ $("li", $tpList).mouseover(function() {
+ if (!keyDown) {
+ $("li.selected", $tpDiv).removeClass("selected");
+ $(this).addClass("selected");
+ }
+ }).mousedown(function() {
+ tpOver = true;
+ }).click(function() {
+ setTimeVal(elm, this, $tpDiv, settings);
+ tpOver = false;
+ });
+
+ var showPicker = function() {
+ if ($tpDiv.is(":visible")) {
+ return false;
+ }
+ $("li", $tpDiv).removeClass("selected");
+
+ // Show picker. This has to be done before scrollTop is set since that
+ // can't be done on hidden elements.
+ $tpDiv.show();
+
+ // Try to find a time in the list that matches the entered time.
+ var time = elm.value ? timeStringToDate(elm.value, settings) : startTime;
+ var startMin = startTime.getHours() * 60 + startTime.getMinutes();
+ var min = (time.getHours() * 60 + time.getMinutes()) - startMin;
+ var steps = Math.round(min / settings.step);
+ var roundTime = normaliseTime(new Date(0, 0, 0, 0, (steps * settings.step + startMin), 0));
+ roundTime = (startTime < roundTime && roundTime <= endTime) ? roundTime : startTime;
+ var $matchedTime = $("li:contains(" + formatTime(roundTime, settings) + ")", $tpDiv);
+
+ if ($matchedTime.length) {
+ $matchedTime.addClass("selected");
+ // Scroll to matched time.
+ $tpDiv[0].scrollTop = $matchedTime[0].offsetTop;
+ }
+ return true;
+ };
+ // Attach to click as well as focus so timePicker can be shown again when
+ // clicking on the input when it already has focus.
+ $(elm).focus(showPicker).click(showPicker);
+ // Hide timepicker on blur
+ $(elm).blur(function() {
+ if (!tpOver) {
+ $tpDiv.hide();
+ }
+ });
+ // Keypress doesn't repeat on Safari for non-text keys.
+ // Keydown doesn't repeat on Firefox and Opera on Mac.
+ // Using kepress for Opera and Firefox and keydown for the rest seems to
+ // work with up/down/enter/esc.
+ var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown';
+ $(elm)[event](function(e) {
+ var $selected;
+ keyDown = true;
+ var top = $tpDiv[0].scrollTop;
+ switch (e.keyCode) {
+ case 38: // Up arrow.
+ // Just show picker if it's hidden.
+ if (showPicker()) {
+ return false;
+ };
+ $selected = $("li.selected", $tpList);
+ var prev = $selected.prev().addClass("selected")[0];
+ if (prev) {
+ $selected.removeClass("selected");
+ // Scroll item into view.
+ if (prev.offsetTop < top) {
+ $tpDiv[0].scrollTop = top - prev.offsetHeight;
+ }
+ }
+ else {
+ // Loop to next item.
+ $selected.removeClass("selected");
+ prev = $("li:last", $tpList).addClass("selected")[0];
+ $tpDiv[0].scrollTop = prev.offsetTop - prev.offsetHeight;
+ }
+ return false;
+ break;
+ case 40: // Down arrow, similar in behaviour to up arrow.
+ if (showPicker()) {
+ return false;
+ };
+ $selected = $("li.selected", $tpList);
+ var next = $selected.next().addClass("selected")[0];
+ if (next) {
+ $selected.removeClass("selected");
+ if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) {
+ $tpDiv[0].scrollTop = top + next.offsetHeight;
+ }
+ }
+ else {
+ $selected.removeClass("selected");
+ next = $("li:first", $tpList).addClass("selected")[0];
+ $tpDiv[0].scrollTop = 0;
+ }
+ return false;
+ break;
+ case 13: // Enter
+ if ($tpDiv.is(":visible")) {
+ var sel = $("li.selected", $tpList)[0];
+ setTimeVal(elm, sel, $tpDiv, settings);
+ }
+ return false;
+ break;
+ case 27: // Esc
+ $tpDiv.hide();
+ return false;
+ break;
+ }
+ return true;
+ });
+ $(elm).keyup(function(e) {
+ keyDown = false;
+ });
+ // Helper function to get an inputs current time as Date object.
+ // Returns a Date object.
+ this.getTime = function() {
+ return timeStringToDate(elm.value, settings);
+ };
+ // Helper function to set a time input.
+ // Takes a Date object.
+ this.setTime = function(time) {
+ elm.value = formatTime(normaliseTime(time), settings);
+ // Trigger element's change events.
+ $(elm).change();
+ };
+
+ }; // End fn;
+
+ // Plugin defaults.
+ $.fn.timePicker.defaults = {
+ step:30,
+ startTime: new Date(0, 0, 0, 0, 0, 0),
+ endTime: new Date(0, 0, 0, 23, 30, 0),
+ selectedTime: new Date(0, 0, 0, 0, 0, 0),
+ separator: ':',
+ show24Hours: true
+ };
+
+ // Private functions.
+
+ function setTimeVal(elm, sel, $tpDiv, settings) {
+ // Update input field
+ elm.value = $(sel).text();
+ // Trigger element's change events.
+ $(elm).change();
+ // Keep focus for all but IE (which doesn't like it)
+ if (!$.browser.msie) {
+ elm.focus();
+ }
+ // Hide picker
+ $tpDiv.hide();
+ }
+
+ function formatTime(time, settings) {
+ var h = time.getHours();
+ var hours = settings.show24Hours ? h : (((h + 11) % 12) + 1);
+ var minutes = time.getMinutes();
+ return formatNumber(hours) + settings.separator + formatNumber(minutes) + (settings.show24Hours ? '' : ((h < 12) ? ' AM' : ' PM'));
+ }
+
+ function formatNumber(value) {
+ return (value < 10 ? '0' : '') + value;
+ }
+
+ function timeToDate(input, settings) {
+ return (typeof input == 'object') ? normaliseTime(input) : timeStringToDate(input, settings);
+ }
+
+ function timeStringToDate(input, settings) {
+ if (input) {
+ var array = input.split(settings.separator);
+ var hours = parseFloat(array[0]);
+ var minutes = parseFloat(array[1]);
+
+ // Convert AM/PM hour to 24-hour format.
+ if (!settings.show24Hours) {
+ if (hours === 12 && input.substr('AM') !== -1) {
+ hours = 0;
+ }
+ else if (hours !== 12 && input.indexOf('PM') !== -1) {
+ hours += 12;
+ }
+ }
+ var time = new Date(0, 0, 0, hours, minutes, 0);
+ return normaliseTime(time);
+ }
+ return null;
+ }
+
+ /* Normalise time object to a common date. */
+ function normaliseTime(time) {
+ time.setFullYear(2001);
+ time.setMonth(0);
+ time.setDate(0);
+ return time;
+ }
+
+})(jQuery);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/jquery.timepicker.css Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,65 @@
+/*div.time-holder {
+ position: relative;
+ display: none;
+ z-index: 99;
+ width: 100px;
+}
+
+div.time-holder div.times {
+ position: absolute;
+ top: 0;
+ height: 120px;
+ overflow: auto;
+ background: #fff;
+ border: 1px solid #000;
+}
+
+div.time-holder div.times ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ width: 80px;
+}
+
+div.time-holder div.times li {
+ padding: 1px;
+}
+
+div.time-holder div.times li.selected {
+ background: #316AC5;
+ color: #fff;
+}
+*/
+
+div.time-picker {
+ position: absolute;
+ height: 200px;
+ width: 5em; /* needed for IE */
+ overflow: auto;
+ background: #fff;
+ border: 1px solid #000;
+ z-index: 99;
+}
+div.time-picker-12hours {
+ width:8em; /* needed for IE */
+}
+
+div.time-picker ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+div.time-picker li {
+ background: none;
+ list-style-type: none;
+ padding: 1px;
+ cursor: pointer;
+}
+div.time-picker li.selected {
+ background: #316AC5;
+ color: #fff;
+}
+
+input.timepicker {
+ width: 5em;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/jquery.ui.css Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,406 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
+.ui-widget-content a { color: #333333; }
+.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; outline: none; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; outline: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; outline: none; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; outline: none; }
+.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; outline: none; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; outline: none; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #ffffff; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-right { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
+.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -webkit-border-radius: 5px; }/* Accordion
+----------------------------------*/
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
+.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker
+----------------------------------*/
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ display: none; /*sorry for IE5*/
+ display/**/: block; /*sorry for IE5*/
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}/* Dialog
+----------------------------------*/
+.ui-dialog { position: relative; padding: .2em; width: 300px; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/* Progressbar
+----------------------------------*/
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
+----------------------------------*/
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
+----------------------------------*/
+.ui-tabs { padding: .2em; zoom: 1; }
+.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
+.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/jquery.ui.js Mon Feb 08 13:35:07 2010 +0100
@@ -0,0 +1,298 @@
+/*
+ * jQuery UI 1.7.2
+ *
+ * Copyright (c) 2009 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.2",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m<n.length;m++){if(j.options[n[m][0]]){n[m][1].apply(j.element,k)}}}},contains:function(k,j){return document.compareDocumentPosition?k.compareDocumentPosition(j)&16:k!==j&&k.contains(j)},hasScroll:function(m,k){if(c(m).css("overflow")=="hidden"){return false}var j=(k&&k=="left")?"scrollLeft":"scrollTop",l=false;if(m[j]>0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
+ * jQuery UI Draggable 1.7.2
+ *
+ * Copyright (c) 2009 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.2",eventPrefix:"drag",defaults:{addClasses:true,appendTo:"parent",axis:false,cancel:":input,option",connectToSortable:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,grid:false,handle:false,helper:"original",iframeFix:false,opacity:false,refreshPositions:false,revert:false,revertDuration:500,scope:"default",scroll:true,scrollSensitivity:20,scrollSpeed:20,snap:false,snapMode:"both",snapTolerance:20,stack:false,zIndex:false}});a.ui.plugin.add("draggable","connectToSortable",{start:function(c,e){var d=a(this).data("draggable"),f=d.options,b=a.extend({},e,{item:d.element});d.sortables=[];a(f.connectToSortable).each(function(){var g=a.data(this,"sortable");if(g&&!g.options.disabled){d.sortables.push({instance:g,shouldRevert:g.options.revert});g._refreshItems();g._trigger("activate",c,b)}})},stop:function(c,e){var d=a(this).data("draggable"),b=a.extend({},e,{item:d.element});a.each(d.sortables,function(){if(this.instance.isOver){this.instance.isOver=0;d.cancelHelperRemoval=true;this.instance.cancelHelperRemoval=false;if(this.shouldRevert){this.instance.options.revert=true}this.instance._mouseStop(c);this.instance.options.helper=this.instance.options._helper;if(d.options.helper=="original"){this.instance.currentItem.css({top:"auto",left:"auto"})}}else{this.instance.cancelHelperRemoval=false;this.instance._trigger("deactivate",c,b)}})},drag:function(c,f){var e=a(this).data("draggable"),b=this;var d=function(i){var n=this.offset.click.top,m=this.offset.click.left;var g=this.positionAbs.top,k=this.positionAbs.left;var j=i.height,l=i.width;var p=i.top,h=i.left;return a.ui.isOver(g+n,k+m,p,h,j,l)};a.each(e.sortables,function(g){this.instance.positionAbs=e.positionAbs;this.instance.helperProportions=e.helperProportions;this.instance.offset.click=e.offset.click;if(this.instance._intersectsWith(this.instance.containerCache)){if(!this.instance.isOver){this.instance.isOver=1;this.instance.currentItem=a(b).clone().appendTo(this.instance.element).data("sortable-item",true);this.instance.options._helper=this.instance.options.helper;this.instance.options.helper=function(){return f.helper[0]};c.target=this.instance.currentItem[0];this.instance._mouseCapture(c,true);this.instance._mouseStart(c,true,true);this.instance.offset.click.top=e.offset.click.top;this.instance.offset.click.left=e.offset.click.left;this.instance.offset.parent.left-=e.offset.parent.left-this.instance.offset.parent.left;this.instance.offset.parent.top-=e.offset.parent.top-this.instance.offset.parent.top;e._trigger("toSortable",c);e.dropped=this.instance.element;e.currentItem=e.element;this.instance.fromOutside=e}if(this.instance.currentItem){this.instance._mouseDrag(c)}}else{if(this.instance.isOver){this.instance.isOver=0;this.instance.cancelHelperRemoval=true;this.instance.options.revert=false;this.instance._trigger("out",c,this.instance._uiHash(this.instance));this.instance._mouseStop(c,true);this.instance.options.helper=this.instance.options._helper;this.instance.currentItem.remove();if(this.instance.placeholder){this.instance.placeholder.remove()}e._trigger("fromSortable",c);e.dropped=false}}})}});a.ui.plugin.add("draggable","cursor",{start:function(c,d){var b=a("body"),e=a(this).data("draggable").options;if(b.css("cursor")){e._cursor=b.css("cursor")}b.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._cursor){a("body").css("cursor",d._cursor)}}});a.ui.plugin.add("draggable","iframeFix",{start:function(b,c){var d=a(this).data("draggable").options;a(d.iframeFix===true?"iframe":d.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1000}).css(a(this).offset()).appendTo("body")})},stop:function(b,c){a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)})}});a.ui.plugin.add("draggable","opacity",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("opacity")){e._opacity=b.css("opacity")}b.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._opacity){a(c.helper).css("opacity",d._opacity)}}});a.ui.plugin.add("draggable","scroll",{start:function(c,d){var b=a(this).data("draggable");if(b.scrollParent[0]!=document&&b.scrollParent[0].tagName!="HTML"){b.overflowOffset=b.scrollParent.offset()}},drag:function(d,e){var c=a(this).data("draggable"),f=c.options,b=false;if(c.scrollParent[0]!=document&&c.scrollParent[0].tagName!="HTML"){if(!f.axis||f.axis!="x"){if((c.overflowOffset.top+c.scrollParent[0].offsetHeight)-d.pageY<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop+f.scrollSpeed}else{if(d.pageY-c.overflowOffset.top<f.scrollSensitivity){c.scrollParent[0].scrollTop=b=c.scrollParent[0].scrollTop-f.scrollSpeed}}}if(!f.axis||f.axis!="y"){if((c.overflowOffset.left+c.scrollParent[0].offsetWidth)-d.pageX<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft+f.scrollSpeed}else{if(d.pageX-c.overflowOffset.left<f.scrollSensitivity){c.scrollParent[0].scrollLeft=b=c.scrollParent[0].scrollLeft-f.scrollSpeed}}}}else{if(!f.axis||f.axis!="x"){if(d.pageY-a(document).scrollTop()<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-f.scrollSpeed)}else{if(a(window).height()-(d.pageY-a(document).scrollTop())<f.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+f.scrollSpeed)}}}if(!f.axis||f.axis!="y"){if(d.pageX-a(document).scrollLeft()<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-f.scrollSpeed)}else{if(a(window).width()-(d.pageX-a(document).scrollLeft())<f.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+f.scrollSpeed)}}}}if(b!==false&&a.ui.ddmanager&&!f.dropBehaviour){a.ui.ddmanager.prepareOffsets(c,d)}}});a.ui.plugin.add("draggable","snap",{start:function(c,d){var b=a(this).data("draggable"),e=b.options;b.snapElements=[];a(e.snap.constructor!=String?(e.snap.items||":data(draggable)"):e.snap).each(function(){var g=a(this);var f=g.offset();if(this!=b.element[0]){b.snapElements.push({item:this,width:g.outerWidth(),height:g.outerHeight(),top:f.top,left:f.left})}})},drag:function(u,p){var g=a(this).data("draggable"),q=g.options;var y=q.snapTolerance;var x=p.offset.left,w=x+g.helperProportions.width,f=p.offset.top,e=f+g.helperProportions.height;for(var v=g.snapElements.length-1;v>=0;v--){var s=g.snapElements[v].left,n=s+g.snapElements[v].width,m=g.snapElements[v].top,A=m+g.snapElements[v].height;if(!((s-y<x&&x<n+y&&m-y<f&&f<A+y)||(s-y<x&&x<n+y&&m-y<e&&e<A+y)||(s-y<w&&w<n+y&&m-y<f&&f<A+y)||(s-y<w&&w<n+y&&m-y<e&&e<A+y))){if(g.snapElements[v].snapping){(g.options.snap.release&&g.options.snap.release.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=false;continue}if(q.snapMode!="inner"){var c=Math.abs(m-e)<=y;var z=Math.abs(A-f)<=y;var j=Math.abs(s-w)<=y;var k=Math.abs(n-x)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m-g.helperProportions.height,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s-g.helperProportions.width}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n}).left-g.margins.left}}var h=(c||z||j||k);if(q.snapMode!="outer"){var c=Math.abs(m-f)<=y;var z=Math.abs(A-e)<=y;var j=Math.abs(s-x)<=y;var k=Math.abs(n-w)<=y;if(c){p.position.top=g._convertPositionTo("relative",{top:m,left:0}).top-g.margins.top}if(z){p.position.top=g._convertPositionTo("relative",{top:A-g.helperProportions.height,left:0}).top-g.margins.top}if(j){p.position.left=g._convertPositionTo("relative",{top:0,left:s}).left-g.margins.left}if(k){p.position.left=g._convertPositionTo("relative",{top:0,left:n-g.helperProportions.width}).left-g.margins.left}}if(!g.snapElements[v].snapping&&(c||z||j||k||h)){(g.options.snap.snap&&g.options.snap.snap.call(g.element,u,a.extend(g._uiHash(),{snapItem:g.snapElements[v].item})))}g.snapElements[v].snapping=(c||z||j||k||h)}}});a.ui.plugin.add("draggable","stack",{start:function(b,c){var e=a(this).data("draggable").options;var d=a.makeArray(a(e.stack.group)).sort(function(g,f){return(parseInt(a(g).css("zIndex"),10)||e.stack.min)-(parseInt(a(f).css("zIndex"),10)||e.stack.min)});a(d).each(function(f){this.style.zIndex=e.stack.min+f});this[0].style.zIndex=e.stack.min+d.length}});a.ui.plugin.add("draggable","zIndex",{start:function(c,d){var b=a(d.helper),e=a(this).data("draggable").options;if(b.css("zIndex")){e._zIndex=b.css("zIndex")}b.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;if(d._zIndex){a(c.helper).css("zIndex",d._zIndex)}}})})(jQuery);;/*
+ * jQuery UI Droppable 1.7.2
+ *
+ * Copyright (c) 2009 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.2",eventPrefix:"drop",defaults:{accept:"*",activeClass:false,addClasses:true,greedy:false,hoverClass:false,scope:"default",tolerance:"intersect"}});a.ui.intersect=function(q,j,o){if(!j.offset){return false}var e=(q.positionAbs||q.position.absolute).left,d=e+q.helperProportions.width,n=(q.positionAbs||q.position.absolute).top,m=n+q.helperProportions.height;var g=j.offset.left,c=g+j.proportions.width,p=j.offset.top,k=p+j.proportions.height;switch(o){case"fit":return(g<e&&d<c&&p<n&&m<k);break;case"intersect":return(g<e+(q.helperProportions.width/2)&&d-(q.helperProportions.width/2)<c&&p<n+(q.helperProportions.height/2)&&m-(q.helperProportions.height/2)<k);break;case"pointer":var h=((q.positionAbs||q.position.absolute).left+(q.clickOffset||q.offset.click).left),i=((q.positionAbs||q.position.absolute).top+(q.clickOffset||q.offset.click).top),f=a.ui.isOver(i,h,p,g,j.proportions.height,j.proportions.width);return f;break;case"touch":return((n>=p&&n<=k)||(m>=p&&m<=k)||(n<p&&m>k))&&((e>=g&&e<=c)||(d>=g&&d<=c)||(e<g&&d>c));break;default:return false;break}};a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,g){var b=a.ui.ddmanager.droppables[e.options.scope];var f=g?g.type:null;var h=(e.currentItem||e.element).find(":data(droppable)").andSelf();droppablesLoop:for(var d=0;d<b.length;d++){if(b[d].options.disabled||(e&&!b[d].options.accept.call(b[d].element[0],(e.currentItem||e.element)))){continue}for(var c=0;c<h.length;c++){if(h[c]==b[d].element[0]){b[d].proportions.height=0;continue droppablesLoop}}b[d].visible=b[d].element.css("display")!="none";if(!b[d].visible){continue}b[d].offset=b[d].element.offset();b[d].proportions={width:b[d].element[0].offsetWidth,height:b[d].element[0].offsetHeight};if(f=="mousedown"){b[d]._activate.call(b[d],g)}}},drop:function(b,c){var d=false;a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(!this.options){return}if(!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)){d=this._drop.call(this,c)}if(!this.options.disabled&&this.visible&&this.options.accept.call(this.element[0],(b.currentItem||b.element))){this.isout=1;this.isover=0;this._deactivate.call(this,c)}});return d},drag:function(b,c){if(b.options.refreshPositions){a.ui.ddmanager.prepareOffsets(b,c)}a.each(a.ui.ddmanager.droppables[b.options.scope],function(){if(this.options.disabled||this.greedyChild||!this.visible){return}var e=a.ui.intersect(b,this,this.options.tolerance);var g=!e&&this.isover==1?"isout":(e&&this.isover==0?"isover":null);if(!g){return}var f;if(this.options.greedy){var d=this.element.parents(":data(droppable):eq(0)");if(d.length){f=a.data(d[0],"droppable");f.greedyChild=(g=="isover"?1:0)}}if(f&&g=="isover"){f.isover=0;f.isout=1;f._out.call(f,c)}this[g]=1;this[g=="isout"?"isover":"isout"]=0;this[g=="isover"?"_over":"_out"].call(this,c);if(f&&g=="isout"){f.isout=0;f.isover=1;f._over.call(f,c)}})}}})(jQuery);;/*
+ * jQuery UI Resizable 1.7.2
+ *
+ * Copyright (c) 2009 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/Resizables
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(c){c.widget("ui.resizable",c.extend({},c.ui.mouse,{_init:function(){var e=this,j=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(j.aspectRatio),aspectRatio:j.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:j.helper||j.ghost||j.animate?j.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){if(/relative/.test(this.element.css("position"))&&c.browser.opera){this.element.css({position:"relative",top:"auto",left:"auto"})}this.element.wrap(c('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=j.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var k=this.handles.split(",");this.handles={};for(var f=0;f<k.length;f++){var h=c.trim(k[f]),d="ui-resizable-"+h;var g=c('<div class="ui-resizable-handle '+d+'"></div>');if(/sw|se|ne|nw/.test(h)){g.css({zIndex:++j.zIndex})}if("se"==h){g.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[h]=".ui-resizable-"+h;this.element.append(g)}}this._renderAxis=function(p){p=p||this.element;for(var m in this.handles){if(this.handles[m].constructor==String){this.handles[m]=c(this.handles[m],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var n=c(this.handles[m],this.element),o=0;o=/sw|ne|nw|se|n|s/.test(m)?n.outerHeight():n.outerWidth();var l=["padding",/ne|nw|n/.test(m)?"Top":/se|sw|s/.test(m)?"Bottom":/^e$/.test(m)?"Right":"Left"].join("");p.css(l,o);this._proportionallyResize()}if(!c(this.handles[m]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!e.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}e.axis=i&&i[1]?i[1]:"se"}});if(j.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){c(this).removeClass("ui-resizable-autohide");e._handles.show()},function(){if(!e.resizing){c(this).addClass("ui-resizable-autohide");e._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var d=function(f){c(f).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){d(this.element);var e=this.element;e.parent().append(this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")})).end().remove()}this.originalElement.css("resize",this.originalResizeStyle);d(this.originalElement)},_mouseCapture:function(e){var f=false;for(var d in this.handles){if(c(this.handles[d])[0]==e.target){f=true}}return this.options.disabled||!!f},_mouseStart:function(f){var i=this.options,e=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(d.is(".ui-draggable")||(/absolute/).test(d.css("position"))){d.css({position:"absolute",top:e.top,left:e.left})}if(c.browser.opera&&(/relative/).test(d.css("position"))){d.css({position:"relative",top:"auto",left:"auto"})}this._renderProxy();var j=b(this.helper.css("left")),g=b(this.helper.css("top"));if(i.containment){j+=c(i.containment).scrollLeft()||0;g+=c(i.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:j,top:g};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:j,top:g};this.sizeDiff={width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:f.pageX,top:f.pageY};this.aspectRatio=(typeof i.aspectRatio=="number")?i.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var h=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",h=="auto"?this.axis+"-resize":h);d.addClass("ui-resizable-resizing");this._propagate("start",f);return true},_mouseDrag:function(d){var g=this.helper,f=this.options,l={},p=this,i=this.originalMousePosition,m=this.axis;var q=(d.pageX-i.left)||0,n=(d.pageY-i.top)||0;var h=this._change[m];if(!h){return false}var k=h.apply(this,[d,q,n]),j=c.browser.msie&&c.browser.version<7,e=this.sizeDiff;if(this._aspectRatio||d.shiftKey){k=this._updateRatio(k,d)}k=this._respectSize(k,d);this._propagate("resize",d);g.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(k);this._trigger("resize",d,this.ui());return false},_mouseStop:function(g){this.resizing=false;var h=this.options,l=this;if(this._helper){var f=this._proportionallyResizeElements,d=f.length&&(/textarea/i).test(f[0].nodeName),e=d&&c.ui.hasScroll(f[0],"left")?0:l.sizeDiff.height,j=d?0:l.sizeDiff.width;var m={width:(l.size.width-j),height:(l.size.height-e)},i=(parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left))||null,k=(parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top))||null;if(!h.animate){this.element.css(c.extend(m,{top:k,left:i}))}l.helper.height(l.size.height);l.helper.width(l.size.width);if(this._helper&&!h.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",g);if(this._helper){this.helper.remove()}return false},_updateCache:function(d){var e=this.options;this.offset=this.helper.offset();if(a(d.left)){this.position.left=d.left}if(a(d.top)){this.position.top=d.top}if(a(d.height)){this.size.height=d.height}if(a(d.width)){this.size.width=d.width}},_updateRatio:function(g,f){var h=this.options,i=this.position,e=this.size,d=this.axis;if(g.height){g.width=(e.height*this.aspectRatio)}else{if(g.width){g.height=(e.width/this.aspectRatio)}}if(d=="sw"){g.left=i.left+(e.width-g.width);g.top=null}if(d=="nw"){g.top=i.top+(e.height-g.height);g.left=i.left+(e.width-g.width)}return g},_respectSize:function(k,f){var i=this.helper,h=this.options,q=this._aspectRatio||f.shiftKey,p=this.axis,s=a(k.width)&&h.maxWidth&&(h.maxWidth<k.width),l=a(k.height)&&h.maxHeight&&(h.maxHeight<k.height),g=a(k.width)&&h.minWidth&&(h.minWidth>k.width),r=a(k.height)&&h.minHeight&&(h.minHeight>k.height);if(g){k.width=h.minWidth}if(r){k.height=h.minHeight}if(s){k.width=h.maxWidth}if(l){k.height=h.maxHeight}var e=this.originalPosition.left+this.originalSize.width,n=this.position.top+this.size.height;var j=/sw|nw|w/.test(p),d=/nw|ne|n/.test(p);if(g&&j){k.left=e-h.minWidth}if(s&&j){k.left=e-h.maxWidth}if(r&&d){k.top=n-h.minHeight}if(l&&d){k.top=n-h.maxHeight}var m=!k.width&&!k.height;if(m&&!k.left&&k.top){k.top=null}else{if(m&&!k.top&&k.left){k.left=null}}return k},_proportionallyResize:function(){var j=this.options;if(!this._proportionallyResizeElements.length){return}var f=this.helper||this.element;for(var e=0;e<this._proportionallyResizeElements.length;e++){var g=this._proportionallyResizeElements[e];if(!this.borderDif){var d=[g.css("borderTopWidth"),g.css("borderRightWidth"),g.css("borderBottomWidth"),g.css("borderLeftWidth")],h=[g.css("paddingTop"),g.css("paddingRight"),g.css("paddingBottom"),g.css("paddingLeft")];this.borderDif=c.map(d,function(k,m){var l=parseInt(k,10)||0,n=parseInt(h[m],10)||0;return l+n})}if(c.browser.msie&&!(!(c(f).is(":hidden")||c(f).parents(":hidden").length))){continue}g.css({height:(f.height()-this.borderDif[0]-this.borderDif[2])||0,width:(f.width()-this.borderDif[1]-this.borderDif[3])||0})}},_renderProxy:function(){var e=this.element,h=this.options;this.elementOffset=e.offset();if(this._helper){this.helper=this.helper||c('<div style="overflow:hidden;"></div>');var d=c.browser.msie&&c.browser.version<7,f=(d?1:0),g=(d?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+g,height:this.element.outerHeight()+g,position:"absolute",left:this.elementOffset.left-f+"px",top:this.elementOffset.top-f+"px",zIndex:++h.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(f,e,d){return{width:this.originalSize.width+e}},w:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{left:h.left+e,width:f.width-e}},n:function(g,e,d){var i=this.options,f=this.originalSize,h=this.originalPosition;return{top:h.top+d,height:f.height-d}},s:function(f,e,d){return{height:this.originalSize.height+d}},se:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},sw:function(f,e,d){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[f,e,d]))},ne:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[f,e,d]))},nw:function(f,e,d){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[f,e,d]))}},_propagate:function(e,d){c.ui.plugin.call(this,e,[d,this.ui()]);(e!="resize"&&this._trigger(e,d,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}));c.extend(c.ui.resizable,{version:"1.7.2",eventPrefix:"resize",defaults:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,cancel:":input,option",containment:false,delay:0,distance:1,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000}});c.ui.plugin.add("resizable","alsoResize",{start:function(e,f){var d=c(this).data("resizable"),g=d.options;_store=function(h){c(h).each(function(){c(this).data("resizable-alsoresize",{width:parseInt(c(this).width(),10),height:parseInt(c(this).height(),10),left:parseInt(c(this).css("left"),10),top:parseInt(c(this).css("top"),10)})})};if(typeof(g.alsoResize)=="object"&&!g.alsoResize.parentNode){if(g.alsoResize.length){g.alsoResize=g.alsoResize[0];_store(g.alsoResize)}else{c.each(g.alsoResize,function(h,i){_store(h)})}}else{_store(g.alsoResize)}},resize:function(f,h){var e=c(this).data("resizable"),i=e.options,g=e.originalSize,k=e.originalPosition;var j={height:(e.size.height-g.height)||0,width:(e.size.width-g.width)||0,top:(e.position.top-k.top)||0,left:(e.position.left-k.left)||0},d=function(l,m){c(l).each(function(){var p=c(this),q=c(this).data("resizable-alsoresize"),o={},n=m&&m.length?m:["width","height","top","left"];c.each(n||["width","height","top","left"],function(r,t){var s=(q[t]||0)+(j[t]||0);if(s&&s>=0){o[t]=s||null}});if(/relative/.test(p.css("position"))&&c.browser.opera){e._revertToRelativePosition=true;p.css({position:"absolute",top:"auto",left:"auto"})}p.css(o)})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.nodeType){c.each(i.alsoResize,function(l,m){d(l,m)})}else{d(i.alsoResize)}},stop:function(e,f){var d=c(this).data("resizable");if(d._revertToRelativePosition&&c.browser.opera){d._revertToRelativePosition=false;el.css({position:"relative"})}c(this).removeData("resizable-alsoresize-start")}});c.ui.plugin.add("resizable","animate",{stop:function(h,m){var n=c(this).data("resizable"),i=n.options;var g=n._proportionallyResizeElements,d=g.length&&(/textarea/i).test(g[0].nodeName),e=d&&c.ui.hasScroll(g[0],"left")?0:n.sizeDiff.height,k=d?0:n.sizeDiff.width;var f={width:(n.size.width-k),height:(n.size.height-e)},j=(parseInt(n.element.css("left"),10)+(n.position.left-n.originalPosition.left))||null,l=(parseInt(n.element.css("top"),10)+(n.position.top-n.originalPosition.top))||null;n.element.animate(c.extend(f,l&&j?{top:l,left:j}:{}),{duration:i.animateDuration,easing:i.animateEasing,step:function(){var o={width:parseInt(n.element.css("width"),10),height:parseInt(n.element.css("height"),10),top:parseInt(n.element.css("top"),10),left:parseInt(n.element.css("left"),10)};if(g&&g.length){c(g[0]).css({width:o.width,height:o.height})}n._updateCache(o);n._propagate("resize",h)}})}});c.ui.plugin.add("resizable","containment",{start:function(e,q){var s=c(this).data("resizable"),i=s.options,k=s.element;var f=i.containment,j=(f instanceof c)?f.get(0):(/parent/.test(f))?k.parent().get(0):f;if(!j){return}s.containerElement=c(j);if(/document/.test(f)||f==document){s.containerOffset={left:0,top:0};s.containerPosition={left:0,top:0};s.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var m=c(j),h=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){h[p]=b(m.css("padding"+o))});s.containerOffset=m.offset();s.containerPosition=m.position();s.containerSize={height:(m.innerHeight()-h[3]),width:(m.innerWidth()-h[1])};var n=s.containerOffset,d=s.containerSize.height,l=s.containerSize.width,g=(c.ui.hasScroll(j,"left")?j.scrollWidth:l),r=(c.ui.hasScroll(j)?j.scrollHeight:d);s.parentData={element:j,left:n.left,top:n.top,width:g,height:r}}},resize:function(f,p){var s=c(this).data("resizable"),h=s.options,e=s.containerSize,n=s.containerOffset,l=s.size,m=s.position,q=s._aspectRatio||f.shiftKey,d={top:0,left:0},g=s.containerElement;if(g[0]!=document&&(/static/).test(g.css("position"))){d=n}if(m.left<(s._helper?n.left:0)){s.size.width=s.size.width+(s._helper?(s.position.left-n.left):(s.position.left-d.left));if(q){s.size.height=s.size.width/h.aspectRatio}s.position.left=h.helper?n.left:0}if(m.top<(s._helper?n.top:0)){s.size.height=s.size.height+(s._helper?(s.position.top-n.top):s.position.top);if(q){s.size.width=s.size.height*h.aspectRatio}s.position.top=s._helper?n.top:0}s.offset.left=s.parentData.left+s.position.left;s.offset.top=s.parentData.top+s.position.top;var k=Math.abs((s._helper?s.offset.left-d.left:(s.offset.left-d.left))+s.sizeDiff.width),r=Math.abs((s._helper?s.offset.top-d.top:(s.offset.top-n.top))+s.sizeDiff.height);var j=s.containerElement.get(0)==s.element.parent().get(0),i=/relative|absolute/.test(s.containerElement.css("position"));if(j&&i){k-=s.parentData.left}if(k+s.size.width>=s.parentData.width){s.size.width=s.parentData.width-k;if(q){s.size.height=s.size.width/s.aspectRatio}}if(r+s.size.height>=s.parentData.height){s.size.height=s.parentData.height-r;if(q){s.size.width=s.size.height*s.aspectRatio}}},stop:function(e,m){var p=c(this).data("resizable"),f=p.options,k=p.position,l=p.containerOffset,d=p.containerPosition,g=p.containerElement;var i=c(p.helper),q=i.offset(),n=i.outerWidth()-p.sizeDiff.width,j=i.outerHeight()-p.sizeDiff.height;if(p._helper&&!f.animate&&(/relative/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}if(p._helper&&!f.animate&&(/static/).test(g.css("position"))){c(this).css({left:q.left-d.left-l.left,width:n,height:j})}}});c.ui.plugin.add("resizable","ghost",{start:function(f,g){var d=c(this).data("resizable"),h=d.options,e=d.size;d.ghost=d.originalElement.clone();d.ghost.css({opacity:0.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof h.ghost=="string"?h.ghost:"");d.ghost.appendTo(d.helper)},resize:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost){d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})}},stop:function(e,f){var d=c(this).data("resizable"),g=d.options;if(d.ghost&&d.helper){d.helper.get(0).removeChild(d.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(d,l){var n=c(this).data("resizable"),g=n.options,j=n.size,h=n.originalSize,i=n.originalPosition,m=n.axis,k=g._aspectRatio||d.shiftKey;g.grid=typeof g.grid=="number"?[g.grid,g.grid]:g.grid;var f=Math.round((j.width-h.width)/(g.grid[0]||1))*(g.grid[0]||1),e=Math.round((j.height-h.height)/(g.grid[1]||1))*(g.grid[1]||1);if(/^(se|s|e)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e}else{if(/^(ne)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e}else{if(/^(sw)$/.test(m)){n.size.width=h.width+f;n.size.height=h.height+e;n.position.left=i.left-f}else{n.size.width=h.width+f;n.size.height=h.height+e;n.position.top=i.top-e;n.position.left=i.left-f}}}}});var b=function(d){return parseInt(d,10)||0};var a=function(d){return !isNaN(parseInt(d,10))}})(jQuery);;/*
+ * jQuery UI Selectable 1.7.2
+ *
+ * Copyright (c) 2009 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/Selectables
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(a){a.widget("ui.selectable",a.extend({},a.ui.mouse,{_init:function(){var b=this;this.element.addClass("ui-selectable");this.dragged=false;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]);c.each(function(){var d=a(this);var e=d.offset();a.data(this,"selectable-item",{element:this,$element:d,left:e.left,top:e.top,right:e.left+d.outerWidth(),bottom:e.top+d.outerHeight(),startselected:false,selected:d.hasClass("ui-selected"),selecting:d.hasClass("ui-selecting"),unselecting:d.hasClass("ui-unselecting")})})};this.refresh();this.selectees=c.addClass("ui-selectee");this._mouseInit();this.helper=a(document.createElement("div")).css({border:"1px dotted black"}).addClass("ui-selectable-helper")},destroy:function(){this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable");this._mouseDestroy()},_mouseStart:function(d){var b=this;this.opos=[d.pageX,d.pageY];if(this.options.disabled){return}var c=this.options;this.selectees=a(c.filter,this.element[0]);this._trigger("start",d);a(c.appendTo).append(this.helper);this.helper.css({"z-index":100,position:"absolute",left:d.clientX,top:d.clientY,width:0,height:0});if(c.autoRefresh){this.refresh()}this.selectees.filter(".ui-selected").each(function(){var e=a.data(this,"selectable-item");e.startselected=true;if(!d.metaKey){e.$element.removeClass("ui-selected");e.selected=false;e.$element.addClass("ui-unselecting");e.unselecting=true;b._trigger("unselecting",d,{unselecting:e.element})}});a(d.target).parents().andSelf().each(function(){var e=a.data(this,"selectable-item");if(e){e.$element.removeClass("ui-unselecting").addClass("ui-selecting");e.unselecting=false;e.selecting=true;e.selected=true;b._trigger("selecting",d,{selecting:e.element});return false}})},_mouseDrag:function(i){var c=this;this.dragged=true;if(this.options.disabled){return}var e=this.options;var d=this.opos[0],h=this.opos[1],b=i.pageX,g=i.pageY;if(d>b){var f=b;b=d;d=f}if(h>g){var f=g;g=h;h=f}this.helper.css({left:d,top:h,width:b-d,height:g-h});this.selectees.each(function(){var j=a.data(this,"selectable-item");if(!j||j.element==c.element[0]){return}var k=false;if(e.tolerance=="touch"){k=(!(j.left>b||j.right<d||j.top>g||j.bottom<h))}else{if(e.tolerance=="fit"){k=(j.left>d&&j.right<b&&j.top>h&&j.bottom<g)}}if(k){if(j.selected){j.$element.removeClass("ui-selected");j.selected=false}if(j.unselecting){j.$element.removeClass("ui-unselecting");j.unselecting=false}if(!j.selecting){j.$element.addClass("ui-selecting");j.selecting=true;c._trigger("selecting",i,{selecting:j.element})}}else{if(j.selecting){if(i.metaKey&&j.startselected){j.$element.removeClass("ui-selecting");j.selecting=false;j.$element.addClass("ui-selected");j.selected=true}else{j.$element.removeClass("ui-selecting");j.selecting=false;if(j.startselected){j.$element.addClass("ui-unselecting");j.unselecting=true}c._trigger("unselecting",i,{unselecting:j.element})}}if(j.selected){if(!i.metaKey&&!j.startselected){j.$element.removeClass("ui-selected");j.selected=false;j.$element.addClass("ui-unselecting");j.unselecting=true;c._trigger("unselecting",i,{unselecting:j.element})}}}});return false},_mouseStop:function(d){var b=this;this.dragged=false;var c=this.options;a(".ui-unselecting",this.element[0]).each(function(){var e=a.data(this,"selectable-item");e.$element.removeClass("ui-unselecting");e.unselecting=false;e.startselected=false;b._trigger("unselected",d,{unselected:e.element})});a(".ui-selecting",this.element[0]).each(function(){var e=a.data(this,"selectable-item");e.$element.removeClass("ui-selecting").addClass("ui-selected");e.selecting=false;e.selected=true;e.startselected=true;b._trigger("selected",d,{selected:e.element})});this._trigger("stop",d);this.helper.remove();return false}}));a.extend(a.ui.selectable,{version:"1.7.2",defaults:{appendTo:"body",autoRefresh:true,cancel:":input,option",delay:0,distance:0,filter:"*",tolerance:"touch"}})})(jQuery);;/*
+ * jQuery UI Sortable 1.7.2
+ *
+ * Copyright (c) 2009 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/Sortables
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(a){a.widget("ui.sortable",a.extend({},a.ui.mouse,{_init:function(){var b=this.options;this.containerCache={};this.element.addClass("ui-sortable");this.refresh();this.floating=this.items.length?(/left|right/).test(this.items[0].item.css("float")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var b=this.items.length-1;b>=0;b--){this.items[b].item.removeData("sortable-item")}},_mouseCapture:function(e,f){if(this.reverting){return false}if(this.options.disabled||this.options.type=="static"){return false}this._refreshItems(e);var d=null,c=this,b=a(e.target).parents().each(function(){if(a.data(this,"sortable-item")==c){d=a(this);return false}});if(a.data(e.target,"sortable-item")==c){d=a(e.target)}if(!d){return false}if(this.options.handle&&!f){var g=false;a(this.options.handle,d).find("*").andSelf().each(function(){if(this==e.target){g=true}});if(!g){return false}}this.currentItem=d;this._removeCurrentsFromItems();return true},_mouseStart:function(e,f,b){var g=this.options,c=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(e);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");a.extend(this.offset,{click:{left:e.pageX-this.offset.left,top:e.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(e);this.originalPageX=e.pageX;this.originalPageY=e.pageY;if(g.cursorAt){this._adjustOffsetFromHelper(g.cursorAt)}this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};if(this.helper[0]!=this.currentItem[0]){this.currentItem.hide()}this._createPlaceholder();if(g.containment){this._setContainment()}if(g.cursor){if(a("body").css("cursor")){this._storedCursor=a("body").css("cursor")}a("body").css("cursor",g.cursor)}if(g.opacity){if(this.helper.css("opacity")){this._storedOpacity=this.helper.css("opacity")}this.helper.css("opacity",g.opacity)}if(g.zIndex){if(this.helper.css("zIndex")){this._storedZIndex=this.helper.css("zIndex")}this.helper.css("zIndex",g.zIndex)}if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){this.overflowOffset=this.scrollParent.offset()}this._trigger("start",e,this._uiHash());if(!this._preserveHelperProportions){this._cacheHelperProportions()}if(!b){for(var d=this.containers.length-1;d>=0;d--){this.containers[d]._trigger("activate",e,c._uiHash(this))}}if(a.ui.ddmanager){a.ui.ddmanager.current=this}if(a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,e)}this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(e);return true},_mouseDrag:function(f){this.position=this._generatePosition(f);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs){this.lastPositionAbs=this.positionAbs}if(this.options.scroll){var g=this.options,b=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if((this.overflowOffset.top+this.scrollParent[0].offsetHeight)-f.pageY<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop+g.scrollSpeed}else{if(f.pageY-this.overflowOffset.top<g.scrollSensitivity){this.scrollParent[0].scrollTop=b=this.scrollParent[0].scrollTop-g.scrollSpeed}}if((this.overflowOffset.left+this.scrollParent[0].offsetWidth)-f.pageX<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft+g.scrollSpeed}else{if(f.pageX-this.overflowOffset.left<g.scrollSensitivity){this.scrollParent[0].scrollLeft=b=this.scrollParent[0].scrollLeft-g.scrollSpeed}}}else{if(f.pageY-a(document).scrollTop()<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()-g.scrollSpeed)}else{if(a(window).height()-(f.pageY-a(document).scrollTop())<g.scrollSensitivity){b=a(document).scrollTop(a(document).scrollTop()+g.scrollSpeed)}}if(f.pageX-a(document).scrollLeft()<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()-g.scrollSpeed)}else{if(a(window).width()-(f.pageX-a(document).scrollLeft())<g.scrollSensitivity){b=a(document).scrollLeft(a(document).scrollLeft()+g.scrollSpeed)}}}if(b!==false&&a.ui.ddmanager&&!g.dropBehaviour){a.ui.ddmanager.prepareOffsets(this,f)}}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y"){this.helper[0].style.left=this.position.left+"px"}if(!this.options.axis||this.options.axis!="x"){this.helper[0].style.top=this.position.top+"px"}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d],c=e.item[0],h=this._intersectsWithPointer(e);if(!h){continue}if(c!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=c&&!a.ui.contains(this.placeholder[0],c)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],c):true)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(e)){this._rearrange(f,e)}else{break}this._trigger("change",f,this._uiHash());break}}this._contactContainers(f);if(a.ui.ddmanager){a.ui.ddmanager.drag(this,f)}this._trigger("sort",f,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(c,d){if(!c){return}if(a.ui.ddmanager&&!this.options.dropBehaviour){a.ui.ddmanager.drop(this,c)}if(this.options.revert){var b=this;var e=b.placeholder.offset();b.reverting=true;a(this.helper).animate({left:e.left-this.offset.parent.left-b.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-b.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){b._clear(c)})}else{this._clear(c,d)}return false},cancel:function(){var b=this;if(this.dragging){this._mouseUp();if(this.options.helper=="original"){this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}for(var c=this.containers.length-1;c>=0;c--){this.containers[c]._trigger("deactivate",null,b._uiHash(this));if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",null,b._uiHash(this));this.containers[c].containerCache.over=0}}}if(this.placeholder[0].parentNode){this.placeholder[0].parentNode.removeChild(this.placeholder[0])}if(this.options.helper!="original"&&this.helper&&this.helper[0].parentNode){this.helper.remove()}a.extend(this,{helper:null,dragging:false,reverting:false,_noFinalSort:null});if(this.domPosition.prev){a(this.domPosition.prev).after(this.currentItem)}else{a(this.domPosition.parent).prepend(this.currentItem)}return true},serialize:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};a(b).each(function(){var e=(a(d.item||this).attr(d.attribute||"id")||"").match(d.expression||(/(.+)[-=_](.+)/));if(e){c.push((d.key||e[1]+"[]")+"="+(d.key&&d.expression?e[1]:e[2]))}});return c.join("&")},toArray:function(d){var b=this._getItemsAsjQuery(d&&d.connected);var c=[];d=d||{};b.each(function(){c.push(a(d.item||this).attr(d.attribute||"id")||"")});return c},_intersectsWith:function(m){var e=this.positionAbs.left,d=e+this.helperProportions.width,k=this.positionAbs.top,j=k+this.helperProportions.height;var f=m.left,c=f+m.width,n=m.top,i=n+m.height;var o=this.offset.click.top,h=this.offset.click.left;var g=(k+o)>n&&(k+o)<i&&(e+h)>f&&(e+h)<c;if(this.options.tolerance=="pointer"||this.options.forcePointerForContainers||(this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>m[this.floating?"width":"height"])){return g}else{return(f<e+(this.helperProportions.width/2)&&d-(this.helperProportions.width/2)<c&&n<k+(this.helperProportions.height/2)&&j-(this.helperProportions.height/2)<i)}},_intersectsWithPointer:function(d){var e=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,d.top,d.height),c=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,d.left,d.width),g=e&&c,b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(!g){return false}return this.floating?(((f&&f=="right")||b=="down")?2:1):(b&&(b=="down"?2:1))},_intersectsWithSides:function(e){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+(e.height/2),e.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+(e.width/2),e.width),b=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();if(this.floating&&f){return((f=="right"&&d)||(f=="left"&&!d))}else{return b&&((b=="down"&&c)||(b=="up"&&!c))}},_getDragVerticalDirection:function(){var b=this.positionAbs.top-this.lastPositionAbs.top;return b!=0&&(b>0?"down":"up")},_getDragHorizontalDirection:function(){var b=this.positionAbs.left-this.lastPositionAbs.left;return b!=0&&(b>0?"right":"left")},refresh:function(b){this._refreshItems(b);this.refreshPositions()},_connectWith:function(){var b=this.options;return b.connectWith.constructor==String?[b.connectWith]:b.connectWith},_getItemsAsjQuery:function(b){var l=this;var g=[];var e=[];var h=this._connectWith();if(h&&b){for(var d=h.length-1;d>=0;d--){var k=a(h[d]);for(var c=k.length-1;c>=0;c--){var f=a.data(k[c],"sortable");if(f&&f!=this&&!f.options.disabled){e.push([a.isFunction(f.options.items)?f.options.items.call(f.element):a(f.options.items,f.element).not(".ui-sortable-helper"),f])}}}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper"),this]);for(var d=e.length-1;d>=0;d--){e[d][0].each(function(){g.push(this)})}return a(g)},_removeCurrentsFromItems:function(){var d=this.currentItem.find(":data(sortable-item)");for(var c=0;c<this.items.length;c++){for(var b=0;b<d.length;b++){if(d[b]==this.items[c].item[0]){this.items.splice(c,1)}}}},_refreshItems:function(b){this.items=[];this.containers=[this];var h=this.items;var p=this;var f=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]];var l=this._connectWith();if(l){for(var e=l.length-1;e>=0;e--){var m=a(l[e]);for(var d=m.length-1;d>=0;d--){var g=a.data(m[d],"sortable");if(g&&g!=this&&!g.options.disabled){f.push([a.isFunction(g.options.items)?g.options.items.call(g.element[0],b,{item:this.currentItem}):a(g.options.items,g.element),g]);this.containers.push(g)}}}}for(var e=f.length-1;e>=0;e--){var k=f[e][1];var c=f[e][0];for(var d=0,n=c.length;d<n;d++){var o=a(c[d]);o.data("sortable-item",k);h.push({item:o,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){if(this.offsetParent&&this.helper){this.offset.parent=this._getParentOffset()}for(var d=this.items.length-1;d>=0;d--){var e=this.items[d];if(e.instance!=this.currentContainer&&this.currentContainer&&e.item[0]!=this.currentItem[0]){continue}var c=this.options.toleranceElement?a(this.options.toleranceElement,e.item):e.item;if(!b){e.width=c.outerWidth();e.height=c.outerHeight()}var f=c.offset();e.left=f.left;e.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers){this.options.custom.refreshContainers.call(this)}else{for(var d=this.containers.length-1;d>=0;d--){var f=this.containers[d].element.offset();this.containers[d].containerCache.left=f.left;this.containers[d].containerCache.top=f.top;this.containers[d].containerCache.width=this.containers[d].element.outerWidth();this.containers[d].containerCache.height=this.containers[d].element.outerHeight()}}},_createPlaceholder:function(d){var b=d||this,e=b.options;if(!e.placeholder||e.placeholder.constructor==String){var c=e.placeholder;e.placeholder={element:function(){var f=a(document.createElement(b.currentItem[0].nodeName)).addClass(c||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!c){f.style.visibility="hidden"}return f},update:function(f,g){if(c&&!e.forcePlaceholderSize){return}if(!g.height()){g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10))}if(!g.width()){g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||0,10))}}}}b.placeholder=a(e.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);e.placeholder.update(b,b.placeholder)},_contactContainers:function(d){for(var c=this.containers.length-1;c>=0;c--){if(this._intersectsWith(this.containers[c].containerCache)){if(!this.containers[c].containerCache.over){if(this.currentContainer!=this.containers[c]){var h=10000;var g=null;var e=this.positionAbs[this.containers[c].floating?"left":"top"];for(var b=this.items.length-1;b>=0;b--){if(!a.ui.contains(this.containers[c].element[0],this.items[b].item[0])){continue}var f=this.items[b][this.containers[c].floating?"left":"top"];if(Math.abs(f-e)<h){h=Math.abs(f-e);g=this.items[b]}}if(!g&&!this.options.dropOnEmpty){continue}this.currentContainer=this.containers[c];g?this._rearrange(d,g,null,true):this._rearrange(d,null,this.containers[c].element,true);this._trigger("change",d,this._uiHash());this.containers[c]._trigger("change",d,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder)}this.containers[c]._trigger("over",d,this._uiHash(this));this.containers[c].containerCache.over=1}}else{if(this.containers[c].containerCache.over){this.containers[c]._trigger("out",d,this._uiHash(this));this.containers[c].containerCache.over=0}}}},_createHelper:function(c){var d=this.options;var b=a.isFunction(d.helper)?a(d.helper.apply(this.element[0],[c,this.currentItem])):(d.helper=="clone"?this.currentItem.clone():this.currentItem);if(!b.parents("body").length){a(d.appendTo!="parent"?d.appendTo:this.currentItem[0].parentNode)[0].appendChild(b[0])}if(b[0]==this.currentItem[0]){this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}}if(b[0].style.width==""||d.forceHelperSize){b.width(this.currentItem.width())}if(b[0].style.height==""||d.forceHelperSize){b.height(this.currentItem.height())}return b},_adjustOffsetFromHelper:function(b){if(b.left!=undefined){this.offset.click.left=b.left+this.margins.left}if(b.right!=undefined){this.offset.click.left=this.helperProportions.width-b.right+this.margins.left}if(b.top!=undefined){this.offset.click.top=b.top+this.margins.top}if(b.bottom!=undefined){this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top}},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])){b.left+=this.scrollParent.scrollLeft();b.top+=this.scrollParent.scrollTop()}if((this.offsetParent[0]==document.body)||(this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)){b={top:0,left:0}}return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var b=this.currentItem.position();return{top:b.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:b.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else{return{top:0,left:0}}},_cacheMargins:function(){this.margins={left:(parseInt(this.currentItem.css("marginLeft"),10)||0),top:(parseInt(this.currentItem.css("marginTop"),10)||0)}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e=this.options;if(e.containment=="parent"){e.containment=this.helper[0].parentNode}if(e.containment=="document"||e.containment=="window"){this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(e.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(e.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]}if(!(/^(document|window|parent)$/).test(e.containment)){var c=a(e.containment)[0];var d=a(e.containment).offset();var b=(a(c).css("overflow")!="hidden");this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(b?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(b?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(f,h){if(!h){h=this.position}var c=f=="absolute"?1:-1;var e=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=(/(html|body)/i).test(b[0].tagName);return{top:(h.top+this.offset.relative.top*c+this.offset.parent.top*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(g?0:b.scrollTop()))*c)),left:(h.left+this.offset.relative.left*c+this.offset.parent.left*c-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:b.scrollLeft())*c))}},_generatePosition:function(e){var h=this.options,b=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,i=(/(html|body)/i).test(b[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0])){this.offset.relative=this._getRelativeOffset()}var d=e.pageX;var c=e.pageY;if(this.originalPosition){if(this.containment){if(e.pageX-this.offset.click.left<this.containment[0]){d=this.containment[0]+this.offset.click.left}if(e.pageY-this.offset.click.top<this.containment[1]){c=this.containment[1]+this.offset.click.top}if(e.pageX-this.offset.click.left>this.containment[2]){d=this.containment[2]+this.offset.click.left}if(e.pageY-this.offset.click.top>this.containment[3]){c=this.containment[3]+this.offset.click.top}}if(h.grid){var g=this.originalPageY+Math.round((c-this.originalPageY)/h.grid[1])*h.grid[1];c=this.containment?(!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:(!(g-this.offset.click.top<this.containment[1])?g-h.grid[1]:g+h.grid[1])):g;var f=this.originalPageX+Math.round((d-this.originalPageX)/h.grid[0])*h.grid[0];d=this.containment?(!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:(!(f-this.offset.click.left<this.containment[0])?f-h.grid[0]:f+h.grid[0])):f}}return{top:(c-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():(i?0:b.scrollTop())))),left:(d-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():i?0:b.scrollLeft())))}},_rearrange:function(g,f,c,e){c?c[0].appendChild(this.placeholder[0]):f.item[0].parentNode.insertBefore(this.placeholder[0],(this.direction=="down"?f.item[0]:f.item[0].nextSibling));this.counter=this.counter?++this.counter:1;var d=this,b=this.counter;window.setTimeout(function(){if(b==d.counter){d.refreshPositions(!e)}},0)},_clear:function(d,e){this.reverting=false;var f=[],b=this;if(!this._noFinalSort&&this.currentItem[0].parentNode){this.placeholder.before(this.currentItem)}this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var c in this._storedCSS){if(this._storedCSS[c]=="auto"||this._storedCSS[c]=="static"){this._storedCSS[c]=""}}this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else{this.currentItem.show()}if(this.fromOutside&&!e){f.push(function(g){this._trigger("receive",g,this._uiHash(this.fromOutside))})}if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!e){f.push(function(g){this._trigger("update",g,this._uiHash())})}if(!a.ui.contains(this.element[0],this.currentItem[0])){if(!e){f.push(function(g){this._trigger("remove",g,this._uiHash())})}for(var c=this.containers.length-1;c>=0;c--){if(a.ui.contains(this.containers[c].element[0],this.currentItem[0])&&!e){f.push((function(g){return function(h){g._trigger("receive",h,this._uiHash(this))}}).call(this,this.containers[c]));f.push((function(g){return function(h){g._trigger("update",h,this._uiHash(this))}}).call(this,this.containers[c]))}}}for(var c=this.containers.length-1;c>=0;c--){if(!e){f.push((function(g){return function(h){g._trigger("deactivate",h,this._uiHash(this))}}).call(this,this.containers[c]))}if(this.containers[c].containerCache.over){f.push((function(g){return function(h){g._trigger("out",h,this._uiHash(this))}}).call(this,this.containers[c]));this.containers[c].containerCache.over=0}}if(this._storedCursor){a("body").css("cursor",this._storedCursor)}if(this._storedOpacity){this.helper.css("opacity",this._storedOpacity)}if(this._storedZIndex){this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex)}this.dragging=false;if(this.cancelHelperRemoval){if(!e){this._trigger("beforeStop",d,this._uiHash());for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}return false}if(!e){this._trigger("beforeStop",d,this._uiHash())}this.placeholder[0].parentNode.removeChild(this.placeholder[0]);if(this.helper[0]!=this.currentItem[0]){this.helper.remove()}this.helper=null;if(!e){for(var c=0;c<f.length;c++){f[c].call(this,d)}this._trigger("stop",d,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){if(a.widget.prototype._trigger.apply(this,arguments)===false){this.cancel()}},_uiHash:function(c){var b=c||this;return{helper:b.helper,placeholder:b.placeholder||a([]),position:b.position,absolutePosition:b.positionAbs,offset:b.positionAbs,item:b.currentItem,sender:c?c.element:null}}}));a.extend(a.ui.sortable,{getter:"serialize toArray",version:"1.7.2",eventPrefix:"sort",defaults:{appendTo:"parent",axis:false,cancel:":input,option",connectWith:false,containment:false,cursor:"auto",cursorAt:false,delay:0,distance:1,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1000}})})(jQuery);;/*
+ * jQuery UI Accordion 1.7.2
+ *
+ * Copyright (c) 2009 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/Accordion
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(a){a.widget("ui.accordion",{_init:function(){var d=this.options,b=this;this.running=0;if(d.collapsible==a.ui.accordion.defaults.collapsible&&d.alwaysOpen!=a.ui.accordion.defaults.alwaysOpen){d.collapsible=!d.alwaysOpen}if(d.navigation){var c=this.element.find("a").filter(d.navigationFilter);if(c.length){if(c.filter(d.header).length){this.active=c}else{this.active=c.parent().parent().prev();c.addClass("ui-accordion-content-active")}}}this.element.addClass("ui-accordion ui-widget ui-helper-reset");if(this.element[0].nodeName=="UL"){this.element.children("li").addClass("ui-accordion-li-fix")}this.headers=this.element.find(d.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){a(this).removeClass("ui-state-focus")});this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");this.active=this._findActive(this.active||d.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");this.active.next().addClass("ui-accordion-content-active");a("<span/>").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.find(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);if(a.browser.msie){this.element.find("a").css("zoom","1")}this.resize();this.element.attr("role","tablist");this.headers.attr("role","tab").bind("keydown",function(e){return b._keydown(e)}).next().attr("role","tabpanel");this.headers.not(this.active||"").attr("aria-expanded","false").attr("tabIndex","-1").next().hide();if(!this.active.length){this.headers.eq(0).attr("tabIndex","0")}else{this.active.attr("aria-expanded","true").attr("tabIndex","0")}if(!a.browser.safari){this.headers.find("a").attr("tabIndex","-1")}if(d.event){this.headers.bind((d.event)+".accordion",function(e){return b._clickHandler.call(b,e,this)})}},destroy:function(){var c=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role").unbind(".accordion").removeData("accordion");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex");this.headers.find("a").removeAttr("tabindex");this.headers.children(".ui-icon").remove();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active");if(c.autoHeight||c.fillHeight){b.css("height","")}},_setData:function(b,c){if(b=="alwaysOpen"){b="collapsible";c=!c}a.widget.prototype._setData.apply(this,arguments)},_keydown:function(e){var g=this.options,f=a.ui.keyCode;if(g.disabled||e.altKey||e.ctrlKey){return}var d=this.headers.length;var b=this.headers.index(e.target);var c=false;switch(e.keyCode){case f.RIGHT:case f.DOWN:c=this.headers[(b+1)%d];break;case f.LEFT:case f.UP:c=this.headers[(b-1+d)%d];break;case f.SPACE:case f.ENTER:return this._clickHandler({target:e.target},e.target)}if(c){a(e.target).attr("tabIndex","-1");a(c).attr("tabIndex","0");c.focus();return false}return true},resize:function(){var e=this.options,d;if(e.fillSpace){if(a.browser.msie){var b=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}d=this.element.parent().height();if(a.browser.msie){this.element.parent().css("overflow",b)}this.headers.each(function(){d-=a(this).outerHeight()});var c=0;this.headers.next().each(function(){c=Math.max(c,a(this).innerHeight()-a(this).height())}).height(Math.max(0,d-c)).css("overflow","auto")}else{if(e.autoHeight){d=0;this.headers.next().each(function(){d=Math.max(d,a(this).outerHeight())}).height(d)}}},activate:function(b){var c=this._findActive(b)[0];this._clickHandler({target:c},c)},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,f){var d=this.options;if(d.disabled){return false}if(!b.target&&d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var h=this.active.next(),e={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:h},c=(this.active=a([]));this._toggle(c,h,e);return false}var g=a(b.currentTarget||f);var i=g[0]==this.active[0];if(this.running||(!d.collapsible&&i)){return false}this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");if(!i){g.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").find(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);g.next().addClass("ui-accordion-content-active")}var c=g.next(),h=this.active.next(),e={options:d,newHeader:i&&d.collapsible?a([]):g,oldHeader:this.active,newContent:i&&d.collapsible?a([]):c.find("> *"),oldContent:h.find("> *")},j=this.headers.index(this.active[0])>this.headers.index(g[0]);this.active=i?a([]):g;this._toggle(c,h,e,i,j);return false},_toggle:function(b,i,g,j,k){var d=this.options,m=this;this.toShow=b;this.toHide=i;this.data=g;var c=function(){if(!m){return}return m._completed.apply(m,arguments)};this._trigger("changestart",null,this.data);this.running=i.size()===0?b.size():i.size();if(d.animated){var f={};if(d.collapsible&&j){f={toShow:a([]),toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}else{f={toShow:b,toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}if(!d.proxied){d.proxied=d.animated}if(!d.proxiedDuration){d.proxiedDuration=d.duration}d.animated=a.isFunction(d.proxied)?d.proxied(f):d.proxied;d.duration=a.isFunction(d.proxiedDuration)?d.proxiedDuration(f):d.proxiedDuration;var l=a.ui.accordion.animations,e=d.duration,h=d.animated;if(!l[h]){l[h]=function(n){this.slide(n,{easing:h,duration:e||700})}}l[h](f)}else{if(d.collapsible&&j){b.toggle()}else{i.hide();b.show()}c(true)}i.prev().attr("aria-expanded","false").attr("tabIndex","-1").blur();b.prev().attr("aria-expanded","true").attr("tabIndex","0").focus()},_completed:function(b){var c=this.options;this.running=b?0:--this.running;if(this.running){return}if(c.clearStyle){this.toShow.add(this.toHide).css({height:"",overflow:""})}this._trigger("change",null,this.data)}});a.extend(a.ui.accordion,{version:"1.7.2",defaults:{active:null,alwaysOpen:true,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()==location.href.toLowerCase()}},animations:{slide:function(j,h){j=a.extend({easing:"swing",duration:300},j,h);if(!j.toHide.size()){j.toShow.animate({height:"show"},j);return}if(!j.toShow.size()){j.toHide.animate({height:"hide"},j);return}var c=j.toShow.css("overflow"),g,d={},f={},e=["height","paddingTop","paddingBottom"],b;var i=j.toShow;b=i[0].style.width;i.width(parseInt(i.parent().width(),10)-parseInt(i.css("paddingLeft"),10)-parseInt(i.css("paddingRight"),10)-(parseInt(i.css("borderLeftWidth"),10)||0)-(parseInt(i.css("borderRightWidth"),10)||0));a.each(e,function(k,m){f[m]="hide";var l=(""+a.css(j.toShow[0],m)).match(/^([\d+-.]+)(.*)$/);d[m]={value:l[1],unit:l[2]||"px"}});j.toShow.css({height:0,overflow:"hidden"}).show();j.toHide.filter(":hidden").each(j.complete).end().filter(":visible").animate(f,{step:function(k,l){if(l.prop=="height"){g=(l.now-l.start)/(l.end-l.start)}j.toShow[0].style[l.prop]=(g*d[l.prop].value)+d[l.prop].unit},duration:j.duration,easing:j.easing,complete:function(){if(!j.autoHeight){j.toShow.css("height","")}j.toShow.css("width",b);j.toShow.css({overflow:c});j.complete()}})},bounceslide:function(b){this.slide(b,{easing:b.down?"easeOutBounce":"swing",duration:b.down?1000:200})},easeslide:function(b){this.slide(b,{easing:"easeinout",duration:700})}}})})(jQuery);;/*
+ * jQuery UI Dialog 1.7.2
+ *
+ * Copyright (c) 2009 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/Dialog
+ *
+ * Depends:
+ * ui.core.js
+ * ui.draggable.js
+ * ui.resizable.js
+ */
+(function(c){var b={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},a="ui-dialog ui-widget ui-widget-content ui-corner-all ";c.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr("title");var l=this,m=this.options,j=m.title||this.originalTitle||" ",e=c.ui.dialog.getTitleId(this.element),k=(this.uiDialog=c("<div/>")).appendTo(document.body).hide().addClass(a+m.dialogClass).css({position:"absolute",overflow:"hidden",zIndex:m.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){(m.closeOnEscape&&n.keyCode&&n.keyCode==c.ui.keyCode.ESCAPE&&l.close(n))}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(n){l.moveToTop(false,n)}),g=this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(k),f=(this.uiDialogTitlebar=c("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(k),i=c('<a href="#"/>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){i.addClass("ui-state-hover")},function(){i.removeClass("ui-state-hover")}).focus(function(){i.addClass("ui-state-focus")}).blur(function(){i.removeClass("ui-state-focus")}).mousedown(function(n){n.stopPropagation()}).click(function(n){l.close(n);return false}).appendTo(f),h=(this.uiDialogTitlebarCloseText=c("<span/>")).addClass("ui-icon ui-icon-closethick").text(m.closeText).appendTo(i),d=c("<span/>").addClass("ui-dialog-title").attr("id",e).html(j).prependTo(f);f.find("*").add(f).disableSelection();(m.draggable&&c.fn.draggable&&this._makeDraggable());(m.resizable&&c.fn.resizable&&this._makeResizable());this._createButtons(m.buttons);this._isOpen=false;(m.bgiframe&&c.fn.bgiframe&&k.bgiframe());(m.autoOpen&&this.open())},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");this.uiDialog.remove();(this.originalTitle&&this.element.attr("title",this.originalTitle))},close:function(f){var d=this;if(false===d._trigger("beforeclose",f)){return}(d.overlay&&d.overlay.destroy());d.uiDialog.unbind("keypress.ui-dialog");(d.options.hide?d.uiDialog.hide(d.options.hide,function(){d._trigger("close",f)}):d.uiDialog.hide()&&d._trigger("close",f));c.ui.dialog.overlay.resize();d._isOpen=false;if(d.options.modal){var e=0;c(".ui-dialog").each(function(){if(this!=d.uiDialog[0]){e=Math.max(e,c(this).css("z-index"))}});c.ui.dialog.maxZ=e}},isOpen:function(){return this._isOpen},moveToTop:function(f,e){if((this.options.modal&&!f)||(!this.options.stack&&!this.options.modal)){return this._trigger("focus",e)}if(this.options.zIndex>c.ui.dialog.maxZ){c.ui.dialog.maxZ=this.options.zIndex}(this.overlay&&this.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=++c.ui.dialog.maxZ));var d={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};this.uiDialog.css("z-index",++c.ui.dialog.maxZ);this.element.attr(d);this._trigger("focus",e)},open:function(){if(this._isOpen){return}var e=this.options,d=this.uiDialog;this.overlay=e.modal?new c.ui.dialog.overlay(this):null;(d.next().length&&d.appendTo("body"));this._size();this._position(e.position);d.show(e.show);this.moveToTop(true);(e.modal&&d.bind("keypress.ui-dialog",function(h){if(h.keyCode!=c.ui.keyCode.TAB){return}var g=c(":tabbable",this),i=g.filter(":first")[0],f=g.filter(":last")[0];if(h.target==f&&!h.shiftKey){setTimeout(function(){i.focus()},1)}else{if(h.target==i&&h.shiftKey){setTimeout(function(){f.focus()},1)}}}));c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();this._trigger("open");this._isOpen=true},_createButtons:function(g){var f=this,d=false,e=c("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiDialog.find(".ui-dialog-buttonpane").remove();(typeof g=="object"&&g!==null&&c.each(g,function(){return !(d=true)}));if(d){c.each(g,function(h,i){c('<button type="button"></button>').addClass("ui-state-default ui-corner-all").text(h).click(function(){i.apply(f.element[0],arguments)}).hover(function(){c(this).addClass("ui-state-hover")},function(){c(this).removeClass("ui-state-hover")}).focus(function(){c(this).addClass("ui-state-focus")}).blur(function(){c(this).removeClass("ui-state-focus")}).appendTo(e)});e.appendTo(this.uiDialog)}},_makeDraggable:function(){var d=this,f=this.options,e;this.uiDialog.draggable({cancel:".ui-dialog-content",handle:".ui-dialog-titlebar",containment:"document",start:function(){e=f.height;c(this).height(c(this).height()).addClass("ui-dialog-dragging");(f.dragStart&&f.dragStart.apply(d.element[0],arguments))},drag:function(){(f.drag&&f.drag.apply(d.element[0],arguments))},stop:function(){c(this).removeClass("ui-dialog-dragging").height(e);(f.dragStop&&f.dragStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}})},_makeResizable:function(g){g=(g===undefined?this.options.resizable:g);var d=this,f=this.options,e=typeof g=="string"?g:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",alsoResize:this.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:f.minHeight,start:function(){c(this).addClass("ui-dialog-resizing");(f.resizeStart&&f.resizeStart.apply(d.element[0],arguments))},resize:function(){(f.resize&&f.resize.apply(d.element[0],arguments))},handles:e,stop:function(){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();(f.resizeStop&&f.resizeStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}}).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_position:function(i){var e=c(window),f=c(document),g=f.scrollTop(),d=f.scrollLeft(),h=g;if(c.inArray(i,["center","top","right","bottom","left"])>=0){i=[i=="right"||i=="left"?i:"center",i=="top"||i=="bottom"?i:"middle"]}if(i.constructor!=Array){i=["center","middle"]}if(i[0].constructor==Number){d+=i[0]}else{switch(i[0]){case"left":d+=0;break;case"right":d+=e.width()-this.uiDialog.outerWidth();break;default:case"center":d+=(e.width()-this.uiDialog.outerWidth())/2}}if(i[1].constructor==Number){g+=i[1]}else{switch(i[1]){case"top":g+=0;break;case"bottom":g+=e.height()-this.uiDialog.outerHeight();break;default:case"middle":g+=(e.height()-this.uiDialog.outerHeight())/2}}g=Math.max(g,h);this.uiDialog.css({top:g,left:d})},_setData:function(e,f){(b[e]&&this.uiDialog.data(b[e],f));switch(e){case"buttons":this._createButtons(f);break;case"closeText":this.uiDialogTitlebarCloseText.text(f);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(a+f);break;case"draggable":(f?this._makeDraggable():this.uiDialog.draggable("destroy"));break;case"height":this.uiDialog.height(f);break;case"position":this._position(f);break;case"resizable":var d=this.uiDialog,g=this.uiDialog.is(":data(resizable)");(g&&!f&&d.resizable("destroy"));(g&&typeof f=="string"&&d.resizable("option","handles",f));(g||this._makeResizable(f));break;case"title":c(".ui-dialog-title",this.uiDialogTitlebar).html(f||" ");break;case"width":this.uiDialog.width(f);break}c.widget.prototype._setData.apply(this,arguments)},_size:function(){var e=this.options;this.element.css({height:0,minHeight:0,width:"auto"});var d=this.uiDialog.css({height:"auto",width:e.width}).height();this.element.css({minHeight:Math.max(e.minHeight-d,0),height:e.height=="auto"?"auto":Math.max(e.height-d,0)})}});c.extend(c.ui.dialog,{version:"1.7.2",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1000},getter:"isOpen",uuid:0,maxZ:0,getTitleId:function(d){return"ui-dialog-title-"+(d.attr("id")||++this.uuid)},overlay:function(d){this.$el=c.ui.dialog.overlay.create(d)}});c.extend(c.ui.dialog.overlay,{instances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(d){return d+".dialog-overlay"}).join(" "),create:function(e){if(this.instances.length===0){setTimeout(function(){if(c.ui.dialog.overlay.instances.length){c(document).bind(c.ui.dialog.overlay.events,function(f){var g=c(f.target).parents(".ui-dialog").css("zIndex")||0;return(g>c.ui.dialog.overlay.maxZ)})}},1);c(document).bind("keydown.dialog-overlay",function(f){(e.options.closeOnEscape&&f.keyCode&&f.keyCode==c.ui.keyCode.ESCAPE&&e.close(f))});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var d=c("<div></div>").appendTo(document.body).addClass("ui-widget-overlay").css({width:this.width(),height:this.height()});(e.options.bgiframe&&c.fn.bgiframe&&d.bgiframe());this.instances.push(d);return d},destroy:function(d){this.instances.splice(c.inArray(this.instances,d),1);if(this.instances.length===0){c([document,window]).unbind(".dialog-overlay")}d.remove();var e=0;c.each(this.instances,function(){e=Math.max(e,this.css("z-index"))});this.maxZ=e},height:function(){if(c.browser.msie&&c.browser.version<7){var e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var d=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(e<d){return c(window).height()+"px"}else{return e+"px"}}else{return c(document).height()+"px"}},width:function(){if(c.browser.msie&&c.browser.version<7){var d=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);var e=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);if(d<e){return c(window).width()+"px"}else{return d+"px"}}else{return c(document).width()+"px"}},resize:function(){var d=c([]);c.each(c.ui.dialog.overlay.instances,function(){d=d.add(this)});d.css({width:0,height:0}).css({width:c.ui.dialog.overlay.width(),height:c.ui.dialog.overlay.height()})}});c.extend(c.ui.dialog.overlay.prototype,{destroy:function(){c.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*
+ * jQuery UI Slider 1.7.2
+ *
+ * Copyright (c) 2009 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(){if(!c.disabled){a(this).addClass("ui-state-hover")}},function(){a(this).removeClass("ui-state-hover")}).focus(function(){if(!c.disabled){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}else{a(this).blur()}}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((this.options.values.length==2&&this.options.range===true)&&((e==0&&d>b)||(e==1&&d<b))){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"disabled":if(d){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled")}else{this.handles.removeAttr("disabled")}case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_values:function(b){if(arguments.length){var c=this.options.values[b];if(c<this._valueMin()){c=this._valueMin()}if(c>this._valueMax()){c=this._valueMax()}return c}else{return this.options.values}},_valueMin:function(){var b=this.options.min;return b},_valueMax:function(){var b=this.options.max;return b},_refreshValue:function(c){var f=this.options.range,d=this.options,l=this;if(this.options.values&&this.options.values.length){var i,h;this.handles.each(function(p,n){var o=(l.values(p)-l._valueMin())/(l._valueMax()-l._valueMin())*100;var m={};m[l.orientation=="horizontal"?"left":"bottom"]=o+"%";a(this).stop(1,1)[c?"animate":"css"](m,d.animate);if(l.options.range===true){if(l.orientation=="horizontal"){(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({left:o+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({width:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}else{(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({bottom:(o)+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({height:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}}lastValPercent=o})}else{var j=this.value(),g=this._valueMin(),k=this._valueMax(),e=k!=g?(j-g)/(k-g)*100:0;var b={};b[l.orientation=="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[c?"animate":"css"](b,d.animate);(f=="min")&&(this.orientation=="horizontal")&&this.range.stop(1,1)[c?"animate":"css"]({width:e+"%"},d.animate);(f=="max")&&(this.orientation=="horizontal")&&this.range[c?"animate":"css"]({width:(100-e)+"%"},{queue:false,duration:d.animate});(f=="min")&&(this.orientation=="vertical")&&this.range.stop(1,1)[c?"animate":"css"]({height:e+"%"},d.animate);(f=="max")&&(this.orientation=="vertical")&&this.range[c?"animate":"css"]({height:(100-e)+"%"},{queue:false,duration:d.animate})}}}));a.extend(a.ui.slider,{getter:"value values",version:"1.7.2",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);;/*
+ * jQuery UI Tabs 1.7.2
+ *
+ * Copyright (c) 2009 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/Tabs
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(a){a.widget("ui.tabs",{_init:function(){if(this.options.deselectable!==undefined){this.options.collapsible=this.options.deselectable}this._tabify(true)},_setData:function(b,c){if(b=="selected"){if(this.options.collapsible&&c==this.options.selected){return}this.select(c)}else{this.options[b]=c;if(b=="deselectable"){this.options.collapsible=c}this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+a.data(b)},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+a.data(this.list[0]));return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(c,b){return{tab:c,panel:b,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(n){this.list=this.element.children("ul:first");this.lis=a("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);var p=this,d=this.options;var c=/^#.+/;this.anchors.each(function(r,o){var q=a(o).attr("href");var s=q.split("#")[0],u;if(s&&(s===location.toString().split("#")[0]||(u=a("base")[0])&&s===u.href)){q=o.hash;o.href=q}if(c.test(q)){p.panels=p.panels.add(p._sanitizeSelector(q))}else{if(q!="#"){a.data(o,"href.tabs",q);a.data(o,"load.tabs",q.replace(/#.*$/,""));var w=p._tabId(o);o.href="#"+w;var v=a("#"+w);if(!v.length){v=a(d.panelTemplate).attr("id",w).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(p.panels[r-1]||p.list);v.data("destroy.tabs",true)}p.panels=p.panels.add(v)}else{d.disabled.push(r)}}});if(n){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(d.selected===undefined){if(location.hash){this.anchors.each(function(q,o){if(o.hash==location.hash){d.selected=q;return false}})}if(typeof d.selected!="number"&&d.cookie){d.selected=parseInt(p._cookie(),10)}if(typeof d.selected!="number"&&this.lis.filter(".ui-tabs-selected").length){d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}d.selected=d.selected||0}else{if(d.selected===null){d.selected=-1}}d.selected=((d.selected>=0&&this.anchors[d.selected])||d.selected<0)?d.selected:0;d.disabled=a.unique(d.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(q,o){return p.lis.index(q)}))).sort();if(a.inArray(d.selected,d.disabled)!=-1){d.disabled.splice(a.inArray(d.selected,d.disabled),1)}this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");if(d.selected>=0&&this.anchors.length){this.panels.eq(d.selected).removeClass("ui-tabs-hide");this.lis.eq(d.selected).addClass("ui-tabs-selected ui-state-active");p.element.queue("tabs",function(){p._trigger("show",null,p._ui(p.anchors[d.selected],p.panels[d.selected]))});this.load(d.selected)}a(window).bind("unload",function(){p.lis.add(p.anchors).unbind(".tabs");p.lis=p.anchors=p.panels=null})}else{d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}this.element[d.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");if(d.cookie){this._cookie(d.selected,d.cookie)}for(var g=0,m;(m=this.lis[g]);g++){a(m)[a.inArray(g,d.disabled)!=-1&&!a(m).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled")}if(d.cache===false){this.anchors.removeData("cache.tabs")}this.lis.add(this.anchors).unbind(".tabs");if(d.event!="mouseover"){var f=function(o,i){if(i.is(":not(.ui-state-disabled)")){i.addClass("ui-state-"+o)}};var j=function(o,i){i.removeClass("ui-state-"+o)};this.lis.bind("mouseover.tabs",function(){f("hover",a(this))});this.lis.bind("mouseout.tabs",function(){j("hover",a(this))});this.anchors.bind("focus.tabs",function(){f("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var b,h;if(d.fx){if(a.isArray(d.fx)){b=d.fx[0];h=d.fx[1]}else{b=h=d.fx}}function e(i,o){i.css({display:""});if(a.browser.msie&&o.opacity){i[0].style.removeAttribute("filter")}}var k=h?function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.hide().removeClass("ui-tabs-hide").animate(h,h.duration||"normal",function(){e(o,h);p._trigger("show",null,p._ui(i,o[0]))})}:function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");p._trigger("show",null,p._ui(i,o[0]))};var l=b?function(o,i){i.animate(b,b.duration||"normal",function(){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");e(i,b);p.element.dequeue("tabs")})}:function(o,i,q){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");p.element.dequeue("tabs")};this.anchors.bind(d.event+".tabs",function(){var o=this,r=a(this).closest("li"),i=p.panels.filter(":not(.ui-tabs-hide)"),q=a(p._sanitizeSelector(this.hash));if((r.hasClass("ui-tabs-selected")&&!d.collapsible)||r.hasClass("ui-state-disabled")||r.hasClass("ui-state-processing")||p._trigger("select",null,p._ui(this,q[0]))===false){this.blur();return false}d.selected=p.anchors.index(this);p.abort();if(d.collapsible){if(r.hasClass("ui-tabs-selected")){d.selected=-1;if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){l(o,i)}).dequeue("tabs");this.blur();return false}else{if(!i.length){if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this));this.blur();return false}}}if(d.cookie){p._cookie(d.selected,d.cookie)}if(q.length){if(i.length){p.element.queue("tabs",function(){l(o,i)})}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this))}else{throw"jQuery UI Tabs: Mismatching fragment identifier."}if(a.browser.msie){this.blur()}});this.anchors.bind("click.tabs",function(){return false})},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var c=a.data(this,"href.tabs");if(c){this.href=c}var d=a(this).unbind(".tabs");a.each(["href","load","cache"],function(e,f){d.removeData(f+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){if(a.data(this,"destroy.tabs")){a(this).remove()}else{a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}});if(b.cookie){this._cookie(null,b.cookie)}},add:function(e,d,c){if(c===undefined){c=this.anchors.length}var b=this,g=this.options,i=a(g.tabTemplate.replace(/#\{href\}/g,e).replace(/#\{label\}/g,d)),h=!e.indexOf("#")?e.replace("#",""):this._tabId(a("a",i)[0]);i.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var f=a("#"+h);if(!f.length){f=a(g.panelTemplate).attr("id",h).data("destroy.tabs",true)}f.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(c>=this.lis.length){i.appendTo(this.list);f.appendTo(this.list[0].parentNode)}else{i.insertBefore(this.lis[c]);f.insertBefore(this.panels[c])}g.disabled=a.map(g.disabled,function(k,j){return k>=c?++k:k});this._tabify();if(this.anchors.length==1){i.addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[0],b.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[c],this.panels[c]))},remove:function(b){var d=this.options,e=this.lis.eq(b).remove(),c=this.panels.eq(b).remove();if(e.hasClass("ui-tabs-selected")&&this.anchors.length>1){this.select(b+(b+1<this.anchors.length?1:-1))}d.disabled=a.map(a.grep(d.disabled,function(g,f){return g!=b}),function(g,f){return g>=b?--g:g});this._tabify();this._trigger("remove",null,this._ui(e.find("a")[0],c[0]))},enable:function(b){var c=this.options;if(a.inArray(b,c.disabled)==-1){return}this.lis.eq(b).removeClass("ui-state-disabled");c.disabled=a.grep(c.disabled,function(e,d){return e!=b});this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]))},disable:function(c){var b=this,d=this.options;if(c!=d.selected){this.lis.eq(c).addClass("ui-state-disabled");d.disabled.push(c);d.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}},select:function(b){if(typeof b=="string"){b=this.anchors.index(this.anchors.filter("[href$="+b+"]"))}else{if(b===null){b=-1}}if(b==-1&&this.options.collapsible){b=this.options.selected}this.anchors.eq(b).trigger(this.options.event+".tabs")},load:function(e){var c=this,g=this.options,b=this.anchors.eq(e)[0],d=a.data(b,"load.tabs");this.abort();if(!d||this.element.queue("tabs").length!==0&&a.data(b,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(e).addClass("ui-state-processing");if(g.spinner){var f=a("span",b);f.data("label.tabs",f.html()).html(g.spinner)}this.xhr=a.ajax(a.extend({},g.ajaxOptions,{url:d,success:function(i,h){a(c._sanitizeSelector(b.hash)).html(i);c._cleanup();if(g.cache){a.data(b,"cache.tabs",true)}c._trigger("load",null,c._ui(c.anchors[e],c.panels[e]));try{g.ajaxOptions.success(i,h)}catch(j){}c.element.dequeue("tabs")}}))},abort:function(){this.element.queue([]);this.panels.stop(false,true);if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup()},url:function(c,b){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",b)},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.7.2",getter:"length",defaults:{ajaxOptions:null,cache:false,cookie:null,collapsible:false,disabled:[],event:"click",fx:null,idPrefix:"ui-tabs-",panelTemplate:"<div></div>",spinner:"<em>Loading…</em>",tabTemplate:'<li><a href="#{href}"><span>#{label}</span></a></li>'}});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(d,f){var b=this,g=this.options;var c=b._rotate||(b._rotate=function(h){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var i=g.selected;b.select(++i<b.anchors.length?i:0)},d);if(h){h.stopPropagation()}});var e=b._unrotate||(b._unrotate=!f?function(h){if(h.clientX){b.rotate(null)}}:function(h){t=g.selected;c()});if(d){this.element.bind("tabsshow",c);this.anchors.bind(g.event+".tabs",e);c()}else{clearTimeout(b.rotation);this.element.unbind("tabsshow",c);this.anchors.unbind(g.event+".tabs",e);delete this._rotate;delete this._unrotate}}})})(jQuery);;/*
+ * jQuery UI Datepicker 1.7.2
+ *
+ * Copyright (c) 2009 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/Datepicker
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function($){$.extend($.ui,{datepicker:{version:"1.7.2"}});var PROP_NAME="datepicker";function Datepicker(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._datepickerShowing=false;this._inDialog=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass="ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],dateFormat:"mm/dd/yy",firstDay:0,isRTL:false};this._defaults={showOn:"focus",showAnim:"show",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,showMonthAfterYear:false,yearRange:"-10:+10",showOtherMonths:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"normal",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false};$.extend(this._defaults,this.regional[""]);this.dpDiv=$('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",log:function(){if(this.debug){console.log.apply("",arguments)}},setDefaults:function(settings){extendRemove(this._defaults,settings||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase();var inline=(nodeName=="div"||nodeName=="span");if(!target.id){target.id="dp"+(++this.uuid)}var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{});if(nodeName=="input"){this._connectDatepicker(target,inst)}else{if(inline){this._inlineDatepicker(target,inst)}}},_newInst:function(target,inline){var id=target[0].id.replace(/([:\[\]\.])/g,"\\\\$1");return{id:id,input:target,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:inline,dpDiv:(!inline?this.dpDiv:$('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(target,inst){var input=$(target);inst.append=$([]);inst.trigger=$([]);if(input.hasClass(this.markerClassName)){return}var appendText=this._get(inst,"appendText");var isRTL=this._get(inst,"isRTL");if(appendText){inst.append=$('<span class="'+this._appendClass+'">'+appendText+"</span>");input[isRTL?"before":"after"](inst.append)}var showOn=this._get(inst,"showOn");if(showOn=="focus"||showOn=="both"){input.focus(this._showDatepicker)}if(showOn=="button"||showOn=="both"){var buttonText=this._get(inst,"buttonText");var buttonImage=this._get(inst,"buttonImage");inst.trigger=$(this._get(inst,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:buttonImage,alt:buttonText,title:buttonText}):$('<button type="button"></button>').addClass(this._triggerClass).html(buttonImage==""?buttonText:$("<img/>").attr({src:buttonImage,alt:buttonText,title:buttonText})));input[isRTL?"before":"after"](inst.trigger);inst.trigger.click(function(){if($.datepicker._datepickerShowing&&$.datepicker._lastInput==target){$.datepicker._hideDatepicker()}else{$.datepicker._showDatepicker(target)}return false})}input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst)},_inlineDatepicker:function(target,inst){var divSpan=$(target);if(divSpan.hasClass(this.markerClassName)){return}divSpan.addClass(this.markerClassName).append(inst.dpDiv).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst);this._setDate(inst,this._getDefaultDate(inst));this._updateDatepicker(inst);this._updateAlternate(inst)},_dialogDatepicker:function(input,dateText,onSelect,settings,pos){var inst=this._dialogInst;if(!inst){var id="dp"+(++this.uuid);this._dialogInput=$('<input type="text" id="'+id+'" size="1" style="position: absolute; top: -100px;"/>');this._dialogInput.keydown(this._doKeyDown);$("body").append(this._dialogInput);inst=this._dialogInst=this._newInst(this._dialogInput,false);inst.settings={};$.data(this._dialogInput[0],PROP_NAME,inst)}extendRemove(inst.settings,settings||{});this._dialogInput.val(dateText);this._pos=(pos?(pos.length?pos:[pos.pageX,pos.pageY]):null);if(!this._pos){var browserWidth=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;var browserHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight;var scrollX=document.documentElement.scrollLeft||document.body.scrollLeft;var scrollY=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(browserWidth/2)-100+scrollX,(browserHeight/2)-150+scrollY]}this._dialogInput.css("left",this._pos[0]+"px").css("top",this._pos[1]+"px");inst.settings.onSelect=onSelect;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if($.blockUI){$.blockUI(this.dpDiv)}$.data(this._dialogInput[0],PROP_NAME,inst);return this},_destroyDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();$.removeData(target,PROP_NAME);if(nodeName=="input"){inst.append.remove();inst.trigger.remove();$target.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress)}else{if(nodeName=="div"||nodeName=="span"){$target.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=false;inst.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().removeClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)})},_disableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=true;inst.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().addClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)});this._disabledInputs[this._disabledInputs.length]=target},_isDisabledDatepicker:function(target){if(!target){return false}for(var i=0;i<this._disabledInputs.length;i++){if(this._disabledInputs[i]==target){return true}}return false},_getInst:function(target){try{return $.data(target,PROP_NAME)}catch(err){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(target,name,value){var inst=this._getInst(target);if(arguments.length==2&&typeof name=="string"){return(name=="defaults"?$.extend({},$.datepicker._defaults):(inst?(name=="all"?$.extend({},inst.settings):this._get(inst,name)):null))}var settings=name||{};if(typeof name=="string"){settings={};settings[name]=value}if(inst){if(this._curInst==inst){this._hideDatepicker(null)}var date=this._getDateDatepicker(target);extendRemove(inst.settings,settings);this._setDateDatepicker(target,date);this._updateDatepicker(inst)}},_changeDatepicker:function(target,name,value){this._optionDatepicker(target,name,value)},_refreshDatepicker:function(target){var inst=this._getInst(target);if(inst){this._updateDatepicker(inst)}},_setDateDatepicker:function(target,date,endDate){var inst=this._getInst(target);if(inst){this._setDate(inst,date,endDate);this._updateDatepicker(inst);this._updateAlternate(inst)}},_getDateDatepicker:function(target){var inst=this._getInst(target);if(inst&&!inst.inline){this._setDateFromField(inst)}return(inst?this._getDate(inst):null)},_doKeyDown:function(event){var inst=$.datepicker._getInst(event.target);var handled=true;var isRTL=inst.dpDiv.is(".ui-datepicker-rtl");inst._keyEvent=true;if($.datepicker._datepickerShowing){switch(event.keyCode){case 9:$.datepicker._hideDatepicker(null,"");break;case 13:var sel=$("td."+$.datepicker._dayOverClass+", td."+$.datepicker._currentClass,inst.dpDiv);if(sel[0]){$.datepicker._selectDay(event.target,inst.selectedMonth,inst.selectedYear,sel[0])}else{$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"))}return false;break;case 27:$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"));break;case 33:$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M");break;case 34:$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M");break;case 35:if(event.ctrlKey||event.metaKey){$.datepicker._clearDate(event.target)}handled=event.ctrlKey||event.metaKey;break;case 36:if(event.ctrlKey||event.metaKey){$.datepicker._gotoToday(event.target)}handled=event.ctrlKey||event.metaKey;break;case 37:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?+1:-1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M")}break;case 38:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,-7,"D")}handled=event.ctrlKey||event.metaKey;break;case 39:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?-1:+1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M")}break;case 40:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,+7,"D")}handled=event.ctrlKey||event.metaKey;break;default:handled=false}}else{if(event.keyCode==36&&event.ctrlKey){$.datepicker._showDatepicker(this)}else{handled=false}}if(handled){event.preventDefault();event.stopPropagation()}},_doKeyPress:function(event){var inst=$.datepicker._getInst(event.target);if($.datepicker._get(inst,"constrainInput")){var chars=$.datepicker._possibleChars($.datepicker._get(inst,"dateFormat"));var chr=String.fromCharCode(event.charCode==undefined?event.keyCode:event.charCode);return event.ctrlKey||(chr<" "||!chars||chars.indexOf(chr)>-1)}},_showDatepicker:function(input){input=input.target||input;if(input.nodeName.toLowerCase()!="input"){input=$("input",input.parentNode)[0]}if($.datepicker._isDisabledDatepicker(input)||$.datepicker._lastInput==input){return}var inst=$.datepicker._getInst(input);var beforeShow=$.datepicker._get(inst,"beforeShow");extendRemove(inst.settings,(beforeShow?beforeShow.apply(input,[input,inst]):{}));$.datepicker._hideDatepicker(null,"");$.datepicker._lastInput=input;$.datepicker._setDateFromField(inst);if($.datepicker._inDialog){input.value=""}if(!$.datepicker._pos){$.datepicker._pos=$.datepicker._findPos(input);$.datepicker._pos[1]+=input.offsetHeight}var isFixed=false;$(input).parents().each(function(){isFixed|=$(this).css("position")=="fixed";return !isFixed});if(isFixed&&$.browser.opera){$.datepicker._pos[0]-=document.documentElement.scrollLeft;$.datepicker._pos[1]-=document.documentElement.scrollTop}var offset={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null;inst.rangeStart=null;inst.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});$.datepicker._updateDatepicker(inst);offset=$.datepicker._checkOffset(inst,offset,isFixed);inst.dpDiv.css({position:($.datepicker._inDialog&&$.blockUI?"static":(isFixed?"fixed":"absolute")),display:"none",left:offset.left+"px",top:offset.top+"px"});if(!inst.inline){var showAnim=$.datepicker._get(inst,"showAnim")||"show";var duration=$.datepicker._get(inst,"duration");var postProcess=function(){$.datepicker._datepickerShowing=true;if($.browser.msie&&parseInt($.browser.version,10)<7){$("iframe.ui-datepicker-cover").css({width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4})}};if($.effects&&$.effects[showAnim]){inst.dpDiv.show(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[showAnim](duration,postProcess)}if(duration==""){postProcess()}if(inst.input[0].type!="hidden"){inst.input[0].focus()}$.datepicker._curInst=inst}},_updateDatepicker:function(inst){var dims={width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4};var self=this;inst.dpDiv.empty().append(this._generateHTML(inst)).find("iframe.ui-datepicker-cover").css({width:dims.width,height:dims.height}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){$(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).removeClass("ui-datepicker-next-hover")}}).bind("mouseover",function(){if(!self._isDisabledDatepicker(inst.inline?inst.dpDiv.parent()[0]:inst.input[0])){$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");$(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).addClass("ui-datepicker-next-hover")}}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();var numMonths=this._getNumberOfMonths(inst);var cols=numMonths[1];var width=17;if(cols>1){inst.dpDiv.addClass("ui-datepicker-multi-"+cols).css("width",(width*cols)+"em")}else{inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("")}inst.dpDiv[(numMonths[0]!=1||numMonths[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");inst.dpDiv[(this._get(inst,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(inst.input&&inst.input[0].type!="hidden"&&inst==$.datepicker._curInst){$(inst.input[0]).focus()}},_checkOffset:function(inst,offset,isFixed){var dpWidth=inst.dpDiv.outerWidth();var dpHeight=inst.dpDiv.outerHeight();var inputWidth=inst.input?inst.input.outerWidth():0;var inputHeight=inst.input?inst.input.outerHeight():0;var viewWidth=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+$(document).scrollLeft();var viewHeight=(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)+$(document).scrollTop();offset.left-=(this._get(inst,"isRTL")?(dpWidth-inputWidth):0);offset.left-=(isFixed&&offset.left==inst.input.offset().left)?$(document).scrollLeft():0;offset.top-=(isFixed&&offset.top==(inst.input.offset().top+inputHeight))?$(document).scrollTop():0;offset.left-=(offset.left+dpWidth>viewWidth&&viewWidth>dpWidth)?Math.abs(offset.left+dpWidth-viewWidth):0;offset.top-=(offset.top+dpHeight>viewHeight&&viewHeight>dpHeight)?Math.abs(offset.top+dpHeight+inputHeight*2-viewHeight):0;return offset},_findPos:function(obj){while(obj&&(obj.type=="hidden"||obj.nodeType!=1)){obj=obj.nextSibling}var position=$(obj).offset();return[position.left,position.top]},_hideDatepicker:function(input,duration){var inst=this._curInst;if(!inst||(input&&inst!=$.data(input,PROP_NAME))){return}if(inst.stayOpen){this._selectDate("#"+inst.id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear))}inst.stayOpen=false;if(this._datepickerShowing){duration=(duration!=null?duration:this._get(inst,"duration"));var showAnim=this._get(inst,"showAnim");var postProcess=function(){$.datepicker._tidyDialog(inst)};if(duration!=""&&$.effects&&$.effects[showAnim]){inst.dpDiv.hide(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[(duration==""?"hide":(showAnim=="slideDown"?"slideUp":(showAnim=="fadeIn"?"fadeOut":"hide")))](duration,postProcess)}if(duration==""){this._tidyDialog(inst)}var onClose=this._get(inst,"onClose");if(onClose){onClose.apply((inst.input?inst.input[0]:null),[(inst.input?inst.input.val():""),inst])}this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if($.blockUI){$.unblockUI();$("body").append(this.dpDiv)}}this._inDialog=false}this._curInst=null},_tidyDialog:function(inst){inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(event){if(!$.datepicker._curInst){return}var $target=$(event.target);if(($target.parents("#"+$.datepicker._mainDivId).length==0)&&!$target.hasClass($.datepicker.markerClassName)&&!$target.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&!($.datepicker._inDialog&&$.blockUI)){$.datepicker._hideDatepicker(null,"")}},_adjustDate:function(id,offset,period){var target=$(id);var inst=this._getInst(target[0]);if(this._isDisabledDatepicker(target[0])){return}this._adjustInstDate(inst,offset+(period=="M"?this._get(inst,"showCurrentAtPos"):0),period);this._updateDatepicker(inst)},_gotoToday:function(id){var target=$(id);var inst=this._getInst(target[0]);if(this._get(inst,"gotoCurrent")&&inst.currentDay){inst.selectedDay=inst.currentDay;inst.drawMonth=inst.selectedMonth=inst.currentMonth;inst.drawYear=inst.selectedYear=inst.currentYear}else{var date=new Date();inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear()}this._notifyChange(inst);this._adjustDate(target)},_selectMonthYear:function(id,select,period){var target=$(id);var inst=this._getInst(target[0]);inst._selectingMonthYear=false;inst["selected"+(period=="M"?"Month":"Year")]=inst["draw"+(period=="M"?"Month":"Year")]=parseInt(select.options[select.selectedIndex].value,10);this._notifyChange(inst);this._adjustDate(target)},_clickMonthYear:function(id){var target=$(id);var inst=this._getInst(target[0]);if(inst.input&&inst._selectingMonthYear&&!$.browser.msie){inst.input[0].focus()}inst._selectingMonthYear=!inst._selectingMonthYear},_selectDay:function(id,month,year,td){var target=$(id);if($(td).hasClass(this._unselectableClass)||this._isDisabledDatepicker(target[0])){return}var inst=this._getInst(target[0]);inst.selectedDay=inst.currentDay=$("a",td).html();inst.selectedMonth=inst.currentMonth=month;inst.selectedYear=inst.currentYear=year;if(inst.stayOpen){inst.endDay=inst.endMonth=inst.endYear=null}this._selectDate(id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear));if(inst.stayOpen){inst.rangeStart=this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay));this._updateDatepicker(inst)}},_clearDate:function(id){var target=$(id);var inst=this._getInst(target[0]);inst.stayOpen=false;inst.endDay=inst.endMonth=inst.endYear=inst.rangeStart=null;this._selectDate(target,"")},_selectDate:function(id,dateStr){var target=$(id);var inst=this._getInst(target[0]);dateStr=(dateStr!=null?dateStr:this._formatDate(inst));if(inst.input){inst.input.val(dateStr)}this._updateAlternate(inst);var onSelect=this._get(inst,"onSelect");if(onSelect){onSelect.apply((inst.input?inst.input[0]:null),[dateStr,inst])}else{if(inst.input){inst.input.trigger("change")}}if(inst.inline){this._updateDatepicker(inst)}else{if(!inst.stayOpen){this._hideDatepicker(null,this._get(inst,"duration"));this._lastInput=inst.input[0];if(typeof(inst.input[0])!="object"){inst.input[0].focus()}this._lastInput=null}}},_updateAlternate:function(inst){var altField=this._get(inst,"altField");if(altField){var altFormat=this._get(inst,"altFormat")||this._get(inst,"dateFormat");var date=this._getDate(inst);dateStr=this.formatDate(altFormat,date,this._getFormatConfig(inst));$(altField).each(function(){$(this).val(dateStr)})}},noWeekends:function(date){var day=date.getDay();return[(day>0&&day<6),""]},iso8601Week:function(date){var checkDate=new Date(date.getFullYear(),date.getMonth(),date.getDate());var firstMon=new Date(checkDate.getFullYear(),1-1,4);var firstDay=firstMon.getDay()||7;firstMon.setDate(firstMon.getDate()+1-firstDay);if(firstDay<4&&checkDate<firstMon){checkDate.setDate(checkDate.getDate()-3);return $.datepicker.iso8601Week(checkDate)}else{if(checkDate>new Date(checkDate.getFullYear(),12-1,28)){firstDay=new Date(checkDate.getFullYear()+1,1-1,4).getDay()||7;if(firstDay>4&&(checkDate.getDay()||7)<firstDay-3){return 1}}}return Math.floor(((checkDate-firstMon)/86400000)/7)+1},parseDate:function(format,value,settings){if(format==null||value==null){throw"Invalid arguments"}value=(typeof value=="object"?value.toString():value+"");if(value==""){return null}var shortYearCutoff=(settings?settings.shortYearCutoff:null)||this._defaults.shortYearCutoff;var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var year=-1;var month=-1;var day=-1;var doy=-1;var literal=false;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var getNumber=function(match){lookAhead(match);var origSize=(match=="@"?14:(match=="y"?4:(match=="o"?3:2)));var size=origSize;var num=0;while(size>0&&iValue<value.length&&value.charAt(iValue)>="0"&&value.charAt(iValue)<="9"){num=num*10+parseInt(value.charAt(iValue++),10);size--}if(size==origSize){throw"Missing number at position "+iValue}return num};var getName=function(match,shortNames,longNames){var names=(lookAhead(match)?longNames:shortNames);var size=0;for(var j=0;j<names.length;j++){size=Math.max(size,names[j].length)}var name="";var iInit=iValue;while(size>0&&iValue<value.length){name+=value.charAt(iValue++);for(var i=0;i<names.length;i++){if(name==names[i]){return i+1}}size--}throw"Unknown name at position "+iInit};var checkLiteral=function(){if(value.charAt(iValue)!=format.charAt(iFormat)){throw"Unexpected literal at position "+iValue}iValue++};var iValue=0;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{checkLiteral()}}else{switch(format.charAt(iFormat)){case"d":day=getNumber("d");break;case"D":getName("D",dayNamesShort,dayNames);break;case"o":doy=getNumber("o");break;case"m":month=getNumber("m");break;case"M":month=getName("M",monthNamesShort,monthNames);break;case"y":year=getNumber("y");break;case"@":var date=new Date(getNumber("@"));year=date.getFullYear();month=date.getMonth()+1;day=date.getDate();break;case"'":if(lookAhead("'")){checkLiteral()}else{literal=true}break;default:checkLiteral()}}}if(year==-1){year=new Date().getFullYear()}else{if(year<100){year+=new Date().getFullYear()-new Date().getFullYear()%100+(year<=shortYearCutoff?0:-100)}}if(doy>-1){month=1;day=doy;do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}var date=this._daylightSavingAdjust(new Date(year,month-1,day));if(date.getFullYear()!=year||date.getMonth()+1!=month||date.getDate()!=day){throw"Invalid date"}return date},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TIMESTAMP:"@",W3C:"yy-mm-dd",formatDate:function(format,date,settings){if(!date){return""}var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var formatNumber=function(match,value,len){var num=""+value;if(lookAhead(match)){while(num.length<len){num="0"+num}}return num};var formatName=function(match,value,shortNames,longNames){return(lookAhead(match)?longNames[value]:shortNames[value])};var output="";var literal=false;if(date){for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{output+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":output+=formatNumber("d",date.getDate(),2);break;case"D":output+=formatName("D",date.getDay(),dayNamesShort,dayNames);break;case"o":var doy=date.getDate();for(var m=date.getMonth()-1;m>=0;m--){doy+=this._getDaysInMonth(date.getFullYear(),m)}output+=formatNumber("o",doy,3);break;case"m":output+=formatNumber("m",date.getMonth()+1,2);break;case"M":output+=formatName("M",date.getMonth(),monthNamesShort,monthNames);break;case"y":output+=(lookAhead("y")?date.getFullYear():(date.getYear()%100<10?"0":"")+date.getYear()%100);break;case"@":output+=date.getTime();break;case"'":if(lookAhead("'")){output+="'"}else{literal=true}break;default:output+=format.charAt(iFormat)}}}}return output},_possibleChars:function(format){var chars="";var literal=false;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{chars+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":case"m":case"y":case"@":chars+="0123456789";break;case"D":case"M":return null;case"'":if(lookAhead("'")){chars+="'"}else{literal=true}break;default:chars+=format.charAt(iFormat)}}}return chars},_get:function(inst,name){return inst.settings[name]!==undefined?inst.settings[name]:this._defaults[name]},_setDateFromField:function(inst){var dateFormat=this._get(inst,"dateFormat");var dates=inst.input?inst.input.val():null;inst.endDay=inst.endMonth=inst.endYear=null;var date=defaultDate=this._getDefaultDate(inst);var settings=this._getFormatConfig(inst);try{date=this.parseDate(dateFormat,dates,settings)||defaultDate}catch(event){this.log(event);date=defaultDate}inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();inst.currentDay=(dates?date.getDate():0);inst.currentMonth=(dates?date.getMonth():0);inst.currentYear=(dates?date.getFullYear():0);this._adjustInstDate(inst)},_getDefaultDate:function(inst){var date=this._determineDate(this._get(inst,"defaultDate"),new Date());var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);return date},_determineDate:function(date,defaultDate){var offsetNumeric=function(offset){var date=new Date();date.setDate(date.getDate()+offset);return date};var offsetString=function(offset,getDaysInMonth){var date=new Date();var year=date.getFullYear();var month=date.getMonth();var day=date.getDate();var pattern=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;var matches=pattern.exec(offset);while(matches){switch(matches[2]||"d"){case"d":case"D":day+=parseInt(matches[1],10);break;case"w":case"W":day+=parseInt(matches[1],10)*7;break;case"m":case"M":month+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break;case"y":case"Y":year+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break}matches=pattern.exec(offset)}return new Date(year,month,day)};date=(date==null?defaultDate:(typeof date=="string"?offsetString(date,this._getDaysInMonth):(typeof date=="number"?(isNaN(date)?defaultDate:offsetNumeric(date)):date)));date=(date&&date.toString()=="Invalid Date"?defaultDate:date);if(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0)}return this._daylightSavingAdjust(date)},_daylightSavingAdjust:function(date){if(!date){return null}date.setHours(date.getHours()>12?date.getHours()+2:0);return date},_setDate:function(inst,date,endDate){var clear=!(date);var origMonth=inst.selectedMonth;var origYear=inst.selectedYear;date=this._determineDate(date,new Date());inst.selectedDay=inst.currentDay=date.getDate();inst.drawMonth=inst.selectedMonth=inst.currentMonth=date.getMonth();inst.drawYear=inst.selectedYear=inst.currentYear=date.getFullYear();if(origMonth!=inst.selectedMonth||origYear!=inst.selectedYear){this._notifyChange(inst)}this._adjustInstDate(inst);if(inst.input){inst.input.val(clear?"":this._formatDate(inst))}},_getDate:function(inst){var startDate=(!inst.currentYear||(inst.input&&inst.input.val()=="")?null:this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return startDate},_generateHTML:function(inst){var today=new Date();today=this._daylightSavingAdjust(new Date(today.getFullYear(),today.getMonth(),today.getDate()));var isRTL=this._get(inst,"isRTL");var showButtonPanel=this._get(inst,"showButtonPanel");var hideIfNoPrevNext=this._get(inst,"hideIfNoPrevNext");var navigationAsDateFormat=this._get(inst,"navigationAsDateFormat");var numMonths=this._getNumberOfMonths(inst);var showCurrentAtPos=this._get(inst,"showCurrentAtPos");var stepMonths=this._get(inst,"stepMonths");var stepBigMonths=this._get(inst,"stepBigMonths");var isMultiMonth=(numMonths[0]!=1||numMonths[1]!=1);var currentDate=this._daylightSavingAdjust((!inst.currentDay?new Date(9999,9,9):new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");var drawMonth=inst.drawMonth-showCurrentAtPos;var drawYear=inst.drawYear;if(drawMonth<0){drawMonth+=12;drawYear--}if(maxDate){var maxDraw=this._daylightSavingAdjust(new Date(maxDate.getFullYear(),maxDate.getMonth()-numMonths[1]+1,maxDate.getDate()));maxDraw=(minDate&&maxDraw<minDate?minDate:maxDraw);while(this._daylightSavingAdjust(new Date(drawYear,drawMonth,1))>maxDraw){drawMonth--;if(drawMonth<0){drawMonth=11;drawYear--}}}inst.drawMonth=drawMonth;inst.drawYear=drawYear;var prevText=this._get(inst,"prevText");prevText=(!navigationAsDateFormat?prevText:this.formatDate(prevText,this._daylightSavingAdjust(new Date(drawYear,drawMonth-stepMonths,1)),this._getFormatConfig(inst)));var prev=(this._canAdjustMonth(inst,-1,drawYear,drawMonth)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', -"+stepMonths+", 'M');\" title=\""+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>"));var nextText=this._get(inst,"nextText");nextText=(!navigationAsDateFormat?nextText:this.formatDate(nextText,this._daylightSavingAdjust(new Date(drawYear,drawMonth+stepMonths,1)),this._getFormatConfig(inst)));var next=(this._canAdjustMonth(inst,+1,drawYear,drawMonth)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', +"+stepMonths+", 'M');\" title=\""+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>"));var currentText=this._get(inst,"currentText");var gotoDate=(this._get(inst,"gotoCurrent")&&inst.currentDay?currentDate:today);currentText=(!navigationAsDateFormat?currentText:this.formatDate(currentText,gotoDate,this._getFormatConfig(inst)));var controls=(!inst.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery.datepicker._hideDatepicker();">'+this._get(inst,"closeText")+"</button>":"");var buttonPanel=(showButtonPanel)?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(isRTL?controls:"")+(this._isInRange(inst,gotoDate)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery.datepicker._gotoToday(\'#'+inst.id+"');\">"+currentText+"</button>":"")+(isRTL?"":controls)+"</div>":"";var firstDay=parseInt(this._get(inst,"firstDay"),10);firstDay=(isNaN(firstDay)?0:firstDay);var dayNames=this._get(inst,"dayNames");var dayNamesShort=this._get(inst,"dayNamesShort");var dayNamesMin=this._get(inst,"dayNamesMin");var monthNames=this._get(inst,"monthNames");var monthNamesShort=this._get(inst,"monthNamesShort");var beforeShowDay=this._get(inst,"beforeShowDay");var showOtherMonths=this._get(inst,"showOtherMonths");var calculateWeek=this._get(inst,"calculateWeek")||this.iso8601Week;var endDate=inst.endDay?this._daylightSavingAdjust(new Date(inst.endYear,inst.endMonth,inst.endDay)):currentDate;var defaultDate=this._getDefaultDate(inst);var html="";for(var row=0;row<numMonths[0];row++){var group="";for(var col=0;col<numMonths[1];col++){var selectedDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,inst.selectedDay));var cornerClass=" ui-corner-all";var calender="";if(isMultiMonth){calender+='<div class="ui-datepicker-group ui-datepicker-group-';switch(col){case 0:calender+="first";cornerClass=" ui-corner-"+(isRTL?"right":"left");break;case numMonths[1]-1:calender+="last";cornerClass=" ui-corner-"+(isRTL?"left":"right");break;default:calender+="middle";cornerClass="";break}calender+='">'}calender+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+cornerClass+'">'+(/all|left/.test(cornerClass)&&row==0?(isRTL?next:prev):"")+(/all|right/.test(cornerClass)&&row==0?(isRTL?prev:next):"")+this._generateMonthYearHeader(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,row>0||col>0,monthNames,monthNamesShort)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var thead="";for(var dow=0;dow<7;dow++){var day=(dow+firstDay)%7;thead+="<th"+((dow+firstDay+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+dayNames[day]+'">'+dayNamesMin[day]+"</span></th>"}calender+=thead+"</tr></thead><tbody>";var daysInMonth=this._getDaysInMonth(drawYear,drawMonth);if(drawYear==inst.selectedYear&&drawMonth==inst.selectedMonth){inst.selectedDay=Math.min(inst.selectedDay,daysInMonth)}var leadDays=(this._getFirstDayOfMonth(drawYear,drawMonth)-firstDay+7)%7;var numRows=(isMultiMonth?6:Math.ceil((leadDays+daysInMonth)/7));var printDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,1-leadDays));for(var dRow=0;dRow<numRows;dRow++){calender+="<tr>";var tbody="";for(var dow=0;dow<7;dow++){var daySettings=(beforeShowDay?beforeShowDay.apply((inst.input?inst.input[0]:null),[printDate]):[true,""]);var otherMonth=(printDate.getMonth()!=drawMonth);var unselectable=otherMonth||!daySettings[0]||(minDate&&printDate<minDate)||(maxDate&&printDate>maxDate);tbody+='<td class="'+((dow+firstDay+6)%7>=5?" ui-datepicker-week-end":"")+(otherMonth?" ui-datepicker-other-month":"")+((printDate.getTime()==selectedDate.getTime()&&drawMonth==inst.selectedMonth&&inst._keyEvent)||(defaultDate.getTime()==printDate.getTime()&&defaultDate.getTime()==selectedDate.getTime())?" "+this._dayOverClass:"")+(unselectable?" "+this._unselectableClass+" ui-state-disabled":"")+(otherMonth&&!showOtherMonths?"":" "+daySettings[1]+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" "+this._currentClass:"")+(printDate.getTime()==today.getTime()?" ui-datepicker-today":""))+'"'+((!otherMonth||showOtherMonths)&&daySettings[2]?' title="'+daySettings[2]+'"':"")+(unselectable?"":" onclick=\"DP_jQuery.datepicker._selectDay('#"+inst.id+"',"+drawMonth+","+drawYear+', this);return false;"')+">"+(otherMonth?(showOtherMonths?printDate.getDate():" "):(unselectable?'<span class="ui-state-default">'+printDate.getDate()+"</span>":'<a class="ui-state-default'+(printDate.getTime()==today.getTime()?" ui-state-highlight":"")+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" ui-state-active":"")+'" href="#">'+printDate.getDate()+"</a>"))+"</td>";printDate.setDate(printDate.getDate()+1);printDate=this._daylightSavingAdjust(printDate)}calender+=tbody+"</tr>"}drawMonth++;if(drawMonth>11){drawMonth=0;drawYear++}calender+="</tbody></table>"+(isMultiMonth?"</div>"+((numMonths[0]>0&&col==numMonths[1]-1)?'<div class="ui-datepicker-row-break"></div>':""):"");group+=calender}html+=group}html+=buttonPanel+($.browser.msie&&parseInt($.browser.version,10)<7&&!inst.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,secondary,monthNames,monthNamesShort){minDate=(inst.rangeStart&&minDate&&selectedDate<minDate?selectedDate:minDate);var changeMonth=this._get(inst,"changeMonth");var changeYear=this._get(inst,"changeYear");var showMonthAfterYear=this._get(inst,"showMonthAfterYear");var html='<div class="ui-datepicker-title">';var monthHtml="";if(secondary||!changeMonth){monthHtml+='<span class="ui-datepicker-month">'+monthNames[drawMonth]+"</span> "}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='<select class="ui-datepicker-month" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'M');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(var month=0;month<12;month++){if((!inMinYear||month>=minDate.getMonth())&&(!inMaxYear||month<=maxDate.getMonth())){monthHtml+='<option value="'+month+'"'+(month==drawMonth?' selected="selected"':"")+">"+monthNamesShort[month]+"</option>"}}monthHtml+="</select>"}if(!showMonthAfterYear){html+=monthHtml+((secondary||changeMonth||changeYear)&&(!(changeMonth&&changeYear))?" ":"")}if(secondary||!changeYear){html+='<span class="ui-datepicker-year">'+drawYear+"</span>"}else{var years=this._get(inst,"yearRange").split(":");var year=0;var endYear=0;if(years.length!=2){year=drawYear-10;endYear=drawYear+10}else{if(years[0].charAt(0)=="+"||years[0].charAt(0)=="-"){year=drawYear+parseInt(years[0],10);endYear=drawYear+parseInt(years[1],10)}else{year=parseInt(years[0],10);endYear=parseInt(years[1],10)}}year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='<select class="ui-datepicker-year" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'Y');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(;year<=endYear;year++){html+='<option value="'+year+'"'+(year==drawYear?' selected="selected"':"")+">"+year+"</option>"}html+="</select>"}if(showMonthAfterYear){html+=(secondary||changeMonth||changeYear?" ":"")+monthHtml}html+="</div>";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._daylightSavingAdjust(new Date(year,month,day));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax,checkRange){var date=this._determineDate(this._get(inst,minMax+"Date"),null);return(!checkRange||!inst.rangeStart?date:(!date||inst.rangeStart>date?inst.rangeStart:date))},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var newMinDate=(!inst.rangeStart?null:this._daylightSavingAdjust(new Date(inst.selectedYear,inst.selectedMonth,inst.selectedDay)));newMinDate=(newMinDate&&inst.rangeStart<newMinDate?inst.rangeStart:newMinDate);var minDate=newMinDate||this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");return((!minDate||date>=minDate)&&(!maxDate||date<=maxDate))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}if(options=="option"&&arguments.length==2&&typeof arguments[1]=="string"){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.7.2";window.DP_jQuery=$})(jQuery);;/*
+ * jQuery UI Progressbar 1.7.2
+ *
+ * Copyright (c) 2009 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/Progressbar
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function(a){a.widget("ui.progressbar",{_init:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this._valueMin(),"aria-valuemax":this._valueMax(),"aria-valuenow":this._value()});this.valueDiv=a('<div class="ui-progressbar-value ui-widget-header ui-corner-left"></div>').appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();a.widget.prototype.destroy.apply(this,arguments)},value:function(b){if(b===undefined){return this._value()}this._setData("value",b);return this},_setData:function(b,c){switch(b){case"value":this.options.value=c;this._refreshValue();this._trigger("change",null,{});break}a.widget.prototype._setData.apply(this,arguments)},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_valueMin:function(){var b=0;return b},_valueMax:function(){var b=100;return b},_refreshValue:function(){var b=this.value();this.valueDiv[b==this._valueMax()?"addClass":"removeClass"]("ui-corner-right");this.valueDiv.width(b+"%");this.element.attr("aria-valuenow",b)}});a.extend(a.ui.progressbar,{version:"1.7.2",defaults:{value:0}})})(jQuery);;/*
+ * jQuery UI Effects 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/
+ */
+jQuery.effects||(function(d){d.effects={version:"1.7.2",save:function(g,h){for(var f=0;f<h.length;f++){if(h[f]!==null){g.data("ec.storage."+h[f],g[0].style[h[f]])}}},restore:function(g,h){for(var f=0;f<h.length;f++){if(h[f]!==null){g.css(h[f],g.data("ec.storage."+h[f]))}}},setMode:function(f,g){if(g=="toggle"){g=f.is(":hidden")?"show":"hide"}return g},getBaseline:function(g,h){var i,f;switch(g[0]){case"top":i=0;break;case"middle":i=0.5;break;case"bottom":i=1;break;default:i=g[0]/h.height}switch(g[1]){case"left":f=0;break;case"center":f=0.5;break;case"right":f=1;break;default:f=g[1]/h.width}return{x:f,y:i}},createWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent()}var g={width:f.outerWidth(true),height:f.outerHeight(true),"float":f.css("float")};f.wrap('<div class="ui-effects-wrapper" style="font-size:100%;background:transparent;border:none;margin:0;padding:0"></div>');var j=f.parent();if(f.css("position")=="static"){j.css({position:"relative"});f.css({position:"relative"})}else{var i=f.css("top");if(isNaN(parseInt(i,10))){i="auto"}var h=f.css("left");if(isNaN(parseInt(h,10))){h="auto"}j.css({position:f.css("position"),top:i,left:h,zIndex:f.css("z-index")}).show();f.css({position:"relative",top:0,left:0})}j.css(g);return j},removeWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent().replaceWith(f)}return f},setTransition:function(g,i,f,h){h=h||{};d.each(i,function(k,j){unit=g.cssUnit(j);if(unit[0]>0){h[j]=unit[0]*f+unit[1]}});return h},animateClass:function(h,i,k,j){var f=(typeof k=="function"?k:(j?j:null));var g=(typeof k=="string"?k:null);return this.each(function(){var q={};var o=d(this);var p=o.attr("style")||"";if(typeof p=="object"){p=p.cssText}if(h.toggle){o.hasClass(h.toggle)?h.remove=h.toggle:h.add=h.toggle}var l=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.addClass(h.add)}if(h.remove){o.removeClass(h.remove)}var m=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.removeClass(h.add)}if(h.remove){o.addClass(h.remove)}for(var r in m){if(typeof m[r]!="function"&&m[r]&&r.indexOf("Moz")==-1&&r.indexOf("length")==-1&&m[r]!=l[r]&&(r.match(/color/i)||(!r.match(/color/i)&&!isNaN(parseInt(m[r],10))))&&(l.position!="static"||(l.position=="static"&&!r.match(/left|top|bottom|right/)))){q[r]=m[r]}}o.animate(q,i,g,function(){if(typeof d(this).attr("style")=="object"){d(this).attr("style")["cssText"]="";d(this).attr("style")["cssText"]=p}else{d(this).attr("style",p)}if(h.add){d(this).addClass(h.add)}if(h.remove){d(this).removeClass(h.remove)}if(f){f.apply(this,arguments)}})})}};function c(g,f){var i=g[1]&&g[1].constructor==Object?g[1]:{};if(f){i.mode=f}var h=g[1]&&g[1].constructor!=Object?g[1]:(i.duration?i.duration:g[2]);h=d.fx.off?0:typeof h==="number"?h:d.fx.speeds[h]||d.fx.speeds._default;var j=i.callback||(d.isFunction(g[1])&&g[1])||(d.isFunction(g[2])&&g[2])||(d.isFunction(g[3])&&g[3]);return[g[0],i,h,j]}d.fn.extend({_show:d.fn.show,_hide:d.fn.hide,__toggle:d.fn.toggle,_addClass:d.fn.addClass,_removeClass:d.fn.removeClass,_toggleClass:d.fn.toggleClass,effect:function(g,f,h,i){return d.effects[g]?d.effects[g].call(this,{method:g,options:f||{},duration:h,callback:i}):null},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._show.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"show"))}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._hide.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"hide"))}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||(d.isFunction(arguments[0])||typeof arguments[0]=="boolean")){return this.__toggle.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"toggle"))}},addClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{add:g},f,i,h]):this._addClass(g)},removeClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{remove:g},f,i,h]):this._removeClass(g)},toggleClass:function(g,f,i,h){return((typeof f!=="boolean")&&f)?d.effects.animateClass.apply(this,[{toggle:g},f,i,h]):this._toggleClass(g,f)},morph:function(f,h,g,j,i){return d.effects.animateClass.apply(this,[{add:h,remove:f},g,j,i])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(f){var g=this.css(f),h=[];d.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}});d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(g,f){d.fx.step[f]=function(h){if(h.state==0){h.start=e(h.elem,f);h.end=b(h.end)}h.elem.style[f]="rgb("+[Math.max(Math.min(parseInt((h.pos*(h.end[0]-h.start[0]))+h.start[0],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[1]-h.start[1]))+h.start[1],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[2]-h.start[2]))+h.start[2],10),255),0)].join(",")+")"}});function b(g){var f;if(g&&g.constructor==Array&&g.length==3){return g}if(f=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(g)){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}if(f=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(g)){return[parseFloat(f[1])*2.55,parseFloat(f[2])*2.55,parseFloat(f[3])*2.55]}if(f=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(g)){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}if(f=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(g)){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}if(f=/rgba\(0, 0, 0, 0\)/.exec(g)){return a.transparent}return a[d.trim(g).toLowerCase()]}function e(h,f){var g;do{g=d.curCSS(h,f);if(g!=""&&g!="transparent"||d.nodeName(h,"body")){break}f="backgroundColor"}while(h=h.parentNode);return b(g)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};d.easing.jswing=d.easing.swing;d.extend(d.easing,{def:"easeOutQuad",swing:function(g,h,f,j,i){return d.easing[d.easing.def](g,h,f,j,i)},easeInQuad:function(g,h,f,j,i){return j*(h/=i)*h+f},easeOutQuad:function(g,h,f,j,i){return -j*(h/=i)*(h-2)+f},easeInOutQuad:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h+f}return -j/2*((--h)*(h-2)-1)+f},easeInCubic:function(g,h,f,j,i){return j*(h/=i)*h*h+f},easeOutCubic:function(g,h,f,j,i){return j*((h=h/i-1)*h*h+1)+f},easeInOutCubic:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h+f}return j/2*((h-=2)*h*h+2)+f},easeInQuart:function(g,h,f,j,i){return j*(h/=i)*h*h*h+f},easeOutQuart:function(g,h,f,j,i){return -j*((h=h/i-1)*h*h*h-1)+f},easeInOutQuart:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h+f}return -j/2*((h-=2)*h*h*h-2)+f},easeInQuint:function(g,h,f,j,i){return j*(h/=i)*h*h*h*h+f},easeOutQuint:function(g,h,f,j,i){return j*((h=h/i-1)*h*h*h*h+1)+f},easeInOutQuint:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h*h+f}return j/2*((h-=2)*h*h*h*h+2)+f},easeInSine:function(g,h,f,j,i){return -j*Math.cos(h/i*(Math.PI/2))+j+f},easeOutSine:function(g,h,f,j,i){return j*Math.sin(h/i*(Math.PI/2))+f},easeInOutSine:function(g,h,f,j,i){return -j/2*(Math.cos(Math.PI*h/i)-1)+f},easeInExpo:function(g,h,f,j,i){return(h==0)?f:j*Math.pow(2,10*(h/i-1))+f},easeOutExpo:function(g,h,f,j,i){return(h==i)?f+j:j*(-Math.pow(2,-10*h/i)+1)+f},easeInOutExpo:function(g,h,f,j,i){if(h==0){return f}if(h==i){return f+j}if((h/=i/2)<1){return j/2*Math.pow(2,10*(h-1))+f}return j/2*(-Math.pow(2,-10*--h)+2)+f},easeInCirc:function(g,h,f,j,i){return -j*(Math.sqrt(1-(h/=i)*h)-1)+f},easeOutCirc:function(g,h,f,j,i){return j*Math.sqrt(1-(h=h/i-1)*h)+f},easeInOutCirc:function(g,h,f,j,i){if((h/=i/2)<1){return -j/2*(Math.sqrt(1-h*h)-1)+f}return j/2*(Math.sqrt(1-(h-=2)*h)+1)+f},easeInElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}return -(h*Math.pow(2,10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k))+f},easeOutElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}return h*Math.pow(2,-10*i)*Math.sin((i*l-j)*(2*Math.PI)/k)+m+f},easeInOutElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l/2)==2){return f+m}if(!k){k=l*(0.3*1.5)}if(h<Math.abs(m)){h=m;var j=k/4}else{var j=k/(2*Math.PI)*Math.asin(m/h)}if(i<1){return -0.5*(h*Math.pow(2,10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k))+f}return h*Math.pow(2,-10*(i-=1))*Math.sin((i*l-j)*(2*Math.PI)/k)*0.5+m+f},easeInBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}return k*(h/=j)*h*((i+1)*h-i)+f},easeOutBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}return k*((h=h/j-1)*h*((i+1)*h+i)+1)+f},easeInOutBack:function(g,h,f,k,j,i){if(i==undefined){i=1.70158}if((h/=j/2)<1){return k/2*(h*h*(((i*=(1.525))+1)*h-i))+f}return k/2*((h-=2)*h*(((i*=(1.525))+1)*h+i)+2)+f},easeInBounce:function(g,h,f,j,i){return j-d.easing.easeOutBounce(g,i-h,0,j,i)+f},easeOutBounce:function(g,h,f,j,i){if((h/=i)<(1/2.75)){return j*(7.5625*h*h)+f}else{if(h<(2/2.75)){return j*(7.5625*(h-=(1.5/2.75))*h+0.75)+f}else{if(h<(2.5/2.75)){return j*(7.5625*(h-=(2.25/2.75))*h+0.9375)+f}else{return j*(7.5625*(h-=(2.625/2.75))*h+0.984375)+f}}}},easeInOutBounce:function(g,h,f,j,i){if(h<i/2){return d.easing.easeInBounce(g,h*2,0,j,i)*0.5+f}return d.easing.easeOutBounce(g,h*2-i,0,j,i)*0.5+j*0.5+f}})})(jQuery);;/*
+ * jQuery UI Effects Blind 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Blind
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.blind=function(b){return this.queue(function(){var d=a(this),c=["position","top","left"];var h=a.effects.setMode(d,b.options.mode||"hide");var g=b.options.direction||"vertical";a.effects.save(d,c);d.show();var j=a.effects.createWrapper(d).css({overflow:"hidden"});var e=(g=="vertical")?"height":"width";var i=(g=="vertical")?j.height():j.width();if(h=="show"){j.css(e,0)}var f={};f[e]=h=="show"?i:0;j.animate(f,b.duration,b.options.easing,function(){if(h=="hide"){d.hide()}a.effects.restore(d,c);a.effects.removeWrapper(d);if(b.callback){b.callback.apply(d[0],arguments)}d.dequeue()})})}})(jQuery);;/*
+ * jQuery UI Effects Bounce 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Bounce
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.bounce=function(b){return this.queue(function(){var e=a(this),l=["position","top","left"];var k=a.effects.setMode(e,b.options.mode||"effect");var n=b.options.direction||"up";var c=b.options.distance||20;var d=b.options.times||5;var g=b.duration||250;if(/show|hide/.test(k)){l.push("opacity")}a.effects.save(e,l);e.show();a.effects.createWrapper(e);var f=(n=="up"||n=="down")?"top":"left";var p=(n=="up"||n=="left")?"pos":"neg";var c=b.options.distance||(f=="top"?e.outerHeight({margin:true})/3:e.outerWidth({margin:true})/3);if(k=="show"){e.css("opacity",0).css(f,p=="pos"?-c:c)}if(k=="hide"){c=c/(d*2)}if(k!="hide"){d--}if(k=="show"){var h={opacity:1};h[f]=(p=="pos"?"+=":"-=")+c;e.animate(h,g/2,b.options.easing);c=c/2;d--}for(var j=0;j<d;j++){var o={},m={};o[f]=(p=="pos"?"-=":"+=")+c;m[f]=(p=="pos"?"+=":"-=")+c;e.animate(o,g/2,b.options.easing).animate(m,g/2,b.options.easing);c=(k=="hide")?c*2:c/2}if(k=="hide"){var h={opacity:0};h[f]=(p=="pos"?"-=":"+=")+c;e.animate(h,g/2,b.options.easing,function(){e.hide();a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}})}else{var o={},m={};o[f]=(p=="pos"?"-=":"+=")+c;m[f]=(p=="pos"?"+=":"-=")+c;e.animate(o,g/2,b.options.easing).animate(m,g/2,b.options.easing,function(){a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}})}e.queue("fx",function(){e.dequeue()});e.dequeue()})}})(jQuery);;/*
+ * jQuery UI Effects Clip 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Clip
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.clip=function(b){return this.queue(function(){var f=a(this),j=["position","top","left","height","width"];var i=a.effects.setMode(f,b.options.mode||"hide");var k=b.options.direction||"vertical";a.effects.save(f,j);f.show();var c=a.effects.createWrapper(f).css({overflow:"hidden"});var e=f[0].tagName=="IMG"?c:f;var g={size:(k=="vertical")?"height":"width",position:(k=="vertical")?"top":"left"};var d=(k=="vertical")?e.height():e.width();if(i=="show"){e.css(g.size,0);e.css(g.position,d/2)}var h={};h[g.size]=i=="show"?d:0;h[g.position]=i=="show"?0:d/2;e.animate(h,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){f.hide()}a.effects.restore(f,j);a.effects.removeWrapper(f);if(b.callback){b.callback.apply(f[0],arguments)}f.dequeue()}})})}})(jQuery);;/*
+ * jQuery UI Effects Drop 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Drop
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.drop=function(b){return this.queue(function(){var e=a(this),d=["position","top","left","opacity"];var i=a.effects.setMode(e,b.options.mode||"hide");var h=b.options.direction||"left";a.effects.save(e,d);e.show();a.effects.createWrapper(e);var f=(h=="up"||h=="down")?"top":"left";var c=(h=="up"||h=="left")?"pos":"neg";var j=b.options.distance||(f=="top"?e.outerHeight({margin:true})/2:e.outerWidth({margin:true})/2);if(i=="show"){e.css("opacity",0).css(f,c=="pos"?-j:j)}var g={opacity:i=="show"?1:0};g[f]=(i=="show"?(c=="pos"?"+=":"-="):(c=="pos"?"-=":"+="))+j;e.animate(g,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){e.hide()}a.effects.restore(e,d);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
+ * jQuery UI Effects Explode 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Explode
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.explode=function(b){return this.queue(function(){var k=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;var e=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?(a(this).is(":visible")?"hide":"show"):b.options.mode;var h=a(this).show().css("visibility","hidden");var l=h.offset();l.top-=parseInt(h.css("marginTop"),10)||0;l.left-=parseInt(h.css("marginLeft"),10)||0;var g=h.outerWidth(true);var c=h.outerHeight(true);for(var f=0;f<k;f++){for(var d=0;d<e;d++){h.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-d*(g/e),top:-f*(c/k)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/e,height:c/k,left:l.left+d*(g/e)+(b.options.mode=="show"?(d-Math.floor(e/2))*(g/e):0),top:l.top+f*(c/k)+(b.options.mode=="show"?(f-Math.floor(k/2))*(c/k):0),opacity:b.options.mode=="show"?0:1}).animate({left:l.left+d*(g/e)+(b.options.mode=="show"?0:(d-Math.floor(e/2))*(g/e)),top:l.top+f*(c/k)+(b.options.mode=="show"?0:(f-Math.floor(k/2))*(c/k)),opacity:b.options.mode=="show"?1:0},b.duration||500)}}setTimeout(function(){b.options.mode=="show"?h.css({visibility:"visible"}):h.css({visibility:"visible"}).hide();if(b.callback){b.callback.apply(h[0])}h.dequeue();a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/*
+ * jQuery UI Effects Fold 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Fold
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.fold=function(b){return this.queue(function(){var e=a(this),k=["position","top","left"];var h=a.effects.setMode(e,b.options.mode||"hide");var o=b.options.size||15;var n=!(!b.options.horizFirst);var g=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(e,k);e.show();var d=a.effects.createWrapper(e).css({overflow:"hidden"});var i=((h=="show")!=n);var f=i?["width","height"]:["height","width"];var c=i?[d.width(),d.height()]:[d.height(),d.width()];var j=/([0-9]+)%/.exec(o);if(j){o=parseInt(j[1],10)/100*c[h=="hide"?0:1]}if(h=="show"){d.css(n?{height:0,width:o}:{height:o,width:0})}var m={},l={};m[f[0]]=h=="show"?c[0]:o;l[f[1]]=h=="show"?c[1]:0;d.animate(m,g,b.options.easing).animate(l,g,b.options.easing,function(){if(h=="hide"){e.hide()}a.effects.restore(e,k);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(e[0],arguments)}e.dequeue()})})}})(jQuery);;/*
+ * jQuery UI Effects Highlight 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Highlight
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.highlight=function(b){return this.queue(function(){var e=a(this),d=["backgroundImage","backgroundColor","opacity"];var h=a.effects.setMode(e,b.options.mode||"show");var c=b.options.color||"#ffff99";var g=e.css("backgroundColor");a.effects.save(e,d);e.show();e.css({backgroundImage:"none",backgroundColor:c});var f={backgroundColor:g};if(h=="hide"){f.opacity=0}e.animate(f,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(h=="hide"){e.hide()}a.effects.restore(e,d);if(h=="show"&&a.browser.msie){this.style.removeAttribute("filter")}if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
+ * jQuery UI Effects Pulsate 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Pulsate
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.pulsate=function(b){return this.queue(function(){var d=a(this);var g=a.effects.setMode(d,b.options.mode||"show");var f=b.options.times||5;var e=b.duration?b.duration/2:a.fx.speeds._default/2;if(g=="hide"){f--}if(d.is(":hidden")){d.css("opacity",0);d.show();d.animate({opacity:1},e,b.options.easing);f=f-2}for(var c=0;c<f;c++){d.animate({opacity:0},e,b.options.easing).animate({opacity:1},e,b.options.easing)}if(g=="hide"){d.animate({opacity:0},e,b.options.easing,function(){d.hide();if(b.callback){b.callback.apply(this,arguments)}})}else{d.animate({opacity:0},e,b.options.easing).animate({opacity:1},e,b.options.easing,function(){if(b.callback){b.callback.apply(this,arguments)}})}d.queue("fx",function(){d.dequeue()});d.dequeue()})}})(jQuery);;/*
+ * jQuery UI Effects Scale 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Scale
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.puff=function(b){return this.queue(function(){var f=a(this);var c=a.extend(true,{},b.options);var h=a.effects.setMode(f,b.options.mode||"hide");var g=parseInt(b.options.percent,10)||150;c.fade=true;var e={height:f.height(),width:f.width()};var d=g/100;f.from=(h=="hide")?e:{height:e.height*d,width:e.width*d};c.from=f.from;c.percent=(h=="hide")?g:100;c.mode=h;f.effect("scale",c,b.duration,b.callback);f.dequeue()})};a.effects.scale=function(b){return this.queue(function(){var g=a(this);var d=a.extend(true,{},b.options);var j=a.effects.setMode(g,b.options.mode||"effect");var h=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:(j=="hide"?0:100));var i=b.options.direction||"both";var c=b.options.origin;if(j!="effect"){d.origin=c||["middle","center"];d.restore=true}var f={height:g.height(),width:g.width()};g.from=b.options.from||(j=="show"?{height:0,width:0}:f);var e={y:i!="horizontal"?(h/100):1,x:i!="vertical"?(h/100):1};g.to={height:f.height*e.y,width:f.width*e.x};if(b.options.fade){if(j=="show"){g.from.opacity=0;g.to.opacity=1}if(j=="hide"){g.from.opacity=1;g.to.opacity=0}}d.from=g.from;d.to=g.to;d.mode=j;g.effect("size",d,b.duration,b.callback);g.dequeue()})};a.effects.size=function(b){return this.queue(function(){var c=a(this),n=["position","top","left","width","height","overflow","opacity"];var m=["position","top","left","overflow","opacity"];var j=["width","height","overflow"];var p=["fontSize"];var k=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"];var f=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"];var g=a.effects.setMode(c,b.options.mode||"effect");var i=b.options.restore||false;var e=b.options.scale||"both";var o=b.options.origin;var d={height:c.height(),width:c.width()};c.from=b.options.from||d;c.to=b.options.to||d;if(o){var h=a.effects.getBaseline(o,d);c.from.top=(d.height-c.from.height)*h.y;c.from.left=(d.width-c.from.width)*h.x;c.to.top=(d.height-c.to.height)*h.y;c.to.left=(d.width-c.to.width)*h.x}var l={from:{y:c.from.height/d.height,x:c.from.width/d.width},to:{y:c.to.height/d.height,x:c.to.width/d.width}};if(e=="box"||e=="both"){if(l.from.y!=l.to.y){n=n.concat(k);c.from=a.effects.setTransition(c,k,l.from.y,c.from);c.to=a.effects.setTransition(c,k,l.to.y,c.to)}if(l.from.x!=l.to.x){n=n.concat(f);c.from=a.effects.setTransition(c,f,l.from.x,c.from);c.to=a.effects.setTransition(c,f,l.to.x,c.to)}}if(e=="content"||e=="both"){if(l.from.y!=l.to.y){n=n.concat(p);c.from=a.effects.setTransition(c,p,l.from.y,c.from);c.to=a.effects.setTransition(c,p,l.to.y,c.to)}}a.effects.save(c,i?n:m);c.show();a.effects.createWrapper(c);c.css("overflow","hidden").css(c.from);if(e=="content"||e=="both"){k=k.concat(["marginTop","marginBottom"]).concat(p);f=f.concat(["marginLeft","marginRight"]);j=n.concat(k).concat(f);c.find("*[width]").each(function(){child=a(this);if(i){a.effects.save(child,j)}var q={height:child.height(),width:child.width()};child.from={height:q.height*l.from.y,width:q.width*l.from.x};child.to={height:q.height*l.to.y,width:q.width*l.to.x};if(l.from.y!=l.to.y){child.from=a.effects.setTransition(child,k,l.from.y,child.from);child.to=a.effects.setTransition(child,k,l.to.y,child.to)}if(l.from.x!=l.to.x){child.from=a.effects.setTransition(child,f,l.from.x,child.from);child.to=a.effects.setTransition(child,f,l.to.x,child.to)}child.css(child.from);child.animate(child.to,b.duration,b.options.easing,function(){if(i){a.effects.restore(child,j)}})})}c.animate(c.to,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(g=="hide"){c.hide()}a.effects.restore(c,i?n:m);a.effects.removeWrapper(c);if(b.callback){b.callback.apply(this,arguments)}c.dequeue()}})})}})(jQuery);;/*
+ * jQuery UI Effects Shake 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Shake
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.shake=function(b){return this.queue(function(){var e=a(this),l=["position","top","left"];var k=a.effects.setMode(e,b.options.mode||"effect");var n=b.options.direction||"left";var c=b.options.distance||20;var d=b.options.times||3;var g=b.duration||b.options.duration||140;a.effects.save(e,l);e.show();a.effects.createWrapper(e);var f=(n=="up"||n=="down")?"top":"left";var p=(n=="up"||n=="left")?"pos":"neg";var h={},o={},m={};h[f]=(p=="pos"?"-=":"+=")+c;o[f]=(p=="pos"?"+=":"-=")+c*2;m[f]=(p=="pos"?"-=":"+=")+c*2;e.animate(h,g,b.options.easing);for(var j=1;j<d;j++){e.animate(o,g,b.options.easing).animate(m,g,b.options.easing)}e.animate(o,g,b.options.easing).animate(h,g/2,b.options.easing,function(){a.effects.restore(e,l);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}});e.queue("fx",function(){e.dequeue()});e.dequeue()})}})(jQuery);;/*
+ * jQuery UI Effects Slide 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Slide
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.slide=function(b){return this.queue(function(){var e=a(this),d=["position","top","left"];var i=a.effects.setMode(e,b.options.mode||"show");var h=b.options.direction||"left";a.effects.save(e,d);e.show();a.effects.createWrapper(e).css({overflow:"hidden"});var f=(h=="up"||h=="down")?"top":"left";var c=(h=="up"||h=="left")?"pos":"neg";var j=b.options.distance||(f=="top"?e.outerHeight({margin:true}):e.outerWidth({margin:true}));if(i=="show"){e.css(f,c=="pos"?-j:j)}var g={};g[f]=(i=="show"?(c=="pos"?"+=":"-="):(c=="pos"?"-=":"+="))+j;e.animate(g,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(i=="hide"){e.hide()}a.effects.restore(e,d);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/*
+ * jQuery UI Effects Transfer 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Effects/Transfer
+ *
+ * Depends:
+ * effects.core.js
+ */
+(function(a){a.effects.transfer=function(b){return this.queue(function(){var f=a(this),h=a(b.options.to),e=h.offset(),g={top:e.top,left:e.left,height:h.innerHeight(),width:h.innerWidth()},d=f.offset(),c=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:d.top,left:d.left,height:f.innerHeight(),width:f.innerWidth(),position:"absolute"}).animate(g,b.duration,b.options.easing,function(){c.remove();(b.callback&&b.callback.apply(f[0],arguments));f.dequeue()})})}})(jQuery);;
\ No newline at end of file
--- a/web/facet.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/facet.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,16 +13,16 @@
from datetime import date, datetime, timedelta
from logilab.mtconverter import xml_escape
-
from logilab.common.graph import has_path
from logilab.common.decorators import cached
+from logilab.common.date import datetime2ticks, ustrftime
from logilab.common.compat import all
from rql import parse, nodes
from cubicweb import Unauthorized, typed_eid
from cubicweb.schema import display_name
-from cubicweb.utils import datetime2ticks, make_uid, ustrftime
+from cubicweb.utils import make_uid
from cubicweb.selectors import match_context_prop, partial_relation_possible
from cubicweb.appobject import AppObject
from cubicweb.web.htmlwidgets import HTMLWidget
@@ -109,8 +109,8 @@
if rel.optional in (opt, 'both'):
# optional relation
return ovar
- if all(rschema.rproperty(s, o, 'cardinality')[cardidx] in '1+'
- for s,o in rschema.iter_rdefs()):
+ if all(rdef.cardinality[cardidx] in '1+'
+ for rdef in rschema.rdefs.values()):
# mandatory relation without any restriction on the other variable
for orel in ovar.stinfo['relations']:
if rel is orel:
@@ -253,7 +253,7 @@
class AbstractFacet(AppObject):
__abstract__ = True
__registry__ = 'facets'
- property_defs = {
+ cw_property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the box or not')),
_('order'): dict(type='Int', default=99,
@@ -270,7 +270,7 @@
def __init__(self, req, rset=None, rqlst=None, filtered_variable=None,
**kwargs):
- super(AbstractFacet, self).__init__(req, rset, **kwargs)
+ super(AbstractFacet, self).__init__(req, rset=rset, **kwargs)
assert rset is not None or rqlst is not None
assert filtered_variable
# facet retreived using `object_by_id` from an ajax call
@@ -282,7 +282,7 @@
self.filtered_variable = filtered_variable
def init_from_rset(self):
- self.rqlst = self.rset.syntax_tree().children[0]
+ self.rqlst = self.cw_rset.syntax_tree().children[0]
def init_from_form(self, rqlst):
self.rqlst = rqlst
@@ -290,7 +290,7 @@
@property
def operator(self):
# OR between selected values by default
- return self.req.form.get(self.id + '_andor', 'OR')
+ return self._cw.form.get(self.__regid__ + '_andor', 'OR')
def get_widget(self):
"""return the widget instance to use to display this facet
@@ -315,12 +315,12 @@
if len(vocab) <= 1:
return None
wdg = FacetVocabularyWidget(self)
- selected = frozenset(typed_eid(eid) for eid in self.req.list_form_param(self.id))
+ selected = frozenset(typed_eid(eid) for eid in self._cw.list_form_param(self.__regid__))
for label, value in vocab:
if value is None:
wdg.append(FacetSeparator(label))
else:
- wdg.append(FacetItem(self.req, label, value, value in selected))
+ wdg.append(FacetItem(self._cw, label, value, value in selected))
return wdg
def vocabulary(self):
@@ -339,7 +339,7 @@
def rqlexec(self, rql, args=None, cachekey=None):
try:
- return self.req.execute(rql, args, cachekey)
+ return self._cw.execute(rql, args, cachekey)
except Unauthorized:
return []
@@ -360,7 +360,7 @@
@property
def title(self):
- return display_name(self.req, self.rtype, form=self.role)
+ return display_name(self._cw, self.rtype, form=self.role)
def vocabulary(self):
"""return vocabulary for this facet, eg a list of 2-uple (label, value)
@@ -376,7 +376,7 @@
insert_attr_select_relation(rqlst, mainvar, self.rtype, self.role,
self.target_attr, self.sortfunc, sort)
try:
- rset = self.rqlexec(rqlst.as_string(), self.rset.args, self.rset.cachekey)
+ rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args, self.cw_rset.cachekey)
except:
self.exception('error while getting vocabulary for %s, rql: %s',
self, rqlst.as_string())
@@ -400,7 +400,7 @@
def rset_vocabulary(self, rset):
if self.label_vid is None:
- _ = self.req._
+ _ = self._cw._
return [(_(label), eid) for eid, label in rset]
if self.sortfunc is None:
return sorted((entity.view(self.label_vid), entity.eid)
@@ -410,27 +410,23 @@
@cached
def support_and(self):
- rschema = self.schema.rschema(self.rtype)
- if self.role == 'subject':
- cardidx = 0
- else:
- cardidx = 1
+ rschema = self._cw.vreg.schema.rschema(self.rtype)
# XXX when called via ajax, no rset to compute possible types
- possibletypes = self.rset and self.rset.column_types(0)
- for subjtype, objtype in rschema.iter_rdefs():
+ possibletypes = self.cw_rset and self.cw_rset.column_types(0)
+ for rdef in rschema.rdefs.itervalues():
if possibletypes is not None:
if self.role == 'subject':
- if not subjtype in possibletypes:
+ if not rdef.subject in possibletypes:
continue
- elif not objtype in possibletypes:
+ elif not rdef.object in possibletypes:
continue
- if rschema.rproperty(subjtype, objtype, 'cardinality')[cardidx] in '+*':
+ if rdef.role_cardinality(self.role) in '+*':
return True
return False
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
- value = self.req.form.get(self.id)
+ value = self._cw.form.get(self.__regid__)
if not value:
return
mainvar = self.filtered_variable
@@ -469,7 +465,7 @@
newvar = _prepare_vocabulary_rqlst(rqlst, mainvar, self.rtype, self.role)
_set_orderby(rqlst, newvar, self.sortasc, self.sortfunc)
try:
- rset = self.rqlexec(rqlst.as_string(), self.rset.args, self.rset.cachekey)
+ rset = self.rqlexec(rqlst.as_string(), self.cw_rset.args, self.cw_rset.cachekey)
except:
self.exception('error while getting vocabulary for %s, rql: %s',
self, rqlst.as_string())
@@ -479,7 +475,7 @@
return self.rset_vocabulary(rset)
def rset_vocabulary(self, rset):
- _ = self.req._
+ _ = self._cw._
return [(_(value), value) for value, in rset]
def support_and(self):
@@ -487,7 +483,7 @@
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
- value = self.req.form.get(self.id)
+ value = self._cw.form.get(self.__regid__)
if not value:
return
mainvar = self.filtered_variable
@@ -499,16 +495,16 @@
"""called by javascript to get a rql string from filter form"""
def __init__(self, req):
- self.req = req
+ self._cw = req
def build_rql(self):#, tablefilter=False):
- form = self.req.form
+ form = self._cw.form
facetids = form['facets'].split(',')
select = parse(form['baserql']).children[0] # XXX Union unsupported yet
mainvar = filtered_variable(select)
toupdate = []
for facetid in facetids:
- facet = get_facet(self.req, facetid, select, mainvar)
+ facet = get_facet(self._cw, facetid, select, mainvar)
facet.add_rql_restrictions()
if facet.needs_update:
toupdate.append(facetid)
@@ -529,10 +525,10 @@
return self.wdgclass(self, min(values), max(values))
def infvalue(self):
- return self.req.form.get('%s_inf' % self.id)
+ return self._cw.form.get('%s_inf' % self.__regid__)
def supvalue(self):
- return self.req.form.get('%s_sup' % self.id)
+ return self._cw.form.get('%s_sup' % self.__regid__)
def formatvalue(self, value):
"""format `value` before in order to insert it in the RQL query"""
@@ -571,20 +567,20 @@
@property
def title(self):
- return display_name(self.req, self.rtype, self.role)
+ return display_name(self._cw, self.rtype, self.role)
def support_and(self):
return False
def get_widget(self):
- return CheckBoxFacetWidget(self.req, self,
+ return CheckBoxFacetWidget(self._cw, self,
'%s:%s' % (self.rtype, self),
- self.req.form.get(self.id))
+ self._cw.form.get(self.__regid__))
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
self.rqlst.set_distinct(True) # XXX
- value = self.req.form.get(self.id)
+ value = self._cw.form.get(self.__regid__)
if not value: # no value sent for this facet
return
var = self.rqlst.make_variable()
@@ -607,12 +603,12 @@
def _render(self):
title = xml_escape(self.facet.title)
- facetid = xml_escape(self.facet.id)
+ facetid = xml_escape(self.facet.__regid__)
self.w(u'<div id="%s" class="facet">\n' % facetid)
self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
(xml_escape(facetid), title))
if self.facet.support_and():
- _ = self.facet.req._
+ _ = self.facet._cw._
self.w(u'''<select name="%s" class="radio facetOperator" title="%s">
<option value="OR">%s</option>
<option value="AND">%s</option>
@@ -637,7 +633,7 @@
def _render(self):
title = xml_escape(self.facet.title)
- facetid = xml_escape(self.facet.id)
+ facetid = xml_escape(self.facet.__regid__)
self.w(u'<div id="%s" class="facet">\n' % facetid)
self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
(facetid, title))
@@ -677,11 +673,11 @@
def _render(self):
facet = self.facet
- facet.req.add_js('ui.slider.js')
- facet.req.add_css('ui.all.css')
+ facet._cw.add_js('ui.slider.js')
+ facet._cw.add_css('ui.all.css')
sliderid = make_uid('theslider')
- facetid = xml_escape(self.facet.id)
- facet.req.html_headers.add_onload(self.onload % {
+ facetid = xml_escape(self.facet.__regid__)
+ facet._cw.html_headers.add_onload(self.onload % {
'sliderid': sliderid,
'facetid': facetid,
'minvalue': self.minvalue,
@@ -715,8 +711,8 @@
super(DateFacetRangeWidget, self).__init__(facet,
datetime2ticks(minvalue),
datetime2ticks(maxvalue))
- fmt = facet.req.property_value('ui.date-format')
- facet.req.html_headers.define_var('DATE_FMT', fmt)
+ fmt = facet._cw.property_value('ui.date-format')
+ facet._cw.html_headers.define_var('DATE_FMT', fmt)
class FacetItem(HTMLWidget):
@@ -725,7 +721,7 @@
unselected_img = "no-check-no-border.png"
def __init__(self, req, label, value, selected=False):
- self.req = req
+ self._cw = req
self.label = label
self.value = value
self.selected = selected
@@ -733,12 +729,12 @@
def _render(self):
if self.selected:
cssclass = ' facetValueSelected'
- imgsrc = self.req.datadir_url + self.selected_img
- imgalt = self.req._('selected')
+ imgsrc = self._cw.datadir_url + self.selected_img
+ imgalt = self._cw._('selected')
else:
cssclass = ''
- imgsrc = self.req.datadir_url + self.unselected_img
- imgalt = self.req._('not selected')
+ imgsrc = self._cw.datadir_url + self.unselected_img
+ imgalt = self._cw._('not selected')
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
% (cssclass, xml_escape(unicode(self.value))))
self.w(u'<img src="%s" alt="%s"/> ' % (imgsrc, imgalt))
@@ -751,23 +747,23 @@
unselected_img = "black-uncheck.png"
def __init__(self, req, facet, value, selected):
- self.req = req
+ self._cw = req
self.facet = facet
self.value = value
self.selected = selected
def _render(self):
title = xml_escape(self.facet.title)
- facetid = xml_escape(self.facet.id)
+ facetid = xml_escape(self.facet.__regid__)
self.w(u'<div id="%s" class="facet">\n' % facetid)
if self.selected:
cssclass = ' facetValueSelected'
- imgsrc = self.req.datadir_url + self.selected_img
- imgalt = self.req._('selected')
+ imgsrc = self._cw.datadir_url + self.selected_img
+ imgalt = self._cw._('selected')
else:
cssclass = ''
- imgsrc = self.req.datadir_url + self.unselected_img
- imgalt = self.req._('not selected')
+ imgsrc = self._cw.datadir_url + self.unselected_img
+ imgalt = self._cw._('not selected')
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
% (cssclass, xml_escape(unicode(self.value))))
self.w(u'<div class="facetCheckBoxWidget">')
--- a/web/form.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/form.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,18 +7,20 @@
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
+
from logilab.common.decorators import iclassmethod
+from logilab.common.deprecation import deprecated
from cubicweb.appobject import AppObject
from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.common import tags
+from cubicweb import tags
from cubicweb.web import stdmsgs, httpcache, formfields
class FormViewMixIn(object):
"""abstract form view mix-in"""
category = 'form'
- controller = 'edit'
http_cache_manager = httpcache.NoHTTPCacheManager
add_to_breadcrumbs = False
@@ -37,158 +39,6 @@
return False
-# XXX should disappear
-class FormMixIn(object):
- """abstract form mix-in
- XXX: you should inherit from this FIRST (obscure pb with super call)
- """
- force_session_key = None
-
- def session_key(self):
- """return the key that may be used to store / retreive data about a
- previous post which failed because of a validation error
- """
- if self.force_session_key is None:
- return '%s#%s' % (self.req.url(), self.domid)
- return self.force_session_key
-
- def restore_previous_post(self, sessionkey):
- # get validation session data which may have been previously set.
- # deleting validation errors here breaks form reloading (errors are
- # no more available), they have to be deleted by application's publish
- # method on successful commit
- forminfo = self.req.get_session_data(sessionkey, pop=True)
- if forminfo:
- # XXX remove req.data assigment once cw.web.widget is killed
- self.req.data['formvalues'] = self._form_previous_values = forminfo['values']
- self.req.data['formerrors'] = self._form_valerror = forminfo['errors']
- self.req.data['displayederrors'] = self.form_displayed_errors = set()
- # if some validation error occured on entity creation, we have to
- # get the original variable name from its attributed eid
- foreid = self.form_valerror.entity
- for var, eid in forminfo['eidmap'].items():
- if foreid == eid:
- self.form_valerror.eid = var
- break
- else:
- self.form_valerror.eid = foreid
- else:
- self._form_previous_values = {}
- self._form_valerror = None
-
- @property
- def form_previous_values(self):
- if self.parent_form is None:
- return self._form_previous_values
- return self.parent_form.form_previous_values
-
- @property
- def form_valerror(self):
- if self.parent_form is None:
- return self._form_valerror
- return self.parent_form.form_valerror
-
- # XXX deprecated with new form system. Should disappear
-
- domid = 'entityForm'
- category = 'form'
- controller = 'edit'
- http_cache_manager = httpcache.NoHTTPCacheManager
- add_to_breadcrumbs = False
-
- def html_headers(self):
- """return a list of html headers (eg something to be inserted between
- <head> and </head> of the returned page
-
- by default forms are neither indexed nor followed
- """
- return [NOINDEX, NOFOLLOW]
-
- def linkable(self):
- """override since forms are usually linked by an action,
- so we don't want them to be listed by appli.possible_views
- """
- return False
-
-
- def button(self, label, klass='validateButton', tabindex=None, **kwargs):
- if tabindex is None:
- tabindex = self.req.next_tabindex()
- return tags.input(value=label, klass=klass, **kwargs)
-
- def action_button(self, label, onclick=None, __action=None, **kwargs):
- if onclick is None:
- onclick = "postForm('__action_%s', \'%s\', \'%s\')" % (
- __action, label, self.domid)
- return self.button(label, onclick=onclick, **kwargs)
-
- def button_ok(self, label=None, type='submit', name='defaultsubmit',
- **kwargs):
- label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
- return self.button(label, name=name, type=type, **kwargs)
-
- def button_apply(self, label=None, type='button', **kwargs):
- label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
- return self.action_button(label, __action='apply', type=type, **kwargs)
-
- def button_delete(self, label=None, type='button', **kwargs):
- label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
- return self.action_button(label, __action='delete', type=type, **kwargs)
-
- def button_cancel(self, label=None, type='button', **kwargs):
- label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
- return self.action_button(label, __action='cancel', type=type, **kwargs)
-
- def button_reset(self, label=None, type='reset', name='__action_cancel',
- **kwargs):
- label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
- return self.button(label, type=type, **kwargs)
-
- def need_multipart(self, entity, categories=('primary', 'secondary')):
- """return a boolean indicating if form's enctype should be multipart
- """
- for rschema, _, x in entity.relations_by_category(categories):
- if entity.get_widget(rschema, x).need_multipart:
- return True
- # let's find if any of our inlined entities needs multipart
- for rschema, targettypes, x in entity.relations_by_category('inlineview'):
- assert len(targettypes) == 1, \
- "I'm not able to deal with several targets and inlineview"
- ttype = targettypes[0]
- inlined_entity = self.vreg.etype_class(ttype)(self.req, None, None)
- for irschema, _, x in inlined_entity.relations_by_category(categories):
- if inlined_entity.get_widget(irschema, x).need_multipart:
- return True
- return False
-
- def error_message(self):
- """return formatted error message
-
- This method should be called once inlined field errors has been consumed
- """
- errex = self.req.data.get('formerrors') or self.form_valerror
- # get extra errors
- if errex is not None:
- errormsg = self.req._('please correct the following errors:')
- displayed = self.req.data.get('displayederrors') or self.form_displayed_errors
- errors = sorted((field, err) for field, err in errex.errors.items()
- if not field in displayed)
- if errors:
- if len(errors) > 1:
- templstr = '<li>%s</li>\n'
- else:
- templstr = ' %s\n'
- for field, err in errors:
- if field is None:
- errormsg += templstr % err
- else:
- errormsg += templstr % '%s: %s' % (self.req._(field), err)
- if len(errors) > 1:
- errormsg = '<ul>%s</ul>' % errormsg
- return u'<div class="errorMessage">%s</div>' % errormsg
- return u''
-
-
###############################################################################
class metafieldsform(type):
@@ -215,11 +65,16 @@
found
"""
-class Form(FormMixIn, AppObject):
+class Form(AppObject):
__metaclass__ = metafieldsform
__registry__ = 'forms'
parent_form = None
+ force_session_key = None
+
+ def __init__(self, req, rset, **kwargs):
+ super(Form, self).__init__(req, rset=rset, **kwargs)
+ self.restore_previous_post(self.session_key())
@property
def root_form(self):
@@ -228,6 +83,20 @@
return self
return self.parent_form.root_form
+ @property
+ def form_valerror(self):
+ """the validation error exception if any"""
+ if self.parent_form is None:
+ return self._form_valerror
+ return self.parent_form.form_valerror
+
+ @property
+ def form_previous_values(self):
+ """previously posted values (on validation error)"""
+ if self.parent_form is None:
+ return self._form_previous_values
+ return self.parent_form.form_previous_values
+
@iclassmethod
def _fieldsattr(cls_or_self):
if isinstance(cls_or_self, type):
@@ -237,17 +106,17 @@
return fields
@iclassmethod
- def field_by_name(cls_or_self, name, role='subject'):
+ def field_by_name(cls_or_self, name, role=None):
"""return field with the given name and role.
Raise FieldNotFound if the field can't be found.
"""
for field in cls_or_self._fieldsattr():
if field.name == name and field.role == role:
return field
- raise FieldNotFound(name)
+ raise FieldNotFound(name, role)
@iclassmethod
- def fields_by_name(cls_or_self, name, role='subject'):
+ def fields_by_name(cls_or_self, name, role=None):
"""return a list of fields with the given name and role"""
return [field for field in cls_or_self._fieldsattr()
if field.name == name and field.role == role]
@@ -273,3 +142,59 @@
field = cls_or_self.field_by_name(name, role)
fields = cls_or_self._fieldsattr()
fields.insert(fields.index(field)+1, new_field)
+
+ def session_key(self):
+ """return the key that may be used to store / retreive data about a
+ previous post which failed because of a validation error
+ """
+ if self.force_session_key is None:
+ return '%s#%s' % (self._cw.url(), self.domid)
+ return self.force_session_key
+
+ def restore_previous_post(self, sessionkey):
+ # get validation session data which may have been previously set.
+ # deleting validation errors here breaks form reloading (errors are
+ # no more available), they have to be deleted by application's publish
+ # method on successful commit
+ forminfo = self._cw.get_session_data(sessionkey, pop=True)
+ if forminfo:
+ self._form_previous_values = forminfo['values']
+ self._form_valerror = forminfo['error']
+ # if some validation error occured on entity creation, we have to
+ # get the original variable name from its attributed eid
+ foreid = self.form_valerror.entity
+ for var, eid in forminfo['eidmap'].items():
+ if foreid == eid:
+ self.form_valerror.eid = var
+ break
+ else:
+ self.form_valerror.eid = foreid
+ else:
+ self._form_previous_values = {}
+ self._form_valerror = None
+
+ def field_error(self, field):
+ """return field's error if specified in current validation exception"""
+ if self.form_valerror:
+ if field.eidparam and self.edited_entity.eid != self.form_valerror.eid:
+ return None
+ try:
+ return self.form_valerror.errors.pop(field.role_name())
+ except KeyError:
+ if field.role and field.name in self.form_valerror:
+ warn('%s: errors key of attribute/relation should be suffixed by "-<role>"'
+ % self.form_valerror.__class__, DeprecationWarning)
+ return self.form_valerror.errors.pop(field.name)
+ return None
+
+ def remaining_errors(self):
+ return sorted(self.form_valerror.errors.items())
+
+ @deprecated('[3.6] use form.field_error and/or new renderer.render_error method')
+ def form_field_error(self, field):
+ """return validation error for widget's field, if any"""
+ err = self.field_error(field)
+ if err:
+ return u'<span class="error">%s</span>' % err
+ return u''
+
--- a/web/formfields.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/formfields.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,4 +1,6 @@
-"""field classes for form construction
+"""Fields are used to control what's displayed in forms. It makes the link
+between something to edit and its display in the form. Actual display is handled
+by a widget associated to the field.
:organization: Logilab
:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -11,15 +13,22 @@
from datetime import datetime
from logilab.mtconverter import xml_escape
+from logilab.common.date import ustrftime
+from logilab.common.decorators import cached
+
+from yams.schema import KNOWN_METAATTRIBUTES
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
FormatConstraint)
-from cubicweb.utils import ustrftime
-from cubicweb.common import tags, uilib
-from cubicweb.web import INTERNAL_FIELD_VALUE
-from cubicweb.web.formwidgets import (
- HiddenInput, TextInput, FileInput, PasswordInput, TextArea, FCKEditor,
- Radio, Select, DateTimePicker)
+from cubicweb import Binary, tags, uilib
+from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \
+ formwidgets as fw
+
+
+class UnmodifiedField(Exception):
+ """raise this when a field has not actually been edited and you want to skip
+ it
+ """
def vocab_sort(vocab):
@@ -37,15 +46,14 @@
result += sorted(partresult)
return result
+_MARKER = object()
class Field(object):
- """field class is introduced to control what's displayed in forms. It makes
- the link between something to edit and its display in the form. Actual
- display is handled by a widget associated to the field.
+ """This class is the abstract base class for all fields. It hold a bunch
+ of attributes which may be used for fine control of the behaviour of a
+ concret field.
- Attributes
- ----------
- all the attributes described below have sensible default value which may be
+ All the attributes described below have sensible default value which may be
overriden by value given to field's constructor.
:name:
@@ -62,8 +70,8 @@
class which may be overriden per instance.
:required:
bool flag telling if the field is required or not.
- :initial:
- initial value, used when no value specified by other means.
+ :value:
+ field's value, used when no value specified by other means. XXX explain
:choices:
static vocabulary for this field. May be a list of values or a list of
(label, value) tuples if specified.
@@ -87,38 +95,46 @@
"""
# default widget associated to this class of fields. May be overriden per
# instance
- widget = TextInput
+ widget = fw.TextInput
# does this field requires a multipart form
needs_multipart = False
# class attribute used for ordering of fields in a form
__creation_rank = 0
- def __init__(self, name=None, id=None, label=None, help=None,
- widget=None, required=False, initial=None,
- choices=None, sort=True, internationalizable=False,
- eidparam=False, role='subject', fieldset=None, order=None):
+ eidparam = False
+ role = None
+ id = None
+ help = None
+ required = False
+ choices = None
+ sort = True
+ internationalizable = False
+ fieldset = None
+ order = None
+ value = _MARKER
+
+ def __init__(self, name=None, label=_MARKER, widget=None, **kwargs):
+ for key, val in kwargs.items():
+ if key == 'initial':
+ warn('[3.6] use value instead of initial', DeprecationWarning,
+ stacklevel=3)
+ key = 'value'
+ assert hasattr(self.__class__, key) and not key[0] == '_', key
+ setattr(self, key, val)
self.name = name
- self.id = id or name
- self.label = label or name
- self.help = help
- self.required = required
- self.initial = initial
- self.choices = choices
- self.sort = sort
- self.internationalizable = internationalizable
- self.eidparam = eidparam
- self.role = role
- self.fieldset = fieldset
+ if label is _MARKER:
+ label = name or _MARKER
+ self.label = label
+ # has to be done after other attributes initialization
self.init_widget(widget)
- self.order = order
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
Field.__creation_rank += 1
def __unicode__(self):
- return u'<%s name=%r label=%r id=%r initial=%r visible=%r @%x>' % (
- self.__class__.__name__, self.name, self.label,
- self.id, self.initial, self.is_visible(), id(self))
+ return u'<%s name=%r eidparam=%s role=%r id=%r value=%r visible=%r @%x>' % (
+ self.__class__.__name__, self.name, self.eidparam, self.role,
+ self.id, self.value, self.is_visible(), id(self))
def __repr__(self):
return self.__unicode__().encode('utf-8')
@@ -127,22 +143,20 @@
if widget is not None:
self.widget = widget
elif self.choices and not self.widget.vocabulary_widget:
- self.widget = Select()
+ self.widget = fw.Select()
if isinstance(self.widget, type):
self.widget = self.widget()
def set_name(self, name):
- """automatically set .id and .label when name is set"""
+ """automatically set .label when name is set"""
assert name
self.name = name
- if not self.id:
- self.id = name
- if not self.label:
+ if self.label is _MARKER:
self.label = name
def is_visible(self):
"""return true if the field is not an hidden field"""
- return not isinstance(self.widget, HiddenInput)
+ return not isinstance(self.widget, fw.HiddenInput)
def actual_fields(self, form):
"""return actual fields composing this field in case of a compound
@@ -170,6 +184,61 @@
"""return the widget instance associated to this field"""
return self.widget
+ # cached is necessary else we get some pb on entity creation : entity.eid is
+ # modified from creation mark (eg 'X') to its actual eid (eg 123), and then
+ # `field.input_name()` won't return the right key anymore if not cached
+ # (first call to input_name done *before* eventual eid affectation).
+ @cached
+ def input_name(self, form, suffix=None):
+ """return 'qualified name' for this field"""
+ name = self.role_name()
+ if suffix is not None:
+ name += suffix
+ if self.eidparam:
+ return eid_param(name, form.edited_entity.eid)
+ return name
+
+ def role_name(self):
+ """return <field.name>-<field.role> if role is specified, else field.name"""
+ if self.role is not None:
+ return '%s-%s' % (self.name, self.role)
+ return self.name
+
+ def dom_id(self, form, suffix=None):
+ """return an html dom identifier for this field"""
+ id = self.id or self.role_name()
+ if suffix is not None:
+ id += suffix
+ if self.eidparam:
+ return eid_param(id, form.edited_entity.eid)
+ return id
+
+ def typed_value(self, form, load_bytes=False):
+ if self.eidparam and self.role is not None:
+ entity = form.edited_entity
+ if form._cw.vreg.schema.rschema(self.name).final:
+ if entity.has_eid() or self.name in entity:
+ return getattr(entity, self.name)
+ elif entity.has_eid() or entity.relation_cached(self.name, self.role):
+ return [r[0] for r in entity.related(self.name, self.role)]
+ return self.initial_typed_value(form, load_bytes)
+
+ def initial_typed_value(self, form, load_bytes):
+ if self.value is not _MARKER:
+ if callable(self.value):
+ return self.value(form)
+ return self.value
+ formattr = '%s_%s_default' % (self.role, self.name)
+ if hasattr(form, formattr):
+ warn('[3.6] %s.%s deprecated, use field.value' % (
+ form.__class__.__name__, formattr), DeprecationWarning)
+ return getattr(form, formattr)()
+ if self.eidparam and self.role is not None:
+ if form._cw.vreg.schema.rschema(self.name).final:
+ return form.edited_entity.e_schema.default(self.name)
+ return ()
+ return None
+
def example_format(self, req):
"""return a sample string describing what can be given as input for this
field
@@ -183,58 +252,147 @@
widget = self.get_widget(form)
return widget.render(form, self, renderer)
- def vocabulary(self, form):
+ def vocabulary(self, form, **kwargs):
"""return vocabulary for this field. This method will be called by
- widgets which desire it."""
- if self.choices is not None:
- if callable(self.choices):
+ widgets which requires a vocabulary.
+ """
+ assert self.choices is not None
+ if callable(self.choices):
+ try:
+ if getattr(self.choices, 'im_self', None) is self:
+ vocab = self.choices(form=form, **kwargs)
+ else:
+ vocab = self.choices(form=form, field=self, **kwargs)
+ except TypeError:
try:
- vocab = self.choices(form=form)
+ vocab = self.choices(form=form, **kwargs)
+ warn('[3.6] %s: choices should now take '
+ 'the form and field as named arguments' % self,
+ DeprecationWarning)
except TypeError:
- warn('[3.3] vocabulary method (eg field.choices) should now take '
- 'the form instance as argument', DeprecationWarning)
- vocab = self.choices(req=form.req)
- else:
- vocab = self.choices
- if vocab and not isinstance(vocab[0], (list, tuple)):
- vocab = [(x, x) for x in vocab]
+ warn('[3.3] %s: choices should now take '
+ 'the form and field as named arguments' % self,
+ DeprecationWarning)
+ vocab = self.choices(req=form._cw, **kwargs)
else:
- vocab = form.form_field_vocabulary(self)
+ vocab = self.choices
+ if vocab and not isinstance(vocab[0], (list, tuple)):
+ vocab = [(x, x) for x in vocab]
if self.internationalizable:
# the short-cirtcuit 'and' boolean operator is used here to permit
# a valid empty string in vocabulary without attempting to translate
# it by gettext (which can lead to weird strings display)
- vocab = [(label and form.req._(label), value) for label, value in vocab]
+ vocab = [(label and form._cw._(label), value) for label, value in vocab]
if self.sort:
vocab = vocab_sort(vocab)
return vocab
+ def format(self, form):
+ """return MIME type used for the given (text or bytes) field"""
+ if self.eidparam and self.role == 'subject':
+ entity = form.edited_entity
+ if entity.e_schema.has_metadata(self.name, 'format') and (
+ entity.has_eid() or '%s_format' % self.name in entity):
+ return form.edited_entity.attr_metadata(self.name, 'format')
+ return form._cw.property_value('ui.default-text-format')
+
+ def encoding(self, form):
+ """return encoding used for the given (text) field"""
+ if self.eidparam:
+ entity = form.edited_entity
+ if entity.e_schema.has_metadata(self.name, 'encoding') and (
+ entity.has_eid() or '%s_encoding' % self.name in entity):
+ return form.edited_entity.attr_metadata(self.name, 'encoding')
+ return form._cw.encoding
+
def form_init(self, form):
"""method called before by build_context to trigger potential field
initialization requiring the form instance
"""
pass
+ def has_been_modified(self, form):
+ # fields not corresponding to an entity attribute / relations
+ # are considered modified
+ if not self.eidparam or not self.role or not form.edited_entity.has_eid():
+ return True # XXX
+ try:
+ if self.role == 'subject':
+ previous_value = getattr(form.edited_entity, self.name)
+ else:
+ previous_value = getattr(form.edited_entity,
+ 'reverse_%s' % self.name)
+ except AttributeError:
+ # fields with eidparam=True but not corresponding to an actual
+ # attribute or relation
+ return True
+ # if it's a non final relation, we need the eids
+ if isinstance(previous_value, tuple):
+ # widget should return a set of untyped eids
+ previous_value = set(unicode(e.eid) for e in previous_value)
+ try:
+ new_value = self.process_form_value(form)
+ except ProcessFormError:
+ return True
+ except UnmodifiedField:
+ return False
+ if previous_value == new_value:
+ return False # not modified
+ return True
+
+ def process_form_value(self, form):
+ """process posted form and return correctly typed value"""
+ try:
+ return form.formvalues[self]
+ except KeyError:
+ value = form.formvalues[self] = self._process_form_value(form)
+ return value
+
+ def _process_form_value(self, form):
+ widget = self.get_widget(form)
+ value = widget.process_field_data(form, self)
+ return self._ensure_correctly_typed(form, value)
+
+ def _ensure_correctly_typed(self, form, value):
+ """widget might to return date as a correctly formatted string or as
+ correctly typed objects, but process_for_value must return a typed value.
+ Override this method to type the value if necessary
+ """
+ return value or None
+
+ def process_posted(self, form):
+ for field in self.actual_fields(form):
+ if field is self:
+ try:
+ yield field, field.process_form_value(form)
+ except UnmodifiedField:
+ continue
+ else:
+ # recursive function: we might have compound fields
+ # of compound fields (of compound fields of ...)
+ for field, value in field.process_posted(form):
+ yield field, value
+
class StringField(Field):
- widget = TextArea
+ widget = fw.TextArea
size = 45
- def __init__(self, max_length=None, **kwargs):
+ def __init__(self, name=None, max_length=None, **kwargs):
self.max_length = max_length # must be set before super call
- super(StringField, self).__init__(**kwargs)
+ super(StringField, self).__init__(name=name, **kwargs)
def init_widget(self, widget):
if widget is None:
if self.choices:
- widget = Select()
+ widget = fw.Select()
elif self.max_length and self.max_length < 257:
- widget = TextInput()
+ widget = fw.TextInput()
super(StringField, self).init_widget(widget)
- if isinstance(self.widget, TextArea):
+ if isinstance(self.widget, fw.TextArea):
self.init_text_area(self.widget)
- elif isinstance(self.widget, TextInput):
+ elif isinstance(self.widget, fw.TextInput):
self.init_text_input(self.widget)
def init_text_input(self, widget):
@@ -248,6 +406,18 @@
widget.attrs.setdefault('rows', 5)
+class PasswordField(StringField):
+ widget = fw.PasswordInput
+
+ def typed_value(self, form, load_bytes=False):
+ if self.eidparam:
+ # no way to fetch actual password value with cw
+ if form.edited_entity.has_eid():
+ return INTERNAL_FIELD_VALUE
+ return self.initial_typed_value(form, load_bytes)
+ return super(PasswordField, self).typed_value(form, load_bytes)
+
+
class RichTextField(StringField):
widget = None
def __init__(self, format_field=None, **kwargs):
@@ -260,8 +430,8 @@
def get_widget(self, form):
if self.widget is None:
if self.use_fckeditor(form):
- return FCKEditor()
- widget = TextArea()
+ return fw.FCKEditor()
+ widget = fw.TextArea()
self.init_text_area(widget)
return widget
return self.widget
@@ -271,23 +441,24 @@
return self.format_field
# we have to cache generated field since it's use as key in the
# context dictionnary
- req = form.req
+ req = form._cw
try:
return req.data[self]
except KeyError:
- fkwargs = {'eidparam': self.eidparam}
+ fkwargs = {'eidparam': self.eidparam, 'role': self.role}
if self.use_fckeditor(form):
# if fckeditor is used and format field isn't explicitly
# deactivated, we want an hidden field for the format
- fkwargs['widget'] = HiddenInput()
- fkwargs['initial'] = 'text/html'
+ fkwargs['widget'] = fw.HiddenInput()
+ fkwargs['value'] = 'text/html'
else:
# else we want a format selector
- fkwargs['widget'] = Select()
+ fkwargs['widget'] = fw.Select()
fcstr = FormatConstraint()
fkwargs['choices'] = fcstr.vocabulary(form=form)
fkwargs['internationalizable'] = True
- fkwargs['initial'] = lambda f: f.form_field_format(self)
+ fkwargs['value'] = self.format
+ fkwargs['eidparam'] = self.eidparam
field = StringField(name=self.name + '_format', **fkwargs)
req.data[self] = field
return field
@@ -302,8 +473,8 @@
"""return True if fckeditor should be used to edit entity's attribute named
`attr`, according to user preferences
"""
- if form.req.use_fckeditor():
- return form.form_field_format(self) == 'text/html'
+ if form._cw.use_fckeditor():
+ return self.format(form) == 'text/html'
return False
def render(self, form, renderer):
@@ -319,7 +490,7 @@
class FileField(StringField):
- widget = FileInput
+ widget = fw.FileInput
needs_multipart = True
def __init__(self, format_field=None, encoding_field=None, name_field=None,
@@ -338,15 +509,28 @@
if self.name_field:
yield self.name_field
+ def typed_value(self, form, load_bytes=False):
+ if self.eidparam and self.role is not None:
+ if form.edited_entity.has_eid():
+ if load_bytes:
+ return getattr(form.edited_entity, self.name)
+ # don't actually load data
+ # XXX value should reflect if some file is already attached
+ # * try to display name metadata
+ # * check length(data) / data != null
+ return True
+ return False
+ return super(FileField, self).typed_value(form, load_bytes)
+
def render(self, form, renderer):
wdgs = [self.get_widget(form).render(form, self, renderer)]
if self.format_field or self.encoding_field:
- divid = '%s-advanced' % form.context[self]['name']
+ divid = '%s-advanced' % self.input_name(form)
wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
(xml_escape(uilib.toggle_action(divid)),
- form.req._('show advanced fields'),
- xml_escape(form.req.build_url('data/puce_down.png')),
- form.req._('show advanced fields')))
+ form._cw._('show advanced fields'),
+ xml_escape(form._cw.build_url('data/puce_down.png')),
+ form._cw._('show advanced fields')))
wdgs.append(u'<div id="%s" class="hidden">' % divid)
if self.name_field:
wdgs.append(self.render_subfield(form, self.name_field, renderer))
@@ -355,12 +539,12 @@
if self.encoding_field:
wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
wdgs.append(u'</div>')
- if not self.required and form.context[self]['value']:
+ if not self.required and self.typed_value(form):
# trick to be able to delete an uploaded file
wdgs.append(u'<br/>')
- wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'],
+ wdgs.append(tags.input(name=self.input_name(form, u'__detach'),
type=u'checkbox'))
- wdgs.append(form.req._('detach attached file'))
+ wdgs.append(form._cw._('detach attached file'))
return u'\n'.join(wdgs)
def render_subfield(self, form, field, renderer):
@@ -369,54 +553,94 @@
+ renderer.render_help(form, field)
+ u'<br/>')
+ def _process_form_value(self, form):
+ posted = form._cw.form
+ if self.input_name(form, u'__detach') in posted:
+ # drop current file value on explictily asked to detach
+ return None
+ try:
+ value = posted[self.input_name(form)]
+ except KeyError:
+ # raise UnmodifiedField instead of returning None, since the later
+ # will try to remove already attached file if any
+ raise UnmodifiedField()
+ # skip browser submitted mime type
+ filename, _, stream = value
+ # value is a 3-uple (filename, mimetype, stream)
+ value = Binary(stream.read())
+ if not value.getvalue(): # usually an unexistant file
+ value = None
+ else:
+ # set filename on the Binary instance, may be used later in hooks
+ value.filename = filename
+ return value
+
class EditableFileField(FileField):
editable_formats = ('text/plain', 'text/html', 'text/rest')
def render(self, form, renderer):
wdgs = [super(EditableFileField, self).render(form, renderer)]
- if form.form_field_format(self) in self.editable_formats:
- data = form.form_field_value(self, load_bytes=True)
+ if self.format(form) in self.editable_formats:
+ data = self.typed_value(form, load_bytes=True)
if data:
- encoding = form.form_field_encoding(self)
+ encoding = self.encoding(form)
try:
- form.context[self]['value'] = unicode(data.getvalue(), encoding)
+ form.formvalues[self] = unicode(data.getvalue(), encoding)
except UnicodeError:
pass
else:
if not self.required:
- msg = form.req._(
+ msg = form._cw._(
'You can either submit a new file using the browse button above'
', or choose to remove already uploaded file by checking the '
'"detach attached file" check-box, or edit file content online '
'with the widget below.')
else:
- msg = form.req._(
+ msg = form._cw._(
'You can either submit a new file using the browse button above'
', or edit file content online with the widget below.')
wdgs.append(u'<p><b>%s</b></p>' % msg)
- wdgs.append(TextArea(setdomid=False).render(form, self, renderer))
+ wdgs.append(fw.TextArea(setdomid=False).render(form, self, renderer))
# XXX restore form context?
return '\n'.join(wdgs)
+ def _process_form_value(self, form):
+ value = form._cw.form.get(self.input_name(form))
+ if isinstance(value, unicode):
+ # file modified using a text widget
+ return Binary(value.encode(self.encoding(form)))
+ return super(EditableFileField, self)._process_form_value(form)
+
class IntField(Field):
def __init__(self, min=None, max=None, **kwargs):
super(IntField, self).__init__(**kwargs)
self.min = min
self.max = max
- if isinstance(self.widget, TextInput):
+ if isinstance(self.widget, fw.TextInput):
self.widget.attrs.setdefault('size', 5)
self.widget.attrs.setdefault('maxlength', 15)
+ def _ensure_correctly_typed(self, form, value):
+ if isinstance(value, basestring):
+ try:
+ return int(value)
+ except ValueError:
+ raise ProcessFormError(form._cw._('an integer is expected'))
+ return value
+
class BooleanField(Field):
- widget = Radio
+ widget = fw.Radio
def vocabulary(self, form):
if self.choices:
return super(BooleanField, self).vocabulary(form)
- return [(form.req._('yes'), '1'), (form.req._('no'), '')]
+ return [(form._cw._('yes'), '1'), (form._cw._('no'), '')]
+
+ def _ensure_correctly_typed(self, form, value):
+ return bool(value)
class FloatField(IntField):
@@ -429,81 +653,175 @@
def render_example(self, req):
return self.format_single_value(req, 1.234)
+ def _ensure_correctly_typed(self, form, value):
+ if isinstance(value, basestring):
+ try:
+ return float(value)
+ except ValueError:
+ raise ProcessFormError(form._cw._('a float is expected'))
+ return None
+
class DateField(StringField):
+ widget = fw.JQueryDatePicker
format_prop = 'ui.date-format'
- widget = DateTimePicker
+ etype = 'Date'
def format_single_value(self, req, value):
- return value and ustrftime(value, req.property_value(self.format_prop)) or u''
+ if value:
+ return ustrftime(value, req.property_value(self.format_prop))
+ return u''
def render_example(self, req):
return self.format_single_value(req, datetime.now())
+ def _ensure_correctly_typed(self, form, value):
+ if isinstance(value, basestring):
+ try:
+ value = form._cw.parse_datetime(value, self.etype)
+ except ValueError, ex:
+ raise ProcessFormError(unicode(ex))
+ return value
+
class DateTimeField(DateField):
+ widget = fw.JQueryDateTimePicker
format_prop = 'ui.datetime-format'
+ etype = 'Datetime'
class TimeField(DateField):
+ widget = fw.JQueryTimePicker
format_prop = 'ui.time-format'
- widget = TextInput
+ etype = 'Time'
-class HiddenInitialValueField(Field):
- def __init__(self, visible_field):
- name = 'edit%s-%s' % (visible_field.role[0], visible_field.name)
- super(HiddenInitialValueField, self).__init__(
- name=name, widget=HiddenInput, eidparam=True)
- self.visible_field = visible_field
+# relation vocabulary helper functions #########################################
+
+def relvoc_linkedto(entity, rtype, role):
+ # first see if its specified by __linkto form parameters
+ linkedto = entity.linked_to(rtype, role)
+ if linkedto:
+ buildent = entity._cw.entity_from_eid
+ return [(buildent(eid).view('combobox'), eid) for eid in linkedto]
+ return []
+
+def relvoc_init(entity, rtype, role, required=False):
+ # it isn't, check if the entity provides a method to get correct values
+ vocab = []
+ if not required:
+ vocab.append(('', INTERNAL_FIELD_VALUE))
+ # vocabulary doesn't include current values, add them
+ if entity.has_eid():
+ rset = entity.related(rtype, role)
+ vocab += [(e.view('combobox'), e.eid) for e in rset.entities()]
+ return vocab
- def format_single_value(self, req, value):
- return self.visible_field.format_single_value(req, value)
+def relvoc_unrelated(entity, rtype, role, limit=None):
+ if isinstance(rtype, basestring):
+ rtype = entity._cw.vreg.schema.rschema(rtype)
+ if entity.has_eid():
+ done = set(row[0] for row in entity.related(rtype, role))
+ else:
+ done = None
+ result = []
+ rsetsize = None
+ for objtype in rtype.targets(entity.e_schema, role):
+ if limit is not None:
+ rsetsize = limit - len(result)
+ result += _relvoc_unrelated(entity, rtype, objtype, role, rsetsize, done)
+ if limit is not None and len(result) >= limit:
+ break
+ return result
+
+def _relvoc_unrelated(entity, rtype, targettype, role, limit, done):
+ """return unrelated entities for a given relation and target entity type
+ for use in vocabulary
+ """
+ if done is None:
+ done = set()
+ res = []
+ for entity in entity.unrelated(rtype, targettype, role, limit).entities():
+ if entity.eid in done:
+ continue
+ done.add(entity.eid)
+ res.append((entity.view('combobox'), entity.eid))
+ return res
class RelationField(Field):
- # XXX (syt): iirc, we originaly don't sort relation vocabulary since we want
- # to let entity.unrelated_rql control this, usually to get most recently
- # modified entities in the select box instead of by alphabetical order. Now,
- # we first use unrelated_rql to get the vocabulary, which may be limited
- # (hence we get the latest modified entities) and we can sort here for
- # better readability
- #
- # def __init__(self, **kwargs):
- # kwargs.setdefault('sort', False)
- # super(RelationField, self).__init__(**kwargs)
+ """the relation field to edit non final relations of an entity"""
@staticmethod
def fromcardinality(card, **kwargs):
- kwargs.setdefault('widget', Select(multiple=card in '*+'))
+ kwargs.setdefault('widget', fw.Select(multiple=card in '*+'))
return RelationField(**kwargs)
- def vocabulary(self, form):
+ def choices(self, form, limit=None):
+ """Take care, choices function for relation field instance should take
+ an extra 'limit' argument, with default to None.
+
+ This argument is used by the 'unrelateddivs' view (see in autoform) and
+ when it's specified (eg not None), vocabulary returned should:
+ * not include already related entities
+ * have a max size of `limit` entities
+ """
entity = form.edited_entity
- req = entity.req
# first see if its specified by __linkto form parameters
- linkedto = entity.linked_to(self.name, self.role)
- if linkedto:
- entities = (req.entity_from_eid(eid) for eid in linkedto)
- return [(entity.view('combobox'), entity.eid) for entity in entities]
+ if limit is None:
+ linkedto = relvoc_linkedto(entity, self.name, self.role)
+ if linkedto:
+ return linkedto
+ vocab = relvoc_init(entity, self.name, self.role, self.required)
+ else:
+ vocab = []
# it isn't, check if the entity provides a method to get correct values
- res = []
- if not self.required:
- res.append(('', INTERNAL_FIELD_VALUE))
- # vocabulary doesn't include current values, add them
- if entity.has_eid():
- rset = entity.related(self.name, self.role)
- relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()]
- else:
- relatedvocab = []
- vocab = res + form.form_field_vocabulary(self) + relatedvocab
+ method = '%s_%s_vocabulary' % (self.role, self.name)
+ try:
+ vocab += getattr(form, method)(self.name, limit)
+ warn('[3.6] found %s on %s, should override field.choices instead (need tweaks)'
+ % (method, form), DeprecationWarning)
+ except AttributeError:
+ vocab += relvoc_unrelated(entity, self.name, self.role, limit)
if self.sort:
vocab = vocab_sort(vocab)
return vocab
+ def form_init(self, form):
+ #if not self.display_value(form):
+ value = form.edited_entity.linked_to(self.name, self.role)
+ if value:
+ searchedvalues = ['%s:%s:%s' % (self.name, eid, self.role)
+ for eid in value]
+ # remove associated __linkto hidden fields
+ for field in form.root_form.fields_by_name('__linkto'):
+ if field.value in searchedvalues:
+ form.root_form.remove_field(field)
+ form.formvalues[self] = value
+
def format_single_value(self, req, value):
return value
+ def _process_form_value(self, form):
+ """process posted form and return correctly typed value"""
+ widget = self.get_widget(form)
+ values = widget.process_field_data(form, self)
+ if values is None:
+ values = ()
+ elif not isinstance(values, list):
+ values = (values,)
+ eids = set()
+ for eid in values:
+ # XXX 'not eid' for AutoCompletionWidget, deal with this in the widget
+ if not eid or eid == INTERNAL_FIELD_VALUE:
+ continue
+ typed_eid = form.actual_eid(eid)
+ if typed_eid is None:
+ form._cw.data['pendingfields'].add( (form, self) )
+ return None
+ eids.add(typed_eid)
+ return eids
+
class CompoundField(Field):
def __init__(self, fields, *args, **kwargs):
@@ -522,62 +840,53 @@
'subjschema rschema objschema' according to information found in the schema
"""
fieldclass = None
- card = eschema.cardinality(rschema, role)
+ rdef = eschema.rdef(rschema, role)
if role == 'subject':
- targetschema = rschema.objects(eschema)[0]
- help = rschema.rproperty(eschema, targetschema, 'description')
+ targetschema = rdef.object
if rschema.final:
- if rschema.rproperty(eschema, targetschema, 'internationalizable'):
+ if rdef.get('internationalizable'):
kwargs.setdefault('internationalizable', True)
- def get_default(form, es=eschema, rs=rschema):
- return es.default(rs)
- kwargs.setdefault('initial', get_default)
else:
- targetschema = rschema.subjects(eschema)[0]
- help = rschema.rproperty(targetschema, eschema, 'description')
+ targetschema = rdef.subject
+ card = rdef.role_cardinality(role)
kwargs['required'] = card in '1+'
kwargs['name'] = rschema.type
+ kwargs['role'] = role
if role == 'object':
kwargs.setdefault('label', (eschema.type, rschema.type + '_object'))
else:
kwargs.setdefault('label', (eschema.type, rschema.type))
- kwargs.setdefault('help', help)
+ kwargs['eidparam'] = True
+ kwargs.setdefault('help', rdef.description)
if rschema.final:
if skip_meta_attr and rschema in eschema.meta_attributes():
return None
fieldclass = FIELDS[targetschema]
if fieldclass is StringField:
- if targetschema == 'Password':
- # special case for Password field: specific PasswordInput widget
- kwargs.setdefault('widget', PasswordInput())
- return StringField(**kwargs)
if eschema.has_metadata(rschema, 'format'):
# use RichTextField instead of StringField if the attribute has
# a "format" metadata. But getting information from constraints
# may be useful anyway...
- constraints = rschema.rproperty(eschema, targetschema, 'constraints')
- for cstr in constraints:
+ for cstr in rdef.constraints:
if isinstance(cstr, StaticVocabularyConstraint):
raise Exception('rich text field with static vocabulary')
return RichTextField(**kwargs)
- constraints = rschema.rproperty(eschema, targetschema, 'constraints')
# init StringField parameters according to constraints
- for cstr in constraints:
+ for cstr in rdef.constraints:
if isinstance(cstr, StaticVocabularyConstraint):
kwargs.setdefault('choices', cstr.vocabulary)
break
- for cstr in constraints:
+ for cstr in rdef.constraints:
if isinstance(cstr, SizeConstraint) and cstr.max is not None:
kwargs['max_length'] = cstr.max
return StringField(**kwargs)
if fieldclass is FileField:
- for metadata in ('format', 'encoding', 'name'):
+ for metadata in KNOWN_METAATTRIBUTES:
metaschema = eschema.has_metadata(rschema, metadata)
if metaschema is not None:
kwargs['%s_field' % metadata] = guess_field(eschema, metaschema,
skip_meta_attr=False)
return fieldclass(**kwargs)
- kwargs['role'] = role
return RelationField.fromcardinality(card, **kwargs)
@@ -589,7 +898,7 @@
'Int': IntField,
'Float': FloatField,
'Decimal': StringField,
- 'Password': StringField,
+ 'Password': PasswordField,
'String' : StringField,
'Time': TimeField,
}
--- a/web/formwidgets.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/formwidgets.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,7 +1,7 @@
"""widget classes for form construction
:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
@@ -10,10 +10,12 @@
from datetime import date
from warnings import warn
-from cubicweb.common import tags, uilib
-from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
+from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import deprecated
+from logilab.common.date import todatetime
-from logilab.mtconverter import xml_escape
+from cubicweb import tags, uilib
+from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
class FieldWidget(object):
"""abstract widget class"""
@@ -23,10 +25,12 @@
# automatically set id and tabindex attributes ?
setdomid = True
settabindex = True
+ # to ease usage as a sub-widgets (eg widget used by another widget)
+ suffix = None
# does this widget expect a vocabulary
vocabulary_widget = False
- def __init__(self, attrs=None, setdomid=None, settabindex=None):
+ def __init__(self, attrs=None, setdomid=None, settabindex=None, suffix=None):
if attrs is None:
attrs = {}
self.attrs = attrs
@@ -36,50 +40,108 @@
if settabindex is not None:
# override class's default value
self.settabindex = settabindex
+ if suffix is not None:
+ self.suffix = suffix
def add_media(self, form):
"""adds media (CSS & JS) required by this widget"""
if self.needs_js:
- form.req.add_js(self.needs_js)
+ form._cw.add_js(self.needs_js)
if self.needs_css:
- form.req.add_css(self.needs_css)
+ form._cw.add_css(self.needs_css)
+
+
+ def render(self, form, field, renderer=None):
+ self.add_media(form)
+ return self._render(form, field, renderer)
+
+ def _render(self, form, field, renderer):
+ raise NotImplementedError()
+
+ def typed_value(self, form, field):
+ """return field's *typed* value specified in:
+ 3. extra form values given to render()
+ 4. field's typed value
+ """
+ qname = field.input_name(form)
+ for key in (field, qname):
+ try:
+ return form.formvalues[key]
+ except KeyError:
+ continue
+ if field.name != qname and field.name in form.formvalues:
+ return form.formvalues[field.name]
+ return field.typed_value(form)
+
+ def format_value(self, form, field, value):
+ return field.format_value(form._cw, value)
+
+ def values_and_attributes(self, form, field):
+ """found field's *string* value in:
+ 1. previously submitted form values if any (eg on validation error)
+ 2. req.form
+ 3. extra form values given to render()
+ 4. field's typed value
- def render(self, form, field, renderer):
- """render the widget for the given `field` of `form`.
- To override in concrete class
+ values found in 1. and 2. are expected te be already some 'display'
+ value while those found in 3. and 4. are expected to be correctly typed.
+
+ 3 and 4 are handle by the .typed_value(form, field) method
"""
- raise NotImplementedError
+ attrs = dict(self.attrs)
+ if self.setdomid:
+ attrs['id'] = field.dom_id(form, self.suffix)
+ if self.settabindex and not 'tabindex' in attrs:
+ attrs['tabindex'] = form._cw.next_tabindex()
+ return self.values(form, field), attrs
+ def values(self, form, field):
+ qname = field.input_name(form, self.suffix)
+ if qname in form.form_previous_values:
+ values = form.form_previous_values[qname]
+ elif qname in form._cw.form:
+ values = form._cw.form[qname]
+ elif field.name != qname and field.name in form._cw.form:
+ # compat: accept attr=value in req.form to specify value of attr-subject
+ values = form._cw.form[field.name]
+ else:
+ values = self.typed_value(form, field)
+ if values != INTERNAL_FIELD_VALUE:
+ values = self.format_value(form, field, values)
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ return values
+
+ def process_field_data(self, form, field):
+ posted = form._cw.form
+ val = posted.get(field.input_name(form, self.suffix))
+ if isinstance(val, basestring):
+ val = val.strip()
+ return val
+
+ @deprecated('[3.6] use values_and_attributes')
def _render_attrs(self, form, field):
"""return html tag name, attributes and a list of values for the field
"""
- name = form.context[field]['name']
- values = form.context[field]['value']
- if not isinstance(values, (tuple, list)):
- values = (values,)
- attrs = dict(self.attrs)
- if self.setdomid:
- attrs['id'] = form.context[field]['id']
- if self.settabindex and not 'tabindex' in attrs:
- attrs['tabindex'] = form.req.next_tabindex()
- return name, values, attrs
+ values, attrs = self.values_and_attributes(form, field)
+ return field.input_name(form, self.suffix), values, attrs
class Input(FieldWidget):
"""abstract widget class for <input> tag based widgets"""
type = None
- def render(self, form, field, renderer):
+ def _render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
Generate one <input> tag for each field's value
"""
- self.add_media(form)
- name, values, attrs = self._render_attrs(form, field)
+ values, attrs = self.values_and_attributes(form, field)
# ensure something is rendered
if not values:
values = (INTERNAL_FIELD_VALUE,)
- inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
+ inputs = [tags.input(name=field.input_name(form, self.suffix),
+ type=self.type, value=value, **attrs)
for value in values]
return u'\n'.join(inputs)
@@ -97,38 +159,49 @@
"""
type = 'password'
- def render(self, form, field, renderer):
- self.add_media(form)
- name, values, attrs = self._render_attrs(form, field)
+ def _render(self, form, field, renderer):
+ assert self.suffix is None, 'suffix not supported'
+ values, attrs = self.values_and_attributes(form, field)
assert len(values) == 1
id = attrs.pop('id')
- try:
- confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
- except TypeError:
- confirmname = '%s-confirm' % name
- inputs = [tags.input(name=name, value=values[0], type=self.type, id=id,
- **attrs),
+ inputs = [tags.input(name=field.input_name(form),
+ value=values[0], type=self.type, id=id, **attrs),
'<br/>',
- tags.input(name=confirmname, value=values[0], type=self.type,
- **attrs),
- ' ', tags.span(form.req._('confirm password'),
+ tags.input(name=field.input_name(form, '-confirm'),
+ value=values[0], type=self.type, **attrs),
+ ' ', tags.span(form._cw._('confirm password'),
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
+ def process_field_data(self, form, field):
+ passwd1 = super(PasswordInput, self).process_field_data(form, field)
+ passwd2 = form._cw.form.get(field.input_name(form, '-confirm'))
+ if passwd1 == passwd2:
+ if passwd1 is None:
+ return None
+ return passwd1.encode('utf-8')
+ raise ProcessFormError(form._cw._("password and confirmation don't match"))
+
class PasswordSingleInput(Input):
"""<input type='password'> without a confirmation field"""
type = 'password'
+ def process_field_data(self, form, field):
+ value = super(PasswordSingleInput, self).process_field_data(form, field)
+ if value is not None:
+ return value.encode('utf-8')
+ return value
+
class FileInput(Input):
"""<input type='file'>"""
type = 'file'
- def _render_attrs(self, form, field):
+ def values_and_attributes(self, form, field):
# ignore value which makes no sense here (XXX even on form validation error?)
- name, values, attrs = super(FileInput, self)._render_attrs(form, field)
- return name, ('',), attrs
+ values, attrs = super(FileInput, self).values_and_attributes(form, field)
+ return ('',), attrs
class HiddenInput(Input):
@@ -150,8 +223,8 @@
class TextArea(FieldWidget):
"""<textarea>"""
- def render(self, form, field, renderer):
- name, values, attrs = self._render_attrs(form, field)
+ def _render(self, form, field, renderer):
+ values, attrs = self.values_and_attributes(form, field)
attrs.setdefault('onkeyup', 'autogrow(this)')
if not values:
value = u''
@@ -165,7 +238,8 @@
linecount += len(line) / 80
attrs.setdefault('cols', 80)
attrs.setdefault('rows', min(15, linecount + 2))
- return tags.textarea(value, name=name, **attrs)
+ return tags.textarea(value, name=field.input_name(form, self.suffix),
+ **attrs)
class FCKEditor(TextArea):
@@ -174,9 +248,9 @@
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
- def render(self, form, field, renderer):
- form.req.fckeditor_config()
- return super(FCKEditor, self).render(form, field, renderer)
+ def _render(self, form, field, renderer):
+ form._cw.fckeditor_config()
+ return super(FCKEditor, self)._render(form, field, renderer)
class Select(FieldWidget):
@@ -188,7 +262,7 @@
self._multiple = multiple
def render(self, form, field, renderer):
- name, curvalues, attrs = self._render_attrs(form, field)
+ curvalues, attrs = self.values_and_attributes(form, field)
if not 'size' in attrs:
attrs['size'] = self._multiple and '5' or '1'
options = []
@@ -213,8 +287,8 @@
options.append(tags.option(label, value=value, **oattrs))
if optgroup_opened:
options.append(u'</optgroup>')
- return tags.select(name=name, multiple=self._multiple,
- options=options, **attrs)
+ return tags.select(name=field.input_name(form, self.suffix),
+ multiple=self._multiple, options=options, **attrs)
class CheckBox(Input):
@@ -225,7 +299,7 @@
vocabulary_widget = True
def render(self, form, field, renderer):
- name, curvalues, attrs = self._render_attrs(form, field)
+ curvalues, attrs = self.values_and_attributes(form, field)
domid = attrs.pop('id', None)
sep = attrs.pop('separator', u'<br/>\n')
options = []
@@ -241,7 +315,8 @@
iattrs.setdefault('id', domid)
if value in curvalues:
iattrs['checked'] = u'checked'
- tag = tags.input(name=name, type=self.type, value=value, **iattrs)
+ tag = tags.input(name=field.input_name(form, self.suffix),
+ type=self.type, value=value, **iattrs)
options.append(tag + label)
return sep.join(options)
@@ -276,9 +351,9 @@
actual_fields = field.fields
assert len(actual_fields) == 2
return u'<div>%s %s %s %s</div>' % (
- form.req._('from_interval_start'),
+ form._cw._('from_interval_start'),
actual_fields[0].render(form, renderer),
- form.req._('to_interval_end'),
+ form._cw._('to_interval_end'),
actual_fields[1].render(form, renderer),
)
@@ -316,7 +391,7 @@
@classmethod
def add_localized_infos(cls, req):
"""inserts JS variables defining localized months and days"""
- # import here to avoid dependancy from cubicweb-common to simplejson
+ # import here to avoid dependancy from cubicweb to simplejson
_ = req._
monthnames = [_(mname) for mname in cls.monthnames]
daynames = [_(dname) for dname in cls.daynames]
@@ -325,23 +400,120 @@
def render(self, form, field, renderer):
txtwidget = super(DateTimePicker, self).render(form, field, renderer)
- self.add_localized_infos(form.req)
+ self.add_localized_infos(form._cw)
cal_button = self._render_calendar_popup(form, field)
return txtwidget + cal_button
def _render_calendar_popup(self, form, field):
- value = form.form_field_value(field)
+ value = field.typed_value(form)
if not value:
value = date.today()
- inputid = form.context[field]['id']
+ inputid = field.dom_id(form)
helperid = '%shelper' % inputid
year, month = value.year, value.month
return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
<img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
% (helperid, inputid, year, month,
- form.req.external_resource('CALENDAR_ICON'),
- form.req._('calendar'), helperid) )
+ form._cw.external_resource('CALENDAR_ICON'),
+ form._cw._('calendar'), helperid) )
+
+
+class JQueryDatePicker(FieldWidget):
+ """use jquery.ui.datepicker to define a date time picker"""
+ needs_js = ('jquery.ui.js', )
+ needs_css = ('jquery.ui.css',)
+
+ def __init__(self, datestr=None, **kwargs):
+ super(JQueryDatePicker, self).__init__(**kwargs)
+ self.datestr = datestr
+
+ def _render(self, form, field, renderer):
+ req = form._cw
+ domid = field.dom_id(form, self.suffix)
+ # XXX find a way to understand every format
+ fmt = req.property_value('ui.date-format')
+ fmt = fmt.replace('%Y', 'yy').replace('%m', 'mm').replace('%d', 'dd')
+ req.add_onload(u'jqNode("%s").datepicker('
+ '{buttonImage: "%s", dateFormat: "%s", firstDay: 1,'
+ ' showOn: "button", buttonImageOnly: true})' % (
+ domid, req.external_resource('CALENDAR_ICON'), fmt))
+ if self.datestr is None:
+ value = self.values(form, field)[0]
+ else:
+ value = self.datestr
+ return tags.input(id=domid, name=domid, value=value,
+ type='text', size='10')
+
+
+class JQueryTimePicker(FieldWidget):
+ """use jquery.timePicker.js to define a js time picker"""
+ needs_js = ('jquery.timePicker.js',)
+ needs_css = ('jquery.timepicker.css',)
+
+ def __init__(self, timestr=None, timesteps=30, **kwargs):
+ super(JQueryTimePicker, self).__init__(**kwargs)
+ self.timestr = timestr
+ self.timesteps = timesteps
+ def _render(self, form, field, renderer):
+ req = form._cw
+ domid = field.dom_id(form, self.suffix)
+ req.add_onload(u'jqNode("%s").timePicker({selectedTime: "%s", step: %s})' % (
+ domid, self.timestr, self.timesteps))
+ if self.timestr is None:
+ value = self.values(form, field)[0]
+ else:
+ value = self.timestr
+ return tags.input(id=domid, name=domid, value=value,
+ type='text', size='5')
+
+
+class JQueryDateTimePicker(FieldWidget):
+ def __init__(self, initialtime=None, timesteps=15, **kwargs):
+ super(JQueryDateTimePicker, self).__init__(**kwargs)
+ self.initialtime = initialtime
+ self.timesteps = timesteps
+
+ def _render(self, form, field, renderer):
+ """render the widget for the given `field` of `form`.
+
+ Generate one <input> tag for each field's value
+ """
+ req = form._cw
+ dateqname = field.input_name(form, 'date')
+ timeqname = field.input_name(form, 'time')
+ if dateqname in form.form_previous_values:
+ datestr = form.form_previous_values[dateqname]
+ timestr = form.form_previous_values[timeqname]
+ else:
+ datestr = timestr = u''
+ if field.name in req.form:
+ value = req.parse_datetime(req.form[field.name])
+ else:
+ value = self.typed_value(form, field)
+ if value:
+ datestr = req.format_date(value)
+ timestr = req.format_time(value)
+ elif self.initialtime:
+ timestr = req.format_time(self.initialtime)
+ datepicker = JQueryDatePicker(datestr=datestr, suffix='date')
+ timepicker = JQueryTimePicker(timestr=timestr, timesteps=self.timesteps,
+ suffix='time')
+ return u'<div id="%s">%s%s</div>' % (field.dom_id(form),
+ datepicker.render(form, field),
+ timepicker.render(form, field))
+
+ def process_field_data(self, form, field):
+ req = form._cw
+ datestr = req.form.get(field.input_name(form, 'date')).strip() or None
+ timestr = req.form.get(field.input_name(form, 'time')).strip() or None
+ if datestr is None:
+ return None
+ date = todatetime(req.parse_datetime(datestr, 'Date'))
+ if timestr is None:
+ return date
+ time = req.parse_datetime(timestr, 'Time')
+ return date.replace(hour=time.hour, minute=time.minute, second=time.second)
# ajax widgets ################################################################
@@ -363,9 +535,8 @@
if inputid is not None:
self.attrs['cubicweb:inputid'] = inputid
- def render(self, form, field, renderer):
- self.add_media(form)
- attrs = self._render_attrs(form, field)[-1]
+ def _render(self, form, field, renderer):
+ attrs = self.values_and_attributes(form, field)[-1]
return tags.div(**attrs)
@@ -389,14 +560,14 @@
self.autocomplete_initfunc = None
super(AutoCompletionWidget, self).__init__(*args, **kwargs)
- def _render_attrs(self, form, field):
- name, values, attrs = super(AutoCompletionWidget, self)._render_attrs(form, field)
+ def values_and_attributes(self, form, field):
+ values, attrs = super(AutoCompletionWidget, self).values_and_attributes(form, field)
init_ajax_attributes(attrs, self.wdgtype, self.loadtype)
# XXX entity form specific
attrs['cubicweb:dataurl'] = self._get_url(form.edited_entity, field)
if not values:
values = ('',)
- return name, values, attrs
+ return values, attrs
def _get_url(self, entity, field):
if self.autocomplete_initfunc is None:
@@ -404,8 +575,8 @@
fname = entity.autocomplete_initfuncs[field.name]
else:
fname = self.autocomplete_initfunc
- return entity.req.build_url('json', fname=fname, mode='remote',
- pageid=entity.req.pageid)
+ return entity._cw.build_url('json', fname=fname, mode='remote',
+ pageid=entity._cw.pageid)
class StaticFileAutoCompletionWidget(AutoCompletionWidget):
@@ -418,7 +589,7 @@
fname = entity.autocomplete_initfuncs[field.name]
else:
fname = self.autocomplete_initfunc
- return entity.req.datadir_url + fname
+ return entity._cw.datadir_url + fname
class RestrictedAutoCompletionWidget(AutoCompletionWidget):
@@ -427,15 +598,15 @@
class AddComboBoxWidget(Select):
- def _render_attrs(self, form, field):
- name, values, attrs = super(AddComboBoxWidget, self)._render_attrs(form, field)
+ def values_and_attributes(self, form, field):
+ values, attrs = super(AddComboBoxWidget, self).values_and_attributes(form, field)
init_ajax_attributes(self.attrs, 'AddComboBox')
# XXX entity form specific
entity = form.edited_entity
attrs['cubicweb:etype_to'] = entity.e_schema
etype_from = entity.e_schema.subjrels[field.name].objects(entity.e_schema)[0]
attrs['cubicweb:etype_from'] = etype_from
- return name, values, attrs
+ return values, attrs
def render(self, form, field, renderer):
return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
@@ -470,7 +641,7 @@
self.attrs.setdefault('klass', 'validateButton')
def render(self, form, field=None, renderer=None):
- label = form.req._(self.label)
+ label = form._cw._(self.label)
attrs = self.attrs.copy()
if self.cwaction:
assert self.onclick is None
@@ -483,9 +654,9 @@
if self.setdomid:
attrs['id'] = self.name
if self.settabindex and not 'tabindex' in attrs:
- attrs['tabindex'] = form.req.next_tabindex()
+ attrs['tabindex'] = form._cw.next_tabindex()
if self.icon:
- img = tags.img(src=form.req.external_resource(self.icon), alt=self.icon)
+ img = tags.img(src=form._cw.external_resource(self.icon), alt=self.icon)
else:
img = u''
return tags.button(img + xml_escape(label), escapecontent=False,
@@ -518,13 +689,88 @@
self.label = label
def render(self, form, field=None, renderer=None):
- label = form.req._(self.label)
- imgsrc = form.req.external_resource(self.imgressource)
+ label = form._cw._(self.label)
+ imgsrc = form._cw.external_resource(self.imgressource)
return '<a id="%(domid)s" href="%(href)s">'\
'<img src="%(imgsrc)s" alt="%(label)s"/>%(label)s</a>' % {
'label': label, 'imgsrc': imgsrc,
'domid': self.domid, 'href': self.href}
+# more widgets #################################################################
-# XXX EntityLinkComboBoxWidget, [Raw]DynamicComboBoxWidget
+class EditableURLWidget(FieldWidget):
+ """custom widget to edit separatly an url path / query string (used by
+ default for Bookmark.path for instance), dealing with url quoting nicely
+ (eg user edit the unquoted value).
+ """
+
+ def _render(self, form, field, renderer):
+ """render the widget for the given `field` of `form`.
+
+ Generate one <input> tag for each field's value
+ """
+ assert self.suffix is None, 'not supported'
+ req = form._cw
+ pathqname = field.input_name(form, 'path')
+ fqsqname = field.input_name(form, 'fqs') # formatted query string
+ if pathqname in form.form_previous_values:
+ path = form.form_previous_values[pathqname]
+ fqs = form.form_previous_values[fqsqname]
+ else:
+ if field.name in req.form:
+ value = req.form[field.name]
+ else:
+ value = self.typed_value(form, field)
+ try:
+ path, qs = value.split('?', 1)
+ except ValueError:
+ path = value
+ qs = ''
+ fqs = u'\n'.join(u'%s=%s' % (k, v) for k, v in req.url_parse_qsl(qs))
+ attrs = dict(self.attrs)
+ if self.setdomid:
+ attrs['id'] = field.dom_id(form)
+ if self.settabindex and not 'tabindex' in attrs:
+ attrs['tabindex'] = req.next_tabindex()
+ # ensure something is rendered
+ inputs = [u'<table><tr><th>',
+ req._('i18n_bookmark_url_path'),
+ u'</th><td>',
+ tags.input(name=pathqname, type='string', value=path, **attrs),
+ u'</td></tr><tr><th>',
+ req._('i18n_bookmark_url_fqs'),
+ u'</th><td>']
+ if self.setdomid:
+ attrs['id'] = field.dom_id(form, 'fqs')
+ if self.settabindex:
+ attrs['tabindex'] = req.next_tabindex()
+ attrs.setdefault('onkeyup', 'autogrow(this)')
+ inputs += [tags.textarea(fqs, name=fqsqname, **attrs),
+ u'</td></tr></table>']
+ # surrounding div necessary for proper error localization
+ return u'<div id="%s">%s%s</div>' % (
+ field.dom_id(form), u'\n'.join(inputs))
+
+ def process_field_data(self, form, field):
+ req = form._cw
+ values = {}
+ path = req.form.get(field.input_name(form, 'path'))
+ if isinstance(path, basestring):
+ path = path.strip() or None
+ fqs = req.form.get(field.input_name(form, 'fqs'))
+ if isinstance(fqs, basestring):
+ fqs = fqs.strip() or None
+ if fqs:
+ for i, line in enumerate(fqs.split('\n')):
+ line = line.strip()
+ if line:
+ try:
+ key, val = line.split('=', 1)
+ except ValueError:
+ raise ProcessFormError(req._("wrong query parameter line %s") % (i+1))
+ # value will be url quoted by build_url_params
+ values.setdefault(key.encode(req.encoding), []).append(val)
+ if not values:
+ return path
+ return u'%s?%s' % (path, req.build_url_params(**values))
--- a/web/htmlwidgets.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/htmlwidgets.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,6 +1,6 @@
"""html widgets
-those are in cubicweb.common since we need to know available widgets at schema
+those are in cubicweb since we need to know available widgets at schema
serialization time
:organization: Logilab
@@ -9,10 +9,13 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from math import floor
+import random
+
from logilab.mtconverter import xml_escape
from cubicweb.utils import UStringIO
-from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
# XXX HTMLWidgets should have access to req (for datadir / static urls,
@@ -288,8 +291,8 @@
When using remember to include the required css and js with:
- self.req.add_js('jquery.tablesorter.js')
- self.req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
+ self._cw.add_js('jquery.tablesorter.js')
+ self._cw.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
"""
highlight = "onmouseover=\"addElementClass(this, 'highlighted');\" " \
"onmouseout=\"removeElementClass(this, 'highlighted');\""
@@ -335,27 +338,90 @@
class ProgressBarWidget(HTMLWidget):
"""display a progress bar widget"""
+ precision = 0.1
+ red_threshold = 1.1
+ orange_threshold = 1.05
+ yellow_threshold = 1
+
def __init__(self, done, todo, total):
self.done = done
self.todo = todo
- self.total = total
+ self.budget = total
+
+ @property
+ def overrun(self):
+ """overrun = done + todo - """
+ if self.done + self.todo > self.budget:
+ overrun = self.done + self.todo - self.budget
+ else:
+ overrun = 0
+ if overrun < self.precision:
+ overrun = 0
+ return overrun
+
+ @property
+ def overrun_percentage(self):
+ """pourcentage overrun = overrun / budget"""
+ if self.budget == 0:
+ return 0
+ else:
+ return self.overrun * 100. / self.budget
def _render(self):
- try:
- percent = self.done*100./self.total
- except ZeroDivisionError:
- percent = 0
- real_percent = percent
- if percent > 100 :
- color = 'done'
- percent = 100
- elif self.todo + self.done > self.total :
- color = 'overpassed'
+ done = self.done
+ todo = self.todo
+ budget = self.budget
+ if budget == 0:
+ pourcent = 100
+ todo_pourcent = 0
+ else:
+ pourcent = done*100./budget
+ todo_pourcent = min(todo*100./budget, 100-pourcent)
+ bar_pourcent = pourcent
+ if pourcent > 100.1:
+ color = 'red'
+ bar_pourcent = 100
+ elif todo+done > self.red_threshold*budget:
+ color = 'red'
+ elif todo+done > self.orange_threshold*budget:
+ color = 'orange'
+ elif todo+done > self.yellow_threshold*budget:
+ color = 'yellow'
+ else:
+ color = 'green'
+ if pourcent < 0:
+ pourcent = 0
+
+ if floor(done) == done or done>100:
+ done_str = '%i' % done
else:
- color = 'inprogress'
- if percent < 0:
- percent = 0
- self.w(u'<div class="progressbarback" title="%i %%">' % real_percent)
- self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, percent))
- self.w(u'</div>')
+ done_str = '%.1f' % done
+ if floor(budget) == budget or budget>100:
+ budget_str = '%i' % budget
+ else:
+ budget_str = '%.1f' % budget
+ title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent)
+ short_title = title
+ if self.overrun_percentage:
+ title += u' overrun +%sj (+%i%%)' % (self.overrun,
+ self.overrun_percentage)
+ overrun = self.overrun
+ if floor(overrun) == overrun or overrun>100:
+ overrun_str = '%i' % overrun
+ else:
+ overrun_str = '%.1f' % overrun
+ short_title += u' +%s' % overrun_str
+ # write bars
+ maxi = max(done+todo, budget)
+ if maxi == 0:
+ maxi = 1
+
+ cid = random.randint(0, 100000)
+ self.w(u'%s<br/>'
+ u'<canvas class="progressbar" id="canvas%i" width="100" height="10"></canvas>'
+ u'<script type="application/x-javascript">'
+ u'draw_progressbar("canvas%i", %i, %i, %i, "%s");</script>'
+ % (short_title.replace(' ',' '), cid, cid,
+ int(100.*done/maxi), int(100.*(done+todo)/maxi),
+ int(100.*budget/maxi), color))
--- a/web/httpcache.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/httpcache.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,8 +18,8 @@
"""default cache manager: set no-cache cache control policy"""
def __init__(self, view):
self.view = view
- self.req = view.req
- self.rset = view.rset
+ self.req = view._cw
+ self.cw_rset = view.cw_rset
def set_headers(self):
self.req.set_header('Cache-control', 'no-cache')
@@ -43,7 +43,7 @@
"""
def etag(self):
- return self.view.id + '/' + ','.join(sorted(self.req.user.groups))
+ return self.view.__regid__ + '/' + ','.join(sorted(self.req.user.groups))
def max_age(self):
# 0 to actually force revalidation
@@ -79,12 +79,12 @@
with a modification time to consider) using the `last_modified` method
"""
def etag(self):
- if self.rset is None or len(self.rset) == 0: # entity startup view for instance
+ if self.cw_rset is None or len(self.cw_rset) == 0: # entity startup view for instance
return super(EntityHTTPCacheManager, self).etag()
- if len(self.rset) > 1:
+ if len(self.cw_rset) > 1:
raise NoEtag()
etag = super(EntityHTTPCacheManager, self).etag()
- eid = self.rset[0][0]
+ eid = self.cw_rset[0][0]
if self.req.user.owns(eid):
etag += ',owners'
return str(eid) + '/' + etag
@@ -116,7 +116,7 @@
# XXX check view module's file modification time in dev mod ?
ctime = datetime.utcnow()
if self.cache_max_age:
- mtime = self.req.header_if_modified_since()
+ mtime = self._cw.header_if_modified_since()
if mtime:
tdelta = (ctime - mtime)
if tdelta.days * 24*60*60 + tdelta.seconds <= self.cache_max_age:
--- a/web/request.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/request.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,19 +12,21 @@
import time
import random
import base64
+from datetime import date
from urlparse import urlsplit
from itertools import count
+from simplejson import dumps
+
from rql.utils import rqlvar_maker
from logilab.common.decorators import cached
from logilab.common.deprecation import deprecated
-
from logilab.mtconverter import xml_escape
from cubicweb.dbapi import DBAPIRequest
-from cubicweb.common.mail import header
-from cubicweb.common.uilib import remove_html_tags
+from cubicweb.mail import header
+from cubicweb.uilib import remove_html_tags
from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
@@ -83,7 +85,8 @@
# tabindex generator
self.tabindexgen = count(1)
self.next_tabindex = self.tabindexgen.next
- self.varmaker = rqlvar_maker()
+ # page id, set by htmlheader template
+ self.pageid = None
self.datadir_url = self._datadir_url()
self._set_pageid()
@@ -98,6 +101,25 @@
self.pageid = pid
self.html_headers.define_var('pageid', pid, override=False)
+ @property
+ def varmaker(self):
+ """the rql varmaker is exposed both as a property and as the
+ set_varmaker function since we've two use cases:
+
+ * accessing the req.varmaker property to get a new variable name
+
+ * calling req.set_varmaker() to ensure a varmaker is set for later ajax
+ calls sharing our .pageid
+ """
+ return self.set_varmaker()
+
+ def set_varmaker(self):
+ varmaker = self.get_page_data('rql_varmaker')
+ if varmaker is None:
+ varmaker = rqlvar_maker()
+ self.set_page_data('rql_varmaker', varmaker)
+ return varmaker
+
def set_connection(self, cnx, user=None):
"""method called by the session handler when the user is authenticated
or an anonymous connection is open
@@ -122,6 +144,15 @@
return
# 3. default language
self.set_default_language(vreg)
+ # XXX code smell
+ # have to be done here because language is not yet set in setup_params
+ #
+ # special key for created entity, added in controller's reset method
+ # if no message set, we don't want this neither
+ if '__createdpath' in self.form and self.message:
+ self.message += ' (<a href="%s">%s</a>)' % (
+ self.build_url(self.form.pop('__createdpath')),
+ self._('click here to see created entity'))
def set_language(self, lang):
gettext, self.pgettext = self.translations[lang]
@@ -164,12 +195,6 @@
del self.form[k]
else:
self.form[k] = v
- # special key for created entity, added in controller's reset method
- # if no message set, we don't want this neither
- if '__createdpath' in params and self.message:
- self.message += ' (<a href="%s">%s</a>)' % (
- self.build_url(params.pop('__createdpath')),
- self._('click here to see created entity'))
def no_script_form_param(self, param, default=None, value=None):
"""ensure there is no script in a user form param
@@ -268,6 +293,24 @@
return breadcrumbs.pop()
return self.base_url()
+ def user_rql_callback(self, args, msg=None):
+ """register a user callback to execute some rql query and return an url
+ to call it ready to be inserted in html
+ """
+ def rqlexec(req, rql, args=None, key=None):
+ req.execute(rql, args, key)
+ return self.user_callback(rqlexec, args, msg)
+
+ def user_callback(self, cb, args, msg=None, nonify=False):
+ """register the given user callback and return an url to call it ready to be
+ inserted in html
+ """
+ self.add_js('cubicweb.ajax.js')
+ cbname = self.register_onetime_callback(cb, *args)
+ msg = dumps(msg or '')
+ return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+ cbname, msg)
+
def register_onetime_callback(self, func, *args):
cbname = 'cb_%s' % (
sha.sha('%s%s%s%s' % (time.time(), func.__name__,
@@ -317,7 +360,7 @@
try:
eids = form['eid']
except KeyError:
- raise NothingToEdit(None, {None: self._('no selected entities')})
+ raise NothingToEdit(self._('no selected entities'))
if isinstance(eids, basestring):
eids = (eids,)
for peid in eids:
@@ -329,7 +372,7 @@
yield peid
yielded = True
if not yielded:
- raise NothingToEdit(None, {None: self._('no selected entities')})
+ raise NothingToEdit(self._('no selected entities'))
# minparams=3 by default: at least eid, __type, and some params to change
def extract_entity_params(self, eid, minparams=3):
@@ -354,37 +397,7 @@
raise RequestError(self._('missing parameters for entity %s') % eid)
return params
- def get_pending_operations(self, entity, relname, role):
- operations = {'insert' : [], 'delete' : []}
- for optype in ('insert', 'delete'):
- data = self.get_session_data('pending_%s' % optype) or ()
- for eidfrom, rel, eidto in data:
- if relname == rel:
- if role == 'subject' and entity.eid == eidfrom:
- operations[optype].append(eidto)
- if role == 'object' and entity.eid == eidto:
- operations[optype].append(eidfrom)
- return operations
-
- def get_pending_inserts(self, eid=None):
- """shortcut to access req's pending_insert entry
-
- This is where are stored relations being added while editing
- an entity. This used to be stored in a temporary cookie.
- """
- pending = self.get_session_data('pending_insert') or ()
- return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
- if eid is None or eid in (subj, obj)]
-
- def get_pending_deletes(self, eid=None):
- """shortcut to access req's pending_delete entry
-
- This is where are stored relations being removed while editing
- an entity. This used to be stored in a temporary cookie.
- """
- pending = self.get_session_data('pending_delete') or ()
- return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
- if eid is None or eid in (subj, obj)]
+ # XXX this should go to the GenericRelationsField. missing edition cancel protocol.
def remove_pending_operations(self):
"""shortcut to clear req's pending_{delete,insert} entries
@@ -426,7 +439,7 @@
except KeyError:
return Cookie.SimpleCookie()
- def set_cookie(self, cookie, key, maxage=300):
+ def set_cookie(self, cookie, key, maxage=300, expires=None):
"""set / update a cookie key
by default, cookie will be available for the next 5 minutes.
@@ -436,20 +449,15 @@
morsel = cookie[key]
if maxage is not None:
morsel['Max-Age'] = maxage
+ if expires:
+ morsel['expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S %z')
# make sure cookie is set on the correct path
morsel['path'] = self.base_url_path()
self.add_header('Set-Cookie', morsel.OutputString())
def remove_cookie(self, cookie, key):
"""remove a cookie by expiring it"""
- morsel = cookie[key]
- morsel['Max-Age'] = 0
- # The only way to set up cookie age for IE is to use an old "expired"
- # syntax. IE doesn't support Max-Age there is no library support for
- # managing
- # ===> Do _NOT_ comment this line :
- morsel['expires'] = 'Thu, 01-Jan-1970 00:00:00 GMT'
- self.add_header('Set-Cookie', morsel.OutputString())
+ self.set_cookie(cookie, key, maxage=0, expires=date(1970, 1, 1))
def set_content_type(self, content_type, filename=None, encoding=None):
"""set output content type for this request. An optional filename
@@ -640,7 +648,7 @@
auth, ex.__class__.__name__, ex)
return None, None
- @deprecated("use parse_accept_header('Accept-Language')")
+ @deprecated("[3.4] use parse_accept_header('Accept-Language')")
def header_accept_language(self):
"""returns an ordered list of preferred languages"""
return [value.split('-')[0] for value in
--- a/web/test/data/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/data/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -24,7 +24,7 @@
subject = 'BlogEntry'
object = 'CWUser'
cardinality = '?*'
- permissions = {
+ __permissions__ = {
'add': ('managers',),
'read': ('managers', 'users'),
'delete': ('managers',),
--- a/web/test/test_views.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/test_views.py Mon Feb 08 13:35:07 2010 +0100
@@ -6,7 +6,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.devtools.testlib import WebTest, AutomaticWebTest
+from cubicweb.devtools.testlib import CubicWebTC, AutoPopulateTest, AutomaticWebTest
from cubicweb.view import AnyRsetView
AutomaticWebTest.application_rql = [
@@ -15,7 +15,7 @@
'Any COUNT(X) WHERE X is CWUser',
]
-class ComposityCopy(WebTest):
+class ComposityCopy(CubicWebTC):
def test_regr_copy_view(self):
"""regression test: make sure we can ask a copy of a
@@ -27,14 +27,14 @@
class SomeView(AnyRsetView):
- id = 'someview'
+ __regid__ = 'someview'
def call(self):
- self.req.add_js('spam.js')
- self.req.add_js('spam.js')
+ self._cw.add_js('spam.js')
+ self._cw.add_js('spam.js')
-class ManualWebTests(WebTest):
+class ManualCubicWebTCs(AutoPopulateTest):
def setup_database(self):
self.auto_populate(10)
@@ -52,18 +52,18 @@
def test_js_added_only_once(self):
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(SomeView)
+ self.vreg.register(SomeView)
rset = self.execute('CWUser X')
source = self.view('someview', rset).source
self.assertEquals(source.count('spam.js'), 1)
-class ExplicitViewsTest(WebTest):
+class ExplicitViewsTest(CubicWebTC):
def test_unrelateddivs(self):
rset = self.execute('Any X WHERE X is CWUser, X login "admin"')
- group = self.add_entity('CWGroup', name=u'R&D')
+ group = self.request().create_entity('CWGroup', name=u'R&D')
req = self.request(relation='in_group_subject')
self.view('unrelateddivs', rset, req)
--- a/web/test/unittest_application.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_application.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,7 +14,7 @@
from logilab.common.testlib import TestCase, unittest_main
from logilab.common.decorators import clear_cache
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.fake import FakeRequest
from cubicweb.web import Redirect, AuthenticationError, ExplicitLogin, INTERNAL_FIELD_VALUE
from cubicweb.web.views.basecontrollers import ViewController
@@ -37,25 +37,25 @@
class FakeController(ViewController):
def __init__(self, form=None):
- self.req = FakeRequest()
- self.req.form = form or {}
- self._cursor = self.req.cursor = MockCursor()
+ self._cw = FakeRequest()
+ self._cw.form = form or {}
+ self._cursor = self._cw.cursor = MockCursor()
def new_cursor(self):
- self._cursor = self.req.cursor = MockCursor()
+ self._cursor = self._cw.cursor = MockCursor()
def set_form(self, form):
- self.req.form = form
+ self._cw.form = form
class RequestBaseTC(TestCase):
def setUp(self):
- self.req = FakeRequest()
+ self._cw = FakeRequest()
def test_list_arg(self):
"""tests the list_arg() function"""
- list_arg = self.req.list_form_param
+ list_arg = self._cw.list_form_param
self.assertEquals(list_arg('arg3', {}), [])
d = {'arg1' : "value1",
'arg2' : ('foo', INTERNAL_FIELD_VALUE,),
@@ -69,8 +69,8 @@
def test_from_controller(self):
- self.req.vreg['controllers'] = {'view': 1, 'login': 1}
- self.assertEquals(self.req.from_controller(), 'view')
+ self._cw.vreg['controllers'] = {'view': 1, 'login': 1}
+ self.assertEquals(self._cw.from_controller(), 'view')
req = FakeRequest(url='project?vid=list')
req.vreg['controllers'] = {'view': 1, 'login': 1}
# this assertion is just to make sure that relative_path can be
@@ -123,7 +123,7 @@
self.ctrl.new_cursor()
- self.ctrl.req.form = {'__linkto' : 'works_for:12_13_14:object'}
+ self.ctrl._cw.form = {'__linkto' : 'works_for:12_13_14:object'}
self.ctrl.execute_linkto(eid=8)
self.assertEquals(self.ctrl._cursor.executed,
['SET Y works_for X WHERE X eid 8, Y eid %s' % i
@@ -137,37 +137,13 @@
for i in (12, 13, 14)])
-class ApplicationTC(EnvBasedTC):
+class ApplicationTC(CubicWebTC):
def setUp(self):
super(ApplicationTC, self).setUp()
def raise_hdlr(*args, **kwargs):
raise
self.app.error_handler = raise_hdlr
- def publish(self, req, path='view'):
- return self.app.publish(path, req)
-
- def expect_redirect(self, callback, req):
- try:
- res = callback(req)
- print res
- except Redirect, ex:
- try:
- path, params = ex.location.split('?', 1)
- except ValueError:
- path = ex.location
- params = {}
- else:
- cleanup = lambda p: (p[0], unquote(p[1]))
- params = dict(cleanup(p.split('=', 1)) for p in params.split('&') if p)
- path = path[len(req.base_url()):]
- return path, params
- else:
- self.fail('expected a Redirect exception')
-
- def expect_redirect_publish(self, req, path='view'):
- return self.expect_redirect(lambda x: self.publish(x, path), req)
-
def test_cnx_user_groups_sync(self):
user = self.user()
self.assertEquals(user.groups, set(('managers',)))
@@ -193,53 +169,52 @@
def test_publish_validation_error(self):
req = self.request()
user = self.user()
+ eid = unicode(user.eid)
req.form = {
- 'eid': `user.eid`,
- '__type:'+`user.eid`: 'CWUser',
- 'login:'+`user.eid`: '', # ERROR: no login specified
- 'edits-login:'+`user.eid`: unicode(user.login),
+ 'eid': eid,
+ '__type:'+eid: 'CWUser', '_cw_edited_fields:'+eid: 'login-subject',
+ 'login-subject:'+eid: '', # ERROR: no login specified
# just a sample, missing some necessary information for real life
'__errorurl': 'view?vid=edition...'
}
- path, params = self.expect_redirect_publish(req, 'edit')
+ path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
forminfo = req.get_session_data('view?vid=edition...')
eidmap = forminfo['eidmap']
self.assertEquals(eidmap, {})
values = forminfo['values']
- self.assertEquals(values['login:'+`user.eid`], '')
- self.assertEquals(values['edits-login:'+`user.eid`], user.login)
- self.assertEquals(values['eid'], `user.eid`)
- errors = forminfo['errors']
- self.assertEquals(errors.entity, user.eid)
- self.assertEquals(errors.errors['login'], 'required attribute')
+ self.assertEquals(values['login-subject:'+eid], '')
+ self.assertEquals(values['eid'], eid)
+ error = forminfo['error']
+ self.assertEquals(error.entity, user.eid)
+ self.assertEquals(error.errors['login'], 'required attribute')
def test_validation_error_dont_loose_subentity_data(self):
"""test creation of two linked entities
"""
req = self.request()
- form = {'eid': ['X', 'Y'],
- '__type:X': 'CWUser',
+ form = {'eid': ['X', 'Y'], '__maineid': 'X',
+ '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,surname-subject',
# missing required field
- 'login:X': u'', 'edits-login:X': '',
- 'surname:X': u'Mr Ouaoua', 'edits-surname:X': '',
- '__type:Y': 'EmailAddress',
+ 'login-subject:X': u'',
+ 'surname-subject:X': u'Mr Ouaoua',
# but email address is set
- 'address:Y': u'bougloup@logilab.fr', 'edits-address:Y': '',
- 'alias:Y': u'', 'edits-alias:Y': '',
- 'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
+ '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject,alias-subject,use_email-object',
+ 'address-subject:Y': u'bougloup@logilab.fr',
+ 'alias-subject:Y': u'',
+ 'use_email-object:Y': 'X',
# necessary to get validation error handling
'__errorurl': 'view?vid=edition...',
}
req.form = form
# monkey patch edited_eid to ensure both entities are edited, not only X
req.edited_eids = lambda : ('Y', 'X')
- path, params = self.expect_redirect_publish(req, 'edit')
+ path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req)
forminfo = req.get_session_data('view?vid=edition...')
- self.assertUnorderedIterableEquals(forminfo['eidmap'].keys(), ['X', 'Y'])
- self.assertEquals(forminfo['errors'].entity, forminfo['eidmap']['X'])
- self.assertEquals(forminfo['errors'].errors, {'login': 'required attribute',
- 'upassword': 'required attribute'})
+ self.assertEquals(set(forminfo['eidmap']), set('XY'))
+ self.assertEquals(forminfo['error'].entity, forminfo['eidmap']['X'])
+ self.assertEquals(forminfo['error'].errors, {'login': 'required attribute',
+ 'upassword': 'required attribute'})
self.assertEquals(forminfo['values'], form)
def _test_cleaned(self, kwargs, injected, cleaned):
@@ -286,63 +261,37 @@
req = self.request()
origcnx = req.cnx
req.form['__fblogin'] = u'turlututu'
- page = self.publish(req)
+ page = self.app_publish(req)
self.failIf(req.cnx is origcnx)
self.assertEquals(req.user.login, 'turlututu')
self.failUnless('turlututu' in page, page)
# authentication tests ####################################################
- def _init_auth(self, authmode, anonuser=None):
- self.set_option('auth-mode', authmode)
- self.set_option('anonymous-user', anonuser)
- req = self.request()
- origcnx = req.cnx
- req.cnx = None
- sh = self.app.session_handler
- # not properly cleaned between tests
- self.open_sessions = sh.session_manager._sessions = {}
- return req, origcnx
-
- def _test_auth_succeed(self, req, origcnx):
- sh = self.app.session_handler
- path, params = self.expect_redirect(lambda x: self.app.connect(x), req)
- cnx = req.cnx
- self.assertEquals(len(self.open_sessions), 1, self.open_sessions)
- self.assertEquals(cnx.login, origcnx.login)
- self.assertEquals(cnx.password, origcnx.password)
- self.assertEquals(cnx.anonymous_connection, False)
- self.assertEquals(path, 'view')
- self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login})
-
- def _test_auth_fail(self, req):
- self.assertRaises(AuthenticationError, self.app.connect, req)
+ def test_http_auth_no_anon(self):
+ req, origcnx = self.init_authentication('http')
+ self.assertAuthFailure(req)
+ self.assertRaises(ExplicitLogin, self.app_publish, req, 'login')
self.assertEquals(req.cnx, None)
- self.assertEquals(len(self.open_sessions), 0)
- clear_cache(req, 'get_authorization')
-
- def test_http_auth_no_anon(self):
- req, origcnx = self._init_auth('http')
- self._test_auth_fail(req)
- self.assertRaises(ExplicitLogin, self.publish, req, 'login')
- self.assertEquals(req.cnx, None)
- authstr = base64.encodestring('%s:%s' % (origcnx.login, origcnx.password))
+ authstr = base64.encodestring('%s:%s' % (origcnx.login, origcnx.authinfo['password']))
req._headers['Authorization'] = 'basic %s' % authstr
- self._test_auth_succeed(req, origcnx)
- self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+ self.assertAuthSuccess(req, origcnx)
+ self.assertEquals(req.cnx.authinfo, {'password': origcnx.authinfo['password']})
+ self.assertRaises(AuthenticationError, self.app_publish, req, 'logout')
self.assertEquals(len(self.open_sessions), 0)
def test_cookie_auth_no_anon(self):
- req, origcnx = self._init_auth('cookie')
- self._test_auth_fail(req)
- form = self.publish(req, 'login')
+ req, origcnx = self.init_authentication('cookie')
+ self.assertAuthFailure(req)
+ form = self.app_publish(req, 'login')
self.failUnless('__login' in form)
self.failUnless('__password' in form)
self.assertEquals(req.cnx, None)
req.form['__login'] = origcnx.login
- req.form['__password'] = origcnx.password
- self._test_auth_succeed(req, origcnx)
- self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+ req.form['__password'] = origcnx.authinfo['password']
+ self.assertAuthSuccess(req, origcnx)
+ self.assertEquals(req.cnx.authinfo, {'password': origcnx.authinfo['password']})
+ self.assertRaises(AuthenticationError, self.app_publish, req, 'logout')
self.assertEquals(len(self.open_sessions), 0)
def test_login_by_email(self):
@@ -352,28 +301,20 @@
'WHERE U login %(login)s', {'address': address, 'login': login})
self.commit()
# option allow-email-login not set
- req, origcnx = self._init_auth('cookie')
+ req, origcnx = self.init_authentication('cookie')
req.form['__login'] = address
- req.form['__password'] = origcnx.password
- self._test_auth_fail(req)
+ req.form['__password'] = origcnx.authinfo['password']
+ self.assertAuthFailure(req)
# option allow-email-login set
origcnx.login = address
self.set_option('allow-email-login', True)
req.form['__login'] = address
- req.form['__password'] = origcnx.password
- self._test_auth_succeed(req, origcnx)
- self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+ req.form['__password'] = origcnx.authinfo['password']
+ self.assertAuthSuccess(req, origcnx)
+ self.assertEquals(req.cnx.authinfo, {'password': origcnx.authinfo['password']})
+ self.assertRaises(AuthenticationError, self.app_publish, req, 'logout')
self.assertEquals(len(self.open_sessions), 0)
- def _test_auth_anon(self, req):
- self.app.connect(req)
- acnx = req.cnx
- self.assertEquals(len(self.open_sessions), 1)
- self.assertEquals(acnx.login, 'anon')
- self.assertEquals(acnx.password, 'anon')
- self.failUnless(acnx.anonymous_connection)
- self._reset_cookie(req)
-
def _reset_cookie(self, req):
# preparing the suite of the test
# set session id in cookie
@@ -384,6 +325,15 @@
# reset cnx as if it was a new incoming request
req.cnx = None
+ def _test_auth_anon(self, req):
+ self.app.connect(req)
+ acnx = req.cnx
+ self.assertEquals(len(self.open_sessions), 1)
+ self.assertEquals(acnx.login, 'anon')
+ self.assertEquals(acnx.authinfo['password'], 'anon')
+ self.failUnless(acnx.anonymous_connection)
+ self._reset_cookie(req)
+
def _test_anon_auth_fail(self, req):
self.assertEquals(len(self.open_sessions), 1)
self.app.connect(req)
@@ -393,34 +343,36 @@
self._reset_cookie(req)
def test_http_auth_anon_allowed(self):
- req, origcnx = self._init_auth('http', 'anon')
+ req, origcnx = self.init_authentication('http', 'anon')
self._test_auth_anon(req)
authstr = base64.encodestring('toto:pouet')
req._headers['Authorization'] = 'basic %s' % authstr
self._test_anon_auth_fail(req)
- authstr = base64.encodestring('%s:%s' % (origcnx.login, origcnx.password))
+ authstr = base64.encodestring('%s:%s' % (origcnx.login, origcnx.authinfo['password']))
req._headers['Authorization'] = 'basic %s' % authstr
- self._test_auth_succeed(req, origcnx)
- self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+ self.assertAuthSuccess(req, origcnx)
+ self.assertEquals(req.cnx.authinfo, {'password': origcnx.authinfo['password']})
+ self.assertRaises(AuthenticationError, self.app_publish, req, 'logout')
self.assertEquals(len(self.open_sessions), 0)
def test_cookie_auth_anon_allowed(self):
- req, origcnx = self._init_auth('cookie', 'anon')
+ req, origcnx = self.init_authentication('cookie', 'anon')
self._test_auth_anon(req)
req.form['__login'] = 'toto'
req.form['__password'] = 'pouet'
self._test_anon_auth_fail(req)
req.form['__login'] = origcnx.login
- req.form['__password'] = origcnx.password
- self._test_auth_succeed(req, origcnx)
- self.assertRaises(AuthenticationError, self.publish, req, 'logout')
+ req.form['__password'] = origcnx.authinfo['password']
+ self.assertAuthSuccess(req, origcnx)
+ self.assertEquals(req.cnx.authinfo, {'password': origcnx.authinfo['password']})
+ self.assertRaises(AuthenticationError, self.app_publish, req, 'logout')
self.assertEquals(len(self.open_sessions), 0)
def test_non_regr_optional_first_var(self):
req = self.request()
# expect a rset with None in [0][0]
req.form['rql'] = 'rql:Any OV1, X WHERE X custom_workflow OV1?'
- self.publish(req)
+ self.app_publish(req)
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_breadcrumbs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_breadcrumbs.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,10 +1,11 @@
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
-class BreadCrumbsTC(WebTest):
+class BreadCrumbsTC(CubicWebTC):
def test_base(self):
- f1 = self.add_entity('Folder', name=u'par&ent')
- f2 = self.add_entity('Folder', name=u'chi&ld')
+ req = self.request()
+ f1 = req.create_entity('Folder', name=u'par&ent')
+ f2 = req.create_entity('Folder', name=u'chi&ld')
self.execute('SET F2 filed_under F1 WHERE F1 eid %(f1)s, F2 eid %(f2)s',
{'f1' : f1.eid, 'f2' : f2.eid})
self.commit()
@@ -15,3 +16,7 @@
self.assertEquals(ibc.render(),
"""<span id="breadcrumbs" class="pathbar"> > <a href="http://testing.fr/cubicweb/Folder">folder_plural</a> > <a href="http://testing.fr/cubicweb/folder/%s" title="">par&ent</a> > 
chi&ld</span>""" % f1.eid)
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
--- a/web/test/unittest_controller.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_controller.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,37 +7,35 @@
from logilab.common.testlib import unittest_main
-from cubicweb.devtools import apptest
+from cubicweb.devtools import testlib
-class BaseControllerTC(apptest.ControllerTC):
+class BaseControllerTC(testlib.CubicWebTC):
def test_parse_datetime_ok(self):
- self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18'),
- datetime)
- self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24'),
- date)
- self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24 12:18', 'Datetime'),
- datetime)
- self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Datetime'),
- datetime)
- self.assertIsInstance(self.ctrl.parse_datetime('2006/06/24', 'Date'),
- date)
- self.assertIsInstance(self.ctrl.parse_datetime('12:18', 'Time'),
- time)
+ ctrl = self.vreg['controllers'].select('view', self.request())
+ pd = ctrl._cw.parse_datetime
+ self.assertIsInstance(pd('2006/06/24 12:18'), datetime)
+ self.assertIsInstance(pd('2006/06/24'), date)
+ self.assertIsInstance(pd('2006/06/24 12:18', 'Datetime'), datetime)
+ self.assertIsInstance(pd('2006/06/24', 'Datetime'), datetime)
+ self.assertIsInstance(pd('2006/06/24', 'Date'), date)
+ self.assertIsInstance(pd('12:18', 'Time'), time)
def test_parse_datetime_ko(self):
+ ctrl = self.vreg['controllers'].select('view', self.request())
+ pd = ctrl._cw.parse_datetime
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '2006/06/24 12:188', 'Datetime')
+ pd, '2006/06/24 12:188', 'Datetime')
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '2006/06/240', 'Datetime')
+ pd, '2006/06/240', 'Datetime')
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '2006/06/24 12:18', 'Date')
+ pd, '2006/06/24 12:18', 'Date')
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '2006/24/06', 'Date')
+ pd, '2006/24/06', 'Date')
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '2006/06/240', 'Date')
+ pd, '2006/06/240', 'Date')
self.assertRaises(ValueError,
- self.ctrl.parse_datetime, '12:188', 'Time')
+ pd, '12:188', 'Time')
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_form.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_form.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,27 +13,27 @@
from logilab.common.compat import any
from cubicweb import Binary
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.formfields import (IntField, StringField, RichTextField,
- DateTimeField, DateTimePicker,
+ PasswordField, DateTimeField,
FileField, EditableFileField)
-from cubicweb.web.formwidgets import PasswordInput, Input
+from cubicweb.web.formwidgets import PasswordInput, Input, DateTimePicker
from cubicweb.web.views.forms import EntityFieldsForm, FieldsForm
from cubicweb.web.views.workflow import ChangeStateForm
from cubicweb.web.views.formrenderers import FormRenderer
-class FieldsFormTC(WebTest):
+class FieldsFormTC(CubicWebTC):
def test_form_field_format(self):
form = FieldsForm(self.request(), None)
- self.assertEquals(form.form_field_format(None), 'text/html')
+ self.assertEquals(StringField().format(form), 'text/html')
self.execute('INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
self.commit()
- self.assertEquals(form.form_field_format(None), 'text/rest')
+ self.assertEquals(StringField().format(form), 'text/rest')
-class EntityFieldsFormTC(WebTest):
+class EntityFieldsFormTC(CubicWebTC):
def setUp(self):
super(EntityFieldsFormTC, self).setUp()
@@ -41,63 +41,49 @@
self.entity = self.user(self.req)
def test_form_field_vocabulary_unrelated(self):
- b = self.add_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller')
- t = self.add_entity('Tag', name=u'x')
- form1 = EntityFieldsForm(self.request(), None, entity=t)
- unrelated = [reid for rview, reid in form1.subject_relation_vocabulary('tags')]
+ b = self.req.create_entity('BlogEntry', title=u'di mascii code', content=u'a best-seller')
+ t = self.req.create_entity('Tag', name=u'x')
+ form1 = self.vreg['forms'].select('edition', self.req, entity=t)
+ unrelated = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
self.failUnless(b.eid in unrelated, unrelated)
- form2 = EntityFieldsForm(self.request(), None, entity=b)
- unrelated = [reid for rview, reid in form2.object_relation_vocabulary('tags')]
+ form2 = self.vreg['forms'].select('edition', self.req, entity=b)
+ unrelated = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
self.failUnless(t.eid in unrelated, unrelated)
self.execute('SET X tags Y WHERE X is Tag, Y is BlogEntry')
- unrelated = [reid for rview, reid in form1.subject_relation_vocabulary('tags')]
+ unrelated = [reid for rview, reid in form1.field_by_name('tags', 'subject', t.e_schema).choices(form1)]
self.failIf(b.eid in unrelated, unrelated)
- unrelated = [reid for rview, reid in form2.object_relation_vocabulary('tags')]
+ unrelated = [reid for rview, reid in form2.field_by_name('tags', 'object', t.e_schema).choices(form2)]
self.failIf(t.eid in unrelated, unrelated)
def test_form_field_vocabulary_new_entity(self):
- e = self.etype_instance('CWUser')
- form = EntityFieldsForm(self.request(), None, entity=e)
- unrelated = [rview for rview, reid in form.subject_relation_vocabulary('in_group')]
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
+ form = self.vreg['forms'].select('edition', self.req, entity=e)
+ unrelated = [rview for rview, reid in form.field_by_name('in_group', 'subject').choices(form)]
# should be default groups but owners, i.e. managers, users, guests
self.assertEquals(unrelated, [u'guests', u'managers', u'users'])
- # def test_subject_in_state_vocabulary(self):
- # # on a new entity
- # e = self.etype_instance('CWUser')
- # form = EntityFieldsForm(self.request(), None, entity=e)
- # states = list(form.subject_in_state_vocabulary('in_state'))
- # self.assertEquals(len(states), 1)
- # self.assertEquals(states[0][0], u'activated') # list of (combobox view, state eid)
- # # on an existant entity
- # e = self.user()
- # form = EntityFieldsForm(self.request(), None, entity=e)
- # states = list(form.subject_in_state_vocabulary('in_state'))
- # self.assertEquals(len(states), 1)
- # self.assertEquals(states[0][0], u'deactivated') # list of (combobox view, state eid)
-
def test_consider_req_form_params(self):
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
e.eid = 'A'
form = EntityFieldsForm(self.request(login=u'toto'), None, entity=e)
- field = StringField(name='login', eidparam=True)
+ field = StringField(name='login', role='subject', eidparam=True)
form.append_field(field)
form.build_context({})
- self.assertEquals(form.form_field_display_value(field, {}), 'toto')
+ self.assertEquals(field.widget.values(form, field), (u'toto',))
def test_linkto_field_duplication(self):
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
e.eid = 'A'
- e.req = self.req
+ e._cw = self.req
geid = self.execute('CWGroup X WHERE X name "users"')[0][0]
self.req.form['__linkto'] = 'in_group:%s:subject' % geid
form = self.vreg['forms'].select('edition', self.req, entity=e)
form.content_type = 'text/html'
pageinfo = self._check_html(form.render(), form, template=None)
inputs = pageinfo.find_tag('select', False)
- self.failUnless(any(attrs for t, attrs in inputs if attrs.get('name') == 'in_group:A'))
+ self.failUnless(any(attrs for t, attrs in inputs if attrs.get('name') == 'in_group-subject:A'))
inputs = pageinfo.find_tag('input', False)
self.failIf(any(attrs for t, attrs in inputs if attrs.get('name') == '__linkto'))
@@ -106,8 +92,7 @@
form = self.vreg['views'].select('doreledit', self.request(),
rset=rset, row=0, rtype='content')
data = form.render(row=0, rtype='content')
- self.failUnless('edits-content' in data)
- self.failUnless('edits-content_format' in data)
+ self.failUnless('content_format' in data)
# form view tests #########################################################
@@ -140,98 +125,103 @@
def _render_entity_field(self, name, form):
form.build_context({})
renderer = FormRenderer(self.req)
- return form.field_by_name(name).render(form, renderer)
+ return form.field_by_name(name, 'subject').render(form, renderer)
def _test_richtextfield(self, expected):
class RTFForm(EntityFieldsForm):
- description = RichTextField()
- state = self.execute('State X WHERE X name "activated", X state_of WF, WF workflow_of ET, ET name "CWUser"').get_entity(0, 0)
+ description = RichTextField(eidparam=True, role='subject')
+ state = self.vreg['etypes'].etype_class('State')(self.req)
+ state.eid = 'S'
form = RTFForm(self.req, redirect_path='perdu.com', entity=state)
# make it think it can use fck editor anyway
- form.form_field_format = lambda x: 'text/html'
+ form.field_by_name('description', 'subject').format = lambda x: 'text/html'
self.assertTextEquals(self._render_entity_field('description', form),
expected % {'eid': state.eid})
def test_richtextfield_1(self):
self.req.use_fckeditor = lambda: False
- self._test_richtextfield('''<select id="description_format:%(eid)s" name="description_format:%(eid)s" size="1" style="display: block" tabindex="1">
+ self._test_richtextfield('''<select id="description_format-subject:%(eid)s" name="description_format-subject:%(eid)s" size="1" style="display: block" tabindex="1">
<option value="text/cubicweb-page-template">text/cubicweb-page-template</option>
-<option value="text/html">text/html</option>
+<option selected="selected" value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
-<option selected="selected" value="text/rest">text/rest</option>
-</select><textarea cols="80" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="2"></textarea>''')
+<option value="text/rest">text/rest</option>
+</select><textarea cols="80" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="2"></textarea>''')
def test_richtextfield_2(self):
self.req.use_fckeditor = lambda: True
- self._test_richtextfield('<input name="description_format:%(eid)s" type="hidden" value="text/rest" /><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="1"></textarea>')
+ self._test_richtextfield('<input name="description_format-subject:%(eid)s" type="hidden" value="text/html" /><textarea cols="80" cubicweb:type="wysiwyg" id="description-subject:%(eid)s" name="description-subject:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="1"></textarea>')
def test_filefield(self):
class FFForm(EntityFieldsForm):
- data = FileField(format_field=StringField(name='data_format', max_length=50),
- encoding_field=StringField(name='data_encoding', max_length=20))
- file = self.add_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8',
+ data = FileField(
+ format_field=StringField(name='data_format', max_length=50,
+ eidparam=True, role='subject'),
+ encoding_field=StringField(name='data_encoding', max_length=20,
+ eidparam=True, role='subject'),
+ eidparam=True, role='subject')
+ file = self.req.create_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8',
data=Binary('new widgets system'))
form = FFForm(self.req, redirect_path='perdu.com', entity=file)
self.assertTextEquals(self._render_entity_field('data', form),
- '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="1" type="file" value="" />
-<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
-<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
+ '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" tabindex="1" type="file" value="" />
+<a href="javascript: toggleVisibility('data-subject:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
+<div id="data-subject:%(eid)s-advanced" class="hidden">
+<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
+<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
</div>
<br/>
-<input name="data:%(eid)s__detach" type="checkbox" />
+<input name="data-subject__detach:%(eid)s" type="checkbox" />
detach attached file
''' % {'eid': file.eid})
def test_editablefilefield(self):
class EFFForm(EntityFieldsForm):
- data = EditableFileField(format_field=StringField(name='data_format', max_length=50),
- encoding_field=StringField(name='data_encoding', max_length=20))
- def form_field_encoding(self, field):
- return 'ascii'
- def form_field_format(self, field):
- return 'text/plain'
- file = self.add_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8',
+ data = EditableFileField(
+ format_field=StringField('data_format', max_length=50,
+ eidparam=True, role='subject'),
+ encoding_field=StringField('data_encoding', max_length=20,
+ eidparam=True, role='subject'),
+ eidparam=True, role='subject')
+ file = self.req.create_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8',
data=Binary('new widgets system'))
form = EFFForm(self.req, redirect_path='perdu.com', entity=file)
self.assertTextEquals(self._render_entity_field('data', form),
- '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="1" type="file" value="" />
-<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
-<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
+ '''<input id="data-subject:%(eid)s" name="data-subject:%(eid)s" tabindex="1" type="file" value="" />
+<a href="javascript: toggleVisibility('data-subject:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
+<div id="data-subject:%(eid)s-advanced" class="hidden">
+<label for="data_format-subject:%(eid)s">data_format</label><input id="data_format-subject:%(eid)s" maxlength="50" name="data_format-subject:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
+<label for="data_encoding-subject:%(eid)s">data_encoding</label><input id="data_encoding-subject:%(eid)s" maxlength="20" name="data_encoding-subject:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
</div>
<br/>
-<input name="data:%(eid)s__detach" type="checkbox" />
+<input name="data-subject__detach:%(eid)s" type="checkbox" />
detach attached file
<p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
-<textarea cols="80" name="data:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="4">new widgets system</textarea>''' % {'eid': file.eid})
+<textarea cols="80" name="data-subject:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="4">new widgets system</textarea>''' % {'eid': file.eid})
def test_passwordfield(self):
class PFForm(EntityFieldsForm):
- upassword = StringField(widget=PasswordInput)
+ upassword = PasswordField(eidparam=True, role='subject')
form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity)
self.assertTextEquals(self._render_entity_field('upassword', form),
- '''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
+ '''<input id="upassword-subject:%(eid)s" name="upassword-subject:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
<br/>
-<input name="upassword-confirm:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
+<input name="upassword-subject-confirm:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
 
<span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
- def test_datefield(self):
- class DFForm(EntityFieldsForm):
- creation_date = DateTimeField(widget=Input)
- form = DFForm(self.req, entity=self.entity)
- init, cur = (fromstring(self._render_entity_field(attr, form)).get('value')
- for attr in ('edits-creation_date', 'creation_date'))
- self.assertEquals(init, cur)
+ # def test_datefield(self):
+ # class DFForm(EntityFieldsForm):
+ # creation_date = DateTimeField(widget=Input)
+ # form = DFForm(self.req, entity=self.entity)
+ # init, cur = (fromstring(self._render_entity_field(attr, form)).get('value')
+ # for attr in ('edits-creation_date', 'creation_date'))
+ # self.assertEquals(init, cur)
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_formfields.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_formfields.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
from yams.constraints import StaticVocabularyConstraint, SizeConstraint
from cubicweb.devtools import TestServerConfiguration
-from cubicweb.devtools.testlib import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.formwidgets import PasswordInput, TextArea, Select, Radio
from cubicweb.web.formfields import *
from cubicweb.web.views.forms import EntityFieldsForm
@@ -46,7 +46,6 @@
description_format_field = guess_field(schema['State'], schema['description_format'], skip_meta_attr=False)
self.assertEquals(description_format_field.internationalizable, True)
self.assertEquals(description_format_field.sort, True)
- self.assertEquals(description_format_field.initial(None), 'text/rest')
# wikiid_field = guess_field(schema['State'], schema['wikiid'])
# self.assertIsInstance(wikiid_field, StringField)
@@ -92,9 +91,10 @@
def test_constraints_priority(self):
salesterm_field = guess_field(schema['Salesterm'], schema['reason'])
- constraints = schema['reason'].rproperty('Salesterm', 'String', 'constraints')
+ constraints = schema['reason'].rdef('Salesterm', 'String').constraints
self.assertEquals([c.__class__ for c in constraints],
[SizeConstraint, StaticVocabularyConstraint])
+ self.assertIsInstance(salesterm_field, StringField)
self.assertIsInstance(salesterm_field.widget, Select)
@@ -102,9 +102,8 @@
field = guess_field(schema['CWAttribute'], schema['indexed'])
self.assertIsInstance(field, BooleanField)
self.assertEquals(field.required, False)
- self.assertEquals(field.initial(None), None)
self.assertIsInstance(field.widget, Radio)
- self.assertEquals(field.vocabulary(mock(req=mock(_=unicode))),
+ self.assertEquals(field.vocabulary(mock(_cw=mock(_=unicode))),
[(u'yes', '1'), (u'no', '')])
def test_bool_field_explicit_choices(self):
@@ -115,21 +114,21 @@
[(u'maybe', '1'), (u'no', '')])
-class MoreFieldsTC(EnvBasedTC):
+class MoreFieldsTC(CubicWebTC):
def test_rtf_format_field(self):
req = self.request()
req.use_fckeditor = lambda: False
- e = self.etype_instance('State')
+ e = self.vreg['etypes'].etype_class('State')(req)
form = EntityFieldsForm(req, entity=e)
description_field = guess_field(schema['State'], schema['description'])
description_format_field = description_field.get_format_field(form)
self.assertEquals(description_format_field.internationalizable, True)
self.assertEquals(description_format_field.sort, True)
# unlike below, initial is bound to form.form_field_format
- self.assertEquals(description_format_field.initial(form), 'text/html')
+ self.assertEquals(description_format_field.value(form), 'text/html')
self.execute('INSERT CWProperty X: X pkey "ui.default-text-format", X value "text/rest", X for_user U WHERE U login "admin"')
self.commit()
- self.assertEquals(description_format_field.initial(form), 'text/rest')
+ self.assertEquals(description_format_field.value(form), 'text/rest')
class UtilsTC(TestCase):
--- a/web/test/unittest_magicsearch.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_magicsearch.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,21 +13,17 @@
from rql import BadRQLQuery, RQLSyntaxError
-from cubicweb.devtools.apptest import EnvBasedTC, TestEnvironment
+from cubicweb.devtools.testlib import CubicWebTC
translations = {
u'CWUser' : u"Utilisateur",
-# u'Workcase' : u"Affaire",
u'EmailAddress' : u"Adresse",
-# u'Division' : u"Division",
-# u'Comment' : u"Commentaire",
u'name' : u"nom",
u'alias' : u"nom",
u'surname' : u"nom",
u'firstname' : u"prénom",
u'state' : u"état",
-# u'subject' : u"sujet",
u'address' : u"adresse",
u'use_email' : u"adel",
}
@@ -40,12 +36,12 @@
from cubicweb.web.views.magicsearch import translate_rql_tree, QSPreProcessor, QueryTranslator
-class QueryTranslatorTC(EnvBasedTC):
+class QueryTranslatorTC(CubicWebTC):
"""test suite for QueryTranslatorTC"""
def setUp(self):
super(QueryTranslatorTC, self).setUp()
- self.req = self.env.create_request()
+ self.req = self.request()
self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
proc = self.vreg['components'].select('magicsearch', self.req)
self.proc = [p for p in proc.processors if isinstance(p, QueryTranslator)][0]
@@ -53,20 +49,20 @@
def test_basic_translations(self):
"""tests basic translations (no ambiguities)"""
rql = "Any C WHERE C is Adresse, P adel C, C adresse 'Logilab'"
- rql, = self.proc.preprocess_query(rql, self.req)
+ rql, = self.proc.preprocess_query(rql)
self.assertEquals(rql, "Any C WHERE C is EmailAddress, P use_email C, C address 'Logilab'")
def test_ambiguous_translations(self):
"""tests possibly ambiguous translations"""
rql = "Any P WHERE P adel C, C is EmailAddress, C nom 'Logilab'"
- rql, = self.proc.preprocess_query(rql, self.req)
+ rql, = self.proc.preprocess_query(rql)
self.assertEquals(rql, "Any P WHERE P use_email C, C is EmailAddress, C alias 'Logilab'")
rql = "Any P WHERE P is Utilisateur, P adel C, P nom 'Smith'"
- rql, = self.proc.preprocess_query(rql, self.req)
+ rql, = self.proc.preprocess_query(rql)
self.assertEquals(rql, "Any P WHERE P is CWUser, P use_email C, P surname 'Smith'")
-class QSPreProcessorTC(EnvBasedTC):
+class QSPreProcessorTC(CubicWebTC):
"""test suite for QSPreProcessor"""
def setUp(self):
super(QSPreProcessorTC, self).setUp()
@@ -74,7 +70,7 @@
self.req = self.request()
proc = self.vreg['components'].select('magicsearch', self.req)
self.proc = [p for p in proc.processors if isinstance(p, QSPreProcessor)][0]
- self.proc.req = self.req
+ self.proc._cw = self.req
def test_entity_translation(self):
"""tests QSPreProcessor._get_entity_name()"""
@@ -91,7 +87,6 @@
eschema = self.schema.eschema('CWUser')
self.assertEquals(translate(u'prénom', eschema), "firstname")
self.assertEquals(translate(u'nom', eschema), 'surname')
- #self.assert_(translate(u'nom') in ('name', 'surname'))
eschema = self.schema.eschema('EmailAddress')
self.assertEquals(translate(u'adresse', eschema), "address")
self.assertEquals(translate(u'nom', eschema), 'alias')
@@ -128,7 +123,6 @@
self.assertEquals(transform(u'adresse', 'Logi%'),
('EmailAddress E WHERE E alias LIKE %(text)s', {'text': 'Logi%'}))
self.assertRaises(BadRQLQuery, transform, "pers", "taratata")
- #self.assertEquals(transform('CWUser', '%mi'), 'CWUser E WHERE P surname LIKE "%mi"')
def test_three_words_query(self):
"""tests the 'three words shortcut queries'"""
@@ -174,16 +168,16 @@
(u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
]
for query, expected in queries:
- self.assertEquals(self.proc.preprocess_query(query, self.req), expected)
+ self.assertEquals(self.proc.preprocess_query(query), expected)
self.assertRaises(BadRQLQuery,
- self.proc.preprocess_query, 'Any X WHERE X is Something', self.req)
+ self.proc.preprocess_query, 'Any X WHERE X is Something')
## Processor Chains tests ############################################
-class ProcessorChainTC(EnvBasedTC):
+class ProcessorChainTC(CubicWebTC):
"""test suite for magic_search's processor chains"""
def setUp(self):
@@ -198,7 +192,7 @@
(u'foo',
("Any X WHERE X has_text %(text)s", {'text': u'foo'})),
# XXX this sounds like a language translator test...
- # and it fail
+ # and it fails
(u'Utilisateur Smith',
('CWUser C WHERE C has_text %(text)s', {'text': u'Smith'})),
(u'utilisateur nom Smith',
@@ -207,21 +201,21 @@
('Any P WHERE P is CWUser, P surname "Smith"', None)),
]
for query, expected in queries:
- rset = self.proc.process_query(query, self.req)
+ rset = self.proc.process_query(query)
self.assertEquals((rset.rql, rset.args), expected)
def test_iso88591_fulltext(self):
"""we must be able to type accentuated characters in the search field"""
- rset = self.proc.process_query(u'écrire', self.req)
+ rset = self.proc.process_query(u'écrire')
self.assertEquals(rset.rql, "Any X WHERE X has_text %(text)s")
self.assertEquals(rset.args, {'text': u'écrire'})
def test_explicit_component(self):
self.assertRaises(RQLSyntaxError,
- self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",', self.req)
+ self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",')
self.assertRaises(BadRQLQuery,
- self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"', self.req)
- rset = self.proc.process_query(u'text: utilisateur Smith', self.req)
+ self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"')
+ rset = self.proc.process_query(u'text: utilisateur Smith')
self.assertEquals(rset.rql, 'Any X WHERE X has_text %(text)s')
self.assertEquals(rset.args, {'text': u'utilisateur Smith'})
--- a/web/test/unittest_uicfg.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_uicfg.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,13 +1,10 @@
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web import uicfg
-class UICFGTC(EnvBasedTC):
+class UICFGTC(CubicWebTC):
- def test_autoform_section_inlined(self):
- self.assertEquals(uicfg.autoform_is_inlined.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'),
- True)
- self.assertEquals(uicfg.autoform_section.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'),
- 'generated')
+ def test(self):
+ self.skip('write some tests')
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
--- a/web/test/unittest_urlpublisher.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_urlpublisher.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,26 +11,26 @@
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools._apptest import FakeRequest
-
from cubicweb.rset import ResultSet
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.devtools.fake import FakeRequest
from cubicweb.web import NotFound, Redirect
from cubicweb.web.views.urlrewrite import SimpleReqRewriter
-class URLPublisherTC(EnvBasedTC):
+class URLPublisherTC(CubicWebTC):
"""test suite for QSPreProcessor"""
def setup_database(self):
self.create_user(u'ÿsaÿe')
- b = self.add_entity('BlogEntry', title=u'hell\'o', content=u'blabla')
- c = self.add_entity('Tag', name=u'yo') # take care: Tag's name normalized to lower case
+ req = self.request()
+ 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}, 'b')
def process(self, url):
req = self.req = self.request()
- return self.env.app.url_resolver.process(req, url)
+ return self.app.url_resolver.process(req, url)
def test_raw_path(self):
"""tests raw path resolution'"""
--- a/web/test/unittest_urlrewrite.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_urlrewrite.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,13 +7,13 @@
"""
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools._apptest import FakeRequest
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.devtools.fake import FakeRequest
from cubicweb.web.views.urlrewrite import SimpleReqRewriter, SchemaBasedRewriter, rgx, rgx_action
-class UrlRewriteTC(TestCase):
+class UrlRewriteTC(CubicWebTC):
def test_auto_extend_rules(self):
class Rewriter(SimpleReqRewriter):
@@ -39,7 +39,7 @@
('/notfound', dict(vid='404')),
('/error', dict(vid='error')),
('/sparql', dict(vid='sparql')),
- ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'eschema'}),
+ ('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'primary'}),
('/add/([^/]+?)/?$' , dict(vid='creation', etype=r'\1')),
('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
('/doc/?$', dict(fid='main', vid='wdoc')),
@@ -64,8 +64,8 @@
def test_basic_transformation(self):
"""test simple string-based rewrite"""
- rewriter = SimpleReqRewriter()
req = FakeRequest()
+ rewriter = SimpleReqRewriter(req)
self.assertRaises(KeyError, rewriter.rewrite, req, '/view?vid=whatever')
self.assertEquals(req.form, {})
rewriter.rewrite(req, '/index')
@@ -73,8 +73,8 @@
def test_regexp_transformation(self):
"""test regexp-based rewrite"""
- rewriter = SimpleReqRewriter()
req = FakeRequest()
+ rewriter = SimpleReqRewriter(req)
rewriter.rewrite(req, '/add/Task')
self.assertEquals(req.form, {'vid' : "creation", 'etype' : "Task"})
req = FakeRequest()
@@ -84,7 +84,7 @@
-class RgxActionRewriteTC(EnvBasedTC):
+class RgxActionRewriteTC(CubicWebTC):
def setup_database(self):
self.p1 = self.create_user(u'user1')
@@ -100,8 +100,8 @@
transforms={'sn' : unicode.capitalize,
'fn' : unicode.lower,})),
]
- rewriter = TestSchemaBasedRewriter()
req = self.request()
+ rewriter = TestSchemaBasedRewriter(req)
pmid, rset = rewriter.rewrite(req, u'/DaLToN/JoE')
self.assertEquals(len(rset), 1)
self.assertEquals(rset[0][0], self.p1.eid)
@@ -125,8 +125,8 @@
),
]
- rewriter = Rewriter()
req = self.request()
+ rewriter = Rewriter(req)
pmid, rset = rewriter.rewrite(req, '/collector')
self.assertEquals(rset.rql, RQL1)
self.assertEquals(req.form, {'vid' : "baseindex"})
@@ -159,8 +159,8 @@
),
]
- rewriter = Rewriter()
req = self.request()
+ rewriter = Rewriter(req)
pmid, rset = rewriter.rewrite(req, '/collector')
self.assertEquals(rset.rql, RQL2)
self.assertEquals(req.form, {'vid' : "index"})
--- a/web/test/unittest_views_actions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_actions.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,26 +7,26 @@
"""
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
-class ActionsTC(EnvBasedTC):
+class ActionsTC(CubicWebTC):
def test_view_action(self):
req = self.request(__message='bla bla bla', vid='rss', rql='CWUser X')
rset = self.execute('CWUser X')
- vaction = [action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
- if action.id == 'view'][0]
+ actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+ vaction = [action for action in actions if action.__regid__ == 'view'][0]
self.assertEquals(vaction.url(), 'http://testing.fr/cubicweb/view?rql=CWUser%20X')
def test_sendmail_action(self):
req = self.request()
rset = self.execute('Any X WHERE X login "admin"', req=req)
- self.failUnless([action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
- if action.id == 'sendemail'])
+ actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+ self.failUnless([action for action in actions if action.__regid__ == 'sendemail'])
self.login('anon')
req = self.request()
rset = self.execute('Any X WHERE X login "anon"', req=req)
- self.failIf([action for action in self.vreg['actions'].possible_vobjects(req, rset=rset)
- if action.id == 'sendemail'])
+ actions = self.vreg['actions'].poss_visible_objects(req, rset=rset)
+ self.failIf([action for action in actions if action.__regid__ == 'sendemail'])
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_views_basecontrollers.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_basecontrollers.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,137 +8,135 @@
import simplejson
from logilab.common.testlib import unittest_main, mock_object
-from cubicweb.devtools.apptest import EnvBasedTC, ControllerTC
from cubicweb import Binary, NoSelectableObject, ValidationError
from cubicweb.view import STRICT_DOCTYPE
-from cubicweb.common.uilib import rql_for_eid
-
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.uilib import rql_for_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
-
from cubicweb.entities.authobjs import CWUser
-
+from cubicweb.web.views.autoform import get_pending_inserts, get_pending_deletes
+u = unicode
-class EditControllerTC(ControllerTC):
+def req_form(user):
+ return {'eid': [str(user.eid)],
+ '_cw_edited_fields:%s' % user.eid: '_cw_generic_field',
+ '__type:%s' % user.eid: user.__regid__
+ }
+
+class EditControllerTC(CubicWebTC):
def setUp(self):
- ControllerTC.setUp(self)
+ CubicWebTC.setUp(self)
self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
def tearDown(self):
- ControllerTC.tearDown(self)
+ CubicWebTC.tearDown(self)
self.failUnless('users' in self.schema.eschema('CWGroup').get_groups('read'))
def test_noparam_edit(self):
"""check behaviour of this controller without any form parameter
"""
-
- self.req.form = {}
- self.assertRaises(ValidationError, self.publish, self.req)
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, self.request())
+ self.assertEquals(ex.errors, {None: u'no selected entities'})
def test_validation_unique(self):
"""test creation of two linked entities
"""
user = self.user()
- self.req.form = {'eid': 'X', '__type:X': 'CWUser',
- 'login:X': u'admin', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
- }
- self.assertRaises(ValidationError, self.publish, self.req)
-
+ req = self.request()
+ req.form = {'eid': 'X', '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ 'login-subject:X': u'admin',
+ 'upassword-subject:X': u'toto',
+ 'upassword-subject-confirm:X': u'toto',
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'login': 'the value "admin" is already used, use another one'})
def test_user_editing_itself(self):
"""checking that a manager user can edit itself
"""
user = self.user()
- basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
+ basegroups = [u(eid) for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")')]
- groups = [str(eid) for eid in groupeids]
- stateeid = [eid for eid, in self.execute('State S WHERE S name "activated"')][0]
- self.req.form = {
- 'eid': `user.eid`,
- '__type:'+`user.eid`: 'CWUser',
- 'login:'+`user.eid`: unicode(user.login),
- 'firstname:'+`user.eid`: u'Th\xe9nault',
- 'surname:'+`user.eid`: u'Sylvain',
- 'in_group:'+`user.eid`: groups,
- 'in_state:'+`user.eid`: `stateeid`,
- #
- 'edits-login:'+`user.eid`: unicode(user.login),
- 'edits-firstname:'+`user.eid`: u'',
- 'edits-surname:'+`user.eid`: u'',
- 'edits-in_group:'+`user.eid`: basegroups,
- 'edits-in_state:'+`user.eid`: `stateeid`,
+ groups = [u(eid) for eid in groupeids]
+ req = self.request()
+ eid = u(user.eid)
+ req.form = {
+ 'eid': eid, '__type:'+eid: 'CWUser',
+ '_cw_edited_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
+ 'login-subject:'+eid: u(user.login),
+ 'surname-subject:'+eid: u'Th\xe9nault',
+ 'firstname-subject:'+eid: u'Sylvain',
+ 'in_group-subject:'+eid: groups,
}
- path, params = self.expect_redirect_publish()
+ path, params = self.expect_redirect_publish(req, 'edit')
e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}, 'x').get_entity(0, 0)
- self.assertEquals(e.firstname, u'Th\xe9nault')
- self.assertEquals(e.surname, u'Sylvain')
+ self.assertEquals(e.firstname, u'Sylvain')
+ self.assertEquals(e.surname, u'Th\xe9nault')
self.assertEquals(e.login, user.login)
self.assertEquals([g.eid for g in e.in_group], groupeids)
- self.assertEquals(e.in_state[0].eid, stateeid)
def test_user_can_change_its_password(self):
user = self.create_user('user')
cnx = self.login('user')
req = self.request()
- #self.assertEquals(self.ctrl.schema['CWUser']._groups['read'],
- # ('managers', 'users'))
+ eid = u(user.eid)
req.form = {
- 'eid': `user.eid`, '__type:'+`user.eid`: 'CWUser',
- '__maineid' : str(user.eid),
- 'upassword:'+`user.eid`: 'tournicoton',
- 'upassword-confirm:'+`user.eid`: 'tournicoton',
- 'edits-upassword:'+`user.eid`: '',
+ 'eid': eid, '__maineid' : eid,
+ '__type:'+eid: 'CWUser',
+ '_cw_edited_fields:'+eid: 'upassword-subject',
+ 'upassword-subject:'+eid: 'tournicoton',
+ 'upassword-subject-confirm:'+eid: 'tournicoton',
}
- path, params = self.expect_redirect_publish(req)
+ path, params = self.expect_redirect_publish(req, 'edit')
cnx.commit() # commit to check we don't get late validation error for instance
self.assertEquals(path, 'cwuser/user')
self.failIf('vid' in params)
- def testr_user_editing_itself_no_relation(self):
+ def test_user_editing_itself_no_relation(self):
"""checking we can edit an entity without specifying some required
relations (meaning no changes)
"""
user = self.user()
- groupeids = [eid for eid, in self.execute('CWGroup G WHERE X in_group G, X eid %(x)s', {'x': user.eid})]
- self.req.form = {
- 'eid': `user.eid`,
- '__type:'+`user.eid`: 'CWUser',
- 'login:'+`user.eid`: unicode(user.login),
- 'firstname:'+`user.eid`: u'Th\xe9nault',
- 'surname:'+`user.eid`: u'Sylvain',
- #
- 'edits-login:'+`user.eid`: unicode(user.login),
- 'edits-firstname:'+`user.eid`: u'',
- 'edits-surname:'+`user.eid`: u'',
+ groupeids = [g.eid for g in user.in_group]
+ req = self.request()
+ eid = u(user.eid)
+ req.form = {
+ 'eid': eid,
+ '__type:'+eid: 'CWUser',
+ '_cw_edited_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
+ 'login-subject:'+eid: u(user.login),
+ 'firstname-subject:'+eid: u'Th\xe9nault',
+ 'surname-subject:'+eid: u'Sylvain',
}
- path, params = self.expect_redirect_publish()
+ path, params = self.expect_redirect_publish(req, 'edit')
e = self.execute('Any X WHERE X eid %(x)s', {'x': user.eid}, 'x').get_entity(0, 0)
self.assertEquals(e.login, user.login)
self.assertEquals(e.firstname, u'Th\xe9nault')
self.assertEquals(e.surname, u'Sylvain')
self.assertEquals([g.eid for g in e.in_group], groupeids)
- stateeids = [eid for eid, in self.execute('State S WHERE S name "activated"')]
- self.assertEquals([s.eid for s in e.in_state], stateeids)
+ self.assertEquals(e.state, 'activated')
def test_create_multiple_linked(self):
gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
- self.req.form = {'eid': ['X', 'Y'],
-
- '__type:X': 'CWUser',
- '__maineid' : 'X',
- 'login:X': u'adim', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
- 'surname:X': u'Di Mascio', 'edits-surname:X': '',
+ req = self.request()
+ req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
- 'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+ '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject,surname-subject,in_group-subject',
+ 'login-subject:X': u'adim',
+ 'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
+ 'surname-subject:X': u'Di Mascio',
+ 'in_group-subject:X': u(gueid),
- '__type:Y': 'EmailAddress',
- 'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
- 'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
- }
- path, params = self.expect_redirect_publish()
+ '__type:Y': 'EmailAddress',
+ '_cw_edited_fields:Y': 'address-subject,use_email-object',
+ 'address-subject:Y': u'dima@logilab.fr',
+ 'use_email-object:Y': 'X',
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
# should be redirected on the created person
self.assertEquals(path, 'cwuser/adim')
e = self.execute('Any P WHERE P surname "Di Mascio"').get_entity(0, 0)
@@ -147,40 +145,41 @@
self.assertEquals(email.address, 'dima@logilab.fr')
def test_edit_multiple_linked(self):
- peid = self.create_user('adim').eid
- self.req.form = {'eid': [`peid`, 'Y'],
- '__type:%s'%peid: 'CWUser',
- 'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: '',
+ peid = u(self.create_user('adim').eid)
+ req = self.request()
+ req.form = {'eid': [peid, 'Y'], '__maineid': peid,
+
+ '__type:'+peid: u'CWUser',
+ '_cw_edited_fields:'+peid: u'surname-subject',
+ 'surname-subject:'+peid: u'Di Masci',
- '__type:Y': 'EmailAddress',
- 'address:Y': u'dima@logilab.fr', 'edits-address:Y': '',
- 'use_email:%s'%peid: 'Y', 'edits-use_email:%s'%peid: INTERNAL_FIELD_VALUE,
-
- '__redirectrql': 'Any X WHERE X eid %s'%peid,
- }
- path, params = self.expect_redirect_publish()
+ '__type:Y': u'EmailAddress',
+ '_cw_edited_fields:Y': u'address-subject,use_email-object',
+ 'address-subject:Y': u'dima@logilab.fr',
+ 'use_email-object:Y': peid,
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
# should be redirected on the created person
- eid = params['rql'].split()[-1]
- e = self.execute('Any X WHERE X eid %(x)s', {'x': eid}, 'x').get_entity(0, 0)
- self.assertEquals(e.surname, 'Di Masci')
+ self.assertEquals(path, 'cwuser/adim')
+ e = self.execute('Any P WHERE P surname "Di Masci"').get_entity(0, 0)
email = e.use_email[0]
self.assertEquals(email.address, 'dima@logilab.fr')
- emaileid = email.eid
- self.req.form = {'eid': [`peid`, `emaileid`],
- '__type:%s'%peid: 'CWUser',
- 'surname:%s'%peid: u'Di Masci', 'edits-surname:%s'%peid: 'Di Masci',
- '__type:%s'%emaileid: 'EmailAddress',
- 'address:%s'%emaileid: u'adim@logilab.fr', 'edits-address:%s'%emaileid: 'dima@logilab.fr',
- 'use_email:%s'%peid: `emaileid`, 'edits-use_email:%s'%peid: `emaileid`,
- '__redirectrql': 'Any X WHERE X eid %s'%peid,
- }
- path, params = self.expect_redirect_publish()
- # should be redirected on the created person
- eid = params['rql'].split()[-1]
- e = self.execute('Any X WHERE X eid %(x)s', {'x': eid}, 'x').get_entity(0, 0)
- self.assertEquals(e.surname, 'Di Masci')
- email = e.use_email[0]
+ emaileid = u(email.eid)
+ req = self.request()
+ req.form = {'eid': [peid, emaileid],
+
+ '__type:'+peid: u'CWUser',
+ '_cw_edited_fields:'+peid: u'surname-subject',
+ 'surname-subject:'+peid: u'Di Masci',
+
+ '__type:'+emaileid: u'EmailAddress',
+ '_cw_edited_fields:'+emaileid: u'address-subject,use_email-object',
+ 'address-subject:'+emaileid: u'adim@logilab.fr',
+ 'use_email-object:'+emaileid: peid,
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
+ email.clear_all_caches()
self.assertEquals(email.address, 'adim@logilab.fr')
@@ -188,41 +187,56 @@
"""test creation of two linked entities
"""
user = self.user()
- self.req.form = {'__cloned_eid:X': user.eid,
- 'eid': 'X', '__type:X': 'CWUser',
- 'login:X': u'toto', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'edits-upassword:X': u'',
- }
- self.assertRaises(ValidationError, self.publish, self.req)
- self.req.form = {'__cloned_eid:X': user.eid,
- 'eid': 'X', '__type:X': 'CWUser',
- 'login:X': u'toto', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'upassword-confirm:X': u'tutu', 'edits-upassword:X': u'',
- }
- self.assertRaises(ValidationError, self.publish, self.req)
+ req = self.request()
+ req.form = {'eid': 'X',
+ '__cloned_eid:X': u(user.eid), '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ 'login-subject:X': u'toto',
+ 'upassword-subject:X': u'toto',
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'upassword-subject': u'password and confirmation don\'t match'})
+ req = self.request()
+ req.form = {'__cloned_eid:X': u(user.eid),
+ 'eid': 'X', '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ 'login-subject:X': u'toto',
+ 'upassword-subject:X': u'toto',
+ 'upassword-subject-confirm:X': u'tutu',
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'upassword-subject': u'password and confirmation don\'t match'})
def test_interval_bound_constraint_success(self):
feid = self.execute('INSERT File X: X data_name "toto.txt", X data %(data)s',
{'data': Binary('yo')})[0][0]
- self.req.form = {'eid': ['X'],
- '__type:X': 'Salesterm',
- 'amount:X': u'-10', 'edits-amount:X': '',
- 'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
- }
- self.assertRaises(ValidationError, self.publish, self.req)
- self.req.form = {'eid': ['X'],
- '__type:X': 'Salesterm',
- 'amount:X': u'110', 'edits-amount:X': '',
- 'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
- }
- self.assertRaises(ValidationError, self.publish, self.req)
- self.req.form = {'eid': ['X'],
- '__type:X': 'Salesterm',
- 'amount:X': u'10', 'edits-amount:X': '',
- 'described_by_test:X': str(feid), 'edits-described_by_test:X': INTERNAL_FIELD_VALUE,
- }
- self.expect_redirect_publish()
+ req = self.request()
+ req.form = {'eid': ['X'],
+ '__type:X': 'Salesterm',
+ '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ 'amount-subject:X': u'-10',
+ 'described_by_test-subject:X': u(feid),
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value -10'})
+ req = self.request()
+ req.form = {'eid': ['X'],
+ '__type:X': 'Salesterm',
+ '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ 'amount-subject:X': u'110',
+ 'described_by_test-subject:X': u(feid),
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'amount': 'value [0;100] constraint failed for value 110'})
+ req = self.request()
+ req.form = {'eid': ['X'],
+ '__type:X': 'Salesterm',
+ '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ 'amount-subject:X': u'10',
+ 'described_by_test-subject:X': u(feid),
+ }
+ self.expect_redirect_publish(req, 'edit')
# should be redirected on the created
#eid = params['rql'].split()[-1]
e = self.execute('Salesterm X').get_entity(0, 0)
@@ -230,14 +244,15 @@
def test_req_pending_insert(self):
"""make sure req's pending insertions are taken into account"""
- tmpgroup = self.add_entity('CWGroup', name=u"test")
+ tmpgroup = self.request().create_entity('CWGroup', name=u"test")
user = self.user()
- self.req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
- path, params = self.expect_redirect_publish()
+ req = self.request(**req_form(user))
+ req.set_session_data('pending_insert', set([(user.eid, 'in_group', tmpgroup.eid)]))
+ path, params = self.expect_redirect_publish(req, 'edit')
usergroups = [gname for gname, in
self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
self.assertUnorderedIterableEquals(usergroups, ['managers', 'test'])
- self.assertEquals(self.req.get_pending_inserts(), [])
+ self.assertEquals(get_pending_inserts(req), [])
def test_req_pending_delete(self):
@@ -250,47 +265,49 @@
# just make sure everything was set correctly
self.assertUnorderedIterableEquals(usergroups, ['managers', 'test'])
# now try to delete the relation
- self.req.set_session_data('pending_delete', set([(user.eid, 'in_group', groupeid)]))
- path, params = self.expect_redirect_publish()
+ req = self.request(**req_form(user))
+ req.set_session_data('pending_delete', set([(user.eid, 'in_group', groupeid)]))
+ path, params = self.expect_redirect_publish(req, 'edit')
usergroups = [gname for gname, in
self.execute('Any N WHERE G name N, U in_group G, U eid %(u)s', {'u': user.eid})]
self.assertUnorderedIterableEquals(usergroups, ['managers'])
- self.assertEquals(self.req.get_pending_deletes(), [])
+ self.assertEquals(get_pending_deletes(req), [])
- def test_custom_attribute_handler(self):
- def custom_login_edit(self, formparams, value, relations):
- formparams['login'] = value.upper()
- relations.append('X login %(login)s')
- CWUser.custom_login_edit = custom_login_edit
- try:
- user = self.user()
- eid = repr(user.eid)
- self.req.form = {
- 'eid': eid,
- '__type:'+eid: 'CWUser',
- 'login:'+eid: u'foo',
- 'edits-login:'+eid: unicode(user.login),
- }
- path, params = self.expect_redirect_publish()
- rset = self.execute('Any L WHERE X eid %(x)s, X login L', {'x': user.eid}, 'x')
- self.assertEquals(rset[0][0], 'FOO')
- finally:
- del CWUser.custom_login_edit
+ # def test_custom_attribute_handler(self):
+ # def custom_login_edit(self, formparams, value, relations):
+ # formparams['login'] = value.upper()
+ # relations.append('X login %(login)s')
+ # CWUser.custom_login_edit = custom_login_edit
+ # try:
+ # user = self.user()
+ # eid = repr(user.eid)
+ # req = self.request()
+ # req.form = {
+ # 'eid': eid,
+ # '__type:'+eid: 'CWUser',
+ # 'login:'+eid: u'foo',
+ # }
+ # path, params = self.expect_redirect_publish(req, 'edit')
+ # rset = self.execute('Any L WHERE X eid %(x)s, X login L', {'x': user.eid}, 'x')
+ # self.assertEquals(rset[0][0], 'FOO')
+ # finally:
+ # del CWUser.custom_login_edit
def test_redirect_apply_button(self):
redirectrql = rql_for_eid(4012) # whatever
- self.req.form = {
- 'eid': 'A', '__type:A': 'BlogEntry',
- '__maineid' : 'A',
- 'content:A': u'"13:03:43"', 'edits-content:A': '',
- 'title:A': u'huuu', 'edits-title:A': '',
- '__redirectrql': redirectrql,
- '__redirectvid': 'primary',
- '__redirectparams': 'toto=tutu&tata=titi',
- '__form_id': 'edition',
- '__action_apply': '',
- }
- path, params = self.expect_redirect_publish()
+ req = self.request()
+ req.form = {
+ 'eid': 'A', '__maineid' : 'A',
+ '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'content-subject,title-subject',
+ 'content-subject:A': u'"13:03:43"',
+ 'title-subject:A': u'huuu',
+ '__redirectrql': redirectrql,
+ '__redirectvid': 'primary',
+ '__redirectparams': 'toto=tutu&tata=titi',
+ '__form_id': 'edition',
+ '__action_apply': '',
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
self.failUnless(path.startswith('blogentry/'))
eid = path.split('/')[1]
self.assertEquals(params['vid'], 'edition')
@@ -301,17 +318,18 @@
def test_redirect_ok_button(self):
redirectrql = rql_for_eid(4012) # whatever
- self.req.form = {
- 'eid': 'A', '__type:A': 'BlogEntry',
- '__maineid' : 'A',
- 'content:A': u'"13:03:43"', 'edits-content:A': '',
- 'title:A': u'huuu', 'edits-title:A': '',
- '__redirectrql': redirectrql,
- '__redirectvid': 'primary',
- '__redirectparams': 'toto=tutu&tata=titi',
- '__form_id': 'edition',
- }
- path, params = self.expect_redirect_publish()
+ req = self.request()
+ req.form = {
+ 'eid': 'A', '__maineid' : 'A',
+ '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'content-subject,title-subject',
+ 'content-subject:A': u'"13:03:43"',
+ 'title-subject:A': u'huuu',
+ '__redirectrql': redirectrql,
+ '__redirectvid': 'primary',
+ '__redirectparams': 'toto=tutu&tata=titi',
+ '__form_id': 'edition',
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
self.assertEquals(path, 'view')
self.assertEquals(params['rql'], redirectrql)
self.assertEquals(params['vid'], 'primary')
@@ -319,88 +337,55 @@
self.assertEquals(params['toto'], 'tutu')
def test_redirect_delete_button(self):
- eid = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid
- self.req.form = {'eid': str(eid), '__type:%s'%eid: 'BlogEntry',
- '__action_delete': ''}
- path, params = self.expect_redirect_publish()
+ req = self.request()
+ eid = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
+ req.form = {'eid': u(eid), '__type:%s'%eid: 'BlogEntry',
+ '__action_delete': ''}
+ path, params = self.expect_redirect_publish(req, 'edit')
self.assertEquals(path, 'blogentry')
self.assertEquals(params, {u'__message': u'entity deleted'})
- eid = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid
+ eid = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
self.execute('SET X use_email E WHERE E eid %(e)s, X eid %(x)s',
- {'x': self.session().user.eid, 'e': eid}, 'x')
+ {'x': self.session.user.eid, 'e': eid}, 'x')
self.commit()
- self.req.form = {'eid': str(eid), '__type:%s'%eid: 'EmailAddress',
- '__action_delete': ''}
- path, params = self.expect_redirect_publish()
+ req = req
+ req.form = {'eid': u(eid), '__type:%s'%eid: 'EmailAddress',
+ '__action_delete': ''}
+ path, params = self.expect_redirect_publish(req, 'edit')
self.assertEquals(path, 'cwuser/admin')
self.assertEquals(params, {u'__message': u'entity deleted'})
- eid1 = self.add_entity('BlogEntry', title=u'hop', content=u'hop').eid
- eid2 = self.add_entity('EmailAddress', address=u'hop@logilab.fr').eid
- self.req.form = {'eid': [str(eid1), str(eid2)],
- '__type:%s'%eid1: 'BlogEntry',
- '__type:%s'%eid2: 'EmailAddress',
- '__action_delete': ''}
- path, params = self.expect_redirect_publish()
+ eid1 = req.create_entity('BlogEntry', title=u'hop', content=u'hop').eid
+ eid2 = req.create_entity('EmailAddress', address=u'hop@logilab.fr').eid
+ req = self.request()
+ req.form = {'eid': [u(eid1), u(eid2)],
+ '__type:%s'%eid1: 'BlogEntry',
+ '__type:%s'%eid2: 'EmailAddress',
+ '__action_delete': ''}
+ path, params = self.expect_redirect_publish(req, 'edit')
self.assertEquals(path, 'view')
self.assertEquals(params, {u'__message': u'entities deleted'})
- def test_nonregr_egroup_etype_editing(self):
- """non-regression test checking that a manager user can edit a CWEType entity (CWGroup)
- """
- groupeids = [eid for eid, in self.execute('CWGroup G WHERE G name "managers"')]
- groups = [str(eid) for eid in groupeids]
- eeetypeeid = self.execute('CWEType X WHERE X name "CWGroup"')[0][0]
- basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
- self.req.form = {
- 'eid': `eeetypeeid`,
- '__type:'+`eeetypeeid`: 'CWEType',
- 'name:'+`eeetypeeid`: u'CWGroup',
- 'final:'+`eeetypeeid`: False,
- 'meta:'+`eeetypeeid`: True,
- 'description:'+`eeetypeeid`: u'users group',
- 'read_permission:'+`eeetypeeid`: groups,
- #
- 'edits-name:'+`eeetypeeid`: u'CWGroup',
- 'edits-final:'+`eeetypeeid`: False,
- 'edits-meta:'+`eeetypeeid`: True,
- 'edits-description:'+`eeetypeeid`: u'users group',
- 'edits-read_permission:'+`eeetypeeid`: basegroups,
- }
- try:
- path, params = self.expect_redirect_publish()
- e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
- self.assertEquals(e.name, 'CWGroup')
- self.assertEquals([g.eid for g in e.read_permission], groupeids)
- finally:
- # restore
- self.execute('SET X read_permission Y WHERE X name "CWGroup", Y eid IN (%s), NOT X read_permission Y' % (','.join(basegroups)))
- self.commit()
-
def test_nonregr_eetype_etype_editing(self):
- """non-regression test checking that a manager user can edit a CWEType entity (CWEType)
+ """non-regression test checking that a manager user can edit a CWEType entity
"""
groupeids = sorted(eid for eid, in self.execute('CWGroup G WHERE G name in ("managers", "users")'))
- groups = [str(eid) for eid in groupeids]
- eeetypeeid = self.execute('CWEType X WHERE X name "CWEType"')[0][0]
- basegroups = [str(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': eeetypeeid})]
- self.req.form = {
- 'eid': `eeetypeeid`,
- '__type:'+`eeetypeeid`: 'CWEType',
- 'name:'+`eeetypeeid`: u'CWEType',
- 'final:'+`eeetypeeid`: False,
- 'meta:'+`eeetypeeid`: True,
- 'description:'+`eeetypeeid`: u'users group',
- 'read_permission:'+`eeetypeeid`: groups,
-
- 'edits-name:'+`eeetypeeid`: u'CWEType',
- 'edits-final:'+`eeetypeeid`: False,
- 'edits-meta:'+`eeetypeeid`: True,
- 'edits-description:'+`eeetypeeid`: u'users group',
- 'edits-read_permission:'+`eeetypeeid`: basegroups,
- }
+ groups = [u(eid) for eid in groupeids]
+ cwetypeeid = self.execute('CWEType X WHERE X name "CWEType"')[0][0]
+ basegroups = [u(eid) for eid, in self.execute('CWGroup G WHERE X read_permission G, X eid %(x)s', {'x': cwetypeeid})]
+ cwetypeeid = u(cwetypeeid)
+ req = self.request()
+ req.form = {
+ 'eid': cwetypeeid,
+ '__type:'+cwetypeeid: 'CWEType',
+ '_cw_edited_fields:'+cwetypeeid: 'name-subject,final-subject,description-subject,read_permission-subject',
+ 'name-subject:'+cwetypeeid: u'CWEType',
+ 'final-subject:'+cwetypeeid: '',
+ 'description-subject:'+cwetypeeid: u'users group',
+ 'read_permission-subject:'+cwetypeeid: groups,
+ }
try:
- path, params = self.expect_redirect_publish()
- e = self.execute('Any X WHERE X eid %(x)s', {'x': eeetypeeid}, 'x').get_entity(0, 0)
+ path, params = self.expect_redirect_publish(req, 'edit')
+ e = self.execute('Any X WHERE X eid %(x)s', {'x': cwetypeeid}, 'x').get_entity(0, 0)
self.assertEquals(e.name, 'CWEType')
self.assertEquals(sorted(g.eid for g in e.read_permission), groupeids)
finally:
@@ -413,12 +398,13 @@
this seems to be postgres (tsearch?) specific
"""
- self.req.form = {
- 'eid': 'A', '__type:A': 'BlogEntry',
- '__maineid' : 'A',
- 'title:A': u'"13:03:40"', 'edits-title:A': '',
- 'content:A': u'"13:03:43"', 'edits-content:A': ''}
- path, params = self.expect_redirect_publish()
+ req = self.request()
+ req.form = {
+ 'eid': 'A', '__maineid' : 'A',
+ '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'title-subject,content-subject',
+ 'title-subject:A': u'"13:03:40"',
+ 'content-subject:A': u'"13:03:43"',}
+ path, params = self.expect_redirect_publish(req, 'edit')
self.failUnless(path.startswith('blogentry/'))
eid = path.split('/')[1]
e = self.execute('Any C, T WHERE C eid %(x)s, C content T', {'x': eid}, 'x').get_entity(0, 0)
@@ -428,29 +414,34 @@
def test_nonregr_multiple_empty_email_addr(self):
gueid = self.execute('CWGroup G WHERE G name "users"')[0][0]
- self.req.form = {'eid': ['X', 'Y'],
+ req = self.request()
+ req.form = {'eid': ['X', 'Y'],
- '__type:X': 'CWUser',
- 'login:X': u'adim', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
- 'in_group:X': `gueid`, 'edits-in_group:X': INTERNAL_FIELD_VALUE,
+ '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject,in_group-subject',
+ 'login-subject:X': u'adim',
+ 'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
+ 'in_group-subject:X': `gueid`,
- '__type:Y': 'EmailAddress',
- 'address:Y': u'', 'edits-address:Y': '',
- 'alias:Y': u'', 'edits-alias:Y': '',
- 'use_email:X': 'Y', 'edits-use_email:X': INTERNAL_FIELD_VALUE,
- }
- self.assertRaises(ValidationError, self.publish, self.req)
+ '__type:Y': 'EmailAddress',
+ '_cw_edited_fields:Y': 'address-subject,alias-subject,use_email-object',
+ 'address-subject:Y': u'',
+ 'alias-subject:Y': u'',
+ 'use_email-object:Y': 'X',
+ }
+ ex = self.assertRaises(ValidationError, self.ctrl_publish, req)
+ self.assertEquals(ex.errors, {'address': u'required attribute'})
def test_nonregr_copy(self):
user = self.user()
- self.req.form = {'__cloned_eid:X': user.eid,
- 'eid': 'X', '__type:X': 'CWUser',
- '__maineid' : 'X',
- 'login:X': u'toto', 'edits-login:X': u'',
- 'upassword:X': u'toto', 'upassword-confirm:X': u'toto', 'edits-upassword:X': u'',
- }
- path, params = self.expect_redirect_publish()
+ req = self.request()
+ req.form = {'__maineid' : 'X', 'eid': 'X',
+ '__cloned_eid:X': user.eid, '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ 'login-subject:X': u'toto',
+ 'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
+ }
+ path, params = self.expect_redirect_publish(req, 'edit')
self.assertEquals(path, 'cwuser/toto')
e = self.execute('Any X WHERE X is CWUser, X login "toto"').get_entity(0, 0)
self.assertEquals(e.login, 'toto')
@@ -463,32 +454,35 @@
old_skips = p.__class__.skip_copy_for
p.__class__.skip_copy_for = ()
try:
- e = self.add_entity('EmailAddress', address=u'doe@doe.com')
+ e = self.request().create_entity('EmailAddress', address=u'doe@doe.com')
self.execute('SET P use_email E, P primary_email E WHERE P eid %(p)s, E eid %(e)s',
{'p' : p.eid, 'e' : e.eid})
- self.req.form = {'__cloned_eid:X': p.eid,
- 'eid': 'X', '__type:X': 'CWUser',
- 'login': u'dodo', 'edits-login': u'dodo',
- 'surname:X': u'Boom', 'edits-surname:X': u'',
- '__errorurl' : "whatever but required",
- }
+ req = self.request()
+ req.form = {'eid': 'X',
+ '__cloned_eid:X': p.eid, '__type:X': 'CWUser',
+ '_cw_edited_fields:X': 'login-subject,surname-subject',
+ 'login-subject': u'dodo',
+ 'surname-subject:X': u'Boom',
+ '__errorurl' : "whatever but required",
+ }
# try to emulate what really happens in the web application
# 1/ validate form => EditController.publish raises a ValidationError
# which fires a Redirect
# 2/ When re-publishing the copy form, the publisher implicitly commits
try:
- self.env.app.publish('edit', self.req)
+ self.app_publish(req, 'edit')
except Redirect:
- self.req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
- self.req.form['vid'] = 'copy'
- self.env.app.publish('view', self.req)
+ req = self.request()
+ req.form['rql'] = 'Any X WHERE X eid %s' % p.eid
+ req.form['vid'] = 'copy'
+ self.app_publish(req, 'view')
rset = self.execute('CWUser P WHERE P surname "Boom"')
self.assertEquals(len(rset), 0)
finally:
p.__class__.skip_copy_for = old_skips
-class EmbedControllerTC(EnvBasedTC):
+class EmbedControllerTC(CubicWebTC):
def test_nonregr_embed_publish(self):
# This test looks a bit stupid but at least it will probably
@@ -500,31 +494,32 @@
result = controller.publish(rset=None)
-class ReportBugControllerTC(EnvBasedTC):
+class ReportBugControllerTC(CubicWebTC):
def test_usable_by_guets(self):
- req = self.request()
- self.vreg['controllers'].select('reportbug', req)
+ self.login('anon')
+ self.vreg['controllers'].select('reportbug', self.request())
-class SendMailControllerTC(EnvBasedTC):
+class SendMailControllerTC(CubicWebTC):
def test_not_usable_by_guets(self):
self.login('anon')
- req = self.request()
- self.assertRaises(NoSelectableObject, self.env.vreg['controllers'].select, 'sendmail', req)
+ self.assertRaises(NoSelectableObject,
+ self.vreg['controllers'].select, 'sendmail', self.request())
-class JSONControllerTC(EnvBasedTC):
+class JSONControllerTC(CubicWebTC):
def ctrl(self, req=None):
req = req or self.request(url='http://whatever.fr/')
return self.vreg['controllers'].select('json', req)
def setup_database(self):
- self.pytag = self.add_entity('Tag', name=u'python')
- self.cubicwebtag = self.add_entity('Tag', name=u'cubicweb')
+ 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')
@@ -565,58 +560,58 @@
def test_pending_insertion(self):
res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '13']])
- deletes = req.get_pending_deletes()
+ deletes = get_pending_deletes(req)
self.assertEquals(deletes, [])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, ['12:tags:13'])
res, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
- deletes = req.get_pending_deletes()
+ deletes = get_pending_deletes(req)
self.assertEquals(deletes, [])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, ['12:tags:13', '12:tags:14'])
- inserts = req.get_pending_inserts(12)
+ inserts = get_pending_inserts(req, 12)
self.assertEquals(inserts, ['12:tags:13', '12:tags:14'])
- inserts = req.get_pending_inserts(13)
+ inserts = get_pending_inserts(req, 13)
self.assertEquals(inserts, ['12:tags:13'])
- inserts = req.get_pending_inserts(14)
+ inserts = get_pending_inserts(req, 14)
self.assertEquals(inserts, ['12:tags:14'])
req.remove_pending_operations()
def test_pending_deletion(self):
res, req = self.remote_call('add_pending_delete', ['12', 'tags', '13'])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, [])
- deletes = req.get_pending_deletes()
+ deletes = get_pending_deletes(req)
self.assertEquals(deletes, ['12:tags:13'])
res, req = self.remote_call('add_pending_delete', ['12', 'tags', '14'])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, [])
- deletes = req.get_pending_deletes()
+ deletes = get_pending_deletes(req)
self.assertEquals(deletes, ['12:tags:13', '12:tags:14'])
- deletes = req.get_pending_deletes(12)
+ deletes = get_pending_deletes(req, 12)
self.assertEquals(deletes, ['12:tags:13', '12:tags:14'])
- deletes = req.get_pending_deletes(13)
+ deletes = get_pending_deletes(req, 13)
self.assertEquals(deletes, ['12:tags:13'])
- deletes = req.get_pending_deletes(14)
+ deletes = get_pending_deletes(req, 14)
self.assertEquals(deletes, ['12:tags:14'])
req.remove_pending_operations()
def test_remove_pending_operations(self):
self.remote_call('add_pending_delete', ['12', 'tags', '13'])
_, req = self.remote_call('add_pending_inserts', [['12', 'tags', '14']])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, ['12:tags:14'])
- deletes = req.get_pending_deletes()
+ deletes = get_pending_deletes(req)
self.assertEquals(deletes, ['12:tags:13'])
req.remove_pending_operations()
- self.assertEquals(req.get_pending_deletes(), [])
- self.assertEquals(req.get_pending_inserts(), [])
+ self.assertEquals(get_pending_deletes(req), [])
+ self.assertEquals(get_pending_inserts(req), [])
def test_add_inserts(self):
res, req = self.remote_call('add_pending_inserts',
[('12', 'tags', '13'), ('12', 'tags', '14')])
- inserts = req.get_pending_inserts()
+ inserts = get_pending_inserts(req)
self.assertEquals(inserts, ['12:tags:13', '12:tags:14'])
req.remove_pending_operations()
--- a/web/test/unittest_views_basetemplates.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_basetemplates.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,11 +5,11 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.devtools.testlib import WebTest
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.devtools.htmlparser import DTDValidator
-class LogFormTemplateTC(WebTest):
+class LogFormTemplateTC(CubicWebTC):
def _login_labels(self):
valid = self.content_type_validators.get('text/html', DTDValidator)()
--- a/web/test/unittest_views_baseviews.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_baseviews.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,7 +10,7 @@
from logilab.common.testlib import unittest_main
from logilab.mtconverter import html_unescape
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.htmlwidgets import TableWidget
from cubicweb.web.views import vid_from_rset
@@ -18,7 +18,7 @@
def loadjson(value):
return loads(html_unescape(value))
-class VidFromRsetTC(EnvBasedTC):
+class VidFromRsetTC(CubicWebTC):
def test_no_rset(self):
req = self.request()
@@ -47,9 +47,9 @@
def test_more_than_one_entity_same_type(self):
req = self.request()
rset = self.execute('Any X WHERE X is CWUser')
- self.assertEquals(vid_from_rset(req, rset, self.schema), 'adaptedlist')
+ self.assertEquals(vid_from_rset(req, rset, self.schema), 'sameetypelist')
rset = self.execute('Any X, L WHERE X login L')
- self.assertEquals(vid_from_rset(req, rset, self.schema), 'adaptedlist')
+ self.assertEquals(vid_from_rset(req, rset, self.schema), 'sameetypelist')
def test_more_than_one_entity_diff_type(self):
req = self.request()
@@ -84,13 +84,13 @@
self.assertEquals(vid_from_rset(req, rset, self.schema), 'table')
-class TableViewTC(EnvBasedTC):
+class TableViewTC(CubicWebTC):
def _prepare_entity(self):
- e = self.add_entity("State", name=u'<toto>', description=u'loo"ong blabla')
- rset = self.execute('Any X, D, CD, NOW - CD WHERE X is State, X description D, X creation_date CD, X eid %(x)s',
+ req = self.request()
+ e = req.create_entity("State", name=u'<toto>', description=u'loo"ong blabla')
+ rset = req.execute('Any X, D, CD, NOW - CD WHERE X is State, X description D, X creation_date CD, X eid %(x)s',
{'x': e.eid}, 'x')
- req = self.request()
view = self.vreg['views'].select('table', req, rset=rset)
return e, rset, view
--- a/web/test/unittest_views_editforms.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_editforms.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,42 +7,50 @@
"""
from logilab.common.testlib import unittest_main, mock_object
from logilab.common.compat import any
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb.devtools.testlib import WebTest
-from cubicweb.web.views.autoform import AutomaticEntityForm as AEF
+
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.web import uicfg
from cubicweb.web.formwidgets import AutoCompletionWidget
-def rbc(entity, category):
- return [(rschema.type, x) for rschema, tschemas, x in AEF.erelations_by_category(entity, category)]
+
+AFFK = uicfg.autoform_field_kwargs
+AFS = uicfg.autoform_section
-class AutomaticEntityFormTC(EnvBasedTC):
+def rbc(entity, formtype, section):
+ return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section)]
+
+class AutomaticEntityFormTC(CubicWebTC):
def test_custom_widget(self):
- AEF.rfields_kwargs.tag_subject_of(('CWUser', 'login', '*'),
- {'widget': AutoCompletionWidget(autocomplete_initfunc='get_logins')})
+ AFFK.tag_subject_of(('CWUser', 'login', '*'),
+ {'widget': AutoCompletionWidget(autocomplete_initfunc='get_logins')})
form = self.vreg['forms'].select('edition', self.request(),
entity=self.user())
- field = form.field_by_name('login')
+ field = form.field_by_name('login', 'subject')
self.assertIsInstance(field.widget, AutoCompletionWidget)
- AEF.rfields_kwargs.del_rtag('CWUser', 'login', '*', 'subject')
+ AFFK.del_rtag('CWUser', 'login', '*', 'subject')
def test_cwuser_relations_by_category(self):
#for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
# if rtype == 'tags':
# print rtype, role, stype, otype, ':', tag
- e = self.etype_instance('CWUser')
+ e = self.vreg['etypes'].etype_class('CWUser')(self.request())
# see custom configuration in views.cwuser
- self.assertEquals(rbc(e, 'primary'),
+ self.assertEquals(rbc(e, 'main', 'attributes'),
[('login', 'subject'),
('upassword', 'subject'),
+ ('firstname', 'subject'),
+ ('surname', 'subject'),
('in_group', 'subject'),
('eid', 'subject'),
])
- self.assertListEquals(rbc(e, 'secondary'),
- [('firstname', 'subject'),
- ('surname', 'subject')
+ self.assertListEquals(rbc(e, 'muledit', 'attributes'),
+ [('login', 'subject'),
+ ('upassword', 'subject'),
+ ('in_group', 'subject'),
+ ('eid', 'subject'),
])
- self.assertListEquals(rbc(e, 'metadata'),
+ self.assertListEquals(rbc(e, 'main', 'metadata'),
[('last_login_time', 'subject'),
('modification_date', 'subject'),
('created_by', 'subject'),
@@ -51,16 +59,18 @@
('owned_by', 'subject'),
('bookmarked_by', 'object'),
])
- self.assertListEquals(rbc(e, 'generic'),
+ self.assertListEquals(rbc(e, 'main', 'relations'),
[('primary_email', 'subject'),
('custom_workflow', 'subject'),
('connait', 'subject'),
('checked_by', 'object'),
])
+ self.assertListEquals(rbc(e, 'main', 'inlined'),
+ [('use_email', 'subject'),
+ ])
# owned_by is defined both as subject and object relations on CWUser
- self.assertListEquals(rbc(e, 'generated'),
- [('use_email', 'subject'),
- ('in_state', 'subject'),
+ self.assertListEquals(rbc(e, 'main', 'hidden'),
+ [('in_state', 'subject'),
('is', 'subject'),
('is_instance_of', 'subject'),
('has_text', 'subject'),
@@ -74,17 +84,15 @@
])
def test_inlined_view(self):
- self.failUnless(AEF.rinlined.etype_get('CWUser', 'use_email', 'subject'))
- self.failIf(AEF.rinlined.etype_get('CWUser', 'primary_email', 'subject'))
+ self.failUnless('main_inlined' in AFS.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'))
+ self.failIf('main_inlined' in AFS.etype_get('CWUser', 'primary_email', 'subject', 'EmailAddress'))
+ self.failUnless('main_relations' in AFS.etype_get('CWUser', 'primary_email', 'subject', 'EmailAddress'))
def test_personne_relations_by_category(self):
- e = self.etype_instance('Personne')
- self.assertListEquals(rbc(e, 'primary'),
+ e = self.vreg['etypes'].etype_class('Personne')(self.request())
+ self.assertListEquals(rbc(e, 'main', 'attributes'),
[('nom', 'subject'),
- ('eid', 'subject')
- ])
- self.assertListEquals(rbc(e, 'secondary'),
- [('prenom', 'subject'),
+ ('prenom', 'subject'),
('sexe', 'subject'),
('promo', 'subject'),
('titre', 'subject'),
@@ -95,20 +103,25 @@
('datenaiss', 'subject'),
('test', 'subject'),
('description', 'subject'),
- ('salary', 'subject')
+ ('salary', 'subject'),
+ ('eid', 'subject')
])
- self.assertListEquals(rbc(e, 'metadata'),
+ self.assertListEquals(rbc(e, 'muledit', 'attributes'),
+ [('nom', 'subject'),
+ ('eid', 'subject')
+ ])
+ self.assertListEquals(rbc(e, 'main', 'metadata'),
[('creation_date', 'subject'),
('cwuri', 'subject'),
('modification_date', 'subject'),
('created_by', 'subject'),
('owned_by', 'subject'),
])
- self.assertListEquals(rbc(e, 'generic'),
+ self.assertListEquals(rbc(e, 'main', 'relations'),
[('travaille', 'subject'),
('connait', 'object')
])
- self.assertListEquals(rbc(e, 'generated'),
+ self.assertListEquals(rbc(e, 'main', 'hidden'),
[('is', 'subject'),
('has_text', 'subject'),
('identity', 'subject'),
@@ -126,7 +139,7 @@
self.failIf(any(f for f in form.fields if f is None))
-class FormViewsTC(WebTest):
+class FormViewsTC(CubicWebTC):
def test_delete_conf_formview(self):
rset = self.execute('CWGroup X')
self.view('deleteconf', rset, template=None).source
--- a/web/test/unittest_views_navigation.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_navigation.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,14 +7,14 @@
"""
from logilab.common.testlib import unittest_main, mock_object
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.views.navigation import PageNavigation, SortedNavigation
from cubicweb.web.views.ibreadcrumbs import BreadCrumbEntityVComponent
BreadCrumbEntityVComponent.visible = True
-class NavigationTC(EnvBasedTC):
+class NavigationTC(CubicWebTC):
def test_navigation_selection_whatever(self):
req = self.request()
@@ -40,10 +40,10 @@
def test_navigation_selection_not_enough(self):
req = self.request()
rset = self.execute('Any X,N LIMIT 10 WHERE X name N')
- navcomp = self.vreg['components'].select_object('navigation', req, rset=rset)
+ navcomp = self.vreg['components'].select_or_none('navigation', req, rset=rset)
self.assertEquals(navcomp, None)
req.set_search_state('W:X:Y:Z')
- navcomp = self.vreg['components'].select_object('navigation', req, rset=rset)
+ navcomp = self.vreg['components'].select_or_none('navigation', req, rset=rset)
self.assertEquals(navcomp, None)
req.set_search_state('normal')
@@ -96,36 +96,35 @@
# XXX deactivate, contextual component has been removed
-# class ContentNavigationTC(EnvBasedTC):
+# class ContentNavigationTC(CubicWebTC):
+ # def test_component_context(self):
+ # view = mock_object(is_primary=lambda x: True)
+ # rset = self.execute('CWUser X LIMIT 1')
+ # req = self.request()
+ # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # req, rset=rset, view=view, context='navtop')
+ # # breadcrumbs should be in headers by default
+ # clsids = set(obj.id for obj in objs)
+ # self.failUnless('breadcrumbs' in clsids)
+ # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # req, rset=rset, view=view, context='navbottom')
+ # # breadcrumbs should _NOT_ be in footers by default
+ # clsids = set(obj.id for obj in objs)
+ # self.failIf('breadcrumbs' in clsids)
+ # self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
+ # 'P value "navbottom"')
+ # # breadcrumbs should now be in footers
+ # req.cnx.commit()
+ # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # req, rset=rset, view=view, context='navbottom')
-# def test_component_context(self):
-# view = mock_object(is_primary=lambda x: True)
-# rset = self.execute('CWUser X LIMIT 1')
-# req = self.request()
-# objs = self.vreg['contentnavigation'].possible_vobjects(
-# req, rset=rset, view=view, context='navtop')
-# # breadcrumbs should be in headers by default
-# clsids = set(obj.id for obj in objs)
-# self.failUnless('breadcrumbs' in clsids)
-# objs = self.vreg['contentnavigation'].possible_vobjects(
-# req, rset=rset, view=view, context='navbottom')
-# # breadcrumbs should _NOT_ be in footers by default
-# clsids = set(obj.id for obj in objs)
-# self.failIf('breadcrumbs' in clsids)
-# self.execute('INSERT CWProperty P: P pkey "contentnavigation.breadcrumbs.context", '
-# 'P value "navbottom"')
-# # breadcrumbs should now be in footers
-# req.cnx.commit()
-# objs = self.vreg['contentnavigation'].possible_vobjects(
-# req, rset=rset, view=view, context='navbottom')
+ # clsids = [obj.id for obj in objs]
+ # self.failUnless('breadcrumbs' in clsids)
+ # objs = self.vreg['contentnavigation'].poss_visible_objects(
+ # req, rset=rset, view=view, context='navtop')
-# clsids = [obj.id for obj in objs]
-# self.failUnless('breadcrumbs' in clsids)
-# objs = self.vreg['contentnavigation'].possible_vobjects(
-# req, rset=rset, view=view, context='navtop')
-
-# clsids = [obj.id for obj in objs]
-# self.failIf('breadcrumbs' in clsids)
+ # clsids = [obj.id for obj in objs]
+ # self.failIf('breadcrumbs' in clsids)
if __name__ == '__main__':
--- a/web/test/unittest_views_pyviews.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_pyviews.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,12 +1,13 @@
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
-class PyViewsTC(EnvBasedTC):
+class PyViewsTC(CubicWebTC):
def test_pyvaltable(self):
- content = self.vreg['views'].render('pyvaltable', self.request(),
- pyvalue=[[1, 'a'], [2, 'b']],
- headers=['num', 'char'])
+ view = self.vreg['views'].select('pyvaltable', self.request(),
+ pyvalue=[[1, 'a'], [2, 'b']])
+ content = view.render(pyvalue=[[1, 'a'], [2, 'b']],
+ headers=['num', 'char'])
self.assertEquals(content.strip(), '''<table class="listing">
<tr><th>num</th><th>char</th></tr>
<tr><td>1</td><td>a</td></tr>
@@ -14,8 +15,9 @@
</table>''')
def test_pyvallist(self):
- content = self.vreg['views'].render('pyvallist', self.request(),
- pyvalue=[1, 'a'])
+ view = self.vreg['views'].select('pyvallist', self.request(),
+ pyvalue=[1, 'a'])
+ content = view.render(pyvalue=[1, 'a'])
self.assertEquals(content.strip(), '''<ul>
<li>1</li>
<li>a</li>
--- a/web/test/unittest_views_searchrestriction.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_views_searchrestriction.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,11 +5,11 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.facet import insert_attr_select_relation, prepare_facets_rqlst
-class InsertAttrRelationTC(EnvBasedTC):
+class InsertAttrRelationTC(CubicWebTC):
def parse(self, query):
rqlst = self.vreg.parse(self.session, query)
@@ -67,16 +67,16 @@
select = self.parse('DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is CWUser,'
'NOT V in_state VS, VS name "published", V login L')
rschema = self.schema['connait']
- for s, o in rschema.iter_rdefs():
- rschema.set_rproperty(s, o, 'cardinality', '++')
+ for rdefs in rschema.rdefs.values():
+ rdefs.cardinality = '++'
try:
self.assertEquals(self._generate(select, 'in_state', 'subject', 'name'),
"DISTINCT Any A,B ORDERBY B WHERE V is CWUser, "
"NOT V in_state VS, VS name 'published', "
"V in_state A, A name B")
finally:
- for s, o in rschema.iter_rdefs():
- rschema.set_rproperty(s, o, 'cardinality', '**')
+ for rdefs in rschema.rdefs.values():
+ rdefs.cardinality = '**'
def test_nonregr3(self):
#'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
--- a/web/test/unittest_viewselector.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_viewselector.py Mon Feb 08 13:35:07 2010 +0100
@@ -4,8 +4,8 @@
"""
from logilab.common.testlib import unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
-from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
+from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary, UnknownProperty
from cubicweb.selectors import (match_user_groups, implements,
specified_etype_implements, rql_condition,
traced_selection)
@@ -19,33 +19,26 @@
from cubes.folder import views as folderviews
-USERACTIONS = [('myprefs', actions.UserPreferencesAction),
- ('myinfos', actions.UserInfoAction),
- ('logout', actions.LogoutAction)]
-SITEACTIONS = [('siteconfig', actions.SiteConfigurationAction),
- ('manage', actions.ManageAction),
- ('schema', schema.ViewSchemaAction),
- ('siteinfo', actions.SiteInfoAction),
- ]
-FOOTERACTIONS = [('help', wdoc.HelpAction),
- ('changelog', wdoc.ChangeLogAction),
- ('about', wdoc.AboutAction),
- ('poweredby', actions.PoweredByAction)]
+USERACTIONS = [actions.UserPreferencesAction,
+ actions.UserInfoAction,
+ actions.LogoutAction]
+SITEACTIONS = [actions.SiteConfigurationAction,
+ actions.ManageAction,
+ schema.ViewSchemaAction,
+ actions.SiteInfoAction]
+FOOTERACTIONS = [wdoc.HelpAction,
+ wdoc.ChangeLogAction,
+ wdoc.AboutAction,
+ actions.PoweredByAction]
-class ViewSelectorTC(EnvBasedTC):
+class ViewSelectorTC(CubicWebTC):
def setup_database(self):
- self.add_entity('BlogEntry', title=u"une news !", content=u"cubicweb c'est beau")
- self.add_entity('Bookmark', title=u"un signet !", path=u"view?vid=index")
- self.add_entity('EmailAddress', address=u"devel@logilab.fr", alias=u'devel')
- self.add_entity('Tag', name=u'x')
-
- def pactions(self, req, rset):
- resdict = self.vreg['actions'].possible_actions(req, rset)
- for cat, actions in resdict.items():
- resdict[cat] = [(a.id, a.__class__) for a in actions]
- return resdict
-
+ req = self.request()
+ req.create_entity('BlogEntry', title=u"une news !", content=u"cubicweb c'est beau")
+ req.create_entity('Bookmark', title=u"un signet !", path=u"view?vid=index")
+ req.create_entity('EmailAddress', address=u"devel@logilab.fr", alias=u'devel')
+ req.create_entity('Tag', name=u'x')
class VRegistryTC(ViewSelectorTC):
"""test the view selector"""
@@ -80,22 +73,23 @@
('manage', startup.ManageView),
('owl', owl.OWLView),
('propertiesform', cwproperties.CWPropertiesForm),
- ('registry', startup.RegistryView),
+ ('registry', debug.RegistryView),
('schema', schema.SchemaView),
('systempropertiesform', cwproperties.SystemCWPropertiesForm),
('tree', folderviews.FolderTreeView),
])
def test_possible_views_noresult(self):
- rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
+ req = self.request()
+ rset = req.execute('Any X WHERE X eid 999999')
self.assertListEqual(self.pviews(req, rset),
[])
def test_possible_views_one_egroup(self):
- rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
+ req = self.request()
+ rset = req.execute('CWGroup X WHERE X name "managers"')
self.assertListEqual(self.pviews(req, rset),
- [('adaptedlist', baseviews.AdaptedListView),
- ('csvexport', csvexport.CSVRsetView),
+ [('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -105,6 +99,7 @@
('primary', primary.PrimaryView),
('rsetxml', xmlrss.XMLRsetView),
('rss', xmlrss.RSSView),
+ ('sameetypelist', baseviews.SameETypeListView),
('secondary', baseviews.SecondaryView),
('security', management.SecurityManagementView),
('table', tableview.TableView),
@@ -115,10 +110,10 @@
])
def test_possible_views_multiple_egroups(self):
- rset, req = self.env.get_rset_and_req('CWGroup X')
+ req = self.request()
+ rset = req.execute('CWGroup X')
self.assertListEqual(self.pviews(req, rset),
- [('adaptedlist', baseviews.AdaptedListView),
- ('csvexport', csvexport.CSVRsetView),
+ [('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -128,6 +123,7 @@
('primary', primary.PrimaryView),
('rsetxml', xmlrss.XMLRsetView),
('rss', xmlrss.RSSView),
+ ('sameetypelist', baseviews.SameETypeListView),
('secondary', baseviews.SecondaryView),
('security', management.SecurityManagementView),
('table', tableview.TableView),
@@ -139,16 +135,20 @@
def test_propertiesform_admin(self):
assert self.vreg['views']['propertiesform']
- rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
- rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+ req1 = self.request()
+ req2 = self.request()
+ rset1 = req1.execute('CWUser X WHERE X login "admin"')
+ rset2 = req2.execute('CWUser X WHERE X login "anon"')
self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=None))
self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=rset1))
self.failUnless(self.vreg['views'].select('propertiesform', req2, rset=rset2))
def test_propertiesform_anon(self):
self.login('anon')
- rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
- rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+ req1 = self.request()
+ req2 = self.request()
+ rset1 = req1.execute('CWUser X WHERE X login "admin"')
+ rset2 = req2.execute('CWUser X WHERE X login "anon"')
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=None)
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset1)
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset2)
@@ -156,14 +156,17 @@
def test_propertiesform_jdoe(self):
self.create_user('jdoe')
self.login('jdoe')
- rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
- rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "jdoe"')
+ req1 = self.request()
+ req2 = self.request()
+ rset1 = req1.execute('CWUser X WHERE X login "admin"')
+ rset2 = req2.execute('CWUser X WHERE X login "jdoe"')
self.failUnless(self.vreg['views'].select('propertiesform', req1, rset=None))
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'propertiesform', req1, rset=rset1)
self.failUnless(self.vreg['views'].select('propertiesform', req2, rset=rset2))
def test_possible_views_multiple_different_types(self):
- rset, req = self.env.get_rset_and_req('Any X')
+ req = self.request()
+ rset = req.execute('Any X')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
@@ -185,7 +188,8 @@
])
def test_possible_views_any_rset(self):
- rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
+ req = self.request()
+ rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', csvexport.CSVRsetView),
('editable-table', tableview.EditableTableView),
@@ -194,10 +198,10 @@
])
def test_possible_views_multiple_eusers(self):
- rset, req = self.env.get_rset_and_req('CWUser X')
+ req = self.request()
+ rset = req.execute('CWUser X')
self.assertListEqual(self.pviews(req, rset),
- [('adaptedlist', baseviews.AdaptedListView),
- ('csvexport', csvexport.CSVRsetView),
+ [('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -208,6 +212,7 @@
('primary', primary.PrimaryView),
('rsetxml', xmlrss.XMLRsetView),
('rss', xmlrss.RSSView),
+ ('sameetypelist', baseviews.SameETypeListView),
('secondary', baseviews.SecondaryView),
('security', management.SecurityManagementView),
('table', tableview.TableView),
@@ -220,59 +225,65 @@
def test_possible_actions_none_rset(self):
req = self.request()
- self.assertDictEqual(self.pactions(req, None),
+ self.assertDictEqual(self.pactionsdict(req, None, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
})
def test_possible_actions_no_entity(self):
- rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('Any X WHERE X eid 999999')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
})
def test_possible_actions_same_type_entities(self):
- rset, req = self.env.get_rset_and_req('CWGroup X')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('CWGroup X')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
- 'mainactions': [('muledit', actions.MultipleEditAction)],
- 'moreactions': [('delete', actions.DeleteAction),
- ('addentity', actions.AddNewAction)],
+ 'mainactions': [actions.MultipleEditAction],
+ 'moreactions': [actions.DeleteAction,
+ actions.AddNewAction],
})
def test_possible_actions_different_types_entities(self):
- rset, req = self.env.get_rset_and_req('Any X')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('Any X')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
- 'moreactions': [('delete', actions.DeleteAction)],
+ 'moreactions': [actions.DeleteAction],
})
def test_possible_actions_final_entities(self):
- rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
})
def test_possible_actions_eetype_cwuser_entity(self):
- rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWUser"')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('CWEType X WHERE X name "CWUser"')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
- 'mainactions': [('edit', actions.ModifyAction)],
- 'moreactions': [('managepermission', actions.ManagePermissionsAction),
- ('addrelated', actions.AddRelatedActions),
- ('delete', actions.DeleteAction),
- ('copy', actions.CopyAction),
+ 'mainactions': [actions.ModifyAction,
+ actions.ViewSameCWEType],
+ 'moreactions': [actions.ManagePermissionsAction,
+ actions.AddRelatedActions,
+ actions.DeleteAction,
+ actions.CopyAction,
],
})
@@ -289,7 +300,7 @@
class CWUserCreationForm(editforms.CreationFormView):
__select__ = specified_etype_implements('CWUser')
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(CWUserCreationForm)
+ self.vreg.register(CWUserCreationForm)
req.form['etype'] = 'CWUser'
self.assertIsInstance(self.vreg['views'].select('creation', req, rset=rset),
CWUserCreationForm)
@@ -306,7 +317,8 @@
self.vreg['views'].select, 'table', req, rset=rset)
# no entity
- rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
+ req = self.request()
+ rset = req.execute('Any X WHERE X eid 999999')
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'index', req, rset=rset)
self.failUnlessRaises(NoSelectableObject,
@@ -316,7 +328,8 @@
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'table', req, rset=rset)
# one entity
- rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
+ req = self.request()
+ rset = req.execute('CWGroup X WHERE X name "managers"')
self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
primary.PrimaryView)
self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -330,7 +343,8 @@
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'index', req, rset=rset)
# list of entities of the same type
- rset, req = self.env.get_rset_and_req('CWGroup X')
+ req = self.request()
+ rset = req.execute('CWGroup X')
self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
primary.PrimaryView)
self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -340,7 +354,8 @@
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'creation', req, rset=rset)
# list of entities of different types
- rset, req = self.env.get_rset_and_req('Any X')
+ req = self.request()
+ rset = req.execute('Any X')
self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
primary.PrimaryView)
self.assertIsInstance(self.vreg['views'].select('list', req, rset=rset),
@@ -352,7 +367,8 @@
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'index', req, rset=rset)
# whatever
- rset, req = self.env.get_rset_and_req('Any N, X WHERE X in_group Y, Y name N')
+ req = self.request()
+ rset = req.execute('Any N, X WHERE X in_group Y, Y name N')
self.assertIsInstance(self.vreg['views'].select('table', req, rset=rset),
tableview.TableView)
self.failUnlessRaises(NoSelectableObject,
@@ -366,7 +382,8 @@
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'edition', req, rset=rset)
# mixed query
- rset, req = self.env.get_rset_and_req('Any U,G WHERE U is CWUser, G is CWGroup')
+ req = self.request()
+ rset = req.execute('Any U,G WHERE U is CWUser, G is CWGroup')
self.failUnlessRaises(NoSelectableObject,
self.vreg['views'].select, 'edition', req, rset=rset)
self.failUnlessRaises(NoSelectableObject,
@@ -375,22 +392,25 @@
tableview.TableView)
def test_interface_selector(self):
- image = self.add_entity('Image', data_name=u'bim.png', data=Binary('bim'))
+ image = self.request().create_entity('Image', data_name=u'bim.png', data=Binary('bim'))
# image primary view priority
- rset, req = self.env.get_rset_and_req('Image X WHERE X data_name "bim.png"')
+ req = self.request()
+ rset = req.execute('Image X WHERE X data_name "bim.png"')
self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
idownloadable.IDownloadablePrimaryView)
def test_score_entity_selector(self):
- image = self.add_entity('Image', data_name=u'bim.png', data=Binary('bim'))
+ image = self.request().create_entity('Image', data_name=u'bim.png', data=Binary('bim'))
# image primary view priority
- rset, req = self.env.get_rset_and_req('Image X WHERE X data_name "bim.png"')
+ req = self.request()
+ rset = req.execute('Image X WHERE X data_name "bim.png"')
self.assertIsInstance(self.vreg['views'].select('image', req, rset=rset),
idownloadable.ImageView)
- fileobj = self.add_entity('File', data_name=u'bim.txt', data=Binary('bim'))
+ fileobj = self.request().create_entity('File', data_name=u'bim.txt', data=Binary('bim'))
# image primary view priority
- rset, req = self.env.get_rset_and_req('File X WHERE X data_name "bim.txt"')
+ req = self.request()
+ rset = req.execute('File X WHERE X data_name "bim.txt"')
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'image', req, rset=rset)
@@ -400,9 +420,11 @@
rset = None
req = self.request()
else:
- rset, req = self.env.get_rset_and_req(rql)
+ req = self.request()
+ rset = req.execute(rql)
try:
- self.vreg['views'].render(vid, req, rset=rset, **args)
+ obj = self.vreg['views'].select(vid, req, rset=rset, **args)
+ return obj.render(**args)
except:
print vid, rset, args
raise
@@ -431,13 +453,13 @@
self.assertEquals(self.vreg.property_value('boxes.edit_box.order'), 2)
self.assertEquals(self.vreg.property_value('boxes.possible_views_box.visible'), False)
self.assertEquals(self.vreg.property_value('boxes.possible_views_box.order'), 10)
- self.assertRaises(KeyError, self.vreg.property_value, 'boxes.actions_box')
+ self.assertRaises(UnknownProperty, self.vreg.property_value, 'boxes.actions_box')
class CWETypeRQLAction(Action):
- id = 'testaction'
+ __regid__ = 'testaction'
__select__ = implements('CWEType') & rql_condition('X name "CWEType"')
title = 'bla'
@@ -446,37 +468,40 @@
def setUp(self):
super(RQLActionTC, self).setUp()
self.vreg._loadedmods[__name__] = {}
- self.vreg.register_appobject_class(CWETypeRQLAction)
+ self.vreg.register(CWETypeRQLAction)
+ actionsreg = self.vreg['actions']
+ actionsreg[0].__registered__(actionsreg)
def tearDown(self):
super(RQLActionTC, self).tearDown()
del self.vreg['actions']['testaction']
def test(self):
- rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWEType"')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('CWEType X WHERE X name "CWEType"')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
- 'mainactions': [('edit', actions.ModifyAction)],
- 'moreactions': [('managepermission', actions.ManagePermissionsAction),
- ('addrelated', actions.AddRelatedActions),
- ('delete', actions.DeleteAction),
- ('copy', actions.CopyAction),
- ('testaction', CWETypeRQLAction),
+ 'mainactions': [actions.ModifyAction, actions.ViewSameCWEType],
+ 'moreactions': [actions.ManagePermissionsAction,
+ actions.AddRelatedActions,
+ actions.DeleteAction,
+ actions.CopyAction,
+ CWETypeRQLAction,
],
})
- rset, req = self.env.get_rset_and_req('CWEType X WHERE X name "CWRType"')
- self.assertDictEqual(self.pactions(req, rset),
+ req = self.request()
+ rset = req.execute('CWEType X WHERE X name "CWRType"')
+ self.assertDictEqual(self.pactionsdict(req, rset, skipcategories=()),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
'footer': FOOTERACTIONS,
- 'mainactions': [('edit', actions.ModifyAction)],
- 'moreactions': [('managepermission', actions.ManagePermissionsAction),
- ('addrelated', actions.AddRelatedActions),
- ('delete', actions.DeleteAction),
- ('copy', actions.CopyAction),
- ],
+ 'mainactions': [actions.ModifyAction, actions.ViewSameCWEType],
+ 'moreactions': [actions.ManagePermissionsAction,
+ actions.AddRelatedActions,
+ actions.DeleteAction,
+ actions.CopyAction,]
})
--- a/web/test/unittest_webconfig.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/test/unittest_webconfig.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,8 +9,7 @@
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools._apptest import FakeRequest
-from cubicweb.devtools import ApptestConfiguration
+from cubicweb.devtools import ApptestConfiguration, fake
class WebconfigTC(TestCase):
def setUp(self):
@@ -21,7 +20,7 @@
def test_nonregr_print_css_as_list(self):
"""make sure PRINT_CSS *must* is a list"""
config = self.config
- req = FakeRequest()
+ req = fake.FakeRequest()
print_css = req.external_resource('STYLESHEETS_PRINT')
self.failUnless(isinstance(print_css, list))
ie_css = req.external_resource('IE_STYLESHEETS')
--- a/web/uicfg.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/uicfg.py Mon Feb 08 13:35:07 2010 +0100
@@ -57,46 +57,80 @@
simple boolean relation tags used to control the "add entity" submenu.
Relations whose rtag is True will appears, other won't.
+
Automatic form configuration
````````````````````````````
+:autoform_section:
+ where to display a relation in entity form, according to form type.
+ `tag_attribute`, `tag_subject_of` and `tag_object_of` methods for this
+ relation tags expect two arguments additionaly to the relation key: a
+ `formtype` and a `section`.
+
+ formtype may be one of:
+ * 'main', the main entity form
+ * 'inlined', the form for an entity inlined into another's one
+ * 'muledit', the multiple entity (table) form
+
+ section may be one of:
+ * 'hidden', don't display
+ * 'attributes', display in the attributes section
+ * 'relations', display in the relations section, using the generic relation
+ selector combobox (available in main form only, and not for attribute
+ relation)
+ * 'inlined', display target entity of the relation in an inlined form
+ (available in main form only, and not for attribute relation)
+ * 'metadata', display in a special metadata form (NOT YET IMPLEMENTED,
+ subject to changes)
+
+:autoform_field:
+ specify a custom field instance to use for a relation
+
+:autoform_field_kwargs:
+ specify a dictionnary of arguments to give to the field constructor for a
+ relation. You usually want to use either `autoform_field` or
+ `autoform_field_kwargs`, not both. The later won't have any effect if the
+ former is specified for a relation.
+
+:autoform_permissions_overrides:
+
+ provide a way to by-pass security checking for dark-corner case where it can't
+ be verified properly. XXX documents.
+
:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
+
from cubicweb import neg_role
-from cubicweb.rtags import (RelationTags, RelationTagsBool,
- RelationTagsSet, RelationTagsDict)
+from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
+ RelationTagsDict, register_rtag, _ensure_str_key)
+from cubicweb.schema import META_RTYPES
from cubicweb.web import formwidgets
-def card_from_role(card, role):
- if role == 'subject':
- return card[0]
- assert role in ('object', 'sobject'), repr(role)
- return card[1]
-
# primary view configuration ##################################################
def init_primaryview_section(rtag, sschema, rschema, oschema, role):
if rtag.get(sschema, rschema, oschema, role) is None:
- card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role)
- composed = rschema.rproperty(sschema, oschema, 'composite') == neg_role(role)
+ rdef = rschema.rdef(sschema, oschema)
if rschema.final:
if rschema.meta or sschema.is_metadata(rschema) \
or oschema.type in ('Password', 'Bytes'):
section = 'hidden'
else:
section = 'attributes'
- elif card in '1+':
- section = 'attributes'
- elif composed:
- section = 'relations'
else:
- section = 'sideboxes'
+ if rdef.role_cardinality(role) in '1+':
+ section = 'attributes'
+ elif rdef.composite == neg_role(role):
+ section = 'relations'
+ else:
+ section = 'sideboxes'
rtag.tag_relation((sschema, rschema, oschema, role), section)
primaryview_section = RelationTags('primaryview_section',
@@ -144,6 +178,7 @@
primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
init_primaryview_display_ctrl)
+
# index view configuration ####################################################
# entity type section in the index/manage page. May be one of
# * 'application'
@@ -152,48 +187,225 @@
# * 'hidden'
# * 'subobject' (not displayed by default)
-indexview_etype_section = {'EmailAddress': 'subobject',
- 'CWUser': 'system',
- 'CWGroup': 'system',
- 'CWPermission': 'system',
- 'CWCache': 'system',
- 'Workflow': 'system',
- 'State': 'hidden',
- 'BaseTransition': 'hidden',
- 'Transition': 'hidden',
- 'WorkflowTransition': 'hidden',
- }
+class InitializableDict(dict):
+ def __init__(self, *args, **kwargs):
+ super(InitializableDict, self).__init__(*args, **kwargs)
+ register_rtag(self)
+ self.__defaults = dict(self)
+ def init(self, schema, check=True):
+ self.update(self.__defaults)
+ for eschema in schema.entities():
+ if eschema.final:
+ continue
+ if eschema.schema_entity():
+ self.setdefault(eschema, 'schema')
+ elif eschema.is_subobject(strict=True):
+ self.setdefault(eschema, 'subobject')
+ else:
+ self.setdefault(eschema, 'application')
+
+indexview_etype_section = InitializableDict(
+ EmailAddress='subobject',
+ # entity types in the 'system' table by default (managers only)
+ CWUser='system', CWGroup='system',
+ CWPermission='system',
+ CWCache='system',
+ Workflow='system',
+ ExternalUri='system',
+ Bookmark='system',
+ )
# autoform.AutomaticEntityForm configuration ##################################
-# relations'section (eg primary/secondary/generic/metadata/generated)
+def _formsections_as_dict(formsections):
+ result = {}
+ for formsection in formsections:
+ formtype, section = formsection.split('_', 1)
+ result[formtype] = section
+ return result
+
+def _card_and_comp(sschema, rschema, oschema, role):
+ rdef = rschema.rdef(sschema, oschema)
+ if role == 'subject':
+ card = rdef.cardinality[0]
+ composed = not rschema.final and rdef.composite == 'object'
+ else:
+ card = rdef.cardinality[1]
+ composed = not rschema.final and rdef.composite == 'subject'
+ return card, composed
+
+class AutoformSectionRelationTags(RelationTagsSet):
+ """autoform relations'section"""
+
+ bw_tag_map = {
+ 'primary': {'main': 'attributes', 'muledit': 'attributes'},
+ 'secondary': {'main': 'attributes', 'muledit': 'hidden'},
+ 'metadata': {'main': 'metadata'},
+ 'generic': {'main': 'relations'},
+ 'generated': {'main': 'hidden'},
+ }
+
+ _allowed_form_types = ('main', 'inlined', 'muledit')
+ _allowed_values = {'main': ('attributes', 'inlined', 'relations',
+ 'metadata', 'hidden'),
+ 'inlined': ('attributes', 'hidden'),
+ 'muledit': ('attributes', 'hidden'),
+ }
-def init_autoform_section(rtag, sschema, rschema, oschema, role):
- if rtag.get(sschema, rschema, oschema, role) is None:
- if autoform_is_inlined.get(sschema, rschema, oschema, role) or \
- autoform_is_inlined.get(sschema, rschema, oschema, neg_role(role)):
- section = 'generated'
- elif sschema.is_metadata(rschema):
- section = 'metadata'
- else:
- if role == 'subject':
- card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
- composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+ def init(self, schema, check=True):
+ super(AutoformSectionRelationTags, self).init(schema, check)
+ self.apply(schema, self._initfunc_step2)
+
+ @staticmethod
+ def _initfunc(self, sschema, rschema, oschema, role):
+ formsections = self.init_get(sschema, rschema, oschema, role)
+ if formsections is None:
+ formsections = self.tag_container_cls()
+ if not any(tag.startswith('inlined') for tag in formsections):
+ if not rschema.final:
+ negsects = self.init_get(sschema, rschema, oschema, neg_role(role))
+ if 'main_inlined' in negsects:
+ formsections.add('inlined_hidden')
+ key = _ensure_str_key( (sschema, rschema, oschema, role) )
+ self._tagdefs[key] = formsections
+
+ @staticmethod
+ def _initfunc_step2(self, sschema, rschema, oschema, role):
+ formsections = self.get(sschema, rschema, oschema, role)
+ sectdict = _formsections_as_dict(formsections)
+ if rschema in META_RTYPES:
+ sectdict.setdefault('main', 'hidden')
+ sectdict.setdefault('muledit', 'hidden')
+ sectdict.setdefault('inlined', 'hidden')
+ # ensure we have a tag for each form type
+ if not 'main' in sectdict:
+ if not rschema.final and (
+ sectdict.get('inlined') == 'attributes' or
+ 'inlined_attributes' in self.init_get(sschema, rschema, oschema,
+ neg_role(role))):
+ sectdict['main'] = 'hidden'
+ elif sschema.is_metadata(rschema):
+ sectdict['main'] = 'metadata'
else:
- card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
- composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
- if card in '1+':
- section = 'primary'
- elif rschema.final:
- section = 'secondary'
- else:
- section = 'generic'
- rtag.tag_relation((sschema, rschema, oschema, role), section)
+ card, composed = _card_and_comp(sschema, rschema, oschema, role)
+ if card in '1+':
+ sectdict['main'] = 'attributes'
+ if not 'muledit' in sectdict:
+ sectdict['muledit'] = 'attributes'
+ elif rschema.final:
+ sectdict['main'] = 'attributes'
+ else:
+ sectdict['main'] = 'relations'
+ if not 'muledit' in sectdict:
+ sectdict['muledit'] = 'hidden'
+ if sectdict['main'] == 'attributes':
+ card, composed = _card_and_comp(sschema, rschema, oschema, role)
+ if card in '1+' and not composed:
+ sectdict['muledit'] = 'attributes'
+ if not 'inlined' in sectdict:
+ sectdict['inlined'] = sectdict['main']
+ # recompute formsections and set it to avoid recomputing
+ for formtype, section in sectdict.iteritems():
+ formsections.add('%s_%s' % (formtype, section))
+
+ def tag_relation(self, key, formtype, section=None):
+ if section is None:
+ tag = formtype
+ for formtype, section in self.bw_tag_map[tag].iteritems():
+ warn('[3.6] add tag to autoform section by specifying form '
+ 'type and tag. Replace %s by formtype="%s", section="%s"'
+ % (tag, formtype, section), DeprecationWarning,
+ stacklevel=3)
+ self.tag_relation(key, formtype, section)
+ assert formtype in self._allowed_form_types, \
+ 'formtype should be in (%s), not %s' % (
+ ','.join(self._allowed_form_types), formtype)
+ assert section in self._allowed_values[formtype], \
+ 'section for %s should be in (%s), not %s' % (
+ formtype, ','.join(self._allowed_values[formtype]), section)
+ rtags = self._tagdefs.setdefault(_ensure_str_key(key),
+ self.tag_container_cls())
+ # remove previous section for this form type if any
+ if rtags:
+ for tag in rtags.copy():
+ if tag.startswith(formtype):
+ rtags.remove(tag)
+ rtags.add('%s_%s' % (formtype, section))
+ return rtags
+
+ def init_get(self, *key):
+ return super(AutoformSectionRelationTags, self).get(*key)
+
+ def get(self, *key):
+ # overriden to avoid recomputing done in parent classes
+ return self._tagdefs.get(key, ())
+
+ def relations_by_section(self, entity, formtype, section,
+ permission=None, strict=False):
+ """return a list of (relation schema, target schemas, role) for the
+ given entity matching categories and permission.
-autoform_section = RelationTags('autoform_section', init_autoform_section,
- set(('primary', 'secondary', 'generic',
- 'metadata', 'generated')))
+ `strict`:
+ bool telling if having local role is enough (strict = False) or not
+ """
+ tag = '%s_%s' % (formtype, section)
+ eschema = entity.e_schema
+ permsoverrides = autoform_permissions_overrides
+ if entity.has_eid():
+ eid = entity.eid
+ else:
+ eid = None
+ strict = False
+ cw = entity._cw
+ for rschema, targetschemas, role in eschema.relation_definitions(True):
+ # check category first, potentially lower cost than checking
+ # permission which may imply rql queries
+ _targetschemas = []
+ for tschema in targetschemas:
+ if not tag in self.etype_get(eschema, rschema, role, tschema):
+ continue
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if permission is not None and \
+ not ((not strict and rdef.has_local_role(permission)) or
+ rdef.has_perm(cw, permission, fromeid=eid)):
+ continue
+ _targetschemas.append(tschema)
+ if not _targetschemas:
+ continue
+ targetschemas = _targetschemas
+ if permission is not None:
+ rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
+ # tag allowing to hijack the permission machinery when
+ # permission is not verifiable until the entity is actually
+ # created...
+ if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+ yield (rschema, targetschemas, role)
+ continue
+ if rschema.final:
+ if not rdef.has_perm(cw, permission, fromeid=eid):
+ continue
+ elif role == 'subject':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (permission == 'add'
+ and rdef.role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rdef.has_perm(cw, 'delete', fromeid=eid,
+ toeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ elif role == 'object':
+ # on relation with cardinality 1 or ?, we need delete perm as well
+ # if the relation is already set
+ if (permission == 'add'
+ and rdef.role_cardinality(role) in '1?'
+ and eid and entity.related(rschema.type, role)
+ and not rdef.has_perm(cw, 'delete', toeid=eid,
+ fromeid=entity.related(rschema.type, role)[0][0])):
+ continue
+ yield (rschema, targetschemas, role)
+
+autoform_section = AutoformSectionRelationTags('autoform_section')
# relations'field class
autoform_field = RelationTags('autoform_field')
@@ -201,11 +413,6 @@
# relations'field explicit kwargs (given to field's __init__)
autoform_field_kwargs = RelationTagsDict()
-# inlined view flag for non final relations: when True for an entry, the
-# entity(ies) at the other end of the relation will be editable from the
-# form of the edited entity
-autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
-
# set of tags of the form <action>_on_new on relations. <action> is a
# schema action (add/update/delete/read), and when such a tag is found
@@ -217,10 +424,29 @@
# 'link' / 'create' relation tags, used to control the "add entity" submenu
def init_actionbox_appearsin_addmenu(rtag, sschema, rschema, oschema, role):
if rtag.get(sschema, rschema, oschema, role) is None:
- card = rschema.rproperty(sschema, oschema, 'cardinality')[role == 'object']
- if not card in '?1' and \
- rschema.rproperty(sschema, oschema, 'composite') == role:
+ if rschema in META_RTYPES:
+ rtag.tag_relation((sschema, rschema, oschema, role), False)
+ return
+ rdef = rschema.rdef(sschema, oschema)
+ if not rdef.role_cardinality(role) in '?1' and rdef.composite == role:
rtag.tag_relation((sschema, rschema, oschema, role), True)
actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
init_actionbox_appearsin_addmenu)
+
+
+# deprecated ###################################################################
+
+class AutoformIsInlined(RelationTags):
+ """XXX for < 3.6 bw compat"""
+ def tag_relation(self, key, tag):
+ warn('autoform_is_inlined rtag is deprecated, use autoform_section '
+ 'with inlined formtype and "attributes" or "hidden" section',
+ DeprecationWarning, stacklevel=3)
+ section = tag and 'inlined' or 'hidden'
+ autoform_section.tag_relation(key, 'main', section)
+
+# inlined view flag for non final relations: when True for an entry, the
+# entity(ies) at the other end of the relation will be editable from the
+# form of the edited entity
+autoform_is_inlined = AutoformIsInlined('autoform_is_inlined')
--- a/web/views/__init__.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/__init__.py Mon Feb 08 13:35:07 2010 +0100
@@ -82,7 +82,7 @@
return 'primary'
return 'outofcontext-search'
if len(rset.column_types(0)) == 1:
- return 'adaptedlist'
+ return 'sameetypelist'
return 'list'
return 'table'
@@ -110,7 +110,7 @@
self.cell_call()
def cell_call(self, row=0, col=0):
- self.row, self.col = row, col # in case one needs it
+ self.cw_row, self.cw_col = row, col # in case one needs it
fd, tmpfile = tempfile.mkstemp('.png')
os.close(fd)
self._generate(tmpfile)
--- a/web/views/actions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/actions.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,17 +8,16 @@
__docformat__ = "restructuredtext en"
_ = unicode
+from cubicweb.schema import display_name
from cubicweb.appobject import objectify_selector
from cubicweb.selectors import (EntitySelector, yes,
- one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
+ one_line_rset, multi_lines_rset, one_etype_rset, relation_possible,
nonempty_rset, non_final_entity,
authenticated_user, match_user_groups, match_search_state,
- has_permission, has_add_permission,
+ has_permission, has_add_permission, implements,
)
-from cubicweb.web import uicfg, controller
-from cubicweb.web.action import Action
+from cubicweb.web import uicfg, controller, action
from cubicweb.web.views import linksearch_select_url, vid_from_rset
-from cubicweb.web.views.autoform import AutomaticEntityForm
class has_editable_relation(EntitySelector):
@@ -31,11 +30,17 @@
def score_entity(self, entity):
# if user has no update right but it can modify some relation,
# display action anyway
- for dummy in AutomaticEntityForm.esrelations_by_category(
- entity, 'generic', 'add', strict=True):
+ form = entity._cw.vreg['forms'].select('edition', entity._cw,
+ entity=entity)
+ for dummy in form.editable_relations():
return 1
- for rschema, targetschemas, role in AutomaticEntityForm.erelations_by_category(
- entity, ('primary', 'secondary'), 'add', strict=True):
+ try:
+ editableattrs = form.editable_attributes(strict=True)
+ except TypeError:
+ warn('[3.6] %s: editable_attributes now take strict=False as '
+ 'optional argument', DeprecationWarning)
+ editableattrs = form.editable_attributes()
+ for rschema, role in editableattrs:
if not rschema.final:
return 1
return 0
@@ -48,7 +53,7 @@
def view_is_not_default_view(cls, req, rset=None, **kwargs):
# interesting if it propose another view than the current one
vid = req.form.get('vid')
- if vid and vid != vid_from_rset(req, rset, cls.schema):
+ if vid and vid != vid_from_rset(req, rset, req.vreg.schema):
return 1
return 0
@@ -61,7 +66,7 @@
select = rqlst.children[0]
if len(select.defined_vars) == 1 and len(select.solutions) == 1:
rset._searched_etype = select.solutions[0].itervalues().next()
- eschema = cls.schema.eschema(rset._searched_etype)
+ eschema = req.vreg.schema.eschema(rset._searched_etype)
if not (eschema.final or eschema.is_subobject(strict=True)) \
and eschema.has_perm(req, 'add'):
return 1
@@ -69,24 +74,25 @@
# generic 'main' actions #######################################################
-class SelectAction(Action):
+class SelectAction(action.Action):
"""base class for link search actions. By default apply on
any size entity result search it the current state is 'linksearch'
if accept match.
"""
- id = 'select'
- __select__ = match_search_state('linksearch') & nonempty_rset() & match_searched_etype()
+ __regid__ = 'select'
+ __select__ = (match_search_state('linksearch') & nonempty_rset()
+ & match_searched_etype())
title = _('select')
category = 'mainactions'
order = 0
def url(self):
- return linksearch_select_url(self.req, self.rset)
+ return linksearch_select_url(self._cw, self.cw_rset)
-class CancelSelectAction(Action):
- id = 'cancel'
+class CancelSelectAction(action.Action):
+ __regid__ = 'cancel'
__select__ = match_search_state('linksearch')
title = _('cancel select')
@@ -94,14 +100,14 @@
order = 10
def url(self):
- target, eid, r_type, searched_type = self.req.search_state[1]
- return self.build_url(str(eid),
- vid='edition', __mode='normal')
+ target, eid, r_type, searched_type = self._cw.search_state[1]
+ return self._cw.build_url(str(eid),
+ vid='edition', __mode='normal')
-class ViewAction(Action):
- id = 'view'
- __select__ = (match_search_state('normal') &
+class ViewAction(action.Action):
+ __regid__ = 'view'
+ __select__ = (action.Action.__select__ &
match_user_groups('users', 'managers') &
view_is_not_default_view() &
non_final_entity())
@@ -111,17 +117,16 @@
order = 0
def url(self):
- params = self.req.form.copy()
+ params = self._cw.form.copy()
for param in ('vid', '__message') + controller.NAV_FORM_PARAMETERS:
params.pop(param, None)
- return self.build_url(self.req.relative_path(includeparams=False),
- **params)
+ return self._cw.build_url(self._cw.relative_path(includeparams=False),
+ **params)
-class ModifyAction(Action):
- id = 'edit'
- __select__ = (match_search_state('normal') &
- one_line_rset() &
+class ModifyAction(action.Action):
+ __regid__ = 'edit'
+ __select__ = (action.Action.__select__ & one_line_rset() &
(has_permission('update') | has_editable_relation('add')))
title = _('modify')
@@ -129,73 +134,73 @@
order = 10
def url(self):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
return entity.absolute_url(vid='edition')
-class MultipleEditAction(Action):
- id = 'muledit' # XXX get strange conflicts if id='edit'
- __select__ = (match_search_state('normal') &
- two_lines_rset() & one_etype_rset() &
- has_permission('update'))
+class MultipleEditAction(action.Action):
+ __regid__ = 'muledit' # XXX get strange conflicts if id='edit'
+ __select__ = (action.Action.__select__ & multi_lines_rset() &
+ one_etype_rset() & has_permission('update'))
title = _('modify')
category = 'mainactions'
order = 10
def url(self):
- return self.build_url('view', rql=self.rset.rql, vid='muledit')
+ return self._cw.build_url('view', rql=self.cw_rset.rql, vid='muledit')
# generic "more" actions #######################################################
-class ManagePermissionsAction(Action):
- id = 'managepermission'
- __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
+class ManagePermissionsAction(action.Action):
+ __regid__ = 'managepermission'
+ __select__ = (action.Action.__select__ & one_line_rset() &
+ non_final_entity() & match_user_groups('managers'))
title = _('manage permissions')
category = 'moreactions'
order = 15
@classmethod
- def registered(cls, vreg):
- super(ManagePermissionsAction, cls).registered(vreg)
- if 'require_permission' in vreg.schema:
+ def __registered__(cls, reg):
+ if 'require_permission' in reg.schema:
cls.__select__ = (one_line_rset() & non_final_entity() &
(match_user_groups('managers')
| relation_possible('require_permission', 'subject', 'CWPermission',
action='add')))
- return super(ManagePermissionsAction, cls).registered(vreg)
+ return super(ManagePermissionsAction, cls).__registered__(reg)
def url(self):
- return self.rset.get_entity(self.row or 0, self.col or 0).absolute_url(vid='security')
+ return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).absolute_url(vid='security')
-class DeleteAction(Action):
- id = 'delete'
- __select__ = has_permission('delete')
+class DeleteAction(action.Action):
+ __regid__ = 'delete'
+ __select__ = action.Action.__select__ & has_permission('delete')
title = _('delete')
category = 'moreactions'
order = 20
def url(self):
- if len(self.rset) == 1:
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
- return self.build_url(entity.rest_path(), vid='deleteconf')
- return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
+ if len(self.cw_rset) == 1:
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ return self._cw.build_url(entity.rest_path(), vid='deleteconf')
+ return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='deleteconf')
-class CopyAction(Action):
- id = 'copy'
- __select__ = one_line_rset() & has_permission('add')
+class CopyAction(action.Action):
+ __regid__ = 'copy'
+ __select__ = (action.Action.__select__ & one_line_rset()
+ & has_permission('add'))
title = _('copy')
category = 'moreactions'
order = 30
def url(self):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
return entity.absolute_url(vid='copy')
@@ -203,10 +208,10 @@
"""when we're seeing more than one entity with the same type, propose to
add a new one
"""
- id = 'addentity'
- __select__ = (match_search_state('normal') &
+ __regid__ = 'addentity'
+ __select__ = (action.Action.__select__ &
(addable_etype_empty_rset()
- | (two_lines_rset() & one_etype_rset & has_add_permission()))
+ | (multi_lines_rset() & one_etype_rset() & has_add_permission()))
)
category = 'moreactions'
@@ -214,22 +219,22 @@
@property
def rsettype(self):
- if self.rset:
- return self.rset.description[0][0]
- return self.rset._searched_etype
+ if self.cw_rset:
+ return self.cw_rset.description[0][0]
+ return self.cw_rset._searched_etype
@property
def title(self):
- return self.req.__('add a %s' % self.rsettype) # generated msgid
+ return self._cw.__('add a %s' % self.rsettype) # generated msgid
def url(self):
- return self.build_url('add/%s' % self.rsettype)
+ return self._cw.build_url('add/%s' % self.rsettype)
-class AddRelatedActions(Action):
+class AddRelatedActions(action.Action):
"""fill 'addrelated' sub-menu of the actions box"""
- id = 'addrelated'
- __select__ = Action.__select__ & one_line_rset() & non_final_entity()
+ __regid__ = 'addrelated'
+ __select__ = action.Action.__select__ & one_line_rset() & non_final_entity()
submenu = _('addrelated')
order = 20
@@ -237,11 +242,11 @@
def fill_menu(self, box, menu):
# when there is only one item in the sub-menu, replace the sub-menu by
# item's title prefixed by 'add'
- menu.label_prefix = self.req._('add')
+ menu.label_prefix = self._cw._('add')
super(AddRelatedActions, self).fill_menu(box, menu)
def actual_actions(self):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
eschema = entity.e_schema
for rschema, teschema, x in self.add_related_schemas(entity):
if x == 'subject':
@@ -250,7 +255,7 @@
else:
label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
url = self.linkto_url(entity, rschema, teschema, 'subject')
- yield self.build_action(self.req._(label), url)
+ yield self.build_action(self._cw._(label), url)
def add_related_schemas(self, entity):
"""this is actually used ui method to generate 'addrelated' actions from
@@ -261,40 +266,60 @@
them by using uicfg.actionbox_appearsin_addmenu
"""
appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
- req = self.req
+ req = self._cw
eschema = entity.e_schema
for role, rschemas in (('subject', eschema.subject_relations()),
('object', eschema.object_relations())):
for rschema in rschemas:
if rschema.final:
continue
- # check the relation can be added as well
- # XXX consider autoform_permissions_overrides?
- if role == 'subject'and not rschema.has_perm(req, 'add',
- fromeid=entity.eid):
- continue
- if role == 'object'and not rschema.has_perm(req, 'add',
- toeid=entity.eid):
- continue
- # check the target types can be added as well
for teschema in rschema.targets(eschema, role):
if not appearsin_addmenu.etype_get(eschema, rschema,
role, teschema):
continue
- if teschema.has_local_role('add') or teschema.has_perm(req, 'add'):
+ rdef = rschema.role_rdef(eschema, teschema, role)
+ # check the relation can be added
+ # XXX consider autoform_permissions_overrides?
+ if role == 'subject'and not rdef.has_perm(
+ req, 'add', fromeid=entity.eid):
+ continue
+ if role == 'object'and not rdef.has_perm(
+ req, 'add', toeid=entity.eid):
+ continue
+ # check the target types can be added as well
+ if teschema.may_have_permission('add', req):
yield rschema, teschema, role
def linkto_url(self, entity, rtype, etype, target):
- return self.build_url('add/%s' % etype,
- __linkto='%s:%s:%s' % (rtype, entity.eid, target),
- __redirectpath=entity.rest_path(), # should not be url quoted!
- __redirectvid=self.req.form.get('vid', ''))
+ return self._cw.build_url('add/%s' % etype,
+ __linkto='%s:%s:%s' % (rtype, entity.eid, target),
+ __redirectpath=entity.rest_path(), # should not be url quoted!
+ __redirectvid=self._cw.form.get('vid', ''))
+
+class ViewSameCWEType(action.Action):
+ """when displaying the schema of a CWEType, offer to list entities of that type
+ """
+ __regid__ = 'entitiesoftype'
+ __select__ = one_line_rset() & implements('CWEType')
+ category = 'mainactions'
+ order = 40
+
+ @property
+ def etype(self):
+ return self.cw_rset.get_entity(0,0).name
+
+ @property
+ def title(self):
+ return self._cw.__('view all %s') % display_name(self._cw, self.etype, 'plural').lower()
+
+ def url(self):
+ return self._cw.build_url(self.etype)
# logged user actions #########################################################
-class UserPreferencesAction(Action):
- id = 'myprefs'
+class UserPreferencesAction(action.Action):
+ __regid__ = 'myprefs'
__select__ = authenticated_user()
title = _('user preferences')
@@ -302,11 +327,11 @@
order = 10
def url(self):
- return self.build_url(self.id)
+ return self._cw.build_url(self.__regid__)
-class UserInfoAction(Action):
- id = 'myinfos'
+class UserInfoAction(action.Action):
+ __regid__ = 'myinfos'
__select__ = authenticated_user()
title = _('personnal informations')
@@ -314,11 +339,11 @@
order = 20
def url(self):
- return self.build_url('cwuser/%s'%self.req.user.login, vid='edition')
+ return self._cw.build_url('cwuser/%s'%self._cw.user.login, vid='edition')
-class LogoutAction(Action):
- id = 'logout'
+class LogoutAction(action.Action):
+ __regid__ = 'logout'
__select__ = authenticated_user()
title = _('logout')
@@ -326,41 +351,43 @@
order = 30
def url(self):
- return self.build_url(self.id)
+ return self._cw.build_url(self.__regid__)
# site actions ################################################################
-class ManagersAction(Action):
+class ManagersAction(action.Action):
__abstract__ = True
__select__ = match_user_groups('managers')
category = 'siteactions'
def url(self):
- return self.build_url(self.id)
+ return self._cw.build_url(self.__regid__)
class SiteConfigurationAction(ManagersAction):
- id = 'siteconfig'
+ __regid__ = 'siteconfig'
title = _('site configuration')
order = 10
class ManageAction(ManagersAction):
- id = 'manage'
+ __regid__ = 'manage'
title = _('manage')
order = 20
class SiteInfoAction(ManagersAction):
- id = 'siteinfo'
+ __regid__ = 'siteinfo'
+ __select__ = match_user_groups('users','managers')
title = _('info')
order = 30
- __select__ = match_user_groups('users','managers')
-class PoweredByAction(Action):
- id = 'poweredby'
+# footer actions ###############################################################
+
+class PoweredByAction(action.Action):
+ __regid__ = 'poweredby'
__select__ = yes()
category = 'footer'
@@ -371,21 +398,9 @@
return 'http://www.cubicweb.org'
-from logilab.common.deprecation import class_moved
-from cubicweb.web.views.bookmark import FollowAction
-FollowAction = class_moved(FollowAction)
-
## default actions ui configuration ###########################################
addmenu = uicfg.actionbox_appearsin_addmenu
-addmenu.tag_subject_of(('*', 'is', '*'), False)
-addmenu.tag_object_of(('*', 'is', '*'), False)
-addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
-addmenu.tag_object_of(('*', 'is_instance_of', '*'), False)
-addmenu.tag_subject_of(('*', 'identity', '*'), False)
-addmenu.tag_object_of(('*', 'identity', '*'), False)
-addmenu.tag_subject_of(('*', 'owned_by', '*'), False)
-addmenu.tag_subject_of(('*', 'created_by', '*'), False)
addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
@@ -395,8 +410,6 @@
addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
-addmenu.tag_object_of(('*', 'owned_by', 'CWUser'), False)
-addmenu.tag_object_of(('*', 'created_by', 'CWUser'), False)
addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
--- a/web/views/ajaxedit.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/ajaxedit.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,20 +19,20 @@
class attributes.
"""
__registry__ = 'views'
+ __regid__ = 'xaddrelation'
__select__ = (match_form_params('rtype', 'target')
| match_kwargs('rtype', 'target'))
- property_defs = {} # don't want to inherit this from Box
- id = 'xaddrelation'
+ cw_property_defs = {} # don't want to inherit this from Box
expected_kwargs = form_params = ('rtype', 'target')
build_js = EditRelationBoxTemplate.build_reload_js_call
def cell_call(self, row, col, rtype=None, target=None, etype=None):
- self.rtype = rtype or self.req.form['rtype']
- self.target = target or self.req.form['target']
- self.etype = etype or self.req.form.get('etype')
- entity = self.entity(row, col)
- rschema = self.schema.rschema(self.rtype)
+ self.rtype = rtype or self._cw.form['rtype']
+ self.target = target or self._cw.form['target']
+ self.etype = etype or self._cw.form.get('etype')
+ entity = self.cw_rset.get_entity(row, col)
+ rschema = self._cw.vreg.schema.rschema(self.rtype)
if not self.etype:
if self.target == 'object':
etypes = rschema.objects(entity.e_schema)
@@ -40,9 +40,9 @@
etypes = rschema.subjects(entity.e_schema)
if len(etypes) == 1:
self.etype = etypes[0]
- self.w(u'<div id="%s">' % self.id)
- self.w(u'<h1>%s</h1>' % self.req._('relation %(relname)s of %(ent)s')
- % {'relname': rschema.display_name(self.req, role(self)),
+ self.w(u'<div id="%s">' % self.__regid__)
+ self.w(u'<h1>%s</h1>' % self._cw._('relation %(relname)s of %(ent)s')
+ % {'relname': rschema.display_name(self._cw, role(self)),
'ent': entity.view('incontext')})
self.w(u'<ul>')
for boxitem in self.unrelated_boxitems(entity):
@@ -59,13 +59,13 @@
if getattr(self, 'etype', None):
rset = entity.unrelated(self.rtype, self.etype, role(self),
ordermethod='fetch_order')
- self.pagination(self.req, rset, w=self.w)
+ self.pagination(self._cw, rset, w=self.w)
return rset.entities()
# in other cases, use vocabulary functions
entities = []
# XXX to update for 3.2
for _, eid in entity.vocabulary(self.rtype, role(self)):
if eid is not None:
- rset = self.req.eid_rset(eid)
+ rset = self._cw.eid_rset(eid)
entities.append(rset.get_entity(0, 0))
return entities
--- a/web/views/apacherewrite.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/apacherewrite.py Mon Feb 08 13:35:07 2010 +0100
@@ -85,7 +85,7 @@
]
"""
__abstract__ = True
- id = 'urlrewriter'
+ __regid__ = 'urlrewriter'
rules = []
def get_rules(self, req):
--- a/web/views/authentication.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/authentication.py Mon Feb 08 13:35:07 2010 +0100
@@ -10,17 +10,61 @@
from logilab.common.decorators import clear_cache
from cubicweb import AuthenticationError, BadConnectionId
+from cubicweb.view import Component
from cubicweb.dbapi import repo_connect, ConnectionProperties
from cubicweb.web import ExplicitLogin, InvalidSession
from cubicweb.web.application import AbstractAuthenticationManager
+class NoAuthInfo(Exception): pass
+
+
+class WebAuthInfoRetreiver(Component):
+ __registry__ = 'webauth'
+ order = None
+
+ def authentication_information(self, req):
+ """retreive authentication information from the given request, raise
+ NoAuthInfo if expected information is not found.
+ """
+ raise NotImplementedError()
+
+ def authenticated(self, req, cnx, retreiver):
+ """callback when return authentication information have opened a
+ repository connection successfully
+ """
+ pass
+
+
+class LoginPasswordRetreiver(WebAuthInfoRetreiver):
+ __regid__ = 'loginpwdauth'
+ order = 10
+
+ def __init__(self, vreg):
+ self.anoninfo = vreg.config.anonymous_user()
+
+ def authentication_information(self, req):
+ """retreive authentication information from the given request, raise
+ NoAuthInfo if expected information is not found.
+ """
+ login, password = req.get_authorization()
+ if not login:
+ # No session and no login -> try anonymous
+ login, password = self.anoninfo
+ if not login: # anonymous not authorized
+ raise NoAuthInfo()
+ return login, {'password': password}
+
class RepositoryAuthenticationManager(AbstractAuthenticationManager):
"""authenticate user associated to a request and check session validity"""
- def __init__(self):
- self.repo = self.config.repository(self.vreg)
- self.log_queries = self.config['query-log-file']
+ def __init__(self, vreg):
+ super(RepositoryAuthenticationManager, self).__init__(vreg)
+ self.repo = vreg.config.repository(vreg)
+ self.log_queries = vreg.config['query-log-file']
+ self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg),
+ key=lambda x: x.order)
+ assert self.authinforetreivers
def validate_session(self, req, session):
"""check session validity, and return eventually hijacked session
@@ -45,8 +89,7 @@
except BadConnectionId:
# check if a connection should be automatically restablished
if (login is None or login == cnx.login):
- login, password = cnx.login, cnx.password
- cnx = self.authenticate(req, login, password)
+ cnx = self._authenticate(req, cnx.login, cnx.authinfo)
user = cnx.user(req)
# backport session's data
cnx.data = session.data
@@ -56,7 +99,7 @@
req.set_connection(cnx, user)
return cnx
- def authenticate(self, req, _login=None, _password=None):
+ def authenticate(self, req):
"""authenticate user and return corresponding user object
:raise ExplicitLogin: if authentication is required (no authentication
@@ -66,42 +109,47 @@
returning a session instance instead of the user. This is expected by
the InMemoryRepositorySessionManager.
"""
- if _login is not None:
- login, password = _login, _password
+ for retreiver in self.authinforetreivers:
+ try:
+ login, authinfo = retreiver.authentication_information(req)
+ except NoAuthInfo:
+ continue
+ cnx = self._authenticate(req, login, authinfo)
+ break
else:
- login, password = req.get_authorization()
- if not login:
- # No session and no login -> try anonymous
- login, password = self.vreg.config.anonymous_user()
- if not login: # anonymous not authorized
- raise ExplicitLogin()
+ raise ExplicitLogin()
+ for retreiver_ in self.authinforetreivers:
+ retreiver_.authenticated(req, cnx, retreiver)
+ return cnx
+
+ def _authenticate(self, req, login, authinfo):
# remove possibly cached cursor coming from closed connection
clear_cache(req, 'cursor')
cnxprops = ConnectionProperties(self.vreg.config.repo_method,
close=False, log=self.log_queries)
try:
- cnx = repo_connect(self.repo, login, password, cnxprops=cnxprops)
+ cnx = repo_connect(self.repo, login, cnxprops=cnxprops, **authinfo)
except AuthenticationError:
req.set_message(req._('authentication failure'))
# restore an anonymous connection if possible
anonlogin, anonpassword = self.vreg.config.anonymous_user()
if anonlogin and anonlogin != login:
- cnx = repo_connect(self.repo, anonlogin, anonpassword,
+ cnx = repo_connect(self.repo, anonlogin, password=anonpassword,
cnxprops=cnxprops)
- self._init_cnx(cnx, anonlogin, anonpassword)
+ self._init_cnx(cnx, anonlogin, {'password': anonpassword})
else:
raise ExplicitLogin()
else:
- self._init_cnx(cnx, login, password)
+ self._init_cnx(cnx, login, authinfo)
# associate the connection to the current request
req.set_connection(cnx)
return cnx
- def _init_cnx(self, cnx, login, password):
+ def _init_cnx(self, cnx, login, authinfo):
# decorate connection
if login == self.vreg.config.anonymous_user()[0]:
cnx.anonymous_connection = True
cnx.vreg = self.vreg
cnx.login = login
- cnx.password = password
+ cnx.authinfo = authinfo
--- a/web/views/autoform.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/autoform.py Mon Feb 08 13:35:07 2010 +0100
@@ -5,160 +5,580 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+
__docformat__ = "restructuredtext en"
_ = unicode
+from simplejson import dumps
+
+from logilab.mtconverter import xml_escape
from logilab.common.decorators import iclassmethod, cached
-from cubicweb import typed_eid
-from cubicweb.web import stdmsgs, uicfg
-from cubicweb.web import form, formwidgets as fwdgs
-from cubicweb.web.formfields import guess_field
-from cubicweb.web.views import forms, editforms
+from cubicweb import typed_eid, neg_role, uilib
+from cubicweb.schema import display_name
+from cubicweb.view import EntityView
+from cubicweb.selectors import (
+ match_kwargs, match_form_params, non_final_entity,
+ specified_etype_implements)
+from cubicweb.web import stdmsgs, uicfg, eid_param, \
+ form as f, formwidgets as fw, formfields as ff
+from cubicweb.web.views import forms
+
+_AFS = uicfg.autoform_section
+_AFFK = uicfg.autoform_field_kwargs
+
+
+# inlined form handling ########################################################
+
+class InlinedFormField(ff.Field):
+ def __init__(self, view=None, **kwargs):
+ if view.role == 'object':
+ fieldset = u'%s_object%s' % view.rtype
+ else:
+ fieldset = view.rtype
+ #kwargs.setdefault('fieldset', fieldset)
+ kwargs.setdefault('label', None)
+ super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
+ eidparam=True, **kwargs)
+ self.view = view
+
+ def render(self, form, renderer):
+ """render this field, which is part of form, using the given form
+ renderer
+ """
+ view = self.view
+ i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
+ view.rtype, view.role)
+ return u'<div class="inline-%s-%s-slot">%s</div>' % (
+ view.rtype, view.role,
+ view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col))
+
+ def form_init(self, form):
+ """method called before by build_context to trigger potential field
+ initialization requiring the form instance
+ """
+ if self.view.form:
+ self.view.form.build_context(form.formvalues)
+
+ @property
+ def needs_multipart(self):
+ if self.view.form:
+ # take a look at inlined forms to check (recursively) if they need
+ # multipart handling.
+ return self.view.form.needs_multipart
+ return False
+
+ def has_been_modified(self, form):
+ return False
+
+ def process_posted(self, form):
+ pass # handled by the subform
+
+
+class InlineEntityEditionFormView(f.FormViewMixIn, EntityView):
+ """
+ :attr peid: the parent entity's eid hosting the inline form
+ :attr rtype: the relation bridging `etype` and `peid`
+ :attr role: the role played by the `peid` in the relation
+ :attr pform: the parent form where this inlined form is being displayed
+ """
+ __regid__ = 'inline-edition'
+ __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+
+ _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype')
+ removejs = "removeInlinedEntity('%s', '%s', '%s')"
+
+ def __init__(self, *args, **kwargs):
+ for attr in self._select_attrs:
+ setattr(self, attr, kwargs.pop(attr, None))
+ super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
+
+ def _entity(self):
+ assert self.cw_row is not None, self
+ return self.cw_rset.get_entity(self.cw_row, self.cw_col)
+
+ @property
+ @cached
+ def form(self):
+ entity = self._entity()
+ form = self._cw.vreg['forms'].select('edition', self._cw,
+ entity=entity,
+ formtype='inlined',
+ form_renderer_id='inline',
+ copy_nav_params=False,
+ mainform=False,
+ parent_form=self.pform,
+ **self.cw_extra_kwargs)
+ if self.pform is None:
+ form.restore_previous_post(form.session_key())
+ #assert form.parent_form
+ self.add_hiddens(form, entity)
+ return form
+
+ def cell_call(self, row, col, i18nctx, **kwargs):
+ """
+ :param peid: the parent entity's eid hosting the inline form
+ :param rtype: the relation bridging `etype` and `peid`
+ :param role: the role played by the `peid` in the relation
+ """
+ entity = self._entity()
+ divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
+ self.peid, self.rtype, entity.eid)
+ self.render_form(i18nctx, divonclick=divonclick, **kwargs)
+
+ def render_form(self, i18nctx, **kwargs):
+ """fetch and render the form"""
+ entity = self._entity()
+ divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+ title = self.form_title(entity, i18nctx)
+ removejs = self.removejs and self.removejs % (
+ self.peid, self.rtype, entity.eid)
+ countkey = '%s_count' % self.rtype
+ try:
+ self._cw.data[countkey] += 1
+ except KeyError:
+ self._cw.data[countkey] = 1
+ self.w(self.form.render(
+ divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
+ counter=self._cw.data[countkey] , **kwargs))
+
+ def form_title(self, entity, i18nctx):
+ return self._cw.pgettext(i18nctx, entity.__regid__)
+
+ def add_hiddens(self, form, entity):
+ """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
+ iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+ # * str(self.rtype) in case it's a schema object
+ # * neged_role() since role is the for parent entity, we want the role
+ # of the inlined entity
+ form.add_hidden(name=str(self.rtype), value=self.peid,
+ role=neg_role(self.role), eidparam=True, id=iid)
+
+ def keep_entity(self, form, entity):
+ if not entity.has_eid():
+ return True
+ # are we regenerating form because of a validation error ?
+ if form.form_previous_values:
+ cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
+ form.form_previous_values)
+ if unicode(entity.eid) not in cdvalues:
+ return False
+ return True
+
+
+class InlineEntityCreationFormView(InlineEntityEditionFormView):
+ """
+ :attr etype: the entity type being created in the inline form
+ """
+ __regid__ = 'inline-creation'
+ __select__ = (match_kwargs('peid', 'rtype')
+ & specified_etype_implements('Any'))
+
+ @property
+ def removejs(self):
+ entity = self._entity()
+ card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
+ # when one is adding an inline entity for a relation of a single card,
+ # the 'add a new xxx' link disappears. If the user then cancel the addition,
+ # we have to make this link appears back. This is done by giving add new link
+ # id to removeInlineForm.
+ if card not in '?1':
+ return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role
+ divid = "addNew%s%s%s:%s" % (
+ self.etype, self.rtype, self.role, self.peid)
+ return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % (
+ self.role, divid)
+
+ @cached
+ def _entity(self):
+ try:
+ cls = self._cw.vreg['etypes'].etype_class(self.etype)
+ except:
+ self.w(self._cw._('no such entity type %s') % etype)
+ return
+ entity = cls(self._cw)
+ entity.eid = self._cw.varmaker.next()
+ return entity
+
+ def call(self, i18nctx, **kwargs):
+ self.render_form(i18nctx, **kwargs)
+
+
+class InlineAddNewLinkView(InlineEntityCreationFormView):
+ """
+ :attr card: the cardinality of the relation according to role of `peid`
+ """
+ __regid__ = 'inline-addnew-link'
+ __select__ = (match_kwargs('peid', 'rtype')
+ & specified_etype_implements('Any'))
+
+ _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
+ form = None # no actual form wrapped
+
+ def call(self, i18nctx, **kwargs):
+ self._cw.set_varmaker()
+ divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
+ self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+ % divid)
+ js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
+ self.peid, self.etype, self.rtype, self.role, i18nctx)
+ if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
+ js = "toggleVisibility('%s'); %s" % (divid, js)
+ __ = self._cw.pgettext
+ self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+ % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
+ self.w(u'</div>')
+
+
+# generic relations handling ##################################################
+
+def relation_id(eid, rtype, role, reid):
+ """return an identifier for a relation between two entities"""
+ if role == 'subject':
+ return u'%s:%s:%s' % (eid, rtype, reid)
+ return u'%s:%s:%s' % (reid, rtype, eid)
+
+def toggleable_relation_link(eid, nodeid, label='x'):
+ """return javascript snippet to delete/undelete a relation between two
+ entities
+ """
+ js = u"javascript: togglePendingDelete('%s', %s);" % (
+ nodeid, xml_escape(dumps(eid)))
+ return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
+ js, nodeid, label)
+
+
+def get_pending_inserts(req, eid=None):
+ """shortcut to access req's pending_insert entry
+
+ This is where are stored relations being added while editing
+ an entity. This used to be stored in a temporary cookie.
+ """
+ pending = req.get_session_data('pending_insert') or ()
+ return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+ if eid is None or eid in (subj, obj)]
+
+def get_pending_deletes(req, eid=None):
+ """shortcut to access req's pending_delete entry
+
+ This is where are stored relations being removed while editing
+ an entity. This used to be stored in a temporary cookie.
+ """
+ pending = req.get_session_data('pending_delete') or ()
+ return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+ if eid is None or eid in (subj, obj)]
+
+def parse_relations_descr(rdescr):
+ """parse a string describing some relations, in the form
+ subjeids:rtype:objeids
+ where subjeids and objeids are eids separeted by a underscore
+ return an iterator on (subject eid, relation type, object eid) found
+ """
+ for rstr in rdescr:
+ subjs, rtype, objs = rstr.split(':')
+ for subj in subjs.split('_'):
+ for obj in objs.split('_'):
+ yield typed_eid(subj), rtype, typed_eid(obj)
+
+def delete_relations(req, rdefs):
+ """delete relations from the repository"""
+ # FIXME convert to using the syntax subject:relation:eids
+ execute = req.execute
+ for subj, rtype, obj in parse_relations_descr(rdefs):
+ rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+ execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+ req.set_message(req._('relations deleted'))
+
+def insert_relations(req, rdefs):
+ """insert relations into the repository"""
+ execute = req.execute
+ for subj, rtype, obj in parse_relations_descr(rdefs):
+ rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+ execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+
+
+class GenericRelationsWidget(fw.FieldWidget):
+
+ def render(self, form, field, renderer):
+ stream = []
+ w = stream.append
+ req = form._cw
+ _ = req._
+ __ = _
+ eid = form.edited_entity.eid
+ w(u'<table id="relatedEntities">')
+ for rschema, role, related in field.relations_table(form):
+ # already linked entities
+ if related:
+ w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, role))
+ w(u'<td>')
+ w(u'<ul>')
+ for viewparams in related:
+ w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
+ % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
+ if not form.force_display and form.maxrelitems < len(related):
+ link = (u'<span class="invisible">'
+ '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
+ '</span>' % _('view all'))
+ w(u'<li class="invisible">%s</li>' % link)
+ w(u'</ul>')
+ w(u'</td>')
+ w(u'</tr>')
+ pendings = list(field.restore_pending_inserts(form))
+ if not pendings:
+ w(u'<tr><th> </th><td> </td></tr>')
+ else:
+ for row in pendings:
+ # soon to be linked to entities
+ w(u'<tr id="tr%s">' % row[1])
+ w(u'<th>%s</th>' % row[3])
+ w(u'<td>')
+ w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
+ (_('cancel this insert'), row[2]))
+ w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
+ % (row[1], row[4], xml_escape(row[5])))
+ w(u'</td>')
+ w(u'</tr>')
+ w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
+ w(u'<th class="labelCol">')
+ w(u'<select id="relationSelector_%s" tabindex="%s" '
+ 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
+ % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
+ w(u'<option value="">%s</option>' % _('select a relation'))
+ for i18nrtype, rschema, role in field.relations:
+ # more entities to link to
+ w(u'<option value="%s_%s">%s</option>' % (rschema, role, i18nrtype))
+ w(u'</select>')
+ w(u'</th>')
+ w(u'<td id="unrelatedDivs_%s"></td>' % eid)
+ w(u'</tr>')
+ w(u'</table>')
+ return '\n'.join(stream)
+
+
+class GenericRelationsField(ff.Field):
+ widget = GenericRelationsWidget
+
+ def __init__(self, relations, name='_cw_generic_field', **kwargs):
+ assert relations
+ kwargs['eidparam'] = True
+ super(GenericRelationsField, self).__init__(name, **kwargs)
+ self.relations = relations
+
+ def process_posted(self, form):
+ todelete = get_pending_deletes(form._cw)
+ if todelete:
+ delete_relations(form._cw, todelete)
+ toinsert = get_pending_inserts(form._cw)
+ if toinsert:
+ insert_relations(form._cw, toinsert)
+ return ()
+
+ def relations_table(self, form):
+ """yiels 3-tuples (rtype, role, related_list)
+ where <related_list> itself a list of :
+ - node_id (will be the entity element's DOM id)
+ - appropriate javascript's togglePendingDelete() function call
+ - status 'pendingdelete' or ''
+ - oneline view of related entity
+ """
+ entity = form.edited_entity
+ pending_deletes = get_pending_deletes(form._cw, entity.eid)
+ for label, rschema, role in self.relations:
+ related = []
+ if entity.has_eid():
+ rset = entity.related(rschema, role, limit=form.related_limit)
+ if rschema.has_perm(form._cw, 'delete'):
+ toggleable_rel_link_func = toggleable_relation_link
+ else:
+ toggleable_rel_link_func = lambda x, y, z: u''
+ for row in xrange(rset.rowcount):
+ nodeid = relation_id(entity.eid, rschema, role,
+ rset[row][0])
+ if nodeid in pending_deletes:
+ status, label = u'pendingDelete', '+'
+ else:
+ status, label = u'', 'x'
+ dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
+ eview = form._cw.view('oneline', rset, row=row)
+ related.append((nodeid, dellink, status, eview))
+ yield (rschema, role, related)
+
+ def restore_pending_inserts(self, form):
+ """used to restore edition page as it was before clicking on
+ 'search for <some entity type>'
+ """
+ entity = form.edited_entity
+ pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
+ for pendingid in pending_inserts:
+ eidfrom, rtype, eidto = pendingid.split(':')
+ if typed_eid(eidfrom) == entity.eid: # subject
+ label = display_name(form._cw, rtype, 'subject',
+ entity.__regid__)
+ reid = eidto
+ else:
+ label = display_name(form._cw, rtype, 'object',
+ entity.__regid__)
+ reid = eidfrom
+ jscall = "javascript: cancelPendingInsert('%s', 'tr', null, %s);" \
+ % (pendingid, entity.eid)
+ rset = form._cw.eid_rset(reid)
+ eview = form._cw.view('text', rset, row=0)
+ # XXX find a clean way to handle baskets
+ if rset.description[0][0] == 'Basket':
+ eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket'))
+ yield rtype, pendingid, jscall, label, reid, eview
+
+
+class UnrelatedDivs(EntityView):
+ __regid__ = 'unrelateddivs'
+ __select__ = match_form_params('relation')
+
+ def cell_call(self, row, col):
+ entity = self.cw_rset.get_entity(row, col)
+ relname, role = self._cw.form.get('relation').rsplit('_', 1)
+ rschema = self._cw.vreg.schema.rschema(relname)
+ hidden = 'hidden' in self._cw.form
+ is_cell = 'is_cell' in self._cw.form
+ self.w(self.build_unrelated_select_div(entity, rschema, role,
+ is_cell=is_cell, hidden=hidden))
+
+ def build_unrelated_select_div(self, entity, rschema, role,
+ is_cell=False, hidden=True):
+ options = []
+ divid = 'div%s_%s_%s' % (rschema.type, role, entity.eid)
+ selectid = 'select%s_%s_%s' % (rschema.type, role, entity.eid)
+ if rschema.symmetric or role == 'subject':
+ targettypes = rschema.objects(entity.e_schema)
+ etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+ else:
+ targettypes = rschema.subjects(entity.e_schema)
+ etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+ etypes = uilib.cut(etypes, self._cw.property_value('navigation.short-line-size'))
+ options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
+ options += self._get_select_options(entity, rschema, role)
+ options += self._get_search_options(entity, rschema, role, targettypes)
+ if 'Basket' in self._cw.vreg.schema: # XXX
+ options += self._get_basket_options(entity, rschema, role, targettypes)
+ relname, role = self._cw.form.get('relation').rsplit('_', 1)
+ return u"""\
+<div class="%s" id="%s">
+ <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
+ %s
+ </select>
+</div>
+""" % (hidden and 'hidden' or '', divid, selectid,
+ xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+ '\n'.join(options))
+
+ def _get_select_options(self, entity, rschema, role):
+ """add options to search among all entities of each possible type"""
+ options = []
+ pending_inserts = get_pending_inserts(self._cw, entity.eid)
+ rtype = rschema.type
+ form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
+ field = form.field_by_name(rschema, role, entity.e_schema)
+ limit = self._cw.property_value('navigation.combobox-limit')
+ # NOTE: expect 'limit' arg on choices method of relation field
+ for eview, reid in field.vocabulary(form, limit=limit):
+ if reid is None:
+ if eview: # skip blank value
+ options.append('<option class="separator">-- %s --</option>'
+ % xml_escape(eview))
+ elif reid != ff.INTERNAL_FIELD_VALUE:
+ optionid = relation_id(entity.eid, rtype, role, reid)
+ if optionid not in pending_inserts:
+ # prefix option's id with letters to make valid XHTML wise
+ options.append('<option id="id%s" value="%s">%s</option>' %
+ (optionid, reid, xml_escape(eview)))
+ return options
+
+ def _get_search_options(self, entity, rschema, role, targettypes):
+ """add options to search among all entities of each possible type"""
+ options = []
+ _ = self._cw._
+ for eschema in targettypes:
+ mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
+ url = self._cw.build_url(entity.rest_path(), vid='search-associate',
+ __mode=mode)
+ options.append((eschema.display_name(self._cw),
+ '<option value="%s">%s %s</option>' % (
+ xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
+ return [o for l, o in sorted(options)]
+
+ # XXX move this out
+ def _get_basket_options(self, entity, rschema, role, targettypes):
+ options = []
+ rtype = rschema.type
+ _ = self._cw._
+ for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
+ role, targettypes):
+ optionid = relation_id(entity.eid, rtype, role, basketeid)
+ options.append('<option id="%s" value="%s">%s %s</option>' % (
+ optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
+ return options
+
+ def _get_basket_links(self, ueid, role, targettypes):
+ targettypes = set(targettypes)
+ for basketeid, basketname, elements in self._get_basket_info(ueid):
+ baskettypes = elements.column_types(0)
+ # if every elements in the basket can be attached to the
+ # edited entity
+ if baskettypes & targettypes:
+ yield basketeid, basketname
+
+ def _get_basket_info(self, ueid):
+ basketref = []
+ basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
+ basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
+ for result in basketresultset:
+ basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
+ rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
+ basketref.append((result[0], result[1], rset))
+ return basketref
+
+
+# The automatic entity form ####################################################
class AutomaticEntityForm(forms.EntityFieldsForm):
"""base automatic form to edit any entity.
Designed to be fully generated from schema but highly configurable through:
- * rtags (rcategories, rfields, rwidgets, inlined, rpermissions)
+
+ * uicfg (autoform_* relation tags)
* various standard form parameters
-
- XXX s/rtags/uicfg/ ?
+ * overriding
You can also easily customise it by adding/removing fields in
- AutomaticEntityForm instances.
+ AutomaticEntityForm instances or by inheriting from it.
"""
- id = 'edition'
+ __regid__ = 'edition'
cwtarget = 'eformframe'
cssclass = 'entityForm'
copy_nav_params = True
- form_buttons = [fwdgs.SubmitButton(),
- fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
- fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
- attrcategories = ('primary', 'secondary')
- # class attributes below are actually stored in the uicfg module since we
- # don't want them to be reloaded
- rcategories = uicfg.autoform_section
- rfields = uicfg.autoform_field
- rfields_kwargs = uicfg.autoform_field_kwargs
- rinlined = uicfg.autoform_is_inlined
- rpermissions_overrides = uicfg.autoform_permissions_overrides
-
- # class methods mapping schema relations to fields in the form ############
-
- @classmethod
- def erelations_by_category(cls, entity, categories=None, permission=None,
- rtags=None, strict=False):
- """return a list of (relation schema, target schemas, role) matching
- categories and permission
-
- `strict`:
- bool telling if having local role is enough (strict = False) or not
- """
- if categories is not None:
- if not isinstance(categories, (list, tuple, set, frozenset)):
- categories = (categories,)
- if not isinstance(categories, (set, frozenset)):
- categories = frozenset(categories)
- eschema = entity.e_schema
- if rtags is None:
- rtags = cls.rcategories
- permsoverrides = cls.rpermissions_overrides
- if entity.has_eid():
- eid = entity.eid
- else:
- eid = None
- strict = False
- for rschema, targetschemas, role in eschema.relation_definitions(True):
- # check category first, potentially lower cost than checking
- # permission which may imply rql queries
- if categories is not None:
- targetschemas = [tschema for tschema in targetschemas
- if rtags.etype_get(eschema, rschema, role, tschema) in categories]
- if not targetschemas:
- continue
- if permission is not None:
- # tag allowing to hijack the permission machinery when
- # permission is not verifiable until the entity is actually
- # created...
- if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
- yield (rschema, targetschemas, role)
- continue
- if rschema.final:
- if not rschema.has_perm(entity.req, permission, eid):
- continue
- elif role == 'subject':
- if not ((not strict and rschema.has_local_role(permission)) or
- rschema.has_perm(entity.req, permission, fromeid=eid)):
- continue
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (permission == 'add'
- and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
- and eid and entity.related(rschema.type, role)
- and not rschema.has_perm(entity.req, 'delete', fromeid=eid,
- toeid=entity.related(rschema.type, role)[0][0])):
- continue
- elif role == 'object':
- if not ((not strict and rschema.has_local_role(permission)) or
- rschema.has_perm(entity.req, permission, toeid=eid)):
- continue
- # on relation with cardinality 1 or ?, we need delete perm as well
- # if the relation is already set
- if (permission == 'add'
- and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
- and eid and entity.related(rschema.type, role)
- and not rschema.has_perm(entity.req, 'delete', toeid=eid,
- fromeid=entity.related(rschema.type, role)[0][0])):
- continue
- yield (rschema, targetschemas, role)
-
- @classmethod
- def esrelations_by_category(cls, entity, categories=None, permission=None,
- strict=False):
- """filter out result of relations_by_category(categories, permission) by
- removing final relations
-
- return a sorted list of (relation's label, relation'schema, role)
- """
- result = []
- for rschema, ttypes, role in cls.erelations_by_category(
- entity, categories, permission, strict=strict):
- if rschema.final:
- continue
- result.append((rschema.display_name(entity.req, role), rschema, role))
- return sorted(result)
+ form_buttons = [fw.SubmitButton(),
+ fw.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
+ fw.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+ # for attributes selection when searching in uicfg.autoform_section
+ formtype = 'main'
+ # set this to a list of [(relation, role)] if you want to explictily tell
+ # which relations should be edited
+ display_fields = None
@iclassmethod
- def field_by_name(cls_or_self, name, role='subject', eschema=None):
+ def field_by_name(cls_or_self, name, role=None, eschema=None):
"""return field with the given name and role. If field is not explicitly
defined for the form but `eclass` is specified, guess_field will be
called.
"""
try:
- return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role)
- except form.FieldNotFound:
- if eschema is None or not name in cls_or_self.schema:
- raise
- rschema = cls_or_self.schema.rschema(name)
- # XXX use a sample target type. Document this.
- tschemas = rschema.targets(eschema, role)
- fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role,
- tschemas[0])
- kwargs = cls_or_self.rfields_kwargs.etype_get(eschema, rschema,
- role, tschemas[0])
- if kwargs is None:
- kwargs = {}
- if fieldcls:
- if not isinstance(fieldcls, type):
- return fieldcls # already and instance
- return fieldcls(name=name, role=role, eidparam=True, **kwargs)
- field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
- if field is None:
- raise
- return field
+ return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role, eschema)
+ except f.FieldNotFound:
+ if name == '_cw_generic_field' and not isinstance(cls_or_self, type):
+ return cls_or_self._generic_relations_field()
+ raise
# base automatic entity form methods #######################################
@@ -167,23 +587,48 @@
entity = self.edited_entity
if entity.has_eid():
entity.complete()
- for rschema, role in self.editable_attributes():
+ for rtype, role in self.editable_attributes():
try:
- self.field_by_name(rschema.type, role)
+ self.field_by_name(str(rtype), role)
continue # explicitly specified
- except form.FieldNotFound:
+ except f.FieldNotFound:
# has to be guessed
try:
- field = self.field_by_name(rschema.type, role,
+ field = self.field_by_name(str(rtype), role,
eschema=entity.e_schema)
self.fields.append(field)
- except form.FieldNotFound:
+ except f.FieldNotFound:
# meta attribute such as <attr>_format
continue
+ if self.formtype == 'main':
+ if self.fieldsets_in_order:
+ fsio = list(self.fieldsets_in_order)
+ else:
+ fsio = [None]
+ self.fieldsets_in_order = fsio
+ # add fields for relation whose target should have an inline form
+ for formview in self.inlined_form_views():
+ field = self._inlined_form_view_field(formview)
+ self.fields.append(field)
+ if not field.fieldset in fsio:
+ fsio.append(field.fieldset)
+ # add the generic relation field if necessary
+ if entity.has_eid() and (
+ self.display_fields is None or
+ '_cw_generic_field' in self.display_fields):
+ try:
+ field = self.field_by_name('_cw_generic_field')
+ except f.FieldNotFound:
+ # no editable relation
+ pass
+ else:
+ self.fields.append(field)
+ if not field.fieldset in fsio:
+ fsio.append(field.fieldset)
+ self.maxrelitems = self._cw.property_value('navigation.related-limit')
+ self.force_display = bool(self._cw.form.get('__force_display'))
fnum = len(self.fields)
self.fields.sort(key=lambda f: f.order is None and fnum or f.order)
- self.maxrelitems = self.req.property_value('navigation.related-limit')
- self.force_display = bool(self.req.form.get('__force_display'))
@property
def related_limit(self):
@@ -191,42 +636,6 @@
return None
return self.maxrelitems + 1
- @property
- def form_needs_multipart(self):
- """true if the form needs enctype=multipart/form-data"""
- return self._subform_needs_multipart()
-
- def build_context(self, rendervalues=None):
- super(AutomaticEntityForm, self).build_context(rendervalues)
- for form in self.inlined_forms():
- form.build_context(rendervalues)
-
- def _subform_needs_multipart(self, _tested=None):
- if _tested is None:
- _tested = set()
- if super(AutomaticEntityForm, self).form_needs_multipart:
- return True
- # take a look at inlined forms to check (recursively) if they
- # need multipart handling.
- # XXX: this is very suboptimal because inlined forms will be
- # selected / instantiated twice : here and during form rendering.
- # Potential solutions:
- # -> use subforms for inlined forms to get easiser access
- # -> use a simple onload js function to check if there is
- # a input type=file in the form
- # -> generate the <form> node when the content is rendered
- # and we know the correct enctype (formrenderer's w attribute
- # is not a StringIO)
- for formview in self.inlined_form_views():
- if formview.form:
- if hasattr(formview.form, '_subform_needs_multipart'):
- needs_multipart = formview.form._subform_needs_multipart(_tested)
- else:
- needs_multipart = formview.form.form_needs_multipart
- if needs_multipart:
- return True
- return False
-
def action(self):
"""return the form's action attribute. Default to validateform if not
explicitly overriden.
@@ -234,7 +643,7 @@
try:
return self._action
except AttributeError:
- return self.build_url('validateform')
+ return self._cw.build_url('validateform')
def set_action(self, value):
"""override default action"""
@@ -242,102 +651,71 @@
action = property(action, set_action)
+ # autoform specific fields #################################################
+
+ def _generic_relations_field(self):
+ try:
+ srels_by_cat = self.srelations_by_category('generic', 'add', strict=True)
+ warn('[3.6] %s: srelations_by_category is deprecated, use uicfg or '
+ 'override editable_relations instead' % classid(form),
+ DeprecationWarning)
+ except AttributeError:
+ srels_by_cat = self.editable_relations()
+ if not srels_by_cat:
+ raise f.FieldNotFound('_cw_generic_field')
+ fieldset = u'%s :' % self._cw.__('This %s' % self.edited_entity.e_schema)
+ fieldset = fieldset.capitalize()
+ return GenericRelationsField(self.editable_relations(),
+ fieldset=fieldset, label=None)
+
+ def _inlined_form_view_field(self, view):
+ # XXX allow more customization
+ kwargs = _AFFK.etype_get(self.edited_entity.e_schema, view.rtype,
+ view.role, view.etype)
+ if kwargs is None:
+ kwargs = {}
+ return InlinedFormField(view=view, **kwargs)
+
# methods mapping edited entity relations to fields in the form ############
- def relations_by_category(self, categories=None, permission=None):
+ def _relations_by_section(self, section, permission='add', strict=False):
"""return a list of (relation schema, target schemas, role) matching
given category(ies) and permission
"""
- return self.erelations_by_category(self.edited_entity, categories,
- permission)
+ return _AFS.relations_by_section(
+ self.edited_entity, self.formtype, section, permission, strict)
+
+ def editable_attributes(self, strict=False):
+ """return a list of (relation schema, role) to edit for the entity"""
+ if self.display_fields is not None:
+ return self.display_fields
+ # XXX we should simply put eid in the generated section, no?
+ return [(rtype, role) for rtype, _, role in self._relations_by_section(
+ 'attributes', strict=strict) if rtype != 'eid']
+
+ def editable_relations(self):
+ """return a sorted list of (relation's label, relation'schema, role) for
+ relations in the 'relations' section
+ """
+ result = []
+ for rschema, _, role in self._relations_by_section('relations',
+ strict=True):
+ result.append( (rschema.display_name(self.edited_entity._cw, role,
+ self.edited_entity.__regid__),
+ rschema, role) )
+ return sorted(result)
def inlined_relations(self):
"""return a list of (relation schema, target schemas, role) matching
given category(ies) and permission
"""
- # we'll need an initialized varmaker if there are some inlined relation
- self.initialize_varmaker()
- return self.erelations_by_category(self.edited_entity, True, 'add',
- self.rinlined)
-
- def srelations_by_category(self, categories=None, permission=None,
- strict=False):
- """filter out result of relations_by_category(categories, permission) by
- removing final relations
+ return self._relations_by_section('inlined')
- return a sorted list of (relation's label, relation'schema, role)
- """
- return self.esrelations_by_category(self.edited_entity, categories,
- permission, strict=strict)
-
- def editable_attributes(self):
- """return a list of (relation schema, role) to edit for the entity"""
- return [(rschema, role) for rschema, _, role in self.relations_by_category(
- self.attrcategories, 'add') if rschema != 'eid']
-
- # generic relations modifier ###############################################
+ # inlined forms control ####################################################
- def relations_table(self):
- """yiels 3-tuples (rtype, target, related_list)
- where <related_list> itself a list of :
- - node_id (will be the entity element's DOM id)
- - appropriate javascript's togglePendingDelete() function call
- - status 'pendingdelete' or ''
- - oneline view of related entity
- """
- entity = self.edited_entity
- pending_deletes = self.req.get_pending_deletes(entity.eid)
- for label, rschema, role in self.srelations_by_category('generic', 'add',
- strict=True):
- relatedrset = entity.related(rschema, role, limit=self.related_limit)
- if rschema.has_perm(self.req, 'delete'):
- toggleable_rel_link_func = editforms.toggleable_relation_link
- else:
- toggleable_rel_link_func = lambda x, y, z: u''
- related = []
- for row in xrange(relatedrset.rowcount):
- nodeid = editforms.relation_id(entity.eid, rschema, role,
- relatedrset[row][0])
- if nodeid in pending_deletes:
- status = u'pendingDelete'
- label = '+'
- else:
- status = u''
- label = 'x'
- dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
- eview = self.view('oneline', relatedrset, row=row)
- related.append((nodeid, dellink, status, eview))
- yield (rschema, role, related)
-
- def restore_pending_inserts(self, cell=False):
- """used to restore edition page as it was before clicking on
- 'search for <some entity type>'
- """
- eid = self.edited_entity.eid
- cell = cell and "div_insert_" or "tr"
- pending_inserts = set(self.req.get_pending_inserts(eid))
- for pendingid in pending_inserts:
- eidfrom, rtype, eidto = pendingid.split(':')
- if typed_eid(eidfrom) == eid: # subject
- label = display_name(self.req, rtype, 'subject')
- reid = eidto
- else:
- label = display_name(self.req, rtype, 'object')
- reid = eidfrom
- jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \
- % (pendingid, cell, eid)
- rset = self.req.eid_rset(reid)
- eview = self.view('text', rset, row=0)
- # XXX find a clean way to handle baskets
- if rset.description[0][0] == 'Basket':
- eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
- yield rtype, pendingid, jscall, label, reid, eview
-
- # inlined forms support ####################################################
-
- @cached
def inlined_form_views(self):
- """compute and return list of inlined form views (hosting the inlined form object)
+ """compute and return list of inlined form views (hosting the inlined
+ form object)
"""
allformviews = []
entity = self.edited_entity
@@ -352,10 +730,7 @@
ttype = ttypes[0].type
if self.should_inline_relation_form(rschema, ttype, role):
formviews = list(self.inline_edition_form_view(rschema, ttype, role))
- if role == 'subject':
- card = rschema.rproperty(entity.e_schema, ttype, 'cardinality')[0]
- else:
- card = rschema.rproperty(ttype, entity.e_schema, 'cardinality')[1]
+ card = rschema.rdef(entity.e_schema, ttype).role_cardinality(role)
# there is no related entity and we need at least one: we need to
# display one explicit inline-creation view
if self.should_display_inline_creation_form(rschema, formviews, card):
@@ -363,22 +738,20 @@
# we can create more than one related entity, we thus display a link
# to add new related entities
if self.should_display_add_new_relation_link(rschema, formviews, card):
- addnewlink = self.vreg['views'].select(
- 'inline-addnew-link', self.req,
+ addnewlink = self._cw.vreg['views'].select(
+ 'inline-addnew-link', self._cw,
etype=ttype, rtype=rschema, role=role,
peid=self.edited_entity.eid, pform=self, card=card)
formviews.append(addnewlink)
allformviews += formviews
return allformviews
- def inlined_forms(self):
- for formview in self.inlined_form_views():
- if formview.form: # may be None for the addnew_link artefact form
- yield formview.form
-
def should_inline_relation_form(self, rschema, targettype, role):
"""return true if the given relation with entity has role and a
targettype target should be inlined
+
+ At this point we now relation has inlined_attributes tag (eg is returned
+ by `inlined_relations()`. Overrides this for more finer control.
"""
return True
@@ -387,7 +760,7 @@
by default true if there is no related entity and we need at least one
"""
- return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
+ return not existant and card in '1+' or self._cw.form.has_key('force_%s_display' % rschema)
def should_display_add_new_relation_link(self, rschema, existant, card):
"""return true if we should add a link to add a new creation form
@@ -396,7 +769,7 @@
by default true if there is no related entity or if the relation has
multiple cardinality
"""
- return not existant or card in '+*'
+ return not existant or card in '+*' # XXX add target type permisssions
def should_hide_add_new_relation_link(self, rschema, card):
"""return true if once an inlined creation form is added, the 'add new'
@@ -413,76 +786,71 @@
entity = self.edited_entity
related = entity.has_eid() and entity.related(rschema, role)
if related:
- vvreg = self.vreg['views']
+ vvreg = self._cw.vreg['views']
# display inline-edition view for all existing related entities
for i, relentity in enumerate(related.entities()):
if relentity.has_perm('update'):
- yield vvreg.select('inline-edition', self.req, rset=related,
- row=i, col=0, rtype=rschema, role=role,
+ yield vvreg.select('inline-edition', self._cw,
+ rset=related, row=i, col=0,
+ etype=ttype, rtype=rschema, role=role,
peid=entity.eid, pform=self)
def inline_creation_form_view(self, rschema, ttype, role):
"""yield inline form views to a newly related (hence created) entity
through the given relation
"""
- yield self.vreg['views'].select('inline-creation', self.req,
- etype=ttype, rtype=rschema, role=role,
- peid=self.edited_entity.eid, pform=self)
-
-
-def etype_relation_field(etype, rtype, role='subject'):
- eschema = AutomaticEntityForm.schema.eschema(etype)
- return AutomaticEntityForm.field_by_name(rtype, role, eschema)
+ yield self._cw.vreg['views'].select('inline-creation', self._cw,
+ etype=ttype, rtype=rschema, role=role,
+ peid=self.edited_entity.eid, pform=self)
## default form ui configuration ##############################################
# use primary and not generated for eid since it has to be an hidden
-uicfg.autoform_section.tag_attribute(('*', 'eid'), 'primary')
-uicfg.autoform_section.tag_attribute(('*', 'description'), 'secondary')
-uicfg.autoform_section.tag_attribute(('*', 'creation_date'), 'metadata')
-uicfg.autoform_section.tag_attribute(('*', 'modification_date'), 'metadata')
-uicfg.autoform_section.tag_attribute(('*', 'cwuri'), 'metadata')
-uicfg.autoform_section.tag_attribute(('*', 'has_text'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'in_state', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata')
-uicfg.autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata')
-uicfg.autoform_section.tag_subject_of(('*', 'is', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'is', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'is_instance_of', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'is_instance_of', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'identity', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'identity', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'by_transition', '*'), 'primary')
-uicfg.autoform_section.tag_object_of(('*', 'by_transition', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'from_state', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'to_state', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'wf_info_for', '*'), 'primary')
-uicfg.autoform_section.tag_object_of(('*', 'wf_info_for', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('*', 'for_user', '*'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'for_user', '*'), 'generated')
-uicfg.autoform_section.tag_subject_of(('CWPermission', 'require_group', '*'), 'primary')
-uicfg.autoform_section.tag_attribute(('CWEType', 'final'), 'generated')
-uicfg.autoform_section.tag_attribute(('CWRType', 'final'), 'generated')
-uicfg.autoform_section.tag_attribute(('CWUser', 'firstname'), 'secondary')
-uicfg.autoform_section.tag_attribute(('CWUser', 'surname'), 'secondary')
-uicfg.autoform_section.tag_attribute(('CWUser', 'last_login_time'), 'metadata')
-uicfg.autoform_section.tag_subject_of(('CWUser', 'in_group', '*'), 'primary')
-uicfg.autoform_section.tag_object_of(('*', 'owned_by', 'CWUser'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'created_by', 'CWUser'), 'generated')
-uicfg.autoform_section.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'metadata')
-uicfg.autoform_section.tag_attribute(('Bookmark', 'path'), 'primary')
-uicfg.autoform_section.tag_subject_of(('*', 'primary_email', '*'), 'generic')
+_AFS.tag_attribute(('*', 'eid'), 'main', 'attributes')
+_AFS.tag_attribute(('*', 'eid'), 'muledit', 'attributes')
+_AFS.tag_attribute(('*', 'description'), 'main', 'attributes')
+_AFS.tag_attribute(('*', 'creation_date'), 'main', 'metadata')
+_AFS.tag_attribute(('*', 'modification_date'), 'main', 'metadata')
+_AFS.tag_attribute(('*', 'cwuri'), 'main', 'metadata')
+_AFS.tag_attribute(('*', 'has_text'), 'main', 'hidden')
+_AFS.tag_subject_of(('*', 'in_state', '*'), 'main', 'hidden')
+_AFS.tag_subject_of(('*', 'owned_by', '*'), 'main', 'metadata')
+_AFS.tag_subject_of(('*', 'created_by', '*'), 'main', 'metadata')
+_AFS.tag_subject_of(('*', 'require_permission', '*'), 'main', 'hidden')
+_AFS.tag_subject_of(('*', 'by_transition', '*'), 'main', 'attributes')
+_AFS.tag_subject_of(('*', 'by_transition', '*'), 'muledit', 'attributes')
+_AFS.tag_object_of(('*', 'by_transition', '*'), 'main', 'hidden')
+_AFS.tag_object_of(('*', 'from_state', '*'), 'main', 'hidden')
+_AFS.tag_object_of(('*', 'to_state', '*'), 'main', 'hidden')
+_AFS.tag_subject_of(('*', 'wf_info_for', '*'), 'main', 'attributes')
+_AFS.tag_subject_of(('*', 'wf_info_for', '*'), 'muledit', 'attributes')
+_AFS.tag_object_of(('*', 'wf_info_for', '*'), 'main', 'hidden')
+_AFS.tag_subject_of(('CWPermission', 'require_group', '*'), 'main', 'attributes')
+_AFS.tag_subject_of(('CWPermission', 'require_group', '*'), 'muledit', 'attributes')
+_AFS.tag_attribute(('CWEType', 'final'), 'main', 'hidden')
+_AFS.tag_attribute(('CWRType', 'final'), 'main', 'hidden')
+_AFS.tag_attribute(('CWUser', 'firstname'), 'main', 'attributes')
+_AFS.tag_attribute(('CWUser', 'surname'), 'main', 'attributes')
+_AFS.tag_attribute(('CWUser', 'last_login_time'), 'main', 'metadata')
+_AFS.tag_subject_of(('CWUser', 'in_group', '*'), 'main', 'attributes')
+_AFS.tag_subject_of(('CWUser', 'in_group', '*'), 'muledit', 'attributes')
+_AFS.tag_subject_of(('*', 'primary_email', '*'), 'main', 'relations')
+_AFS.tag_subject_of(('*', 'use_email', '*'), 'main', 'inlined')
+_AFS.tag_subject_of(('CWRelation', 'relation_type', '*'), 'main', 'inlined')
+_AFS.tag_subject_of(('CWRelation', 'from_entity', '*'), 'main', 'inlined')
+_AFS.tag_subject_of(('CWRelation', 'to_entity', '*'), 'main', 'inlined')
-uicfg.autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
- {'widget': fwdgs.TextInput})
-uicfg.autoform_field_kwargs.tag_attribute(('Bookmark', 'path'),
- {'widget': fwdgs.TextInput})
-uicfg.autoform_field_kwargs.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
- {'widget': fwdgs.HiddenInput})
+_AFFK.tag_attribute(('RQLExpression', 'expression'),
+ {'widget': fw.TextInput})
+_AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
+ {'widget': fw.HiddenInput})
-uicfg.autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
-uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
-uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
-uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'to_entity', '*'), True)
+def registration_callback(vreg):
+ global etype_relation_field
+
+ def etype_relation_field(etype, rtype, role='subject'):
+ eschema = vreg.schema.eschema(etype)
+ return AutomaticEntityForm.field_by_name(rtype, role, eschema)
+
+ vreg.register_all(globals().values(), __name__)
--- a/web/views/basecomponents.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/basecomponents.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,9 +15,9 @@
from logilab.mtconverter import xml_escape
from rql import parse
-from cubicweb.selectors import yes, two_etypes_rset, match_form_params
+from cubicweb.selectors import yes, multi_etypes_rset, match_form_params
from cubicweb.schema import display_name
-from cubicweb.common.uilib import toggle_action
+from cubicweb.uilib import toggle_action
from cubicweb.web import component
from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
BoxLink)
@@ -29,29 +29,29 @@
class RQLInputForm(component.Component):
"""build the rql input form, usually displayed in the header"""
- id = 'rqlinput'
- property_defs = VISIBLE_PROP_DEF
+ __regid__ = 'rqlinput'
+ cw_property_defs = VISIBLE_PROP_DEF
visible = False
def call(self, view=None):
if hasattr(view, 'filter_box_context_info'):
rset = view.filter_box_context_info()[0]
else:
- rset = self.rset
+ rset = self.cw_rset
# display multilines query as one line
- rql = rset is not None and rset.printable_rql(encoded=False) or self.req.form.get('rql', '')
+ rql = rset is not None and rset.printable_rql(encoded=False) or self._cw.form.get('rql', '')
rql = rql.replace(u"\n", u" ")
- req = self.req
+ req = self._cw
self.w(u'''<div id="rqlinput" class="%s">
<form action="%s">
<fieldset>
<input type="text" id="rql" name="rql" value="%s" title="%s" tabindex="%s" accesskey="q" class="searchField" />
<input type="submit" value="" class="rqlsubmit" tabindex="%s" />
</fieldset>
-''' % (not self.propval('visible') and 'hidden' or '',
- self.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
+''' % (not self.cw_propval('visible') and 'hidden' or '',
+ self._cw.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
req.next_tabindex()))
- if self.req.search_state[0] != 'normal':
+ if self._cw.search_state[0] != 'normal':
self.w(u'<input type="hidden" name="__mode" value="%s"/>'
% ':'.join(req.search_state[1]))
self.w(u'</form></div>')
@@ -59,52 +59,63 @@
class ApplLogo(component.Component):
"""build the instance logo, usually displayed in the header"""
- id = 'logo'
- property_defs = VISIBLE_PROP_DEF
+ __regid__ = 'logo'
+ cw_property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
site_wide = True
def call(self):
self.w(u'<a href="%s"><img class="logo" src="%s" alt="logo"/></a>'
- % (self.req.base_url(), self.req.external_resource('LOGO')))
+ % (self._cw.base_url(), self._cw.external_resource('LOGO')))
+
+
+class ApplHelp(component.Component):
+ """build the help button, usually displayed in the header"""
+ __regid__ = 'help'
+ cw_property_defs = VISIBLE_PROP_DEF
+ def call(self):
+ self.w(u'<a href="%s" class="help" title="%s"> </a>'
+ % (self._cw.build_url(_restpath='doc/main'),
+ self._cw._(u'help'),))
+
class UserLink(component.Component):
"""if the user is the anonymous user, build a link to login
else a link to the connected user object with a loggout link
"""
- property_defs = VISIBLE_PROP_DEF
+ cw_property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
site_wide = True
- id = 'loggeduserlink'
+ __regid__ = 'loggeduserlink'
def call(self):
- if not self.req.cnx.anonymous_connection:
+ if not self._cw.cnx.anonymous_connection:
# display useractions and siteactions
- actions = self.vreg['actions'].possible_actions(self.req, rset=self.rset)
+ actions = self._cw.vreg['actions'].possible_actions(self._cw, rset=self.cw_rset)
box = MenuWidget('', 'userActionsBox', _class='', islist=False)
- menu = PopupBoxMenu(self.req.user.login, isitem=False)
+ menu = PopupBoxMenu(self._cw.user.login, isitem=False)
box.append(menu)
for action in actions.get('useractions', ()):
- menu.append(BoxLink(action.url(), self.req._(action.title),
+ menu.append(BoxLink(action.url(), self._cw._(action.title),
action.html_class()))
if actions.get('useractions') and actions.get('siteactions'):
menu.append(BoxSeparator())
for action in actions.get('siteactions', ()):
- menu.append(BoxLink(action.url(), self.req._(action.title),
+ menu.append(BoxLink(action.url(), self._cw._(action.title),
action.html_class()))
box.render(w=self.w)
else:
self.anon_user_link()
def anon_user_link(self):
- if self.config['auth-mode'] == 'cookie':
- self.w(self.req._('anonymous'))
+ if self._cw.vreg.config['auth-mode'] == 'cookie':
+ self.w(self._cw._('anonymous'))
self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
- % (self.req._('i18n_login_popup')))
+ % (self._cw._('i18n_login_popup')))
else:
- self.w(self.req._('anonymous'))
+ self.w(self._cw._('anonymous'))
self.w(u' [<a class="logout" href="%s">%s</a>]'
- % (self.build_url('login'), self.req._('login')))
+ % (self._cw.build_url('login'), self._cw._('login')))
class ApplicationMessage(component.Component):
@@ -112,13 +123,13 @@
section
"""
__select__ = yes()
- id = 'applmessages'
+ __regid__ = 'applmessages'
# don't want user to hide this component using an cwproperty
- property_defs = {}
+ cw_property_defs = {}
def call(self):
- msgs = [msg for msg in (self.req.get_shared_data('sources_error', pop=True),
- self.req.message) if msg]
+ msgs = [msg for msg in (self._cw.get_shared_data('sources_error', pop=True),
+ self._cw.message) if msg]
self.w(u'<div id="appMsg" onclick="%s" class="%s">\n' %
(toggle_action('appMsg'), (msgs and ' ' or 'hidden')))
for msg in msgs:
@@ -129,21 +140,21 @@
class ApplicationName(component.Component):
"""display the instance name"""
- id = 'appliname'
- property_defs = VISIBLE_PROP_DEF
+ __regid__ = 'appliname'
+ cw_property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
site_wide = True
def call(self):
- title = self.req.property_value('ui.site-title')
+ title = self._cw.property_value('ui.site-title')
if title:
self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
- self.req.base_url(), xml_escape(title)))
+ self._cw.base_url(), xml_escape(title)))
class SeeAlsoVComponent(component.RelatedObjectsVComponent):
"""display any entity's see also"""
- id = 'seealso'
+ __regid__ = 'seealso'
context = 'navcontentbottom'
rtype = 'see_also'
role = 'subject'
@@ -157,29 +168,29 @@
"""displays the list of entity types contained in the resultset
to be able to filter accordingly.
"""
- id = 'etypenavigation'
- __select__ = two_etypes_rset() | match_form_params('__restrtype', '__restrtypes',
+ __regid__ = 'etypenavigation'
+ __select__ = multi_etypes_rset() | match_form_params('__restrtype', '__restrtypes',
'__restrrql')
- property_defs = VISIBLE_PROP_DEF
+ cw_property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
site_wide = True
visible = False # disabled by default
def call(self):
- _ = self.req._
+ _ = self._cw._
self.w(u'<div id="etyperestriction">')
- restrtype = self.req.form.get('__restrtype')
- restrtypes = self.req.form.get('__restrtypes', '').split(',')
- restrrql = self.req.form.get('__restrrql')
+ restrtype = self._cw.form.get('__restrtype')
+ restrtypes = self._cw.form.get('__restrtypes', '').split(',')
+ restrrql = self._cw.form.get('__restrrql')
if not restrrql:
- rqlst = self.rset.syntax_tree()
- restrrql = rqlst.as_string(self.req.encoding, self.rset.args)
- restrtypes = self.rset.column_types(0)
+ rqlst = self.cw_rset.syntax_tree()
+ restrrql = rqlst.as_string(self._cw.encoding, self.cw_rset.args)
+ restrtypes = self.cw_rset.column_types(0)
else:
rqlst = parse(restrrql)
html = []
on_etype = False
- etypes = sorted((display_name(self.req, etype).capitalize(), etype)
+ etypes = sorted((display_name(self._cw, etype).capitalize(), etype)
for etype in restrtypes)
for elabel, etype in etypes:
if etype == restrtype:
@@ -189,14 +200,14 @@
rqlst.save_state()
for select in rqlst.children:
select.add_type_restriction(select.selection[0], etype)
- newrql = rqlst.as_string(self.req.encoding, self.rset.args)
- url = self.build_url(rql=newrql, __restrrql=restrrql,
- __restrtype=etype, __restrtypes=','.join(restrtypes))
+ newrql = rqlst.as_string(self._cw.encoding, self.cw_rset.args)
+ url = self._cw.build_url(rql=newrql, __restrrql=restrrql,
+ __restrtype=etype, __restrtypes=','.join(restrtypes))
html.append(u'<span><a href="%s">%s</a></span>' % (
xml_escape(url), elabel))
rqlst.recover()
if on_etype:
- url = self.build_url(rql=restrrql)
+ url = self._cw.build_url(rql=restrrql)
html.insert(0, u'<span><a href="%s">%s</a></span>' % (
url, _('Any')))
else:
@@ -206,26 +217,26 @@
class PdfViewComponent(component.EntityVComponent):
- id = 'view_page_as_pdf'
+ __regid__ = 'pdfview'
+
context = 'ctxtoolbar'
def cell_call(self, row, col, view):
- entity = self.entity(row, col)
- url = entity.absolute_url(vid=view.id, __template='pdf-main-template')
- iconurl = self.req.build_url('data/pdf_icon.gif')
- label = self.req._('Download page as pdf')
+ entity = self.cw_rset.get_entity(row, col)
+ url = entity.absolute_url(vid=view.__regid__, __template='pdf-main-template')
+ iconurl = self._cw.build_url('data/pdf_icon.gif')
+ label = self._cw._('Download page as pdf')
self.w(u'<a href="%s" title="%s" class="toolbarButton"><img src="%s" alt="%s"/></a>' %
(xml_escape(url), label, iconurl, label))
-
class MetaDataComponent(component.EntityVComponent):
- id = 'metadata'
+ __regid__ = 'metadata'
context = 'navbottom'
order = 1
def cell_call(self, row, col, view=None):
- self.wview('metadata', self.rset, row=row, col=col)
+ self.wview('metadata', self.cw_rset, row=row, col=col)
def registration_callback(vreg):
--- a/web/views/basecontrollers.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/basecontrollers.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,11 +15,12 @@
import simplejson
from logilab.common.decorators import cached
+from logilab.common.date import strptime
from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
-from cubicweb.utils import strptime, CubicWebJsonEncoder
+from cubicweb.utils import CubicWebJsonEncoder
from cubicweb.selectors import yes, match_user_groups
-from cubicweb.common.mail import format_mail
+from cubicweb.mail import format_mail
from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed, json_dumps
from cubicweb.web.controller import Controller
from cubicweb.web.views import vid_from_rset
@@ -36,7 +37,7 @@
results
"""
def wrapper(self, *args, **kwargs):
- self.req.set_content_type('application/json')
+ self._cw.set_content_type('application/json')
return json_dumps(func(self, *args, **kwargs))
wrapper.__name__ = func.__name__
return wrapper
@@ -44,9 +45,9 @@
def xhtmlize(func):
"""decorator to sets correct content_type and calls `xmlize` on results"""
def wrapper(self, *args, **kwargs):
- self.req.set_content_type(self.req.html_content_type())
+ self._cw.set_content_type(self._cw.html_content_type())
result = func(self, *args, **kwargs)
- return ''.join((self.req.document_surrounding_div(), result.strip(),
+ return ''.join((self._cw.document_surrounding_div(), result.strip(),
u'</div>'))
wrapper.__name__ = func.__name__
return wrapper
@@ -56,32 +57,32 @@
user's session data
"""
def wrapper(self, *args, **kwargs):
- data = self.req.get_session_data(self.req.pageid)
+ data = self._cw.get_session_data(self._cw.pageid)
if data is None:
- raise RemoteCallFailed(self.req._('pageid-not-found'))
+ raise RemoteCallFailed(self._cw._('pageid-not-found'))
return func(self, *args, **kwargs)
return wrapper
class LoginController(Controller):
- id = 'login'
+ __regid__ = 'login'
def publish(self, rset=None):
"""log in the instance"""
- if self.config['auth-mode'] == 'http':
+ if self._cw.vreg.config['auth-mode'] == 'http':
# HTTP authentication
raise ExplicitLogin()
else:
# Cookie authentication
- return self.appli.need_login_content(self.req)
+ return self.appli.need_login_content(self._cw)
class LogoutController(Controller):
- id = 'logout'
+ __regid__ = 'logout'
def publish(self, rset=None):
"""logout from the instance"""
- return self.appli.session_handler.logout(self.req)
+ return self.appli.session_handler.logout(self._cw)
class ViewController(Controller):
@@ -89,7 +90,7 @@
- build result set
- select and call main template
"""
- id = 'view'
+ __regid__ = 'view'
template = 'main-template'
def publish(self, rset=None):
@@ -97,12 +98,12 @@
view, rset = self._select_view_and_rset(rset)
self.add_to_breadcrumbs(view)
self.validate_cache(view)
- template = self.appli.main_template_id(self.req)
- return self.vreg['views'].main_template(self.req, template,
+ template = self.appli.main_template_id(self._cw)
+ return self._cw.vreg['views'].main_template(self._cw, template,
rset=rset, view=view)
def _select_view_and_rset(self, rset):
- req = self.req
+ req = self._cw
if rset is None and not hasattr(req, '_rql_processed'):
req._rql_processed = True
rset = self.process_rql(req.form.get('rql'))
@@ -116,14 +117,14 @@
except Exception, ex:
self.exception('while handling __method')
req.set_message(req._("error while handling __method: %s") % req._(ex))
- vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
+ vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.vreg.schema)
try:
- view = self.vreg['views'].select(vid, req, rset=rset)
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
except ObjectNotFound:
self.warning("the view %s could not be found", vid)
req.set_message(req._("The view %s could not be found") % vid)
- vid = vid_from_rset(req, rset, self.schema)
- view = self.vreg['views'].select(vid, req, rset=rset)
+ vid = vid_from_rset(req, rset, self._cw.vreg.schema)
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
except NoSelectableObject:
if rset:
req.set_message(req._("The view %s can not be applied to this query") % vid)
@@ -131,8 +132,8 @@
req.set_message(req._("You have no access to this view or it can not "
"be used to display the current data."))
self.warning("the view %s can not be applied to this query", vid)
- vid = req.form.get('fallbackvid') or vid_from_rset(req, rset, self.schema)
- view = self.vreg['views'].select(vid, req, rset=rset)
+ vid = req.form.get('fallbackvid') or vid_from_rset(req, rset, req.vreg.schema)
+ view = req.vreg['views'].select(vid, req, rset=rset)
return view, rset
def add_to_breadcrumbs(self, view):
@@ -140,11 +141,11 @@
# specifies explicitly it should not be added to breadcrumb or the
# view is a binary view
if view.add_to_breadcrumbs and not view.binary:
- self.req.update_breadcrumbs()
+ self._cw.update_breadcrumbs()
def validate_cache(self, view):
view.set_http_cache_headers()
- self.req.validate_cache()
+ self._cw.validate_cache()
def execute_linkto(self, eid=None):
"""XXX __linkto parameter may cause security issue
@@ -152,7 +153,7 @@
defined here since custom application controller inheriting from this
one use this method?
"""
- req = self.req
+ req = self._cw
if not '__linkto' in req.form:
return
if eid is None:
@@ -214,13 +215,13 @@
class FormValidatorController(Controller):
- id = 'validateform'
+ __regid__ = 'validateform'
def response(self, domid, status, args, entity):
- callback = str(self.req.form.get('__onsuccess', 'null'))
- errback = str(self.req.form.get('__onfailure', 'null'))
- cbargs = str(self.req.form.get('__cbargs', 'null'))
- self.req.set_content_type('text/html')
+ callback = str(self._cw.form.get('__onsuccess', 'null'))
+ errback = str(self._cw.form.get('__onfailure', 'null'))
+ cbargs = str(self._cw.form.get('__cbargs', 'null'))
+ self._cw.set_content_type('text/html')
jsargs = simplejson.dumps((status, args, entity), cls=CubicWebJsonEncoder)
return """<script type="text/javascript">
wp = window.parent;
@@ -228,17 +229,17 @@
</script>""" % (domid, callback, errback, jsargs, cbargs)
def publish(self, rset=None):
- self.req.json_request = True
+ self._cw.json_request = True
# XXX unclear why we have a separated controller here vs
# js_validate_form on the json controller
- status, args, entity = _validate_form(self.req, self.vreg)
- domid = self.req.form.get('__domid', 'entityForm').encode(
- self.req.encoding)
+ status, args, entity = _validate_form(self._cw, self._cw.vreg)
+ domid = self._cw.form.get('__domid', 'entityForm').encode(
+ self._cw.encoding)
return self.response(domid, status, args, entity)
class JSonController(Controller):
- id = 'json'
+ __regid__ = 'json'
def publish(self, rset=None):
"""call js_* methods. Expected form keys:
@@ -249,16 +250,16 @@
note: it's the responsability of js_* methods to set the correct
response content type
"""
- self.req.json_request = True
+ self._cw.json_request = True
try:
- fname = self.req.form['fname']
+ fname = self._cw.form['fname']
func = getattr(self, 'js_%s' % fname)
except KeyError:
raise RemoteCallFailed('no method specified')
except AttributeError:
raise RemoteCallFailed('no %s method' % fname)
# no <arg> attribute means the callback takes no argument
- args = self.req.form.get('arg', ())
+ args = self._cw.form.get('arg', ())
if not isinstance(args, (list, tuple)):
args = (args,)
args = [simplejson.loads(arg) for arg in args]
@@ -276,7 +277,7 @@
return ''
# get unicode on @htmlize methods, encoded string on @jsonize methods
elif isinstance(result, unicode):
- return result.encode(self.req.encoding)
+ return result.encode(self._cw.encoding)
return result
def _rebuild_posted_form(self, names, values, action=None):
@@ -302,16 +303,16 @@
def _exec(self, rql, args=None, eidkey=None, rocheck=True):
"""json mode: execute RQL and return resultset as json"""
if rocheck:
- self.ensure_ro_rql(rql)
+ self._cw.ensure_ro_rql(rql)
try:
- return self.req.execute(rql, args, eidkey)
+ return self._cw.execute(rql, args, eidkey)
except Exception, ex:
self.exception("error in _exec(rql=%s): %s", rql, ex)
return None
return None
def _call_view(self, view, **kwargs):
- req = self.req
+ req = self._cw
divid = req.form.get('divid', 'pageContent')
# we need to call pagination before with the stream set
stream = view.set_stream()
@@ -319,7 +320,7 @@
if divid == 'pageContent':
# mimick main template behaviour
stream.write(u'<div id="pageContent">')
- vtitle = self.req.form.get('vtitle')
+ vtitle = self._cw.form.get('vtitle')
if vtitle:
stream.write(u'<h1 class="vtitle">%s</h1>\n' % vtitle)
view.paginate()
@@ -338,30 +339,30 @@
@xhtmlize
def js_view(self):
# XXX try to use the page-content template
- req = self.req
+ req = self._cw
rql = req.form.get('rql')
if rql:
rset = self._exec(rql)
else:
rset = None
- vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
+ vid = req.form.get('vid') or vid_from_rset(req, rset, self._cw.vreg.schema)
try:
- view = self.vreg['views'].select(vid, req, rset=rset)
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
except NoSelectableObject:
vid = req.form.get('fallbackvid', 'noresult')
- view = self.vreg['views'].select(vid, req, rset=rset)
+ view = self._cw.vreg['views'].select(vid, req, rset=rset)
return self._call_view(view)
@xhtmlize
def js_prop_widget(self, propkey, varname, tabindex=None):
"""specific method for CWProperty handling"""
- entity = self.vreg['etypes'].etype_class('CWProperty')(self.req)
+ entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
entity.eid = varname
entity['pkey'] = propkey
- form = self.vreg['forms'].select('edition', self.req, entity=entity)
+ form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
form.build_context()
vfield = form.field_by_name('value')
- renderer = FormRenderer(self.req)
+ renderer = FormRenderer(self._cw)
return vfield.render(form, renderer, tabindex=tabindex) \
+ renderer.render_help(form, vfield)
@@ -371,7 +372,7 @@
rset = self._exec(rql)
else:
rset = None
- comp = self.vreg[registry].select(compid, self.req, rset=rset)
+ comp = self._cw.vreg[registry].select(compid, self._cw, rset=rset)
if extraargs is None:
extraargs = {}
else: # we receive unicode keys which is not supported by the **syntax
@@ -383,7 +384,7 @@
@check_pageid
@xhtmlize
def js_inline_creation_form(self, peid, ttype, rtype, role, i18nctx):
- view = self.vreg['views'].select('inline-creation', self.req,
+ view = self._cw.vreg['views'].select('inline-creation', self._cw,
etype=ttype, peid=peid, rtype=rtype,
role=role)
return self._call_view(view, i18nctx=i18nctx)
@@ -393,68 +394,68 @@
return self.validate_form(action, names, values)
def validate_form(self, action, names, values):
- self.req.form = self._rebuild_posted_form(names, values, action)
- return _validate_form(self.req, self.vreg)
+ self._cw.form = self._rebuild_posted_form(names, values, action)
+ return _validate_form(self._cw, self._cw.vreg)
@xhtmlize
def js_reledit_form(self):
- args = dict((x,self.req.form[x])
+ args = dict((x,self._cw.form[x])
for x in frozenset(('rtype', 'role', 'reload', 'landing_zone')))
- entity = self.req.entity_from_eid(int(self.req.form['eid']))
+ entity = self._cw.entity_from_eid(int(self._cw.form['eid']))
# note: default is reserved in js land
- args['default'] = self.req.form['default_value']
+ args['default'] = self._cw.form['default_value']
args['reload'] = simplejson.loads(args['reload'])
return entity.view('doreledit', **args)
@jsonize
def js_i18n(self, msgids):
"""returns the translation of `msgid`"""
- return [self.req._(msgid) for msgid in msgids]
+ return [self._cw._(msgid) for msgid in msgids]
@jsonize
def js_format_date(self, strdate):
"""returns the formatted date for `msgid`"""
date = strptime(strdate, '%Y-%m-%d %H:%M:%S')
- return self.format_date(date)
+ return self._cw.format_date(date)
@jsonize
def js_external_resource(self, resource):
"""returns the URL of the external resource named `resource`"""
- return self.req.external_resource(resource)
+ return self._cw.external_resource(resource)
@check_pageid
@jsonize
def js_user_callback(self, cbname):
- page_data = self.req.get_session_data(self.req.pageid, {})
+ page_data = self._cw.get_session_data(self._cw.pageid, {})
try:
cb = page_data[cbname]
except KeyError:
return None
- return cb(self.req)
+ return cb(self._cw)
if HAS_SEARCH_RESTRICTION:
@jsonize
def js_filter_build_rql(self, names, values):
form = self._rebuild_posted_form(names, values)
- self.req.form = form
- builder = FilterRQLBuilder(self.req)
+ self._cw.form = form
+ builder = FilterRQLBuilder(self._cw)
return builder.build_rql()
@jsonize
def js_filter_select_content(self, facetids, rql):
- rqlst = self.vreg.parse(self.req, rql) # XXX Union unsupported yet
+ rqlst = self._cw.vreg.parse(self._cw, rql) # XXX Union unsupported yet
mainvar = prepare_facets_rqlst(rqlst)[0]
update_map = {}
for facetid in facetids:
- facet = get_facet(self.req, facetid, rqlst.children[0], mainvar)
+ facet = get_facet(self._cw, facetid, rqlst.children[0], mainvar)
update_map[facetid] = facet.possible_values()
return update_map
def js_unregister_user_callback(self, cbname):
- self.req.unregister_callback(self.req.pageid, cbname)
+ self._cw.unregister_callback(self._cw.pageid, cbname)
def js_unload_page_data(self):
- self.req.del_session_data(self.req.pageid)
+ self._cw.del_session_data(self._cw.pageid)
def js_cancel_edition(self, errorurl):
"""cancelling edition from javascript
@@ -463,21 +464,21 @@
- errorurl
- pending insertions / deletions
"""
- self.req.cancel_edition(errorurl)
+ self._cw.cancel_edition(errorurl)
def js_delete_bookmark(self, beid):
rql = 'DELETE B bookmarked_by U WHERE B eid %(b)s, U eid %(u)s'
- self.req.execute(rql, {'b': typed_eid(beid), 'u' : self.req.user.eid})
+ self._cw.execute(rql, {'b': typed_eid(beid), 'u' : self._cw.user.eid})
def js_node_clicked(self, treeid, nodeeid):
"""add/remove eid in treestate cookie"""
from cubicweb.web.views.treeview import treecookiename
- cookies = self.req.get_cookie()
+ cookies = self._cw.get_cookie()
statename = treecookiename(treeid)
treestate = cookies.get(statename)
if treestate is None:
cookies[statename] = nodeeid
- self.req.set_cookie(cookies, statename)
+ self._cw.set_cookie(cookies, statename)
else:
marked = set(filter(None, treestate.value.split(';')))
if nodeeid in marked:
@@ -485,29 +486,29 @@
else:
marked.add(nodeeid)
cookies[statename] = ';'.join(marked)
- self.req.set_cookie(cookies, statename)
+ self._cw.set_cookie(cookies, statename)
@jsonize
def js_set_cookie(self, cookiename, cookievalue):
# XXX we should consider jQuery.Cookie
cookiename, cookievalue = str(cookiename), str(cookievalue)
- cookies = self.req.get_cookie()
+ cookies = self._cw.get_cookie()
cookies[cookiename] = cookievalue
- self.req.set_cookie(cookies, cookiename)
+ self._cw.set_cookie(cookies, cookiename)
# relations edition stuff ##################################################
def _add_pending(self, eidfrom, rel, eidto, kind):
key = 'pending_%s' % kind
- pendings = self.req.get_session_data(key, set())
+ pendings = self._cw.get_session_data(key, set())
pendings.add( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
- self.req.set_session_data(key, pendings)
+ self._cw.set_session_data(key, pendings)
def _remove_pending(self, eidfrom, rel, eidto, kind):
key = 'pending_%s' % kind
- pendings = self.req.get_session_data(key)
+ pendings = self._cw.get_session_data(key)
pendings.remove( (typed_eid(eidfrom), rel, typed_eid(eidto)) )
- self.req.set_session_data(key, pendings)
+ self._cw.set_session_data(key, pendings)
def js_remove_pending_insert(self, (eidfrom, rel, eidto)):
self._remove_pending(eidfrom, rel, eidto, 'insert')
@@ -526,67 +527,67 @@
@jsonize
def js_add_and_link_new_entity(self, etype_to, rel, eid_to, etype_from, value_from):
# create a new entity
- eid_from = self.req.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
+ eid_from = self._cw.execute('INSERT %s T : T name "%s"' % ( etype_from, value_from ))[0][0]
# link the new entity to the main entity
rql = 'SET F %(rel)s T WHERE F eid %(eid_to)s, T eid %(eid_from)s' % {'rel' : rel, 'eid_to' : eid_to, 'eid_from' : eid_from}
return eid_from
class SendMailController(Controller):
- id = 'sendmail'
+ __regid__ = 'sendmail'
__select__ = match_user_groups('managers', 'users')
def recipients(self):
"""returns an iterator on email's recipients as entities"""
- eids = self.req.form['recipient']
+ eids = self._cw.form['recipient']
# make sure we have a list even though only one recipient was specified
if isinstance(eids, basestring):
eids = (eids,)
rql = 'Any X WHERE X eid in (%s)' % (','.join(eids))
- rset = self.req.execute(rql)
+ rset = self._cw.execute(rql)
for entity in rset.entities():
yield entity
@property
@cached
def smtp(self):
- mailhost, port = self.config['smtp-host'], self.config['smtp-port']
+ mailhost, port = self._cw.config['smtp-host'], self._cw.config['smtp-port']
try:
return SMTP(mailhost, port)
except Exception, ex:
self.exception("can't connect to smtp server %s:%s (%s)",
mailhost, port, ex)
- url = self.build_url(__message=self.req._('could not connect to the SMTP server'))
+ url = self._cw.build_url(__message=self._cw._('could not connect to the SMTP server'))
raise Redirect(url)
def sendmail(self, recipient, subject, body):
- helo_addr = '%s <%s>' % (self.config['sender-name'],
- self.config['sender-addr'])
- msg = format_mail({'email' : self.req.user.get_email(),
- 'name' : self.req.user.dc_title(),},
+ helo_addr = '%s <%s>' % (self._cw.config['sender-name'],
+ self._cw.config['sender-addr'])
+ msg = format_mail({'email' : self._cw.user.get_email(),
+ 'name' : self._cw.user.dc_title(),},
[recipient], body, subject)
self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
def publish(self, rset=None):
# XXX this allows users with access to an cubicweb instance to use it as
# a mail relay
- body = self.req.form['mailbody']
- subject = self.req.form['subject']
+ body = self._cw.form['mailbody']
+ subject = self._cw.form['subject']
for recipient in self.recipients():
text = body % recipient.as_email_context()
self.sendmail(recipient.get_email(), subject, text)
- # breadcrumbs = self.req.get_session_data('breadcrumbs', None)
- url = self.build_url(__message=self.req._('emails successfully sent'))
+ # breadcrumbs = self._cw.get_session_data('breadcrumbs', None)
+ url = self._cw.build_url(__message=self._cw._('emails successfully sent'))
raise Redirect(url)
class MailBugReportController(SendMailController):
- id = 'reportbug'
+ __regid__ = 'reportbug'
__select__ = yes()
def publish(self, rset=None):
- body = self.req.form['description']
- self.sendmail(self.config['submit-mail'], _('%s error report') % self.config.appid, body)
- url = self.build_url(__message=self.req._('bug report sent'))
+ body = self._cw.form['description']
+ self.sendmail(self._cw.config['submit-mail'], _('%s error report') % self._cw.config.appid, body)
+ url = self._cw.build_url(__message=self._cw._('bug report sent'))
raise Redirect(url)
--- a/web/views/baseforms.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,607 +0,0 @@
-"""Set of HTML automatic forms to create, delete, copy or edit a single entity
-or a list of entities of the same type
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from copy import copy
-
-from simplejson import dumps
-
-from logilab.mtconverter import xml_escape
-from logilab.common.decorators import cached
-
-from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat,
- non_final_entity, match_kwargs, one_line_rset)
-from cubicweb.view import View, EntityView
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
-from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
-from cubicweb.web.form import FormMixIn
-from cubicweb.web.views.autoform import AutomaticEntityForm
-
-_ = unicode
-
-
-class EditionForm(FormMixIn, EntityView):
- """primary entity edition form
-
- When generating a new attribute_input, the editor will look for a method
- named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
- name of the attribute being edited. You may use this feature to compute
- dynamic default values such as the 'tomorrow' date or the user's login
- being connected
- """
- id = 'edition'
- __select__ = one_line_rset() & non_final_entity()
-
- title = _('edition')
- controller = 'edit'
- skip_relations = set()
-
- EDITION_BODY = u'''\
- %(errormsg)s
-<form id="%(formid)s" class="entityForm" cubicweb:target="eformframe"
- method="post" onsubmit="%(onsubmit)s" enctype="%(enctype)s" action="%(action)s">
- %(title)s
- <div id="progress">%(inprogress)s</div>
- <div class="iformTitle"><span>%(mainattrs_label)s</span></div>
- <div class="formBody"><fieldset>
- %(base)s
- %(attrform)s
- %(relattrform)s
-</fieldset>
- %(relform)s
- </div>
- <table width="100%%">
- <tbody>
- <tr><td align="center">
- %(validate)s
- </td><td style="align: right; width: 50%%;">
- %(apply)s
- %(cancel)s
- </td></tr>
- </tbody>
- </table>
-</form>
-'''
-
- def cell_call(self, row, col, **kwargs):
- self.req.add_js( ('cubicweb.ajax.js', ) )
- entity = self.complete_entity(row, col)
- self.edit_form(entity, kwargs)
-
- def edit_form(self, entity, kwargs):
- varmaker = self.req.get_page_data('rql_varmaker')
- if varmaker is None:
- varmaker = self.req.varmaker
- self.req.set_page_data('rql_varmaker', varmaker)
- self.varmaker = varmaker
- self.w(self.EDITION_BODY % self.form_context(entity, kwargs))
-
- def form_context(self, entity, kwargs):
- """returns the dictionnary used to fill the EDITION_BODY template
-
- If you create your own edition form, you can probably just override
- `EDITION_BODY` and `form_context`
- """
- if self.need_multipart(entity):
- enctype = 'multipart/form-data'
- else:
- enctype = 'application/x-www-form-urlencoded'
- self._hiddens = []
- if entity.eid is None:
- entity.eid = self.varmaker.next()
- # XXX (hack) action_title might need __linkto req's original value
- # and widgets such as DynamicComboWidget might change it
- # so we need to compute title before calling atttributes_form
- formtitle = self.action_title(entity)
- # be sure to call .*_form first so tabindexes are correct and inlined
- # fields errors are consumed
- if not entity.has_eid() or entity.has_perm('update'):
- attrform = self.attributes_form(entity, kwargs)
- else:
- attrform = ''
- inlineform = self.inline_entities_form(entity, kwargs)
- relform = self.relations_form(entity, kwargs)
- vindex = self.req.next_tabindex()
- aindex = self.req.next_tabindex()
- cindex = self.req.next_tabindex()
- self.add_hidden_web_behaviour_params(entity)
- _ = self.req._
- return {
- 'formid' : self.domid,
- 'onsubmit' : self.on_submit(entity),
- 'enctype' : enctype,
- 'errormsg' : self.error_message(),
- 'action' : self.build_url('validateform'),
- 'eids' : entity.has_eid() and [entity.eid] or [],
- 'inprogress': _('validating...'),
- 'title' : formtitle,
- 'mainattrs_label' : _('main informations'),
- 'reseturl' : self.redirect_url(entity),
- 'attrform' : attrform,
- 'relform' : relform,
- 'relattrform': inlineform,
- 'base' : self.base_form(entity, kwargs),
- 'validate' : self.button_ok(tabindex=vindex),
- 'apply' : self.button_apply(tabindex=aindex),
- 'cancel' : self.button_cancel(tabindex=cindex),
- }
-
- @property
- def formid(self):
- return self.id
-
- def action_title(self, entity):
- """form's title"""
- ptitle = self.req._(self.title)
- return u'<div class="formTitle"><span>%s %s</span></div>' % (
- entity.dc_type(), ptitle and '(%s)' % ptitle)
-
-
- def base_form(self, entity, kwargs):
- output = []
- for name, value, iid in self._hiddens:
- if isinstance(value, basestring):
- value = xml_escape(value)
- if iid:
- output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
- % (iid, name, value))
- else:
- output.append(u'<input type="hidden" name="%s" value="%s" />'
- % (name, value))
- return u'\n'.join(output)
-
- def add_hidden_web_behaviour_params(self, entity):
- """inserts hidden params controlling how errors and redirection
- should be handled
- """
- req = self.req
- self._hiddens.append( (u'__maineid', entity.eid, u'') )
- self._hiddens.append( (u'__errorurl', req.url(), u'errorurl') )
- self._hiddens.append( (u'__form_id', self.formid, u'') )
- for param in NAV_FORM_PARAMETERS:
- value = req.form.get(param)
- if value:
- self._hiddens.append( (param, value, u'') )
- msg = self.submited_message()
- # If we need to directly attach the new object to another one
- for linkto in req.list_form_param('__linkto'):
- self._hiddens.append( ('__linkto', linkto, '') )
- msg = '%s %s' % (msg, self.req._('and linked'))
- self._hiddens.append( ('__message', msg, '') )
-
-
- def attributes_form(self, entity, kwargs, include_eid=True):
- """create a form to edit entity's attributes"""
- html = []
- w = html.append
- eid = entity.eid
- wdg = entity.get_widget
- lines = (wdg(rschema, x) for rschema, x in self.editable_attributes(entity))
- if include_eid:
- self._hiddens.append( ('eid', entity.eid, '') )
- self._hiddens.append( (eid_param('__type', eid), entity.e_schema, '') )
- w(u'<table id="%s" class="%s" style="width:100%%;">' %
- (kwargs.get('tab_id', 'entityForm%s' % eid),
- kwargs.get('tab_class', 'attributeForm')))
- for widget in lines:
- w(u'<tr>\n<th class="labelCol">%s</th>' % widget.render_label(entity))
- error = widget.render_error(entity)
- if error:
- w(u'<td class="error" style="width:100%;">')
- else:
- w(u'<td style="width:100%;">')
- if error:
- w(error)
- w(widget.edit_render(entity))
- w(widget.render_help(entity))
- w(u'</td>\n</tr>')
- w(u'</table>')
- return u'\n'.join(html)
-
- def editable_attributes(self, entity):
- # XXX both (add, delete)
- return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
- if rschema != 'eid']
-
- def relations_form(self, entity, kwargs):
- srels_by_cat = entity.srelations_by_category(('generic', 'metadata'), 'add')
- if not srels_by_cat:
- return u''
- req = self.req
- _ = self.req._
- __ = _
- label = u'%s :' % __('This %s' % entity.e_schema).capitalize()
- eid = entity.eid
- html = []
- w = html.append
- w(u'<fieldset class="subentity">')
- w(u'<legend class="iformTitle">%s</legend>' % label)
- w(u'<table id="relatedEntities">')
- for row in self.relations_table(entity):
- # already linked entities
- if row[2]:
- w(u'<tr><th class="labelCol">%s</th>' % row[0].display_name(req, row[1]))
- w(u'<td>')
- w(u'<ul>')
- for viewparams in row[2]:
- w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
- % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
- if not self.force_display and self.maxrelitems < len(row[2]):
- w(u'<li class="invisible">%s</li>' % self.force_display_link())
- w(u'</ul>')
- w(u'</td>')
- w(u'</tr>')
- pendings = list(self.restore_pending_inserts(entity))
- if not pendings:
- w(u'<tr><th> </th><td> </td></tr>')
- else:
- for row in pendings:
- # soon to be linked to entities
- w(u'<tr id="tr%s">' % row[1])
- w(u'<th>%s</th>' % row[3])
- w(u'<td>')
- w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
- (_('cancel this insert'), row[2]))
- w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], xml_escape(row[5])))
- w(u'</td>')
- w(u'</tr>')
- w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
- w(u'<th class="labelCol">')
- w(u'<span>%s</span>' % _('add relation'))
- w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
- w(u'<option value="">%s</option>' % _('select a relation'))
- for i18nrtype, rschema, target in srels_by_cat:
- # more entities to link to
- w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
- w(u'</select>')
- w(u'</th>')
- w(u'<td id="unrelatedDivs_%s"></td>' % eid)
- w(u'</tr>')
- w(u'</table>')
- w(u'</fieldset>')
- return '\n'.join(html)
-
- def inline_entities_form(self, entity, kwargs):
- """create a form to edit entity's inlined relations"""
- result = []
- _ = self.req._
- for rschema, targettypes, x in entity.relations_by_category('inlineview', 'add'):
- # show inline forms only if there's one possible target type
- # for rschema
- if len(targettypes) != 1:
- self.warning('entity related by the %s relation should have '
- 'inlined form but there is multiple target types, '
- 'dunno what to do', rschema)
- continue
- targettype = targettypes[0].type
- if self.should_inline_relation_form(entity, rschema, targettype, x):
- result.append(u'<div id="inline%sslot">' % rschema)
- existant = entity.has_eid() and entity.related(rschema)
- if existant:
- # display inline-edition view for all existing related entities
- result.append(self.view('inline-edition', existant,
- ptype=entity.e_schema, peid=entity.eid,
- rtype=rschema, role=x, **kwargs))
- if x == 'subject':
- card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
- else:
- card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
- # there is no related entity and we need at least one : we need to
- # display one explicit inline-creation view
- if self.should_display_inline_relation_form(rschema, existant, card):
- result.append(self.view('inline-creation', None, etype=targettype,
- peid=entity.eid, ptype=entity.e_schema,
- rtype=rschema, role=x, **kwargs))
- # we can create more than one related entity, we thus display a link
- # to add new related entities
- if self.should_display_add_inline_relation_link(rschema, existant, card):
- divid = "addNew%s%s%s:%s" % (targettype, rschema, x, entity.eid)
- result.append(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
- % divid)
- js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
- entity.eid, entity.e_schema, targettype, rschema, x)
- if card in '1?':
- js = "toggleVisibility('%s'); %s" % (divid, js)
- result.append(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
- % (rschema, entity.eid, js,
- self.req.__('add a %s' % targettype)))
- result.append(u'</div>')
- result.append(u'</div>')
- return '\n'.join(result)
-
- # should_* method extracted to allow overriding
-
- def should_inline_relation_form(self, entity, rschema, targettype, role):
- return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
- targettype)
-
- def should_display_inline_relation_form(self, rschema, existant, card):
- return not existant and card in '1+'
-
- def should_display_add_inline_relation_link(self, rschema, existant, card):
- return not existant or card in '+*'
-
- def reset_url(self, entity):
- return entity.absolute_url()
-
- def on_submit(self, entity):
- return u'return freezeFormButtons(\'%s\')' % (self.domid)
-
- def submited_message(self):
- return self.req._('element edited')
-
-
-
-class CreationForm(EditionForm):
- __select__ = specified_etype_implements('Any')
- # XXX bw compat, use View.registered since we don't want accept_compat
- # wrapper set in EntityView
- registered = accepts_etype_compat(View.registered)
- id = 'creation'
- title = _('creation')
-
- def call(self, **kwargs):
- """creation view for an entity"""
- self.req.add_js( ('cubicweb.ajax.js',) )
- self.initialize_varmaker()
- etype = kwargs.pop('etype', self.req.form.get('etype'))
- try:
- entity = self.vreg.etype_class(etype)(self.req, None, None)
- except:
- self.w(self.req._('no such entity type %s') % etype)
- else:
- entity.eid = self.varmaker.next()
- self.edit_form(entity, kwargs)
-
- def action_title(self, entity):
- """custom form title if creating a entity with __linkto"""
- if '__linkto' in self.req.form:
- if isinstance(self.req.form['__linkto'], list):
- # XXX which one should be considered (case: add a ticket to a version in jpl)
- rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
- else:
- rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
- linkto_rset = self.req.eid_rset(linkto_eid)
- linkto_type = linkto_rset.description[0][0]
- if role == 'subject':
- title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
- entity.e_schema, entity.e_schema, rtype, linkto_type))
- else:
- title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
- entity.e_schema, linkto_type, rtype, entity.e_schema))
- msg = title % {'linkto' : self.view('incontext', linkto_rset)}
- return u'<div class="formTitle notransform"><span>%s</span></div>' % msg
- else:
- return super(CreationForm, self).action_title(entity)
-
- @property
- def formid(self):
- return 'edition'
-
- def relations_form(self, entity, kwargs):
- return u''
-
- def reset_url(self, entity=None):
- return self.build_url(self.req.form.get('etype', '').lower())
-
- def submited_message(self):
- return self.req._('element created')
-
- def url(self):
- """return the url associated with this view"""
- return self.create_url(self.req.form.get('etype'))
-
-
-class InlineFormMixIn(object):
-
- @cached
- def card(self, etype):
- return self.rschema.rproperty(self.parent_schema, etype, 'cardinality')[0]
-
- def action_title(self, entity):
- return self.rschema.display_name(self.req, self.role)
-
- def add_hidden_web_behaviour_params(self, entity):
- pass
-
- def edit_form(self, entity, ptype, peid, rtype,
- role='subject', **kwargs):
- self.rschema = self.schema.rschema(rtype)
- self.role = role
- self.parent_schema = self.schema.eschema(ptype)
- self.parent_eid = peid
- super(InlineFormMixIn, self).edit_form(entity, kwargs)
-
- def should_inline_relation_form(self, entity, rschema, targettype, role):
- if rschema == self.rschema:
- return False
- return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
- targettype)
-
- @cached
- def keep_entity(self, entity):
- req = self.req
- # are we regenerating form because of a validation error ?
- erroneous_post = req.data.get('formvalues')
- if erroneous_post:
- cdvalues = req.list_form_param('%s:%s' % (self.rschema,
- self.parent_eid),
- erroneous_post)
- if unicode(entity.eid) not in cdvalues:
- return False
- return True
-
- def form_context(self, entity, kwargs):
- ctx = super(InlineFormMixIn, self).form_context(entity, kwargs)
- _ = self.req._
- local_ctx = {'createmsg' : self.req.__('add a %s' % entity.e_schema),
- 'so': self.role[0], # 's' for subject, 'o' for object
- 'eid' : entity.eid,
- 'rtype' : self.rschema,
- 'parenteid' : self.parent_eid,
- 'parenttype' : self.parent_schema,
- 'etype' : entity.e_schema,
- 'novalue' : INTERNAL_FIELD_VALUE,
- 'removemsg' : self.req.__('remove this %s' % entity.e_schema),
- 'notice' : self.req._('click on the box to cancel the deletion'),
- }
- ctx.update(local_ctx)
- return ctx
-
-
-class CopyEditionForm(EditionForm):
- id = 'copy'
- title = _('copy edition')
-
- def cell_call(self, row, col, **kwargs):
- self.req.add_js(('cubicweb.ajax.js',))
- entity = self.complete_entity(row, col, skip_bytes=True)
- # make a copy of entity to avoid altering the entity in the
- # request's cache.
- self.newentity = copy(entity)
- self.copying = self.newentity.eid
- self.newentity.eid = None
- self.edit_form(self.newentity, kwargs)
- del self.newentity
-
- def action_title(self, entity):
- """form's title"""
- msg = super(CopyEditionForm, self).action_title(entity)
- return msg + (u'<script type="text/javascript">updateMessage("%s");</script>\n'
- % self.req._('Please note that this is only a shallow copy'))
- # XXX above message should have style of a warning
-
- @property
- def formid(self):
- return 'edition'
-
- def relations_form(self, entity, kwargs):
- return u''
-
- def reset_url(self, entity):
- return self.build_url('view', rql='Any X WHERE X eid %s' % self.copying)
-
- def attributes_form(self, entity, kwargs, include_eid=True):
- # we don't want __clone_eid on inlined edited entities
- if entity.eid == self.newentity.eid:
- self._hiddens.append((eid_param('__cloned_eid', entity.eid), self.copying, ''))
- return EditionForm.attributes_form(self, entity, kwargs, include_eid)
-
- def submited_message(self):
- return self.req._('element copied')
-
-
-class TableEditForm(FormMixIn, EntityView):
- id = 'muledit'
- title = _('multiple edit')
-
- EDITION_BODY = u'''<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);" action="%(action)s">
- %(error)s
- <div id="progress">%(progress)s</div>
- <fieldset>
- <input type="hidden" name="__errorurl" value="%(url)s" />
- <input type="hidden" name="__form_id" value="%(formid)s" />
- <input type="hidden" name="__redirectvid" value="%(redirectvid)s" />
- <input type="hidden" name="__redirectrql" value="%(redirectrql)s" />
- <table class="listing">
- <tr class="header">
- <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
- %(attrheaders)s
- </tr>
- %(lines)s
- </table>
- <table width="100%%">
- <tr>
- <td align="left">
- <input class="validateButton" type="submit" value="%(okvalue)s" title="%(oktitle)s" />
- <input class="validateButton" type="reset" name="__action_cancel" value="%(cancelvalue)s" title="%(canceltitle)s" />
- </td>
- </tr>
- </table>
- </fieldset>
-</form>
-'''
-
- WIDGET_CELL = u'''\
-<td%(csscls)s>
- %(error)s
- <div>%(widget)s</div>
-</td>'''
-
- def call(self, **kwargs):
- """a view to edit multiple entities of the same type
- the first column should be the eid
- """
- req = self.req
- form = req.form
- _ = req._
- sampleentity = self.complete_entity(0)
- attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
- for rdef in sampleentity.relations_by_category('primary', 'add')
- if rdef[0].type != 'eid']
- ctx = {'action' : self.build_url('edit'),
- 'error': self.error_message(),
- 'progress': _('validating...'),
- 'url': xml_escape(req.url()),
- 'formid': self.id,
- 'redirectvid': xml_escape(form.get('__redirectvid', 'list')),
- 'redirectrql': xml_escape(form.get('__redirectrql', self.rset.printable_rql())),
- 'attrheaders': u'\n'.join(attrheaders),
- 'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
- 'okvalue': _('button_ok').capitalize(),
- 'oktitle': _('validate modifications on selected items').capitalize(),
- 'cancelvalue': _('button_reset').capitalize(),
- 'canceltitle': _('revert changes').capitalize(),
- }
- self.w(self.EDITION_BODY % ctx)
-
-
- def reset_url(self, entity=None):
- self.build_url('view', rql=self.rset.printable_rql())
-
- def edit_form(self, entity):
- html = []
- w = html.append
- entity.complete()
- eid = entity.eid
- values = self.req.data.get('formvalues', ())
- qeid = eid_param('eid', eid)
- checked = qeid in values
- w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
- w(u'<td>%s<input type="hidden" name="__type:%s" value="%s" /></td>'
- % (checkbox('eid', eid, checked=checked), eid, entity.e_schema))
- # attribute relations (skip eid which is handled by the checkbox
- wdg = entity.get_widget
- wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
- if rschema.type != 'eid'] # XXX both (add, delete)
- seid = xml_escape(dumps(eid))
- for wobj in wdgfactories:
- if isinstance(wobj, ComboBoxWidget):
- wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
- elif isinstance(wobj, InputWidget):
- wobj.attrs['onkeypress'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
- error = wobj.render_error(entity)
- if error:
- csscls = u' class="error"'
- else:
- csscls = u''
- w(self.WIDGET_CELL % {'csscls': csscls, 'error': error,
- 'widget': wobj.edit_render(entity)})
- w(u'</tr>')
- return '\n'.join(html)
-
-
-# XXX bw compat
-
-from logilab.common.deprecation import class_moved
-from cubicweb.web.views import editviews
-ComboboxView = class_moved(editviews.ComboboxView)
--- a/web/views/basetemplates.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/basetemplates.py Mon Feb 08 13:35:07 2010 +0100
@@ -24,7 +24,7 @@
self.set_request_content_type()
w = self.w
self.write_doctype()
- self.template_header('text/html', self.req._('login_action'))
+ self.template_header('text/html', self._cw._('login_action'))
w(u'<body>\n')
self.content(w)
w(u'</body>')
@@ -32,37 +32,37 @@
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
w = self.whead
# explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
- w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
+ w(u'<base href="%s"></base>' % xml_escape(self._cw.base_url()))
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
- % (content_type, self.req.encoding))
+ % (content_type, self._cw.encoding))
w(NOINDEX)
w(NOFOLLOW)
w(u'\n'.join(additional_headers) + u'\n')
- self.wview('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.cw_rset)
w(u'<title>%s</title>\n' % xml_escape(page_title))
class LogInTemplate(LogInOutTemplate):
- id = 'login'
+ __regid__ = 'login'
title = 'log in'
def content(self, w):
- self.wview('logform', rset=self.rset, id='loginBox', klass='')
+ self.wview('logform', rset=self.cw_rset, id='loginBox', klass='')
class LoggedOutTemplate(LogInOutTemplate):
- id = 'loggedout'
+ __regid__ = 'loggedout'
title = 'logged out'
def content(self, w):
# FIXME Deprecated code ?
- msg = self.req._('you have been logged out')
+ msg = self._cw._('you have been logged out')
w(u'<h2>%s</h2>\n' % msg)
- if self.config['anonymous-user']:
- indexurl = self.build_url('view', vid='index', __message=msg)
+ if self._cw.vreg.config['anonymous-user']:
+ indexurl = self._cw.build_url('view', vid='index', __message=msg)
w(u'<p><a href="%s">%s</a><p>' % (
xml_escape(indexurl),
- self.req._('go back to the index page')))
+ self._cw._('go back to the index page')))
@objectify_selector
def templatable_view(cls, req, rset, *args, **kwargs):
@@ -78,15 +78,15 @@
class NonTemplatableViewTemplate(MainTemplate):
"""main template for any non templatable views (xml, binaries, etc.)"""
- id = 'main-template'
+ __regid__ = 'main-template'
__select__ = ~templatable_view()
def call(self, view):
view.set_request_content_type()
view.set_stream()
- if (self.req.form.has_key('__notemplate') and view.templatable
- and view.content_type == self.req.html_content_type()):
- view.w(self.req.document_surrounding_div())
+ if (self._cw.form.has_key('__notemplate') and view.templatable
+ and view.content_type == self._cw.html_content_type()):
+ view.w(self._cw.document_surrounding_div())
view.render()
view.w(u'</div>')
else:
@@ -101,7 +101,7 @@
- call header / footer templates
"""
- id = 'main-template'
+ __regid__ = 'main-template'
__select__ = templatable_view()
def call(self, view):
@@ -109,13 +109,13 @@
self.template_header(self.content_type, view)
w = self.w
w(u'<div id="pageContent">\n')
- vtitle = self.req.form.get('vtitle')
+ vtitle = self._cw.form.get('vtitle')
if vtitle:
w(u'<h1 class="vtitle">%s</h1>\n' % xml_escape(vtitle))
# display entity type restriction component
- etypefilter = self.vreg['components'].select_vobject(
- 'etypenavigation', self.req, rset=self.rset)
- if etypefilter:
+ etypefilter = self._cw.vreg['components'].select_or_none(
+ 'etypenavigation', self._cw, rset=self.cw_rset)
+ if etypefilter and etypefilter.cw_propval('visible'):
etypefilter.render(w=w)
self.nav_html = UStringIO()
if view:
@@ -136,30 +136,29 @@
def template_html_header(self, content_type, page_title, additional_headers=()):
w = self.whead
- lang = self.req.lang
+ lang = self._cw.lang
self.write_doctype()
# explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
- w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
+ w(u'<base href="%s"></base>' % xml_escape(self._cw.base_url()))
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
- % (content_type, self.req.encoding))
+ % (content_type, self._cw.encoding))
w(u'\n'.join(additional_headers) + u'\n')
- self.wview('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.cw_rset)
if page_title:
w(u'<title>%s</title>\n' % xml_escape(page_title))
def template_body_header(self, view):
w = self.w
w(u'<body>\n')
- self.wview('header', rset=self.rset, view=view)
+ self.wview('header', rset=self.cw_rset, view=view)
w(u'<div id="page"><table width="100%" border="0" id="mainLayout"><tr>\n')
self.nav_column(view, 'left')
w(u'<td id="contentcol">\n')
- rqlcomp = self.vreg['components'].select_object('rqlinput', self.req,
- rset=self.rset)
+ components = self._cw.vreg['components']
+ rqlcomp = components.select_or_none('rqlinput', self._cw, rset=self.cw_rset)
if rqlcomp:
rqlcomp.render(w=self.w, view=view)
- msgcomp = self.vreg['components'].select_object('applmessages',
- self.req, rset=self.rset)
+ msgcomp = components.select_or_none('applmessages', self._cw, rset=self.cw_rset)
if msgcomp:
msgcomp.render(w=self.w)
self.content_header(view)
@@ -169,12 +168,12 @@
self.w(u'</td>\n')
self.nav_column(view, 'right')
self.w(u'</tr></table></div>\n')
- self.wview('footer', rset=self.rset)
+ self.wview('footer', rset=self.cw_rset)
self.w(u'</body>')
def nav_column(self, view, context):
- boxes = list(self.vreg['boxes'].possible_vobjects(
- self.req, rset=self.rset, view=view, context=context))
+ boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context=context))
if boxes:
self.w(u'<td class="navcol"><div class="navboxes">\n')
for box in boxes:
@@ -183,10 +182,10 @@
def content_header(self, view=None):
"""by default, display informal messages in content header"""
- self.wview('contentheader', rset=self.rset, view=view)
+ self.wview('contentheader', rset=self.cw_rset, view=view)
def content_footer(self, view=None):
- self.wview('contentfooter', rset=self.rset, view=view)
+ self.wview('contentfooter', rset=self.cw_rset, view=view)
class ErrorTemplate(TheMainTemplate):
@@ -194,26 +193,26 @@
main template. This template may be called for authentication error,
which means that req.cnx and req.user may not be set.
"""
- id = 'error-template'
+ __regid__ = 'error-template'
def call(self):
"""display an unexpected error"""
self.set_request_content_type()
- self.req.reset_headers()
- view = self.vreg['views'].select('error', self.req, rset=self.rset)
- self.template_header(self.content_type, view, self.req._('an error occured'),
+ self._cw.reset_headers()
+ view = self._cw.vreg['views'].select('error', self._cw, rset=self.cw_rset)
+ self.template_header(self.content_type, view, self._cw._('an error occured'),
[NOINDEX, NOFOLLOW])
view.render(w=self.w)
self.template_footer(view)
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
w = self.whead
- lang = self.req.lang
+ lang = self._cw.lang
self.write_doctype()
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
- % (content_type, self.req.encoding))
+ % (content_type, self._cw.encoding))
w(u'\n'.join(additional_headers))
- self.wview('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.cw_rset)
w(u'<title>%s</title>\n' % xml_escape(page_title))
self.w(u'<body>\n')
@@ -223,18 +222,18 @@
class SimpleMainTemplate(TheMainTemplate):
- id = 'main-no-top'
+ __regid__ = 'main-no-top'
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
page_title = page_title or view.page_title()
additional_headers = additional_headers or view.html_headers()
whead = self.whead
- lang = self.req.lang
+ lang = self._cw.lang
self.write_doctype()
whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
- % (content_type, self.req.encoding))
+ % (content_type, self._cw.encoding))
whead(u'\n'.join(additional_headers) + u'\n')
- self.wview('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.cw_rset)
w = self.w
w(u'<title>%s</title>\n' % xml_escape(page_title))
w(u'<body>\n')
@@ -242,8 +241,8 @@
w(u'<table width="100%" height="100%" border="0"><tr>\n')
w(u'<td class="navcol">\n')
self.topleft_header()
- boxes = list(self.vreg['boxes'].possible_vobjects(
- self.req, rset=self.rset, view=view, context='left'))
+ boxes = list(self._cw.vreg['boxes'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context='left'))
if boxes:
w(u'<div class="navboxes">\n')
for box in boxes:
@@ -252,14 +251,14 @@
w(u'</td>')
w(u'<td id="contentcol" rowspan="2">')
w(u'<div id="pageContent">\n')
- vtitle = self.req.form.get('vtitle')
+ vtitle = self._cw.form.get('vtitle')
if vtitle:
w(u'<h1 class="vtitle">%s</h1>' % xml_escape(vtitle))
def topleft_header(self):
- logo = self.vreg['components'].select_vobject('logo', self.req,
- rset=self.rset)
- if logo:
+ logo = self._cw.vreg['components'].select_or_none('logo', self._cw,
+ rset=self.cw_rset)
+ if logo and logo.cw_propval('visible'):
self.w(u'<table id="header"><tr>\n')
self.w(u'<td>')
logo.render(w=self.w)
@@ -277,15 +276,15 @@
from cubicweb.ext.xhtml2fo import ReportTransformer
class PdfMainTemplate(TheMainTemplate):
- id = 'pdf-main-template'
+ __regid__ = 'pdf-main-template'
def call(self, view):
"""build the standard view, then when it's all done, convert xhtml to pdf
"""
super(PdfMainTemplate, self).call(view)
- section = self.req.form.pop('section', 'contentmain')
+ section = self._cw.form.pop('section', 'contentmain')
pdf = self.to_pdf(self._stream, section)
- self.req.set_content_type('application/pdf', filename='report.pdf')
+ self._cw.set_content_type('application/pdf', filename='report.pdf')
self.binary = True
self.w = None
self.set_stream()
@@ -312,7 +311,7 @@
class HTMLHeader(View):
"""default html headers"""
- id = 'htmlheader'
+ __regid__ = 'htmlheader'
def call(self, **kwargs):
self.favicon()
@@ -321,12 +320,12 @@
self.alternates()
def favicon(self):
- favicon = self.req.external_resource('FAVICON', None)
+ favicon = self._cw.external_resource('FAVICON', None)
if favicon:
self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
def stylesheets(self):
- req = self.req
+ req = self._cw
add_css = req.add_css
for css in req.external_resource('STYLESHEETS'):
add_css(css, localfile=False)
@@ -336,12 +335,12 @@
add_css(css, localfile=False, ieonly=True)
def javascripts(self):
- for jscript in self.req.external_resource('JAVASCRIPTS'):
- self.req.add_js(jscript, localfile=False)
+ for jscript in self._cw.external_resource('JAVASCRIPTS'):
+ self._cw.add_js(jscript, localfile=False)
def alternates(self):
- urlgetter = self.vreg['components'].select_object('rss_feed_url',
- self.req, rset=self.rset)
+ urlgetter = self._cw.vreg['components'].select_or_none('rss_feed_url',
+ self._cw, rset=self.cw_rset)
if urlgetter is not None:
self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
% xml_escape(urlgetter.feed_url()))
@@ -349,7 +348,7 @@
class HTMLPageHeader(View):
"""default html page header"""
- id = 'header'
+ __regid__ = 'header'
main_cell_components = ('appliname', 'breadcrumbs')
def call(self, view, **kwargs):
@@ -365,44 +364,44 @@
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
self.w(u'<td id="firstcolumn">')
- logo = self.vreg['components'].select_vobject(
- 'logo', self.req, rset=self.rset)
- if logo:
+ logo = self._cw.vreg['components'].select_or_none(
+ 'logo', self._cw, rset=self.cw_rset)
+ if logo and logo.cw_propval('visible'):
logo.render(w=self.w)
self.w(u'</td>\n')
# appliname and breadcrumbs
self.w(u'<td id="headtext">')
for cid in self.main_cell_components:
- comp = self.vreg['components'].select_vobject(
- cid, self.req, rset=self.rset)
- if comp:
+ comp = self._cw.vreg['components'].select_or_none(
+ cid, self._cw, rset=self.cw_rset)
+ if comp and comp.cw_propval('visible'):
comp.render(w=self.w)
self.w(u'</td>')
# logged user and help
self.w(u'<td>\n')
- comp = self.vreg['components'].select_vobject(
- 'loggeduserlink', self.req, rset=self.rset)
- if comp:
+ comp = self._cw.vreg['components'].select_or_none(
+ 'loggeduserlink', self._cw, rset=self.cw_rset)
+ if comp and comp.cw_propval('visible'):
comp.render(w=self.w)
self.w(u'</td>')
# lastcolumn
self.w(u'<td id="lastcolumn">')
self.w(u'</td>\n')
self.w(u'</tr></table>\n')
- self.wview('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+ self.wview('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
title=False, message=False)
def state_header(self):
- state = self.req.search_state
+ state = self._cw.search_state
if state[0] == 'normal':
return
- _ = self.req._
- value = self.view('oneline', self.req.eid_rset(state[1][1]))
+ _ = self._cw._
+ value = self.view('oneline', self._cw.eid_rset(state[1][1]))
msg = ' '.join((_("searching for"),
- display_name(self.req, state[1][3]),
+ display_name(self._cw, state[1][3]),
_("to associate with"), value,
_("by relation"), '"',
- display_name(self.req, state[1][2], state[1][0]),
+ display_name(self._cw, state[1][2], state[1][0]),
'"'))
return self.w(u'<div class="stateMessage">%s</div>' % msg)
@@ -411,16 +410,17 @@
class HTMLPageFooter(View):
"""default html page footer: include footer actions
"""
- id = 'footer'
+ __regid__ = 'footer'
def call(self, **kwargs):
- req = self.req
+ req = self._cw
self.w(u'<div class="footer">')
- actions = self.vreg['actions'].possible_actions(self.req, rset=self.rset)
+ actions = self._cw.vreg['actions'].possible_actions(self._cw,
+ rset=self.cw_rset)
footeractions = actions.get('footer', ())
for i, action in enumerate(footeractions):
self.w(u'<a href="%s">%s</a>' % (action.url(),
- self.req._(action.title)))
+ self._cw._(action.title)))
if i < (len(footeractions) - 1):
self.w(u' | ')
self.w(u'</div>')
@@ -431,12 +431,12 @@
* include message component if selectable for this request
* include selectable content navigation components
"""
- id = 'contentheader'
+ __regid__ = 'contentheader'
def call(self, view, **kwargs):
"""by default, display informal messages in content header"""
- components = self.vreg['contentnavigation'].possible_vobjects(
- self.req, rset=self.rset, view=view, context='navtop')
+ components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context='navtop')
if components:
self.w(u'<div id="contentheader">')
for comp in components:
@@ -448,11 +448,11 @@
"""default html page content footer: include selectable content navigation
components
"""
- id = 'contentfooter'
+ __regid__ = 'contentfooter'
def call(self, view, **kwargs):
- components = self.vreg['contentnavigation'].possible_vobjects(
- self.req, rset=self.rset, view=view, context='navbottom')
+ components = self._cw.vreg['contentnavigation'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, view=view, context='navbottom')
if components:
self.w(u'<div id="contentfooter">')
for comp in components:
@@ -461,16 +461,16 @@
class LogFormTemplate(View):
- id = 'logform'
+ __regid__ = 'logform'
__select__ = match_kwargs('id', 'klass')
title = 'log in'
def call(self, id, klass, title=True, message=True):
- self.req.add_css('cubicweb.login.css')
+ self._cw.add_css('cubicweb.login.css')
self.w(u'<div id="%s" class="%s">' % (id, klass))
if title:
- stitle = self.req.property_value('ui.site-title')
+ stitle = self._cw.property_value('ui.site-title')
if stitle:
stitle = xml_escape(stitle)
else:
@@ -480,7 +480,7 @@
if message:
self.display_message()
- if self.config['auth-mode'] == 'http':
+ if self._cw.vreg.config['auth-mode'] == 'http':
# HTTP authentication
pass
else:
@@ -489,28 +489,35 @@
self.w(u'</div></div>\n')
def display_message(self):
- message = self.req.message
+ message = self._cw.message
if message:
self.w(u'<div class="simpleMessage">%s</div>\n' % message)
def login_form(self, id):
- _ = self.req._
+ _ = self._cw._
+ # XXX turn into a form
self.w(u'<form method="post" action="%s" id="login_form">\n'
- % xml_escape(login_form_url(self.config, self.req)))
+ % xml_escape(login_form_url(self._cw.vreg.config, self._cw)))
self.w(u'<table>\n')
+ self.add_fields()
self.w(u'<tr>\n')
- msg = (self.config['allow-email-login'] and _('login or email')) or _('login')
- self.w(u'<td><label for="__login">%s</label></td>' % msg)
- self.w(u'<td><input name="__login" id="__login" class="data" type="text" /></td>')
- self.w(u'</tr><tr>\n')
- self.w(u'<td><label for="__password" >%s</label></td>' % _('password'))
- self.w(u'<td><input name="__password" id="__password" class="data" type="password" /></td>\n')
- self.w(u'</tr><tr>\n')
self.w(u'<td> </td><td><input type="submit" class="loginButton right" value="%s" />\n</td>' % _('log in'))
self.w(u'</tr>\n')
self.w(u'</table>\n')
self.w(u'</form>\n')
- self.req.html_headers.add_onload('jQuery("#__login:visible").focus()')
+ self._cw.html_headers.add_onload('jQuery("#__login:visible").focus()')
+
+ def add_fields(self):
+ msg = (self._cw.vreg.config['allow-email-login'] and _('login or email')) or _('login')
+ self.add_field('__login', msg, 'text')
+ self.add_field('__password', self._cw._('password'), 'password')
+
+ def add_field(self, name, label, inputtype):
+ self.w(u'<tr>\n')
+ self.w(u'<td><label for="%s" >%s</label></td>' % (name, label))
+ self.w(u'<td><input name="%s" id="%s" class="data" type="%s" /></td>\n' %
+ (name, name, inputtype))
+ self.w(u'</tr>\n')
def login_form_url(config, req):
--- a/web/views/baseviews.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/baseviews.py Mon Feb 08 13:35:07 2010 +0100
@@ -25,12 +25,12 @@
from cubicweb.selectors import yes, empty_rset, one_etype_rset
from cubicweb.schema import display_name
from cubicweb.view import EntityView, AnyRsetView, View
-from cubicweb.common.uilib import cut, printable_value
+from cubicweb.uilib import cut, printable_value
class NullView(AnyRsetView):
"""default view when no result has been found"""
- id = 'null'
+ __regid__ = 'null'
__select__ = yes()
def call(self, **kwargs):
pass
@@ -40,18 +40,18 @@
class NoResultView(View):
"""default view when no result has been found"""
__select__ = empty_rset()
- id = 'noresult'
+ __regid__ = 'noresult'
def call(self, **kwargs):
self.w(u'<div class="searchMessage"><strong>%s</strong></div>\n'
- % self.req._('No result matching query'))
+ % self._cw._('No result matching query'))
class FinalView(AnyRsetView):
"""display values without any transformation (i.e. get a number for
entities)
"""
- id = 'final'
+ __regid__ = 'final'
# record generated i18n catalog messages
_('%d years')
_('%d months')
@@ -69,14 +69,14 @@
_('%d seconds')
def cell_call(self, row, col, props=None, format='text/html'):
- etype = self.rset.description[row][col]
- value = self.rset.rows[row][col]
+ etype = self.cw_rset.description[row][col]
+ value = self.cw_rset.rows[row][col]
if value is None:
self.w(u'')
return
if etype == 'String':
- entity, rtype = self.rset.related_entity(row, col)
+ entity, rtype = self.cw_rset.related_entity(row, col)
if entity is not None:
# yes !
self.w(entity.printable_value(rtype, value, format=format))
@@ -96,53 +96,53 @@
else:
space = ' '
if value.days > 730: # 2 years
- self.w(self.req.__('%%d%syears' % space) % (value.days // 365))
+ self.w(self._cw.__('%%d%syears' % space) % (value.days // 365))
elif value.days > 60: # 2 months
- self.w(self.req.__('%%d%smonths' % space) % (value.days // 30))
+ self.w(self._cw.__('%%d%smonths' % space) % (value.days // 30))
elif value.days > 14: # 2 weeks
- self.w(self.req.__('%%d%sweeks' % space) % (value.days // 7))
+ self.w(self._cw.__('%%d%sweeks' % space) % (value.days // 7))
elif value.days > 2:
- self.w(self.req.__('%%d%sdays' % space) % int(value.days))
+ self.w(self._cw.__('%%d%sdays' % space) % int(value.days))
elif value.seconds > 3600:
- self.w(self.req.__('%%d%shours' % space) % int(value.seconds // 3600))
+ self.w(self._cw.__('%%d%shours' % space) % int(value.seconds // 3600))
elif value.seconds >= 120:
- self.w(self.req.__('%%d%sminutes' % space) % int(value.seconds // 60))
+ self.w(self._cw.__('%%d%sminutes' % space) % int(value.seconds // 60))
else:
- self.w(self.req.__('%%d%sseconds' % space) % int(value.seconds))
+ self.w(self._cw.__('%%d%sseconds' % space) % int(value.seconds))
return
- self.wdata(printable_value(self.req, etype, value, props))
+ self.wdata(printable_value(self._cw, etype, value, props))
# XXX deprecated
class SecondaryView(EntityView):
- id = 'secondary'
+ __regid__ = 'secondary'
title = _('secondary')
def cell_call(self, row, col, **kwargs):
"""the secondary view for an entity
secondary = icon + view(oneline)
"""
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(u' ')
- self.wview('oneline', self.rset, row=row, col=col)
+ self.wview('oneline', self.cw_rset, row=row, col=col)
class OneLineView(EntityView):
- id = 'oneline'
+ __regid__ = 'oneline'
title = _('oneline')
def cell_call(self, row, col, **kwargs):
"""the one line view for an entity: linked text view
"""
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(u'<a href="%s">' % xml_escape(entity.absolute_url()))
- self.w(xml_escape(self.view('text', self.rset, row=row, col=col)))
+ self.w(xml_escape(self._cw.view('text', self.cw_rset, row=row, col=col)))
self.w(u'</a>')
class TextView(EntityView):
"""the simplest text view for an entity"""
- id = 'text'
+ __regid__ = 'text'
title = _('text')
content_type = 'text/plain'
@@ -153,40 +153,40 @@
Views applicable on None result sets have to override this method
"""
- rset = self.rset
+ rset = self.cw_rset
if rset is None:
raise NotImplementedError, self
for i in xrange(len(rset)):
- self.wview(self.id, rset, row=i, **kwargs)
+ self.wview(self.__regid__, rset, row=i, **kwargs)
if len(rset) > 1:
self.w(u"\n")
def cell_call(self, row, col=0, **kwargs):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(cut(entity.dc_title(),
- self.req.property_value('navigation.short-line-size')))
+ self._cw.property_value('navigation.short-line-size')))
class MetaDataView(EntityView):
"""paragraph view of some metadata"""
- id = 'metadata'
+ __regid__ = 'metadata'
show_eid = True
def cell_call(self, row, col):
- _ = self.req._
- entity = self.entity(row, col)
+ _ = self._cw._
+ entity = self.cw_rset.get_entity(row, col)
self.w(u'<div class="metadata">')
if self.show_eid:
self.w(u'%s #%s - ' % (entity.dc_type(), entity.eid))
if entity.modification_date != entity.creation_date:
self.w(u'<span>%s</span> ' % _('latest update on'))
self.w(u'<span class="value">%s</span>, '
- % self.format_date(entity.modification_date))
+ % self._cw.format_date(entity.modification_date))
# entities from external source may not have a creation date (eg ldap)
if entity.creation_date:
self.w(u'<span>%s</span> ' % _('created on'))
self.w(u'<span class="value">%s</span>'
- % self.format_date(entity.creation_date))
+ % self._cw.format_date(entity.creation_date))
if entity.creator:
self.w(u' <span>%s</span> ' % _('by'))
self.w(u'<span class="value">%s</span>' % entity.creator.name())
@@ -194,51 +194,51 @@
class InContextTextView(TextView):
- id = 'textincontext'
+ __regid__ = 'textincontext'
title = None # not listed as a possible view
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(entity.dc_title())
class OutOfContextTextView(InContextTextView):
- id = 'textoutofcontext'
+ __regid__ = 'textoutofcontext'
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(entity.dc_long_title())
class InContextView(EntityView):
- id = 'incontext'
+ __regid__ = 'incontext'
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
desc = cut(entity.dc_description(), 50)
self.w(u'<a href="%s" title="%s">' % (
xml_escape(entity.absolute_url()), xml_escape(desc)))
- self.w(xml_escape(self.view('textincontext', self.rset,
- row=row, col=col)))
+ self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
+ row=row, col=col)))
self.w(u'</a>')
class OutOfContextView(EntityView):
- id = 'outofcontext'
+ __regid__ = 'outofcontext'
- def cell_call(self, row, col, **kwargs):
- entity = self.entity(row, col)
+ def cell_call(self, row, col):
+ entity = self.cw_rset.get_entity(row, col)
desc = cut(entity.dc_description(), 50)
self.w(u'<a href="%s" title="%s">' % (
xml_escape(entity.absolute_url()), xml_escape(desc)))
- self.w(xml_escape(self.view('textoutofcontext', self.rset,
- row=row, col=col)))
+ self.w(xml_escape(self._cw.view('textoutofcontext', self.cw_rset,
+ row=row, col=col)))
self.w(u'</a>')
# list views ##################################################################
class ListView(EntityView):
- id = 'list'
+ __regid__ = 'list'
title = _('list')
item_vid = 'listitem'
@@ -248,36 +248,35 @@
:param listid: the DOM id to use for the root element
"""
# XXX much of the behaviour here should probably be outside this view
- if subvid is None and 'subvid' in self.req.form:
- subvid = self.req.form.pop('subvid') # consume it
+ if subvid is None and 'subvid' in self._cw.form:
+ subvid = self._cw.form.pop('subvid') # consume it
if listid:
listid = u' id="%s"' % listid
else:
listid = u''
- if self.rset.rowcount:
- if title:
- self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title))
- self.w(u'<ul>\n')
- else:
- self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
- for i in xrange(self.rset.rowcount):
- self.cell_call(row=i, col=0, vid=subvid, **kwargs)
- self.w(u'</ul>\n')
- if title:
- self.w(u'</div>\n')
+ if title:
+ self.w(u'<div%s class="%s"><h4>%s</h4>\n' % (listid, klass or 'section', title))
+ self.w(u'<ul>\n')
+ else:
+ self.w(u'<ul%s class="%s">\n' % (listid, klass or 'section'))
+ for i in xrange(self.cw_rset.rowcount):
+ self.cell_call(row=i, col=0, vid=subvid, **kwargs)
+ self.w(u'</ul>\n')
+ if title:
+ self.w(u'</div>\n')
def cell_call(self, row, col=0, vid=None, **kwargs):
self.w(u'<li>')
- self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
+ self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
self.w(u'</li>\n')
class ListItemView(EntityView):
- id = 'listitem'
+ __regid__ = 'listitem'
@property
def redirect_vid(self):
- if self.req.search_state[0] == 'normal':
+ if self._cw.search_state[0] == 'normal':
return 'outofcontext'
return 'outofcontext-search'
@@ -285,50 +284,55 @@
if not vid:
vid = self.redirect_vid
try:
- self.wview(vid, self.rset, row=row, col=col, **kwargs)
+ self.wview(vid, self.cw_rset, row=row, col=col, **kwargs)
except NoSelectableObject:
if vid == self.redirect_vid:
raise
- self.wview(self.redirect_vid, self.rset, row=row, col=col, **kwargs)
+ self.wview(self.redirect_vid, self.cw_rset, row=row, col=col, **kwargs)
class SimpleListView(ListItemView):
"""list without bullets"""
- id = 'simplelist'
+ __regid__ = 'simplelist'
redirect_vid = 'incontext'
-class AdaptedListView(EntityView):
- """list of entities of the same type"""
- id = 'adaptedlist'
+class SameETypeListView(EntityView):
+ """list of entities of the same type, when asked explicitly for adapted list
+ view (for instance, display gallery if only images)
+ """
+ __regid__ = 'sameetypelist'
__select__ = EntityView.__select__ & one_etype_rset()
- item_vid = 'adaptedlistitem'
+ item_vid = 'sameetypelistitem'
@property
def title(self):
- etype = iter(self.rset.column_types(0)).next()
- return display_name(self.req, etype, form='plural')
+ etype = iter(self.cw_rset.column_types(0)).next()
+ return display_name(self._cw, etype, form='plural')
def call(self, **kwargs):
"""display a list of entities by calling their <item_vid> view"""
- if not 'vtitle' in self.req.form:
+ if not 'vtitle' in self._cw.form:
self.w(u'<h1>%s</h1>' % self.title)
- super(AdaptedListView, self).call(**kwargs)
+ super(SameETypeListView, self).call(**kwargs)
def cell_call(self, row, col=0, vid=None, **kwargs):
- self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
+ self.wview(self.item_vid, self.cw_rset, row=row, col=col, vid=vid, **kwargs)
-class AdaptedListItemView(ListItemView):
- id = 'adaptedlistitem'
+class SameETypeListItemView(EntityView):
+ __regid__ = 'sameetypelistitem'
+
+ def cell_call(self, row, col, **kwargs):
+ self.wview('listitem', self.cw_rset, row=row, col=col, **kwargs)
class CSVView(SimpleListView):
- id = 'csv'
+ __regid__ = 'csv'
redirect_vid = 'incontext'
def call(self, **kwargs):
- rset = self.rset
+ rset = self.cw_rset
for i in xrange(len(rset)):
self.cell_call(i, 0, vid=kwargs.get('vid'))
if i < rset.rowcount-1:
@@ -336,10 +340,10 @@
class TreeItemView(ListItemView):
- id = 'treeitem'
+ __regid__ = 'treeitem'
def cell_call(self, row, col):
- self.wview('incontext', self.rset, row=row, col=col)
+ self.wview('incontext', self.cw_rset, row=row, col=col)
class TextSearchResultView(EntityView):
"""this view is used to display full-text search
@@ -348,12 +352,12 @@
XXX: finish me (fixed line width, fixed number of lines, CSS, etc.)
"""
- id = 'tsearch'
+ __regid__ = 'tsearch'
def cell_call(self, row, col, **kwargs):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(entity.view('incontext'))
- searched = self.rset.searched_text()
+ searched = self.cw_rset.searched_text()
if searched is None:
return
searched = searched.lower()
@@ -378,9 +382,9 @@
class TooltipView(EntityView):
"""A entity view used in a tooltip"""
- id = 'tooltip'
+ __regid__ = 'tooltip'
def cell_call(self, row, col):
- self.wview('oneline', self.rset, row=row, col=col)
+ self.wview('oneline', self.cw_rset, row=row, col=col)
# XXX bw compat
--- a/web/views/bookmark.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/bookmark.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,22 +12,31 @@
from cubicweb import Unauthorized
from cubicweb.selectors import implements, one_line_rset
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
-from cubicweb.web import action, box, uicfg
+from cubicweb.web import action, box, uicfg, formwidgets as fw
from cubicweb.web.views import primary
_abaa = uicfg.actionbox_appearsin_addmenu
_abaa.tag_subject_of(('*', 'bookmarked_by', '*'), False)
_abaa.tag_object_of(('*', 'bookmarked_by', '*'), False)
+_afs = uicfg.autoform_section
+_afs.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'main', 'metadata')
+_afs.tag_attribute(('Bookmark', 'path'), 'main', 'attributes')
+_afs.tag_attribute(('Bookmark', 'path'), 'muledit', 'attributes')
+
+_affk = uicfg.autoform_field_kwargs
+_affk.tag_attribute(('Bookmark', 'path'), {'widget': fw.EditableURLWidget})
+
+
class FollowAction(action.Action):
- id = 'follow'
+ __regid__ = 'follow'
__select__ = one_line_rset() & implements('Bookmark')
title = _('follow')
category = 'mainactions'
def url(self):
- return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+ return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).actual_url()
class BookmarkPrimaryView(primary.PrimaryView):
@@ -35,22 +44,22 @@
def cell_call(self, row, col):
"""the primary view for bookmark entity"""
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(u' ')
self.w(u"<span class='title'><b>")
- self.w(u"%s : %s" % (self.req._('Bookmark'), xml_escape(entity.title)))
+ self.w(u"%s : %s" % (self._cw._('Bookmark'), xml_escape(entity.title)))
self.w(u"</b></span>")
self.w(u'<br/><br/><div class="content"><a href="%s">' % (
xml_escape(entity.actual_url())))
self.w(u'</a>')
- self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(xml_escape(u.name())
+ self.w(u'<p>%s%s</p>' % (self._cw._('Used by:'), ', '.join(xml_escape(u.name())
for u in entity.bookmarked_by)))
self.w(u'</div>')
class BookmarksBox(box.UserRQLBoxTemplate):
"""display a box containing all user's bookmarks"""
- id = 'bookmarks_box'
+ __regid__ = 'bookmarks_box'
order = 40
title = _('bookmarks')
rql = ('Any B,T,P ORDERBY lower(T) '
@@ -61,17 +70,17 @@
def call(self, **kwargs):
- req = self.req
+ req = self._cw
ueid = req.user.eid
try:
rset = req.execute(self.rql, {'x': ueid})
except Unauthorized:
# can't access to something in the query, forget this box
return
- box = BoxWidget(req._(self.title), self.id)
+ box = BoxWidget(req._(self.title), self.__regid__)
box.listing_class = 'sideBox'
- rschema = self.schema.rschema(self.rtype)
- eschema = self.schema.eschema(self.etype)
+ rschema = self._cw.vreg.schema.rschema(self.rtype)
+ eschema = self._cw.vreg.schema.eschema(self.etype)
candelete = rschema.has_perm(req, 'delete', toeid=ueid)
if candelete:
req.add_js( ('cubicweb.ajax.js', 'cubicweb.bookmarks.js') )
@@ -91,6 +100,8 @@
# use a relative path so that we can move the instance without
# loosing bookmarks
path = req.relative_path()
+ # XXX if vtitle specified in params, extract it and use it as default value
+ # for bookmark's title
url = self.create_url(self.etype, __linkto=linkto, path=path)
boxmenu.append(self.mk_action(req._('bookmark this page'), url,
category='manage', id='bookmark'))
@@ -105,11 +116,11 @@
build_descr=False)
bookmarksrql %= {'x': ueid}
if erset:
- url = self.build_url(vid='muledit', rql=bookmarksrql)
- boxmenu.append(self.mk_action(self.req._('edit bookmarks'), url, category='manage'))
+ url = self._cw.build_url(vid='muledit', rql=bookmarksrql)
+ boxmenu.append(self.mk_action(self._cw._('edit bookmarks'), url, category='manage'))
url = req.user.absolute_url(vid='xaddrelation', rtype='bookmarked_by',
target='subject')
- boxmenu.append(self.mk_action(self.req._('pick existing bookmarks'), url, category='manage'))
+ boxmenu.append(self.mk_action(self._cw._('pick existing bookmarks'), url, category='manage'))
box.append(boxmenu)
if not box.is_empty():
box.render(self.w)
--- a/web/views/boxes.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/boxes.py Mon Feb 08 13:35:07 2010 +0100
@@ -33,27 +33,27 @@
box with all actions impacting the entity displayed: edit, copy, delete
change state, add related entities
"""
- id = 'edit_box'
+ __regid__ = 'edit_box'
__select__ = BoxTemplate.__select__ & non_final_entity()
title = _('actions')
order = 2
def call(self, view=None, **kwargs):
- _ = self.req._
+ _ = self._cw._
title = _(self.title)
- if self.rset:
- etypes = self.rset.column_types(0)
+ if self.cw_rset:
+ etypes = self.cw_rset.column_types(0)
if len(etypes) == 1:
- plural = self.rset.rowcount > 1 and 'plural' or ''
- etypelabel = display_name(self.req, iter(etypes).next(), plural)
+ plural = self.cw_rset.rowcount > 1 and 'plural' or ''
+ etypelabel = display_name(self._cw, iter(etypes).next(), plural)
title = u'%s - %s' % (title, etypelabel.lower())
- box = BoxWidget(title, self.id, _class="greyBoxFrame")
+ box = BoxWidget(title, self.__regid__, _class="greyBoxFrame")
self._menus_in_order = []
self._menus_by_id = {}
# build list of actions
- actions = self.vreg['actions'].possible_actions(self.req, self.rset,
- view=view)
+ actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset,
+ view=view)
other_menu = self._get_menu('moreactions', _('more actions'))
for category, defaultmenu in (('mainactions', box),
('moreactions', other_menu),
@@ -86,7 +86,7 @@
return self._menus_by_id[id]
except KeyError:
if title is None:
- title = self.req._(id)
+ title = self._cw._(id)
self._menus_by_id[id] = menu = BoxMenu(title)
menu.label_prefix = label_prefix
self._menus_in_order.append(menu)
@@ -107,7 +107,7 @@
class SearchBox(BoxTemplate):
"""display a box with a simple search form"""
- id = 'search_box'
+ __regid__ = 'search_box'
visible = True # enabled by default
title = _('search')
@@ -123,7 +123,7 @@
</form>"""
def call(self, view=None, **kwargs):
- req = self.req
+ req = self._cw
if req.form.pop('__fromsearchbox', None):
rql = req.form.get('rql', '')
else:
@@ -131,7 +131,7 @@
form = self.formdef % (req.build_url('view'), req.next_tabindex(),
xml_escape(rql), req.next_tabindex())
title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
- box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
+ box = BoxWidget(title, self.__regid__, _class="searchBoxFrame", islist=False, escape=False)
box.append(BoxHtml(form))
box.render(self.w)
@@ -140,7 +140,7 @@
class PossibleViewsBox(BoxTemplate):
"""display a box containing links to all possible views"""
- id = 'possible_views_box'
+ __regid__ = 'possible_views_box'
__select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
visible = False
@@ -148,9 +148,9 @@
order = 10
def call(self, **kwargs):
- box = BoxWidget(self.req._(self.title), self.id)
- views = [v for v in self.vreg['views'].possible_views(self.req,
- rset=self.rset)
+ box = BoxWidget(self._cw._(self.title), self.__regid__)
+ views = [v for v in self._cw.vreg['views'].possible_views(self._cw,
+ rset=self.cw_rset)
if v.category != 'startupview']
for category, views in self.sort_actions(views):
menu = BoxMenu(category)
@@ -163,14 +163,14 @@
class StartupViewsBox(BoxTemplate):
"""display a box containing links to all startup views"""
- id = 'startup_views_box'
+ __regid__ = 'startup_views_box'
visible = False # disabled by default
title = _('startup views')
order = 70
def call(self, **kwargs):
- box = BoxWidget(self.req._(self.title), self.id)
- for view in self.vreg['views'].possible_views(self.req, None):
+ box = BoxWidget(self._cw._(self.title), self.__regid__)
+ for view in self._cw.vreg['views'].possible_views(self._cw, None):
if view.category == 'startupview':
box.append(self.box_action(view))
@@ -182,7 +182,7 @@
class SideBoxView(EntityView):
"""helper view class to display some entities in a sidebox"""
- id = 'sidebox'
+ __regid__ = 'sidebox'
def call(self, boxclass='sideBox', title=u''):
"""display a list of entities by calling their <item_vid> view"""
@@ -190,19 +190,19 @@
self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
# if not too much entities, show them all in a list
- maxrelated = self.req.property_value('navigation.related-limit')
- if self.rset.rowcount <= maxrelated:
- if len(self.rset) == 1:
- self.wview('incontext', self.rset, row=0)
- elif 1 < len(self.rset) < 5:
- self.wview('csv', self.rset)
+ maxrelated = self._cw.property_value('navigation.related-limit')
+ if self.cw_rset.rowcount <= maxrelated:
+ if len(self.cw_rset) == 1:
+ self.wview('incontext', self.cw_rset, row=0)
+ elif 1 < len(self.cw_rset) < 5:
+ self.wview('csv', self.cw_rset)
else:
- self.wview('simplelist', self.rset)
+ self.wview('simplelist', self.cw_rset)
# else show links to display related entities
else:
- self.rset.limit(maxrelated)
- rql = self.rset.printable_rql(encoded=False)
- self.wview('simplelist', self.rset)
- self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
- self.req._('see them all')))
+ self.cw_rset.limit(maxrelated)
+ rql = self.cw_rset.printable_rql(encoded=False)
+ self.wview('simplelist', self.cw_rset)
+ self.w(u'[<a href="%s">%s</a>]' % (self._cw.build_url(rql=rql),
+ self._cw._('see them all')))
self.w(u'</div>\n</div>\n')
--- a/web/views/calendar.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/calendar.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,10 +11,10 @@
from datetime import datetime, date, timedelta
from logilab.mtconverter import xml_escape
+from logilab.common.date import strptime, date_range, todate, todatetime
from cubicweb.interfaces import ICalendarable
from cubicweb.selectors import implements
-from cubicweb.utils import strptime, date_range, todate, todatetime
from cubicweb.view import EntityView
@@ -44,12 +44,12 @@
content_type = 'text/calendar'
title = _('iCalendar')
templatable = False
- id = 'ical'
+ __regid__ = 'ical'
def call(self):
ical = iCalendar()
- for i in range(len(self.rset.rows)):
- task = self.complete_entity(i)
+ for i in range(len(self.cw_rset.rows)):
+ task = self.cw_rset.complete_entity(i, 0)
event = ical.add('vevent')
event.add('summary').value = task.dc_title()
event.add('description').value = task.dc_description()
@@ -60,7 +60,7 @@
buff = ical.serialize()
if not isinstance(buff, unicode):
- buff = unicode(buff, self.req.encoding)
+ buff = unicode(buff, self._cw.encoding)
self.w(buff)
except ImportError:
@@ -71,7 +71,7 @@
Does apply to ICalendarable compatible entities
"""
- id = 'hcal'
+ __regid__ = 'hcal'
__select__ = implements(ICalendarable)
paginable = False
title = _('hCalendar')
@@ -79,34 +79,34 @@
def call(self):
self.w(u'<div class="hcalendar">')
- for i in range(len(self.rset.rows)):
- task = self.complete_entity(i)
+ for i in range(len(self.cw_rset.rows)):
+ task = self.cw_rset.complete_entity(i, 0)
self.w(u'<div class="vevent">')
self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
self.w(u'<div class="description">%s</div>'
% task.dc_description(format='text/html'))
if task.start:
- self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self.format_date(task.start)))
+ self.w(u'<abbr class="dtstart" title="%s">%s</abbr>' % (task.start.isoformat(), self._cw.format_date(task.start)))
if task.stop:
- self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self.format_date(task.stop)))
+ self.w(u'<abbr class="dtstop" title="%s">%s</abbr>' % (task.stop.isoformat(), self._cw.format_date(task.stop)))
self.w(u'</div>')
self.w(u'</div>')
class CalendarItemView(EntityView):
- id = 'calendaritem'
+ __regid__ = 'calendaritem'
def cell_call(self, row, col, dates=False):
- task = self.complete_entity(row)
+ task = self.cw_rset.complete_entity(row, 0)
task.view('oneline', w=self.w)
if dates:
if task.start and task.stop:
- self.w('<br/>' % self.req._('from %(date)s' % {'date': self.format_date(task.start)}))
- self.w('<br/>' % self.req._('to %(date)s' % {'date': self.format_date(task.stop)}))
- self.w('<br/>to %s'%self.format_date(task.stop))
+ self.w('<br/>' % self._cw._('from %(date)s' % {'date': self._cw.format_date(task.start)}))
+ self.w('<br/>' % self._cw._('to %(date)s' % {'date': self._cw.format_date(task.stop)}))
+ self.w('<br/>to %s'%self._cw.format_date(task.stop))
class CalendarLargeItemView(CalendarItemView):
- id = 'calendarlargeitem'
+ __regid__ = 'calendarlargeitem'
class _TaskEntry(object):
@@ -129,23 +129,23 @@
class OneMonthCal(EntityView):
"""At some point, this view will probably replace ampm calendars"""
- id = 'onemonthcal'
+ __regid__ = 'onemonthcal'
__select__ = implements(ICalendarable)
paginable = False
title = _('one month')
def call(self):
- self.req.add_js('cubicweb.ajax.js')
- self.req.add_css('cubicweb.calendar.css')
+ self._cw.add_js('cubicweb.ajax.js')
+ self._cw.add_css('cubicweb.calendar.css')
# XXX: restrict courses directy with RQL
_today = datetime.today()
- if 'year' in self.req.form:
- year = int(self.req.form['year'])
+ if 'year' in self._cw.form:
+ year = int(self._cw.form['year'])
else:
year = _today.year
- if 'month' in self.req.form:
- month = int(self.req.form['month'])
+ if 'month' in self._cw.form:
+ month = int(self._cw.form['month'])
else:
month = _today.month
@@ -159,10 +159,10 @@
month_dates = list(date_range(firstday, lastday))
dates = {}
task_max = 0
- for row in xrange(self.rset.rowcount):
- task = self.rset.get_entity(row, 0)
- if len(self.rset[row]) > 1 and self.rset.description[row][1] == 'CWUser':
- user = self.rset.get_entity(row, 1)
+ for row in xrange(self.cw_rset.rowcount):
+ task = self.cw_rset.get_entity(row, 0)
+ if len(self.cw_rset[row]) > 1 and self.cw_rset.description[row][1] == 'CWUser':
+ user = self.cw_rset.get_entity(row, 1)
else:
user = None
the_dates = []
@@ -244,12 +244,12 @@
prevlink, nextlink = self._prevnext_links(curdate) # XXX
self.w(u'<tr><th><a href="%s"><<</a></th><th colspan="5">%s %s</th>'
u'<th><a href="%s">>></a></th></tr>' %
- (xml_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
+ (xml_escape(prevlink), self._cw._(curdate.strftime('%B').lower()),
curdate.year, xml_escape(nextlink)))
# output header
self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
- tuple(self.req._(day) for day in WEEKDAYS))
+ tuple(self._cw._(day) for day in WEEKDAYS))
# build calendar
for mdate, task_rows in zip(month_dates, days):
@@ -263,11 +263,11 @@
def _prevnext_links(self, curdate):
prevdate = curdate - timedelta(31)
nextdate = curdate + timedelta(31)
- rql = self.rset.printable_rql()
- prevlink = self.req.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
+ rql = self.cw_rset.printable_rql()
+ prevlink = self._cw.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
year=prevdate.year,
month=prevdate.month)
- nextlink = self.req.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
+ nextlink = self._cw.build_ajax_replace_url('onemonthcalid', rql, 'onemonthcal',
year=nextdate.year,
month=nextdate.month)
return prevlink, nextlink
@@ -283,16 +283,16 @@
self.w(u'<div class="calCellTitle%s">' % classes)
self.w(u'<div class="day">%s</div>' % celldate.day)
- if len(self.rset.column_types(0)) == 1:
- etype = list(self.rset.column_types(0))[0]
- url = self.build_url(vid='creation', etype=etype,
- schedule=True,
- start=self.format_date(celldate), stop=self.format_date(celldate),
- __redirectrql=self.rset.printable_rql(),
- __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
- __redirectvid=self.id
- )
- self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self.req._(u'add')))
+ if len(self.cw_rset.column_types(0)) == 1:
+ etype = list(self.cw_rset.column_types(0))[0]
+ url = self._cw.build_url(vid='creation', etype=etype,
+ schedule=True,
+ start=self._cw.format_date(celldate), stop=self._cw.format_date(celldate),
+ __redirectrql=self.cw_rset.printable_rql(),
+ __redirectparams=self._cw.build_url_params(year=curdate.year, month=curmonth),
+ __redirectvid=self.__regid__
+ )
+ self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self._cw._(u'add')))
self.w(u' ')
self.w(u'</div>')
self.w(u'<div class="cellContent">')
@@ -302,9 +302,9 @@
self.w(u'<div class="task %s">' % task_descr.color)
task.view('calendaritem', w=self.w )
url = task.absolute_url(vid='edition',
- __redirectrql=self.rset.printable_rql(),
- __redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
- __redirectvid=self.id
+ __redirectrql=self.cw_rset.printable_rql(),
+ __redirectparams=self._cw.build_url_params(year=curdate.year, month=curmonth),
+ __redirectvid=self.__regid__
)
self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
@@ -320,22 +320,22 @@
class OneWeekCal(EntityView):
"""At some point, this view will probably replace ampm calendars"""
- id = 'oneweekcal'
+ __regid__ = 'oneweekcal'
__select__ = implements(ICalendarable)
paginable = False
title = _('one week')
def call(self):
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
- self.req.add_css('cubicweb.calendar.css')
+ self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.calendar.js') )
+ self._cw.add_css('cubicweb.calendar.css')
# XXX: restrict directly with RQL
_today = datetime.today()
- if 'year' in self.req.form:
- year = int(self.req.form['year'])
+ if 'year' in self._cw.form:
+ year = int(self._cw.form['year'])
else:
year = _today.year
- if 'week' in self.req.form:
- week = int(self.req.form['week'])
+ if 'week' in self._cw.form:
+ week = int(self._cw.form['week'])
else:
week = _today.isocalendar()[1]
# week - 1 since we get week number > 0 while we want it to start from 0
@@ -348,8 +348,8 @@
colors = [ "col%x" % i for i in range(12) ]
next_color_index = 0
done_tasks = []
- for row in xrange(self.rset.rowcount):
- task = self.rset.get_entity(row, 0)
+ for row in xrange(self.cw_rset.rowcount):
+ task = self.cw_rset.get_entity(row, 0)
if task in done_tasks:
continue
done_tasks.append(task)
@@ -389,7 +389,7 @@
self.w(u'<th><a href="%s"><<</a></th><th colspan="5">%s %s %s</th>'
u'<th><a href="%s">>></a></th></tr>' %
(xml_escape(prevlink), first_day_of_week.year,
- self.req._(u'week'), first_day_of_week.isocalendar()[1],
+ self._cw._(u'week'), first_day_of_week.isocalendar()[1],
xml_escape(nextlink)))
# output header
@@ -399,9 +399,9 @@
for i, day in enumerate(WEEKDAYS):
wdate = first_day_of_week + timedelta(i)
if wdate.isocalendar() == _today.isocalendar():
- self.w(u'<th class="today">%s<br/>%s</th>' % (self.req._(day), self.format_date(wdate)))
+ self.w(u'<th class="today">%s<br/>%s</th>' % (self._cw._(day), self._cw.format_date(wdate)))
else:
- self.w(u'<th>%s<br/>%s</th>' % (self.req._(day), self.format_date(wdate)))
+ self.w(u'<th>%s<br/>%s</th>' % (self._cw._(day), self._cw.format_date(wdate)))
self.w(u'</tr>')
# build week calendar
@@ -420,14 +420,14 @@
if wdate.isocalendar() == _today.isocalendar():
classes = " today"
self.w(u'<td class="column %s" id="%s">' % (classes, day))
- if len(self.rset.column_types(0)) == 1:
- etype = list(self.rset.column_types(0))[0]
- url = self.build_url(vid='creation', etype=etype,
- schedule=True,
- __redirectrql=self.rset.printable_rql(),
- __redirectparams=self.req.build_url_params(year=year, week=week),
- __redirectvid=self.id
- )
+ if len(self.cw_rset.column_types(0)) == 1:
+ etype = list(self.cw_rset.column_types(0))[0]
+ url = self._cw.build_url(vid='creation', etype=etype,
+ schedule=True,
+ __redirectrql=self.cw_rset.printable_rql(),
+ __redirectparams=self._cw.build_url_params(year=year, week=week),
+ __redirectvid=self.__regid__
+ )
extra = ' ondblclick="addCalendarItem(event, hmin=8, hmax=20, year=%s, month=%s, day=%s, duration=2, baseurl=\'%s\')"' % (
wdate.year, wdate.month, wdate.day, xml_escape(url))
else:
@@ -496,9 +496,9 @@
(task_desc.color, style))
task.view('calendaritem', dates=False, w=self.w)
url = task.absolute_url(vid='edition',
- __redirectrql=self.rset.printable_rql(),
- __redirectparams=self.req.build_url_params(year=date.year, week=date.isocalendar()[1]),
- __redirectvid=self.id
+ __redirectrql=self.cw_rset.printable_rql(),
+ __redirectparams=self._cw.build_url_params(year=date.year, week=date.isocalendar()[1]),
+ __redirectvid=self.__regid__
)
self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
@@ -520,11 +520,11 @@
def _prevnext_links(self, curdate):
prevdate = curdate - timedelta(7)
nextdate = curdate + timedelta(7)
- rql = self.rset.printable_rql()
- prevlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+ rql = self.cw_rset.printable_rql()
+ prevlink = self._cw.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
year=prevdate.year,
week=prevdate.isocalendar()[1])
- nextlink = self.req.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
+ nextlink = self._cw.build_ajax_replace_url('oneweekcalid', rql, 'oneweekcal',
year=nextdate.year,
week=nextdate.isocalendar()[1])
return prevlink, nextlink
--- a/web/views/csvexport.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/csvexport.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
from cubicweb.schema import display_name
-from cubicweb.common.uilib import UnicodeCSVWriter
+from cubicweb.uilib import UnicodeCSVWriter
from cubicweb.view import EntityView, AnyRsetView
class CSVMixIn(object):
@@ -23,36 +23,36 @@
def set_request_content_type(self):
"""overriden to set a .csv filename"""
- self.req.set_content_type(self.content_type, filename='cubicwebexport.csv')
+ self._cw.set_content_type(self.content_type, filename='cubicwebexport.csv')
def csvwriter(self, **kwargs):
params = self.csv_params.copy()
params.update(kwargs)
- return UnicodeCSVWriter(self.w, self.req.encoding, **params)
+ return UnicodeCSVWriter(self.w, self._cw.encoding, **params)
class CSVRsetView(CSVMixIn, AnyRsetView):
"""dumps raw result set in CSV"""
- id = 'csvexport'
+ __regid__ = 'csvexport'
title = _('csv export')
def call(self):
writer = self.csvwriter()
writer.writerow(self.columns_labels())
- rset, descr = self.rset, self.rset.description
- eschema = self.schema.eschema
+ rset, descr = self.cw_rset, self.cw_rset.description
+ eschema = self._cw.vreg.schema.eschema
for rowindex, row in enumerate(rset):
csvrow = []
for colindex, val in enumerate(row):
etype = descr[rowindex][colindex]
if val is not None and not eschema(etype).final:
# csvrow.append(val) # val is eid in that case
- content = self.view('textincontext', rset,
- row=rowindex, col=colindex)
+ content = self._cw.view('textincontext', rset,
+ row=rowindex, col=colindex)
else:
- content = self.view('final', rset,
- format='text/plain',
- row=rowindex, col=colindex)
+ content = self._cw.view('final', rset,
+ format='text/plain',
+ row=rowindex, col=colindex)
csvrow.append(content)
writer.writerow(csvrow)
@@ -64,16 +64,16 @@
resultset. ('table' here only means empty lines separation between table
contents)
"""
- id = 'ecsvexport'
+ __regid__ = 'ecsvexport'
title = _('csv entities export')
def call(self):
- req = self.req
+ req = self._cw
rows_by_type = {}
writer = self.csvwriter()
rowdef_by_type = {}
- for index in xrange(len(self.rset)):
- entity = self.complete_entity(index)
+ for index in xrange(len(self.cw_rset)):
+ entity = self.cw_rset.complete_entity(index)
if entity.e_schema not in rows_by_type:
rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
if at != 'Bytes']
--- a/web/views/cwproperties.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/cwproperties.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,9 +19,10 @@
from cubicweb.web import uicfg, stdmsgs
from cubicweb.web.form import FormViewMixIn
from cubicweb.web.formfields import FIELDS, StringField
-from cubicweb.web.formwidgets import Select, TextInput, Button, SubmitButton
+from cubicweb.web.formwidgets import Select, TextInput, Button, SubmitButton, FieldWidget
from cubicweb.web.views import primary, formrenderers
+uicfg.primaryview_section.tag_object_of(('*', 'for_user', '*'), 'hidden')
# some string we want to be internationalizable for nicer display of property
# groups
@@ -65,7 +66,7 @@
class SystemCWPropertiesForm(FormViewMixIn, StartupView):
"""site-wide properties edition form"""
- id = 'systempropertiesform'
+ __regid__ = 'systempropertiesform'
__select__ = none_rset() & match_user_groups('managers')
form_buttons = [SubmitButton()]
@@ -77,34 +78,34 @@
def url(self):
"""return the url associated with this view. We can omit rql here"""
- return self.build_url('view', vid=self.id)
+ return self._cw.build_url('view', vid=self.__regid__)
def _cookie_name(self, somestr):
- return str('%s_property_%s' % (self.config.appid, somestr))
+ return str('%s_property_%s' % (self._cw.vreg.config.appid, somestr))
def _group_status(self, group, default=u'hidden'):
"""return css class name 'hidden' (collapsed), or '' (open)"""
- cookies = self.req.get_cookie()
+ cookies = self._cw.get_cookie()
cookiename = self._cookie_name(group)
cookie = cookies.get(cookiename)
if cookie is None:
cookies[cookiename] = default
- self.req.set_cookie(cookies, cookiename, maxage=None)
+ self._cw.set_cookie(cookies, cookiename, maxage=None)
status = default
else:
status = cookie.value
return status
def call(self, **kwargs):
- self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
- self.req.add_css('cubicweb.preferences.css')
- vreg = self.vreg
+ self._cw.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
+ self._cw.add_css('cubicweb.preferences.css')
+ vreg = self._cw.vreg
values = self.defined_keys
groupedopts = {}
mainopts = {}
# "self.id=='systempropertiesform'" to skip site wide properties on
# user's preference but not site's configuration
- for key in vreg.user_property_keys(self.id=='systempropertiesform'):
+ for key in vreg.user_property_keys(self.__regid__=='systempropertiesform'):
parts = key.split('.')
if parts[0] in vreg:
# appobject configuration
@@ -121,7 +122,7 @@
groupedopts[group][oid] = self.form(group + '-' + oid, keys, True)
w = self.w
- req = self.req
+ req = self._cw
_ = req._
w(u'<h1>%s</h1>\n' % _(self.title))
for label, group, form in sorted((_(g), g, f)
@@ -141,7 +142,7 @@
(make_togglable_link('fieldset_' + group, label.capitalize())))
w(u'<div id="fieldset_%s" %s>' % (group, status))
# create selection
- sorted_objects = sorted((self.req.__('%s_%s' % (group, o)), o, f)
+ sorted_objects = sorted((self._cw.__('%s_%s' % (group, o)), o, f)
for o, f in objects.iteritems())
for label, oid, form in sorted_objects:
w(u'<div class="component">')
@@ -166,7 +167,7 @@
@property
@cached
def cwprops_rset(self):
- return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
+ return self._cw.execute('Any P,K,V WHERE P is CWProperty, P pkey K, '
'P value V, NOT P for_user U')
@property
@@ -181,26 +182,26 @@
if key in values:
entity = self.cwprops_rset.get_entity(values[key], 0)
else:
- entity = self.vreg['etypes'].etype_class('CWProperty')(self.req)
- entity.eid = self.req.varmaker.next()
+ entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
+ entity.eid = self._cw.varmaker.next()
entity['pkey'] = key
- entity['value'] = self.vreg.property_value(key)
+ entity['value'] = self._cw.vreg.property_value(key)
return entity
def form(self, formid, keys, splitlabel=False):
- form = self.vreg['forms'].select(
- 'composite', self.req, domid=formid, action=self.build_url(),
+ form = self._cw.vreg['forms'].select(
+ 'composite', self._cw, domid=formid, action=self._cw.build_url(),
form_buttons=self.form_buttons,
onsubmit="return validatePrefsForm('%s')" % formid,
- submitmsg=self.req._('changes applied'))
- path = self.req.relative_path()
+ submitmsg=self._cw._('changes applied'))
+ path = self._cw.relative_path()
if '?' in path:
path, params = path.split('?', 1)
- form.form_add_hidden('__redirectparams', params)
- form.form_add_hidden('__redirectpath', path)
+ form.add_hidden('__redirectparams', params)
+ form.add_hidden('__redirectpath', path)
for key in keys:
self.form_row(form, key, splitlabel)
- renderer = self.vreg['formrenderers'].select('cwproperties', self.req,
+ renderer = self._cw.vreg['formrenderers'].select('cwproperties', self._cw,
display_progress_div=False)
return form.render(renderer=renderer)
@@ -210,12 +211,12 @@
label = key.split('.')[-1]
else:
label = key
- subform = self.vreg['forms'].select('base', self.req, entity=entity,
- mainform=False)
- subform.append_field(PropertyValueField(name='value', label=label,
+ subform = self._cw.vreg['forms'].select('base', self._cw, entity=entity,
+ mainform=False)
+ subform.append_field(PropertyValueField(name='value', label=label, role='subject',
eidparam=True))
- subform.vreg = self.vreg
- subform.form_add_hidden('pkey', key, eidparam=True)
+ #subform.vreg = self._cw.vreg
+ subform.add_hidden('pkey', key, eidparam=True, role='subject')
form.add_subform(subform)
return subform
@@ -227,7 +228,7 @@
class CWPropertiesForm(SystemCWPropertiesForm):
"""user's preferences properties edition form"""
- id = 'propertiesform'
+ __regid__ = 'propertiesform'
__select__ = (
(none_rset() & match_user_groups('users','managers'))
| (one_line_rset() & match_user_groups('users') & is_user_prefs())
@@ -238,14 +239,14 @@
@property
def user(self):
- if self.rset is None:
- return self.req.user
- return self.rset.get_entity(self.row or 0, self.col or 0)
+ if self.cw_rset is None:
+ return self._cw.user
+ return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
@property
@cached
def cwprops_rset(self):
- return self.req.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
+ return self._cw.execute('Any P,K,V WHERE P is CWProperty, P pkey K, P value V,'
'P for_user U, U eid %(x)s', {'x': self.user.eid})
def form_row(self, form, key, splitlabel):
@@ -253,28 +254,28 @@
# if user is in the managers group and the property is being created,
# we have to set for_user explicitly
if not subform.edited_entity.has_eid() and self.user.matching_groups('managers'):
- subform.form_add_hidden('for_user', self.user.eid, eidparam=True)
+ subform.add_hidden('for_user', self.user.eid, eidparam=True, role='subject')
# cwproperty form objects ######################################################
-class PlaceHolderWidget(object):
+class PlaceHolderWidget(FieldWidget):
def render(self, form, field, renderer):
- domid = form.context[field]['id']
+ domid = field.dom_id(form)
# empty span as well else html validation fail (label is refering to
# this id)
return '<div id="div:%s"><span id="%s">%s</span></div>' % (
- domid, domid, form.req._('select a key first'))
+ domid, domid, form._cw._('select a key first'))
-class NotEditableWidget(object):
+class NotEditableWidget(FieldWidget):
def __init__(self, value, msg=None):
self.value = value
self.msg = msg
def render(self, form, field, renderer):
- domid = form.context[field]['id']
+ domid = field.dom_id(form)
value = '<span class="value" id="%s">%s</span>' % (domid, self.value)
if self.msg:
value + '<div class="helper">%s</div>' % self.msg
@@ -289,17 +290,17 @@
def render(self, form, renderer):
wdg = self.get_widget(form)
- wdg.attrs['tabindex'] = form.req.next_tabindex()
+ wdg.attrs['tabindex'] = form._cw.next_tabindex()
wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
- form.edited_entity.eid, form.req.next_tabindex())
+ form.edited_entity.eid, form._cw.next_tabindex())
return wdg.render(form, self, renderer)
def vocabulary(self, form):
entity = form.edited_entity
- _ = form.req._
+ _ = form._cw._
if entity.has_eid():
return [(_(entity.pkey), entity.pkey)]
- choices = entity.vreg.user_property_keys()
+ choices = entity._cw.vreg.user_property_keys()
return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
@@ -322,26 +323,26 @@
# on key selection
return
try:
- pdef = form.vreg.property_info(entity.pkey)
+ pdef = form._cw.vreg.property_info(entity.pkey)
except UnknownProperty, ex:
self.warning('%s (you should probably delete that property '
'from the database)', ex)
- msg = form.req._('you should probably delete that property')
+ msg = form._cw._('you should probably delete that property')
self.widget = NotEditableWidget(entity.printable_value('value'),
'%s (%s)' % (msg, ex))
if entity.pkey.startswith('system.'):
- msg = form.req._('value associated to this key is not editable '
+ msg = form._cw._('value associated to this key is not editable '
'manually')
self.widget = NotEditableWidget(entity.printable_value('value'), msg)
# XXX race condition when used from CWPropertyForm, should not rely on
# instance attributes
- self.initial = pdef['default']
+ self.value = pdef['default']
self.help = pdef['help']
vocab = pdef['vocabulary']
if vocab is not None:
if callable(vocab):
# list() just in case its a generator function
- self.choices = list(vocab(form.req))
+ self.choices = list(vocab(form._cw))
else:
self.choices = vocab
wdg = Select()
@@ -354,13 +355,10 @@
self.choices = field.vocabulary(form)
self.widget = wdg
-uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
-uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
-
class CWPropertiesFormRenderer(formrenderers.FormRenderer):
"""specific renderer for properties"""
- id = 'cwproperties'
+ __regid__ = 'cwproperties'
def open_form(self, form, values):
err = '<div class="formsg"></div>'
@@ -371,7 +369,9 @@
w(u'<div class="preffield">\n')
if self.display_label:
w(u'%s' % self.render_label(form, field))
- error = form.form_field_error(field)
+ error = form.field_error(field)
+ if error:
+ w(u'<span class="error">%s</span>' % err)
w(u'%s' % self.render_help(form, field))
w(u'<div class="prefinput">')
w(field.render(form, self))
@@ -383,3 +383,11 @@
for button in form.form_buttons:
w(u'%s\n' % button.render(form))
w(u'</div>')
+
+
+_afs = uicfg.autoform_section
+_afs.tag_subject_of(('*', 'for_user', '*'), 'main', 'hidden')
+_afs.tag_object_of(('*', 'for_user', '*'), 'main', 'hidden')
+_aff = uicfg.autoform_field
+_aff.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
+_aff.tag_attribute(('CWProperty', 'value'), PropertyValueField)
--- a/web/views/cwuser.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/cwuser.py Mon Feb 08 13:35:07 2010 +0100
@@ -25,7 +25,7 @@
uicfg.primaryview_section.tag_object_of(('*', 'require_group', 'CWGroup'), 'relations')
class UserPreferencesEntityAction(action.Action):
- id = 'prefs'
+ __regid__ = 'prefs'
__select__ = (one_line_rset() & implements('CWUser') &
match_user_groups('owners', 'managers'))
@@ -33,12 +33,12 @@
category = 'mainactions'
def url(self):
- login = self.rset.get_entity(self.row or 0, self.col or 0).login
- return self.build_url('cwuser/%s'%login, vid='propertiesform')
+ login = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).login
+ return self._cw.build_url('cwuser/%s'%login, vid='propertiesform')
class FoafView(EntityView):
- id = 'foaf'
+ __regid__ = 'foaf'
__select__ = implements('CWUser')
title = _('foaf')
@@ -49,13 +49,13 @@
self.w(u'''<?xml version="1.0" encoding="%s"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3org/2000/01/rdf-schema#"
- xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self.req.encoding)
- for i in xrange(self.rset.rowcount):
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self._cw.encoding)
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u'</rdf:RDF>\n')
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(u'''<foaf:PersonalProfileDocument rdf:about="">
<foaf:maker rdf:resource="%s"/>
<foaf:primaryTopic rdf:resource="%s"/>
--- a/web/views/debug.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/debug.py Mon Feb 08 13:35:07 2010 +0100
@@ -26,7 +26,7 @@
class DebugView(StartupView):
- id = 'debug'
+ __regid__ = 'debug'
__select__ = none_rset() & match_user_groups('managers')
title = _('server debug information')
@@ -34,7 +34,7 @@
"""display server information"""
w = self.w
w(u'<h1>server sessions</h1>')
- sessions = self.req.cnx._repo._sessions.items()
+ sessions = self._cw.cnx._repo._sessions.items()
if sessions:
w(u'<ul>')
for sid, session in sessions:
@@ -60,3 +60,27 @@
w(u'</ul>')
else:
w(u'<p>no web sessions found</p>')
+
+
+class RegistryView(StartupView):
+ __regid__ = 'registry'
+ __select__ = StartupView.__select__ & match_user_groups('managers')
+ title = _('registry')
+
+ def call(self, **kwargs):
+ """The default view representing the instance's management"""
+ self.w(u'<h1>%s</h1>' % _("Registry's content"))
+ keys = sorted(self._cw.vreg)
+ self.w(u'<p>%s</p>\n' % ' - '.join('<a href="/_registry#%s">%s</a>'
+ % (key, key) for key in keys))
+ for key in keys:
+ self.w(u'<h2><a name="%s">%s</a></h2>' % (key,key))
+ items = self._cw.vreg[key].items()
+ if items:
+ self.w(u'<table><tbody>')
+ for key, value in sorted(items):
+ self.w(u'<tr><td>%s</td><td>%s</td></tr>'
+ % (key, xml_escape(repr(value))))
+ self.w(u'</tbody></table>\n')
+ else:
+ self.w(u'<p>Empty</p>\n')
--- a/web/views/editcontroller.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/editcontroller.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,52 +7,73 @@
"""
__docformat__ = "restructuredtext en"
-from decimal import Decimal
+from rql.utils import rqlvar_maker
-from rql.utils import rqlvar_maker
+from logilab.common.textutils import splitstrip
from cubicweb import Binary, ValidationError, typed_eid
-from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit
-from cubicweb.web.controller import parse_relations_descr
-from cubicweb.web.views.basecontrollers import ViewController
+from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
+from cubicweb.web.views import basecontrollers, autoform
-class ToDoLater(Exception):
- """exception used in the edit controller to indicate that a relation
- can't be handled right now and have to be handled later
- """
+class RqlQuery(object):
+ def __init__(self):
+ self.edited = []
+ self.restrictions = []
+ self.kwargs = {}
+
+ def insert_query(self, etype):
+ if self.edited:
+ rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited))
+ else:
+ rql = 'INSERT %s X' % etype
+ if self.restrictions:
+ rql += ' WHERE %s' % ','.join(self.restrictions)
+ return rql
-class EditController(ViewController):
- id = 'edit'
+ def update_query(self, eid):
+ varmaker = rqlvar_maker()
+ var = varmaker.next()
+ while var in self.kwargs:
+ var = varmaker.next()
+ rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.edited), var)
+ if self.restrictions:
+ rql += ', %s' % ','.join(self.restrictions)
+ self.kwargs[var] = eid
+ return rql
+
+
+class EditController(basecontrollers.ViewController):
+ __regid__ = 'edit'
def publish(self, rset=None):
"""edit / create / copy / delete entity / relations"""
- for key in self.req.form:
+ for key in self._cw.form:
# There should be 0 or 1 action
if key.startswith('__action_'):
cbname = key[1:]
try:
callback = getattr(self, cbname)
except AttributeError:
- raise RequestError(self.req._('invalid action %r' % key))
+ raise RequestError(self._cw._('invalid action %r' % key))
else:
return callback()
self._default_publish()
self.reset()
def _default_publish(self):
- req = self.req
+ req = self._cw
+ self.errors = []
+ self.relations_rql = []
form = req.form
# so we're able to know the main entity from the repository side
if '__maineid' in form:
req.set_shared_data('__maineid', form['__maineid'], querydata=True)
# no specific action, generic edition
self._to_create = req.data['eidmap'] = {}
- self._pending_relations = []
- todelete = self.req.get_pending_deletes()
- toinsert = self.req.get_pending_inserts()
+ self._pending_fields = req.data['pendingfields'] = set()
try:
- methodname = form.pop('__method', None)
+ methodname = req.form.pop('__method', None)
for eid in req.edited_eids():
# __type and eid
formparams = req.extract_entity_params(eid, minparams=2)
@@ -61,315 +82,186 @@
method = getattr(entity, methodname)
method(formparams)
eid = self.edit_entity(formparams)
- except (RequestError, NothingToEdit):
- if '__linkto' in form and 'eid' in form:
+ except (RequestError, NothingToEdit), ex:
+ if '__linkto' in req.form and 'eid' in req.form:
self.execute_linkto()
- elif not ('__delete' in form or '__insert' in form or todelete or toinsert):
- raise ValidationError(None, {None: req._('nothing to edit')})
+ elif not ('__delete' in req.form or '__insert' in req.form):
+ raise ValidationError(None, {None: unicode(ex)})
# handle relations in newly created entities
- if self._pending_relations:
- for rschema, formparams, x, entity in self._pending_relations:
- self.handle_relation(rschema, formparams, x, entity, True)
+ if self._pending_fields:
+ for form, field in self._pending_fields:
+ self.handle_formfield(form, field)
+ # execute rql to set all relations
+ for querydef in self.relations_rql:
+ self._cw.execute(*querydef)
+ # XXX this processes *all* pending operations of *all* entities
+ if req.form.has_key('__delete'):
+ todelete = req.list_form_param('__delete', req.form, pop=True)
+ if todelete:
+ autoform.delete_relations(self._cw, todelete)
+ if req.form.has_key('__insert'):
+ warn('[3.6] stop using __insert, support will be removed',
+ DeprecationWarning)
+ toinsert = req.list_form_param('__insert', req.form, pop=True)
+ if toinsert:
+ autoform.insert_relations(self._cw, toinsert)
+ self._cw.remove_pending_operations()
+ if self.errors:
+ errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
+ raise ValidationError(form.get('__maineid'), errors)
- # XXX this processes *all* pending operations of *all* entities
- if form.has_key('__delete'):
- todelete += req.list_form_param('__delete', form, pop=True)
- if todelete:
- self.delete_relations(parse_relations_descr(todelete))
- if form.has_key('__insert'):
- toinsert = req.list_form_param('__insert', form, pop=True)
- if toinsert:
- self.insert_relations(parse_relations_descr(toinsert))
- self.req.remove_pending_operations()
+ def _insert_entity(self, etype, eid, rqlquery):
+ rql = rqlquery.insert_query(etype)
+ try:
+ # get the new entity (in some cases, the type might have
+ # changed as for the File --> Image mutation)
+ entity = self._cw.execute(rql, rqlquery.kwargs).get_entity(0, 0)
+ neweid = entity.eid
+ except ValidationError, ex:
+ self._to_create[eid] = ex.entity
+ if self._cw.json_request: # XXX (syt) why?
+ ex.entity = eid
+ raise
+ self._to_create[eid] = neweid
+ return neweid
+
+ def _update_entity(self, eid, rqlquery):
+ self._cw.execute(rqlquery.update_query(eid), rqlquery.kwargs)
def edit_entity(self, formparams, multiple=False):
"""edit / create / copy an entity and return its eid"""
etype = formparams['__type']
- entity = self.vreg['etypes'].etype_class(etype)(self.req)
- entity.eid = eid = self._get_eid(formparams['eid'])
- edited = self.req.form.get('__maineid') == formparams['eid']
- # let a chance to do some entity specific stuff.
+ entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
+ entity.eid = formparams['eid']
+ is_main_entity = self._cw.form.get('__maineid') == formparams['eid']
+ # let a chance to do some entity specific stuff
entity.pre_web_edit()
# create a rql query from parameters
- self.relations = []
- self.restrictions = []
+ rqlquery = RqlQuery()
# process inlined relations at the same time as attributes
- # this is required by some external source such as the svn source which
- # needs some information provided by those inlined relation. Moreover
- # this will generate less write queries.
- for rschema in entity.e_schema.subject_relations():
- if rschema.final:
- self.handle_attribute(entity, rschema, formparams)
- elif rschema.inlined:
- self.handle_inlined_relation(rschema, formparams, entity)
- execute = self.req.execute
- if eid is None: # creation or copy
- if self.relations:
- rql = 'INSERT %s X: %s' % (etype, ','.join(self.relations))
- else:
- rql = 'INSERT %s X' % etype
- if self.restrictions:
- rql += ' WHERE %s' % ','.join(self.restrictions)
+ # this will generate less rql queries and might be useful in
+ # a few dark corners
+ formid = self._cw.form.get('__form_id', 'edition')
+ form = self._cw.vreg['forms'].select(formid, self._cw, entity=entity)
+ eid = form.actual_eid(entity.eid)
+ form.formvalues = {} # init fields value cache
+ try:
+ editedfields = formparams['_cw_edited_fields']
+ except KeyError:
+ raise RequestError(self._cw._('no edited fields specified for entity %s' % entity.eid))
+ for editedfield in splitstrip(editedfields):
try:
- # get the new entity (in some cases, the type might have
- # changed as for the File --> Image mutation)
- entity = execute(rql, formparams).get_entity(0, 0)
- eid = entity.eid
- except ValidationError, ex:
- self._to_create[formparams['eid']] = ex.entity
- if self.req.json_request: # XXX (syt) why?
- ex.entity = formparams['eid']
- raise
- self._to_create[formparams['eid']] = eid
- elif self.relations: # edition of an existant entity
- varmaker = rqlvar_maker()
- var = varmaker.next()
- while var in formparams:
- var = varmaker.next()
- rql = 'SET %s WHERE X eid %%(%s)s' % (','.join(self.relations), var)
- if self.restrictions:
- rql += ', %s' % ','.join(self.restrictions)
- formparams[var] = eid
- execute(rql, formparams)
- for rschema in entity.e_schema.subject_relations():
- if rschema.final or rschema.inlined:
- continue
- self.handle_relation(rschema, formparams, 'subject', entity)
- for rschema in entity.e_schema.object_relations():
- if rschema.final:
- continue
- self.handle_relation(rschema, formparams, 'object', entity)
- if edited:
+ name, role = editedfield.split('-')
+ except:
+ name = editedfield
+ role = None
+ if form.field_by_name.im_func.func_code.co_argcount == 4: # XXX
+ field = form.field_by_name(name, role, eschema=entity.e_schema)
+ else:
+ field = form.field_by_name(name, role)
+ if field.has_been_modified(form):
+ self.handle_formfield(form, field, rqlquery)
+ if self.errors:
+ errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
+ raise ValidationError(entity.eid, errors)
+ if eid is None: # creation or copy
+ entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery)
+ elif rqlquery.edited: # edition of an existant entity
+ self._update_entity(eid, rqlquery)
+ if is_main_entity:
self.notify_edited(entity)
if formparams.has_key('__delete'):
- todelete = self.req.list_form_param('__delete', formparams, pop=True)
- self.delete_relations(parse_relations_descr(todelete))
+ # XXX deprecate?
+ todelete = self._cw.list_form_param('__delete', formparams, pop=True)
+ autoform.delete_relations(self._cw, todelete)
if formparams.has_key('__cloned_eid'):
entity.copy_relations(typed_eid(formparams['__cloned_eid']))
- if formparams.has_key('__insert'):
- toinsert = self.req.list_form_param('__insert', formparams, pop=True)
- self.insert_relations(parse_relations_descr(toinsert))
- if edited: # only execute linkto for the main entity
- self.execute_linkto(eid)
+ if is_main_entity: # only execute linkto for the main entity
+ self.execute_linkto(entity.eid)
return eid
+ def handle_formfield(self, form, field, rqlquery=None):
+ eschema = form.edited_entity.e_schema
+ try:
+ for field, value in field.process_posted(form):
+ if not (
+ (field.role == 'subject' and field.name in eschema.subjrels)
+ or
+ (field.role == 'object' and field.name in eschema.objrels)):
+ continue
+ rschema = self._cw.vreg.schema.rschema(field.name)
+ if rschema.final:
+ rqlquery.kwargs[field.name] = value
+ rqlquery.edited.append('X %s %%(%s)s' % (rschema, rschema))
+ else:
+ if form.edited_entity.has_eid():
+ origvalues = set(entity.eid for entity in form.edited_entity.related(field.name, field.role, entities=True))
+ else:
+ origvalues = set()
+ if value is None or value == origvalues:
+ continue # not edited / not modified / to do later
+ if rschema.inlined and rqlquery is not None and field.role == 'subject':
+ self.handle_inlined_relation(form, field, value, origvalues, rqlquery)
+ elif form.edited_entity.has_eid():
+ self.handle_relation(form, field, value, origvalues)
+ else:
+ self._pending_fields.add( (form, field) )
+
+ except ProcessFormError, exc:
+ self.errors.append((field, exc))
+
+ def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
+ """handle edition for the (rschema, x) relation of the given entity
+ """
+ attr = field.name
+ if values:
+ rqlquery.kwargs[attr] = iter(values).next()
+ rqlquery.edited.append('X %s %s' % (attr, attr.upper()))
+ rqlquery.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
+ elif form.edited_entity.has_eid():
+ self.handle_relation(form, field, values, origvalues)
+
+ def handle_relation(self, form, field, values, origvalues):
+ """handle edition for the (rschema, x) relation of the given entity
+ """
+ etype = form.edited_entity.e_schema
+ rschema = self._cw.vreg.schema.rschema(field.name)
+ if field.role == 'subject':
+ desttype = rschema.objects(etype)[0]
+ card = rschema.rdef(etype, desttype).cardinality[0]
+ subjvar, objvar = 'X', 'Y'
+ else:
+ desttype = rschema.subjects(etype)[0]
+ card = rschema.rdef(desttype, etype).cardinality[1]
+ subjvar, objvar = 'Y', 'X'
+ eid = form.edited_entity.eid
+ if field.role == 'object' or not rschema.inlined or not values:
+ # this is not an inlined relation or no values specified,
+ # explicty remove relations
+ rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+ subjvar, rschema, objvar)
+ for reid in origvalues.difference(values):
+ self.relations_rql.append((rql, {'x': eid, 'y': reid}, ('x', 'y')))
+ seteids = values.difference(origvalues)
+ if seteids:
+ rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
+ subjvar, rschema, objvar)
+ for reid in seteids:
+ self.relations_rql.append((rql, {'x': eid, 'y': reid}, ('x', 'y')))
+
def _action_apply(self):
self._default_publish()
self.reset()
def _action_cancel(self):
- errorurl = self.req.form.get('__errorurl')
+ errorurl = self._cw.form.get('__errorurl')
if errorurl:
- self.req.cancel_edition(errorurl)
- self.req.message = self.req._('edit canceled')
+ self._cw.cancel_edition(errorurl)
+ self._cw.message = self._cw._('edit canceled')
return self.reset()
def _action_delete(self):
- self.delete_entities(self.req.edited_eids(withtype=True))
+ self.delete_entities(self._cw.edited_eids(withtype=True))
return self.reset()
- def _needs_edition(self, rtype, formparams, entity):
- """returns True and and the new value if `rtype` was edited"""
- editkey = 'edits-%s' % rtype
- if not editkey in formparams:
- return False, None # not edited
- value = formparams.get(rtype) or None
- if entity.has_eid() and (formparams.get(editkey) or None) == value:
- return False, None # not modified
- if value == INTERNAL_FIELD_VALUE:
- value = None
- return True, value
- def handle_attribute(self, entity, rschema, formparams):
- """append to `relations` part of the rql query to edit the
- attribute described by the given schema if necessary
- """
- attr = rschema.type
- edition_needed, value = self._needs_edition(attr, formparams, entity)
- if not edition_needed:
- return
- # test if entity class defines a special handler for this attribute
- custom_edit = getattr(entity, 'custom_%s_edit' % attr, None)
- if custom_edit:
- custom_edit(formparams, value, self.relations)
- return
- attrtype = rschema.objects(entity.e_schema)[0].type
- # on checkbox or selection, the field may not be in params
- # NOTE: raising ValidationError here is not a good solution because
- # we can't gather all errors at once. Hopefully, the new 3.6.x
- # form handling will fix that
- if attrtype == 'Boolean':
- value = bool(value)
- elif attrtype == 'Decimal':
- value = Decimal(value)
- elif attrtype == 'Bytes':
- # if it is a file, transport it using a Binary (StringIO)
- # XXX later __detach is for the new widget system, the former is to
- # be removed once web/widgets.py has been dropped
- if formparams.has_key('__%s_detach' % attr) or formparams.has_key('%s__detach' % attr):
- # drop current file value
- value = None
- # no need to check value when nor explicit detach nor new file
- # submitted, since it will think the attribute is not modified
- elif isinstance(value, unicode):
- # file modified using a text widget
- encoding = entity.attr_metadata(attr, 'encoding')
- value = Binary(value.encode(encoding))
- elif value:
- # value is a 3-uple (filename, mimetype, stream)
- val = Binary(value[2].read())
- if not val.getvalue(): # usually an unexistant file
- value = None
- else:
- val.filename = value[0]
- # ignore browser submitted MIME type since it may be buggy
- # XXX add a config option to tell if we should consider it
- # or not?
- #if entity.e_schema.has_metadata(attr, 'format'):
- # key = '%s_format' % attr
- # formparams[key] = value[1]
- # self.relations.append('X %s_format %%(%s)s'
- # % (attr, key))
- # XXX suppose a File compatible schema
- if 'name' in entity.e_schema.subjrels \
- and not formparams.get('name'):
- formparams['name'] = value[0]
- self.relations.append('X name %(name)s')
- value = val
- else:
- # no specified value, skip
- return
- elif value is not None:
- if attrtype == 'Int':
- try:
- value = int(value)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid integer value")})
- elif attrtype == 'Float':
- try:
- value = float(value)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid float value")})
- elif attrtype in ('Date', 'Datetime', 'Time'):
- try:
- value = self.parse_datetime(value, attrtype)
- except ValueError:
- raise ValidationError(entity.eid,
- {attr: self.req._("invalid date")})
- elif attrtype == 'Password':
- # check confirmation (see PasswordWidget for confirmation field name)
- confirmval = formparams.get(attr + '-confirm')
- if confirmval != value:
- raise ValidationError(entity.eid,
- {attr: self.req._("password and confirmation don't match")})
- # password should *always* be utf8 encoded
- value = value.encode('UTF8')
- else:
- # strip strings
- value = value.strip()
- elif attrtype == 'Password':
- # skip None password
- return # unset password
- formparams[attr] = value
- self.relations.append('X %s %%(%s)s' % (attr, attr))
-
- def _relation_values(self, rschema, formparams, x, entity, late=False):
- """handle edition for the (rschema, x) relation of the given entity
- """
- rtype = rschema.type
- editkey = 'edit%s-%s' % (x[0], rtype)
- if not editkey in formparams:
- return # not edited
- try:
- values = self._linked_eids(self.req.list_form_param(rtype, formparams), late)
- except ToDoLater:
- self._pending_relations.append((rschema, formparams, x, entity))
- return
- origvalues = set(typed_eid(eid) for eid in self.req.list_form_param(editkey, formparams))
- return values, origvalues
-
- def handle_inlined_relation(self, rschema, formparams, entity, late=False):
- """handle edition for the (rschema, x) relation of the given entity
- """
- try:
- values, origvalues = self._relation_values(rschema, formparams,
- 'subject', entity, late)
- except TypeError:
- return # not edited / to do later
- if values == origvalues:
- return # not modified
- attr = str(rschema)
- if values:
- formparams[attr] = iter(values).next()
- self.relations.append('X %s %s' % (attr, attr.upper()))
- self.restrictions.append('%s eid %%(%s)s' % (attr.upper(), attr))
- elif entity.has_eid():
- self.handle_relation(rschema, formparams, 'subject', entity, late)
-
- def handle_relation(self, rschema, formparams, x, entity, late=False):
- """handle edition for the (rschema, x) relation of the given entity
- """
- try:
- values, origvalues = self._relation_values(rschema, formparams, x,
- entity, late)
- except TypeError:
- return # not edited / to do later
- etype = entity.e_schema
- if values == origvalues:
- return # not modified
- if x == 'subject':
- desttype = rschema.objects(etype)[0]
- card = rschema.rproperty(etype, desttype, 'cardinality')[0]
- subjvar, objvar = 'X', 'Y'
- else:
- desttype = rschema.subjects(etype)[0]
- card = rschema.rproperty(desttype, etype, 'cardinality')[1]
- subjvar, objvar = 'Y', 'X'
- eid = entity.eid
- if x == 'object' or not rschema.inlined or not values:
- # this is not an inlined relation or no values specified,
- # explicty remove relations
- rql = 'DELETE %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
- subjvar, rschema, objvar)
- for reid in origvalues.difference(values):
- self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
- seteids = values.difference(origvalues)
- if seteids:
- rql = 'SET %s %s %s WHERE X eid %%(x)s, Y eid %%(y)s' % (
- subjvar, rschema, objvar)
- for reid in seteids:
- self.req.execute(rql, {'x': eid, 'y': reid}, ('x', 'y'))
-
- def _get_eid(self, eid):
- # should be either an int (existant entity) or a variable (to be
- # created entity)
- assert eid or eid == 0, repr(eid) # 0 is a valid eid
- try:
- return typed_eid(eid)
- except ValueError:
- try:
- return self._to_create[eid]
- except KeyError:
- self._to_create[eid] = None
- return None
-
- def _linked_eids(self, eids, late=False):
- """return a list of eids if they are all known, else raise ToDoLater
- """
- result = set()
- for eid in eids:
- if not eid: # AutoCompletionWidget
- continue
- eid = self._get_eid(eid)
- if eid is None:
- if not late:
- raise ToDoLater()
- # eid is still None while it's already a late call
- # this mean that the associated entity has not been created
- raise Exception('duh')
- result.add(eid)
- return result
-
-
--- a/web/views/editforms.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/editforms.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,63 +16,46 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cached
-from cubicweb import neg_role
from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
specified_etype_implements, yes)
from cubicweb.view import EntityView
-from cubicweb.common import tags
-from cubicweb.web import stdmsgs, eid_param
-from cubicweb.web import uicfg
+from cubicweb import tags
+from cubicweb.web import uicfg, stdmsgs, eid_param, \
+ formfields as ff, formwidgets as fw
from cubicweb.web.form import FormViewMixIn, FieldNotFound
-from cubicweb.web.formfields import guess_field
-from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
from cubicweb.web.views import forms
_pvdc = uicfg.primaryview_display_ctrl
-def relation_id(eid, rtype, role, reid):
- """return an identifier for a relation between two entities"""
- if role == 'subject':
- return u'%s:%s:%s' % (eid, rtype, reid)
- return u'%s:%s:%s' % (reid, rtype, eid)
-
-def toggleable_relation_link(eid, nodeid, label='x'):
- """return javascript snippet to delete/undelete a relation between two
- entities
- """
- js = u"javascript: togglePendingDelete('%s', %s);" % (
- nodeid, xml_escape(dumps(eid)))
- return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
- js, nodeid, label)
-
class DeleteConfForm(forms.CompositeForm):
- id = 'deleteconf'
+ __regid__ = 'deleteconf'
__select__ = non_final_entity()
domid = 'deleteconf'
copy_nav_params = True
- form_buttons = [Button(stdmsgs.BUTTON_DELETE, cwaction='delete'),
- Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+ form_buttons = [fw.Button(stdmsgs.BUTTON_DELETE, cwaction='delete'),
+ fw.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
@property
def action(self):
- return self.build_url('edit')
+ return self._cw.build_url('edit')
def __init__(self, *args, **kwargs):
super(DeleteConfForm, self).__init__(*args, **kwargs)
done = set()
- for entity in self.rset.entities():
+ for entity in self.cw_rset.entities():
if entity.eid in done:
continue
done.add(entity.eid)
- subform = self.vreg['forms'].select('base', self.req, entity=entity,
- mainform=False)
+ subform = self._cw.vreg['forms'].select('base', self._cw,
+ entity=entity,
+ mainform=False)
self.add_subform(subform)
class DeleteConfFormView(FormViewMixIn, EntityView):
"""form used to confirm deletion of some entities"""
- id = 'deleteconf'
+ __regid__ = 'deleteconf'
title = _('delete')
# don't use navigation, all entities asked to be deleted should be displayed
# else we will only delete the displayed page
@@ -80,37 +63,217 @@
def call(self, onsubmit=None):
"""ask for confirmation before real deletion"""
- req, w = self.req, self.w
+ req, w = self._cw, self.w
_ = req._
w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
% _('this action is not reversible!'))
# XXX above message should have style of a warning
w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
- form = self.vreg['forms'].select(self.id, req, rset=self.rset,
- onsubmit=onsubmit)
+ form = self._cw.vreg['forms'].select(self.__regid__, req,
+ rset=self.cw_rset,
+ onsubmit=onsubmit)
w(u'<ul>\n')
- for entity in self.rset.entities():
- # don't use outofcontext view or any other that may contain inline edition form
+ for entity in self.cw_rset.entities():
+ # don't use outofcontext view or any other that may contain inline
+ # edition form
w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
href=entity.absolute_url()))
w(u'</ul>\n')
w(form.render())
+class EditionFormView(FormViewMixIn, EntityView):
+ """display primary entity edition form"""
+ __regid__ = 'edition'
+ # add yes() so it takes precedence over deprecated views in baseforms,
+ # though not baseforms based customized view
+ __select__ = one_line_rset() & non_final_entity() & yes()
+
+ title = _('edition')
+
+ def cell_call(self, row, col, **kwargs):
+ entity = self.cw_rset.complete_entity(row, col)
+ self.render_form(entity)
+
+ def render_form(self, entity):
+ """fetch and render the form"""
+ self.form_title(entity)
+ form = self._cw.vreg['forms'].select('edition', self._cw, rset=entity.cw_rset,
+ row=entity.cw_row, col=entity.cw_col,
+ entity=entity,
+ submitmsg=self.submited_message())
+ self.init_form(form, entity)
+ self.w(form.render(formvid=u'edition'))
+
+ def init_form(self, form, entity):
+ """customize your form before rendering here"""
+ pass
+
+ def form_title(self, entity):
+ """the form view title"""
+ ptitle = self._cw._(self.title)
+ self.w(u'<div class="formTitle"><span>%s %s</span></div>' % (
+ entity.dc_type(), ptitle and '(%s)' % ptitle))
+
+ def submited_message(self):
+ """return the message that will be displayed on successful edition"""
+ return self._cw._('entity edited')
+
+
+class CreationFormView(EditionFormView):
+ """display primary entity creation form"""
+ __regid__ = 'creation'
+ __select__ = specified_etype_implements('Any') & yes()
+
+ title = _('creation')
+
+ def call(self, **kwargs):
+ """creation view for an entity"""
+ # at this point we know etype is a valid entity type, thanks to our
+ # selector
+ etype = kwargs.pop('etype', self._cw.form.get('etype'))
+ entity = self._cw.vreg['etypes'].etype_class(etype)(self._cw)
+ entity.eid = self._cw.varmaker.next()
+ self.render_form(entity)
+
+ def form_title(self, entity):
+ """the form view title"""
+ if '__linkto' in self._cw.form:
+ if isinstance(self._cw.form['__linkto'], list):
+ # XXX which one should be considered (case: add a ticket to a
+ # version in jpl)
+ rtype, linkto_eid, role = self._cw.form['__linkto'][0].split(':')
+ else:
+ rtype, linkto_eid, role = self._cw.form['__linkto'].split(':')
+ linkto_rset = self._cw.eid_rset(linkto_eid)
+ linkto_type = linkto_rset.description[0][0]
+ if role == 'subject':
+ title = self._cw.__('creating %s (%s %s %s %%(linkto)s)' % (
+ entity.e_schema, entity.e_schema, rtype, linkto_type))
+ else:
+ title = self._cw.__('creating %s (%s %%(linkto)s %s %s)' % (
+ entity.e_schema, linkto_type, rtype, entity.e_schema))
+ msg = title % {'linkto' : self._cw.view('incontext', linkto_rset)}
+ self.w(u'<div class="formTitle notransform"><span>%s</span></div>' % msg)
+ else:
+ super(CreationFormView, self).form_title(entity)
+
+ def url(self):
+ """return the url associated with this view"""
+ return self.create_url(self._cw.form.get('etype'))
+
+ def submited_message(self):
+ """return the message that will be displayed on successful edition"""
+ return self._cw._('entity created')
+
+
+class CopyFormView(EditionFormView):
+ """display primary entity creation form initialized with values from another
+ entity
+ """
+ __regid__ = 'copy'
+
+ title = _('copy')
+ warning_message = _('Please note that this is only a shallow copy')
+
+ def render_form(self, entity):
+ """fetch and render the form"""
+ # make a copy of entity to avoid altering the entity in the
+ # request's cache.
+ entity.complete()
+ self.newentity = copy(entity)
+ self.copying = entity
+ self.newentity.eid = self._cw.varmaker.next()
+ self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
+ % self._cw._(self.warning_message))
+ super(CopyFormView, self).render_form(self.newentity)
+ del self.newentity
+
+ def init_form(self, form, entity):
+ """customize your form before rendering here"""
+ super(CopyFormView, self).init_form(form, entity)
+ if entity.eid == self.newentity.eid:
+ form.add_hidden(eid_param('__cloned_eid', entity.eid),
+ self.copying.eid)
+ for rschema, role in form.editable_attributes():
+ if not rschema.final:
+ # ensure relation cache is filed
+ rset = self.copying.related(rschema, role)
+ self.newentity.set_related_cache(rschema, role, rset)
+
+ def submited_message(self):
+ """return the message that will be displayed on successful edition"""
+ return self._cw._('entity copied')
+
+
+class TableEditForm(forms.CompositeForm):
+ __regid__ = 'muledit'
+ domid = 'entityForm'
+ onsubmit = "return validateForm('%s', null);" % domid
+ form_buttons = [fw.SubmitButton(_('validate modifications on selected items')),
+ fw.ResetButton(_('revert changes'))]
+
+ def __init__(self, req, rset, **kwargs):
+ kwargs.setdefault('__redirectrql', rset.printable_rql())
+ super(TableEditForm, self).__init__(req, rset=rset, **kwargs)
+ for row in xrange(len(self.cw_rset)):
+ form = self._cw.vreg['forms'].select('edition', self._cw,
+ rset=self.cw_rset, row=row,
+ formtype='muledit',
+ copy_nav_params=False,
+ mainform=False)
+ # XXX rely on the EntityCompositeFormRenderer to put the eid input
+ form.remove_field(form.field_by_name('eid'))
+ self.add_subform(form)
+
+
+class TableEditFormView(FormViewMixIn, EntityView):
+ __regid__ = 'muledit'
+ __select__ = EntityView.__select__ & yes()
+ title = _('multiple edit')
+
+ def call(self, **kwargs):
+ """a view to edit multiple entities of the same type the first column
+ should be the eid
+ """
+ #self.form_title(entity)
+ form = self._cw.vreg['forms'].select(self.__regid__, self._cw,
+ rset=self.cw_rset,
+ copy_nav_params=True)
+ # XXX overriding formvid (eg __form_id) necessary to make work edition:
+ # the edit controller try to select the form with no rset but
+ # entity=entity, and use this form to edit the entity. So we want
+ # edition form there but specifying formvid may have other undesired
+ # side effect. Maybe we should provide another variable optinally
+ # telling which form the edit controller should select (eg difffers
+ # between html generation / post handling form)
+ self.w(form.render(formvid='edition'))
+
+
+# click and edit handling ('reledit') ##########################################
+
+class DummyForm(object):
+ __slots__ = ('event_args',)
+ def form_render(self, **_args):
+ return u''
+ def render(self, **_args):
+ return u''
+ def append_field(self, *args):
+ pass
+ def field_by_name(self, rtype, role, eschema=None):
+ return None
+
+
class ClickAndEditFormView(FormViewMixIn, EntityView):
"""form used to permit ajax edition of a relation or attribute of an entity
in a view, if logged user have the permission to edit it.
(double-click on the field to see an appropriate edition widget).
"""
- id = 'doreledit'
+ __regid__ = 'doreledit'
__select__ = non_final_entity() & match_kwargs('rtype')
# FIXME editableField class could be toggleable from userprefs
- # add metadata to allow edition of metadata attributes (not considered by
- # edition form by default)
- attrcategories = ('primary', 'secondary', 'metadata')
-
_onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
_onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
"'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
@@ -133,58 +296,57 @@
"""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.req.add_js('cubicweb.edition.js')
- self.req.add_css('cubicweb.form.css')
+ self._cw.add_js('cubicweb.edition.js')
+ self._cw.add_css('cubicweb.form.css')
if default is None:
- default = xml_escape(self.req._('<no value>'))
- entity = self.entity(row, col)
- rschema = entity.schema.rschema(rtype)
+ default = xml_escape(self._cw._('<no value>'))
+ schema = self._cw.vreg.schema
+ entity = self.cw_rset.get_entity(row, col)
+ rschema = schema.rschema(rtype)
lzone = self._build_landing_zone(landing_zone)
# compute value, checking perms, build form
if rschema.final:
- form = self._build_form(entity, rtype, role, 'edition', default, reload, lzone,
- attrcategories=self.attrcategories)
+ form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
if not self.should_edit_attribute(entity, rschema, role, form):
self.w(entity.printable_value(rtype))
return
value = entity.printable_value(rtype) or default
- self.relation_form(lzone, value, form,
- self._build_renderer(entity, rtype, role))
else:
rvid = self._compute_best_vid(entity.e_schema, rschema, role)
rset = entity.related(rtype, role)
if rset:
- value = self.view(rvid, rset)
+ value = self._cw.view(rvid, rset)
else:
value = default
if not self.should_edit_relation(entity, rschema, role, rvid):
if rset:
self.w(value)
return
+ # XXX do we really have to give lzone twice?
form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
dict(vid=rvid, lzone=lzone))
- field = guess_field(entity.e_schema, entity.schema.rschema(rtype), role)
- form.append_field(field)
- self.relation_form(lzone, value, form,
- self._build_renderer(entity, rtype, role))
+ field = form.field_by_name(rtype, role, entity.e_schema)
+ form.append_field(field)
+ self.relation_form(lzone, value, form,
+ self._build_renderer(entity, rtype, role))
def should_edit_attribute(self, entity, rschema, role, form):
rtype = str(rschema)
- ttype = rschema.targets(entity.id, role)[0]
- afs = uicfg.autoform_section.etype_get(entity.id, rtype, role, ttype)
- if not (afs in self.attrcategories and entity.has_perm('update')):
+ ttype = rschema.targets(entity.__regid__, role)[0]
+ afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
+ if 'main_hidden' in afs or not entity.has_perm('update'):
return False
try:
- form.field_by_name(rtype, role)
+ form.field_by_name(rtype, role, entity.e_schema)
except FieldNotFound:
return False
return True
def should_edit_relation(self, entity, rschema, role, rvid):
- if ((role == 'subject' and not rschema.has_perm(self.req, 'add',
+ if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
fromeid=entity.eid))
or
- (role == 'object' and not rschema.has_perm(self.req, 'add',
+ (role == 'object' and not rschema.has_perm(self._cw, 'add',
toeid=entity.eid))):
return False
return True
@@ -206,7 +368,7 @@
w(form.render(renderer=renderer))
w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
divid, xml_escape(self._onclick % form.event_args),
- self.req._(self._landingzonemsg)))
+ self._cw._(self._landingzonemsg)))
w(lzone)
w(u'</div>')
w(u'</div>')
@@ -215,17 +377,18 @@
dispctrl = _pvdc.etype_get(eschema, rschema, role)
if dispctrl.get('rvid'):
return dispctrl['rvid']
- if eschema.cardinality(rschema, role) in '+*':
+ if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
return self._many_rvid
return self._one_rvid
def _build_landing_zone(self, lzone):
- return lzone or self._defaultlandingzone % {'msg' : xml_escape(self.req._(self._landingzonemsg))}
+ return lzone or self._defaultlandingzone % {
+ 'msg': xml_escape(self._cw._(self._landingzonemsg))}
def _build_renderer(self, entity, rtype, role):
- return self.vreg['formrenderers'].select(
- 'base', self.req, entity=entity, display_label=False,
- display_help=False, display_fields=[(rtype, role)], table_class='',
+ return self._cw.vreg['formrenderers'].select(
+ 'base', self._cw, entity=entity, display_label=False,
+ display_help=False, table_class='',
button_bar_class='buttonbar', display_progress_div=False)
def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
@@ -244,40 +407,24 @@
reload, lzone, extradata)
onsubmit = self._onsubmit % event_args
cancelclick = self._cancelclick % (entity.eid, rtype, divid)
- form = self.vreg['forms'].select(
- formid, self.req, entity=entity, domid='%s-form' % divid,
+ form = self._cw.vreg['forms'].select(
+ formid, self._cw, entity=entity, domid='%s-form' % divid,
cssstyle='display: none', onsubmit=onsubmit, action='#',
- form_buttons=[SubmitButton(), Button(stdmsgs.BUTTON_CANCEL,
- onclick=cancelclick)],
+ form_buttons=[fw.SubmitButton(),
+ fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
**formargs)
form.event_args = event_args
return form
-class DummyForm(object):
- __slots__ = ('event_args',)
- def form_render(self, **_args):
- return u''
- def render(self, **_args):
- return u''
- def append_field(self, *args):
- pass
class AutoClickAndEditFormView(ClickAndEditFormView):
"""same as ClickAndEditFormView but checking if the view *should* be applied
by checking uicfg configuration and composite relation property.
"""
- id = 'reledit'
+ __regid__ = 'reledit'
_onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
"'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
- def should_edit_attribute(self, entity, rschema, role, _form):
- rtype = str(rschema)
- ttype = rschema.targets(entity.id, role)[0]
- afs = uicfg.autoform_section.etype_get(entity.id, rtype, role, ttype)
- if not (afs in self.attrcategories and entity.has_perm('update')):
- return False
- return True
-
def should_edit_relation(self, entity, rschema, role, rvid):
eschema = entity.e_schema
rtype = str(rschema)
@@ -286,7 +433,7 @@
vid = dispctrl.get('vid', 'reledit')
if vid != 'reledit': # reledit explicitly disabled
return False
- if eschema.role_rproperty(role, rschema, 'composite') == role:
+ if eschema.rdef(rschema, role).composite == role:
return False
return super(AutoClickAndEditFormView, self).should_edit_relation(
entity, rschema, role, rvid)
@@ -302,318 +449,3 @@
def _build_renderer(self, entity, rtype, role):
pass
-class EditionFormView(FormViewMixIn, EntityView):
- """display primary entity edition form"""
- id = 'edition'
- # add yes() so it takes precedence over deprecated views in baseforms,
- # though not baseforms based customized view
- __select__ = one_line_rset() & non_final_entity() & yes()
-
- title = _('edition')
-
- def cell_call(self, row, col, **kwargs):
- entity = self.complete_entity(row, col)
- self.render_form(entity)
-
- def render_form(self, entity):
- """fetch and render the form"""
- self.form_title(entity)
- form = self.vreg['forms'].select('edition', self.req, rset=entity.rset,
- row=entity.row, col=entity.col, entity=entity,
- submitmsg=self.submited_message())
- self.init_form(form, entity)
- self.w(form.render(rendervalues=dict(formvid=u'edition')))
-
- def init_form(self, form, entity):
- """customize your form before rendering here"""
- pass
-
- def form_title(self, entity):
- """the form view title"""
- ptitle = self.req._(self.title)
- self.w(u'<div class="formTitle"><span>%s %s</span></div>' % (
- entity.dc_type(), ptitle and '(%s)' % ptitle))
-
- def submited_message(self):
- """return the message that will be displayed on successful edition"""
- return self.req._('entity edited')
-
-
-class CreationFormView(EditionFormView):
- """display primary entity creation form"""
- id = 'creation'
- __select__ = specified_etype_implements('Any') & yes()
-
- title = _('creation')
-
- def call(self, **kwargs):
- """creation view for an entity"""
- # at this point we know etype is a valid entity type, thanks to our
- # selector
- etype = kwargs.pop('etype', self.req.form.get('etype'))
- entity = self.vreg['etypes'].etype_class(etype)(self.req)
- self.initialize_varmaker()
- entity.eid = self.varmaker.next()
- self.render_form(entity)
-
- def form_title(self, entity):
- """the form view title"""
- if '__linkto' in self.req.form:
- if isinstance(self.req.form['__linkto'], list):
- # XXX which one should be considered (case: add a ticket to a
- # version in jpl)
- rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
- else:
- rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
- linkto_rset = self.req.eid_rset(linkto_eid)
- linkto_type = linkto_rset.description[0][0]
- if role == 'subject':
- title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
- entity.e_schema, entity.e_schema, rtype, linkto_type))
- else:
- title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
- entity.e_schema, linkto_type, rtype, entity.e_schema))
- msg = title % {'linkto' : self.view('incontext', linkto_rset)}
- self.w(u'<div class="formTitle notransform"><span>%s</span></div>' % msg)
- else:
- super(CreationFormView, self).form_title(entity)
-
- def url(self):
- """return the url associated with this view"""
- return self.create_url(self.req.form.get('etype'))
-
- def submited_message(self):
- """return the message that will be displayed on successful edition"""
- return self.req._('entity created')
-
-
-class CopyFormView(EditionFormView):
- """display primary entity creation form initialized with values from another
- entity
- """
- id = 'copy'
- title = _('copy')
- warning_message = _('Please note that this is only a shallow copy')
-
- def render_form(self, entity):
- """fetch and render the form"""
- # make a copy of entity to avoid altering the entity in the
- # request's cache.
- entity.complete()
- self.newentity = copy(entity)
- self.copying = entity
- self.initialize_varmaker()
- self.newentity.eid = self.varmaker.next()
- self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
- % self.req._(self.warning_message))
- super(CopyFormView, self).render_form(self.newentity)
- del self.newentity
-
- def init_form(self, form, entity):
- """customize your form before rendering here"""
- super(CopyFormView, self).init_form(form, entity)
- if entity.eid == self.newentity.eid:
- form.form_add_hidden(eid_param('__cloned_eid', entity.eid),
- self.copying.eid)
- for rschema, _, role in form.relations_by_category(form.attrcategories,
- 'add'):
- if not rschema.final:
- # ensure relation cache is filed
- rset = self.copying.related(rschema, role)
- self.newentity.set_related_cache(rschema, role, rset)
-
- def submited_message(self):
- """return the message that will be displayed on successful edition"""
- return self.req._('entity copied')
-
-
-class TableEditForm(forms.CompositeForm):
- id = 'muledit'
- domid = 'entityForm'
- onsubmit = "return validateForm('%s', null);" % domid
- form_buttons = [SubmitButton(_('validate modifications on selected items')),
- ResetButton(_('revert changes'))]
-
- def __init__(self, req, rset, **kwargs):
- kwargs.setdefault('__redirectrql', rset.printable_rql())
- super(TableEditForm, self).__init__(req, rset, **kwargs)
- for row in xrange(len(self.rset)):
- form = self.vreg['forms'].select('edition', self.req,
- rset=self.rset, row=row,
- attrcategories=('primary',),
- copy_nav_params=False,
- mainform=False)
- # XXX rely on the EntityCompositeFormRenderer to put the eid input
- form.remove_field(form.field_by_name('eid'))
- self.add_subform(form)
-
-
-class TableEditFormView(FormViewMixIn, EntityView):
- id = 'muledit'
- __select__ = EntityView.__select__ & yes()
- title = _('multiple edit')
-
- def call(self, **kwargs):
- """a view to edit multiple entities of the same type the first column
- should be the eid
- """
- #self.form_title(entity)
- form = self.vreg['forms'].select(self.id, self.req, rset=self.rset,
- copy_nav_params=True)
- self.w(form.render())
-
-
-class InlineEntityEditionFormView(FormViewMixIn, EntityView):
- """
- :attr peid: the parent entity's eid hosting the inline form
- :attr rtype: the relation bridging `etype` and `peid`
- :attr role: the role played by the `peid` in the relation
- :attr pform: the parent form where this inlined form is being displayed
- """
- id = 'inline-edition'
- __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
-
- _select_attrs = ('peid', 'rtype', 'role', 'pform')
- removejs = "removeInlinedEntity('%s', '%s', '%s')"
-
- def __init__(self, *args, **kwargs):
- for attr in self._select_attrs:
- setattr(self, attr, kwargs.pop(attr, None))
- super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
-
- def _entity(self):
- assert self.row is not None, self
- return self.rset.get_entity(self.row, self.col)
-
- @property
- @cached
- def form(self):
- entity = self._entity()
- form = self.vreg['forms'].select('edition', self.req,
- entity=entity,
- form_renderer_id='inline',
- copy_nav_params=False,
- mainform=False,
- parent_form=self.pform,
- **self.extra_kwargs)
- if self.pform is None:
- form.restore_previous_post(form.session_key())
- #assert form.parent_form
- self.add_hiddens(form, entity)
- return form
-
- def cell_call(self, row, col, i18nctx, **kwargs):
- """
- :param peid: the parent entity's eid hosting the inline form
- :param rtype: the relation bridging `etype` and `peid`
- :param role: the role played by the `peid` in the relation
- """
- entity = self._entity()
- divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
- self.peid, self.rtype, entity.eid)
- self.render_form(i18nctx, divonclick=divonclick, **kwargs)
-
- def render_form(self, i18nctx, **kwargs):
- """fetch and render the form"""
- entity = self._entity()
- divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
- title = self.form_title(entity, i18nctx)
- removejs = self.removejs and self.removejs % (
- self.peid, self.rtype, entity.eid)
- countkey = '%s_count' % self.rtype
- try:
- self.req.data[countkey] += 1
- except KeyError:
- self.req.data[countkey] = 1
- # XXX split kwargs into additional rendervalues / formvalues
- self.w(self.form.render(
- rendervalues=dict(divid=divid, title=title, removejs=removejs,
- i18nctx=i18nctx, counter=self.req.data[countkey]),
- formvalues=kwargs))
-
- def form_title(self, entity, i18nctx):
- return self.req.pgettext(i18nctx, 'This %s' % entity.e_schema)
-
- def add_hiddens(self, form, entity):
- """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
- iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
- # * str(self.rtype) in case it's a schema object
- # * neged_role() since role is the for parent entity, we want the role
- # of the inlined entity
- form.form_add_hidden(name=str(self.rtype), value=self.peid,
- role=neg_role(self.role), eidparam=True, id=iid)
-
- def keep_entity(self, form, entity):
- if not entity.has_eid():
- return True
- # are we regenerating form because of a validation error ?
- if form.form_previous_values:
- cdvalues = self.req.list_form_param(eid_param(self.rtype, self.peid),
- form.form_previous_values)
- if unicode(entity.eid) not in cdvalues:
- return False
- return True
-
-
-class InlineEntityCreationFormView(InlineEntityEditionFormView):
- """
- :attr etype: the entity type being created in the inline form
- """
- id = 'inline-creation'
- __select__ = (match_kwargs('peid', 'rtype')
- & specified_etype_implements('Any'))
- _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',)
-
- @property
- def removejs(self):
- entity = self._entity()
- card = entity.e_schema.role_rproperty(neg_role(self.role), self.rtype, 'cardinality')
- card = card[self.role == 'object']
- # when one is adding an inline entity for a relation of a single card,
- # the 'add a new xxx' link disappears. If the user then cancel the addition,
- # we have to make this link appears back. This is done by giving add new link
- # id to removeInlineForm.
- if card not in '?1':
- return "removeInlineForm('%s', '%s', '%s')"
- divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
- return "removeInlineForm('%%s', '%%s', '%%s', '%s')" % divid
-
- @cached
- def _entity(self):
- try:
- cls = self.vreg['etypes'].etype_class(self.etype)
- except:
- self.w(self.req._('no such entity type %s') % etype)
- return
- self.initialize_varmaker()
- entity = cls(self.req)
- entity.eid = self.varmaker.next()
- return entity
-
- def call(self, i18nctx, **kwargs):
- self.render_form(i18nctx, **kwargs)
-
-
-class InlineAddNewLinkView(InlineEntityCreationFormView):
- """
- :attr card: the cardinality of the relation according to role of `peid`
- """
- id = 'inline-addnew-link'
- __select__ = (match_kwargs('peid', 'rtype')
- & specified_etype_implements('Any'))
-
- _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
- form = None # no actual form wrapped
-
- def call(self, i18nctx, **kwargs):
- divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
- self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
- % divid)
- js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
- self.peid, self.etype, self.rtype, self.role, i18nctx)
- if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
- js = "toggleVisibility('%s'); %s" % (divid, js)
- __ = self.req.pgettext
- self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
- % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
- self.w(u'</div>')
--- a/web/views/editviews.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/editviews.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,26 +8,21 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from simplejson import dumps
-
from logilab.common.decorators import cached
from logilab.mtconverter import xml_escape
from cubicweb import typed_eid
from cubicweb.view import EntityView
from cubicweb.selectors import (one_line_rset, non_final_entity,
- match_search_state, match_form_params)
-from cubicweb.common.uilib import cut
-from cubicweb.web.views import linksearch_select_url
-from cubicweb.web.views.editforms import relation_id
-from cubicweb.web.views.baseviews import FinalView
+ match_search_state)
+from cubicweb.web.views import baseviews, linksearch_select_url
class SearchForAssociationView(EntityView):
"""view called by the edition view when the user asks to search for
something to link to the edited eid
"""
- id = 'search-associate'
+ __regid__ = 'search-associate'
__select__ = (one_line_rset() & match_search_state('linksearch')
& non_final_entity())
@@ -35,7 +30,7 @@
def cell_call(self, row, col):
rset, vid, divid, paginate = self.filter_box_context_info()
- self.rset = rset
+ self.cw_rset = rset
self.w(u'<div id="%s">' % divid)
self.paginate()
self.wview(vid, rset, 'noresult')
@@ -43,163 +38,57 @@
@cached
def filter_box_context_info(self):
- entity = self.entity(0, 0)
- role, eid, rtype, etype = self.req.search_state[1]
+ entity = self.cw_rset.get_entity(0, 0)
+ role, eid, rtype, etype = self._cw.search_state[1]
assert entity.eid == typed_eid(eid)
# the default behaviour is to fetch all unrelated entities and display
# them. Use fetch_order and not fetch_unrelated_order as sort method
# since the latter is mainly there to select relevant items in the combo
# box, it doesn't give interesting result in this context
rql, args = entity.unrelated_rql(rtype, etype, role,
- ordermethod='fetch_order',
- vocabconstraints=False)
- rset = self.req.execute(rql, args, tuple(args))
+ ordermethod='fetch_order',
+ vocabconstraints=False)
+ rset = self._cw.execute(rql, args, tuple(args))
return rset, 'list', "search-associate-content", True
class OutOfContextSearch(EntityView):
- id = 'outofcontext-search'
+ __regid__ = 'outofcontext-search'
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
erset = entity.as_rset()
- if self.req.match_search_state(erset):
+ if self._cw.match_search_state(erset):
self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
- xml_escape(linksearch_select_url(self.req, erset)),
- self.req._('select this entity'),
+ xml_escape(linksearch_select_url(self._cw, erset)),
+ self._cw._('select this entity'),
xml_escape(entity.view('textoutofcontext')),
xml_escape(entity.absolute_url(vid='primary')),
- self.req._('view detail for this entity')))
+ self._cw._('view detail for this entity')))
else:
entity.view('outofcontext', w=self.w)
-class UnrelatedDivs(EntityView):
- id = 'unrelateddivs'
- __select__ = match_form_params('relation')
-
- def cell_call(self, row, col):
- entity = self.entity(row, col)
- relname, target = self.req.form.get('relation').rsplit('_', 1)
- rschema = self.schema.rschema(relname)
- hidden = 'hidden' in self.req.form
- is_cell = 'is_cell' in self.req.form
- self.w(self.build_unrelated_select_div(entity, rschema, target,
- is_cell=is_cell, hidden=hidden))
-
- def build_unrelated_select_div(self, entity, rschema, target,
- is_cell=False, hidden=True):
- options = []
- divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
- selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
- if rschema.symetric or target == 'subject':
- targettypes = rschema.objects(entity.e_schema)
- etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
- else:
- targettypes = rschema.subjects(entity.e_schema)
- etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
- etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
- options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
- options += self._get_select_options(entity, rschema, target)
- options += self._get_search_options(entity, rschema, target, targettypes)
- if 'Basket' in self.schema: # XXX
- options += self._get_basket_options(entity, rschema, target, targettypes)
- relname, target = self.req.form.get('relation').rsplit('_', 1)
- return u"""\
-<div class="%s" id="%s">
- <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
- %s
- </select>
-</div>
-""" % (hidden and 'hidden' or '', divid, selectid,
- xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
- '\n'.join(options))
-
- def _get_select_options(self, entity, rschema, target):
- """add options to search among all entities of each possible type"""
- options = []
- eid = entity.eid
- pending_inserts = self.req.get_pending_inserts(eid)
- rtype = rschema.type
- form = self.vreg['forms'].select('edition', self.req, entity=entity)
- field = form.field_by_name(rschema, target, entity.e_schema)
- limit = self.req.property_value('navigation.combobox-limit')
- for eview, reid in form.form_field_vocabulary(field, limit):
- if reid is None:
- options.append('<option class="separator">-- %s --</option>'
- % xml_escape(eview))
- else:
- optionid = relation_id(eid, rtype, target, reid)
- if optionid not in pending_inserts:
- # prefix option's id with letters to make valid XHTML wise
- options.append('<option id="id%s" value="%s">%s</option>' %
- (optionid, reid, xml_escape(eview)))
- return options
-
- def _get_search_options(self, entity, rschema, target, targettypes):
- """add options to search among all entities of each possible type"""
- options = []
- _ = self.req._
- for eschema in targettypes:
- mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
- url = self.build_url(entity.rest_path(), vid='search-associate',
- __mode=mode)
- options.append((eschema.display_name(self.req),
- '<option value="%s">%s %s</option>' % (
- xml_escape(url), _('Search for'), eschema.display_name(self.req))))
- return [o for l, o in sorted(options)]
-
- def _get_basket_options(self, entity, rschema, target, targettypes):
- options = []
- rtype = rschema.type
- _ = self.req._
- for basketeid, basketname in self._get_basket_links(self.req.user.eid,
- target, targettypes):
- optionid = relation_id(entity.eid, rtype, target, basketeid)
- options.append('<option id="%s" value="%s">%s %s</option>' % (
- optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
- return options
-
- def _get_basket_links(self, ueid, target, targettypes):
- targettypes = set(targettypes)
- for basketeid, basketname, elements in self._get_basket_info(ueid):
- baskettypes = elements.column_types(0)
- # if every elements in the basket can be attached to the
- # edited entity
- if baskettypes & targettypes:
- yield basketeid, basketname
-
- def _get_basket_info(self, ueid):
- basketref = []
- basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
- basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
- for result in basketresultset:
- basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
- rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
- basketref.append((result[0], result[1], rset))
- return basketref
-
-
class ComboboxView(EntityView):
"""the view used in combobox (unrelated entities)
THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
"""
- id = 'combobox'
+ __regid__ = 'combobox'
title = None
def cell_call(self, row, col):
"""the combo-box view for an entity: same as text out of context view
by default
"""
- self.wview('textoutofcontext', self.rset, row=row, col=col)
+ self.wview('textoutofcontext', self.cw_rset, row=row, col=col)
-class EditableFinalView(FinalView):
+class EditableFinalView(baseviews.FinalView):
"""same as FinalView but enables inplace-edition when possible"""
- id = 'editable-final'
+ __regid__ = 'editable-final'
def cell_call(self, row, col, props=None):
- entity, rtype = self.rset.related_entity(row, col)
+ entity, rtype = self.cw_rset.related_entity(row, col)
if entity is not None:
self.w(entity.view('reledit', rtype=rtype))
else:
--- a/web/views/emailaddress.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/emailaddress.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,7 +11,7 @@
from cubicweb.schema import display_name
from cubicweb.selectors import implements
-from cubicweb.common import Unauthorized
+from cubicweb import Unauthorized
from cubicweb.web.views import baseviews, primary
class EmailAddressPrimaryView(primary.PrimaryView):
@@ -33,18 +33,18 @@
persons = []
if persons:
emailof = persons[0]
- self.field(display_name(self.req, 'primary_email', 'object'), emailof.view('oneline'))
+ self.field(display_name(self._cw, 'primary_email', 'object'), emailof.view('oneline'))
pemaileid = emailof.eid
else:
pemaileid = None
try:
- emailof = 'use_email' in self.schema and entity.reverse_use_email or ()
+ emailof = 'use_email' in self._cw.vreg.schema and entity.reverse_use_email or ()
emailof = [e for e in emailof if not e.eid == pemaileid]
except Unauthorized:
emailof = []
if emailof:
emailofstr = ', '.join(e.view('oneline') for e in emailof)
- self.field(display_name(self.req, 'use_email', 'object'), emailofstr)
+ self.field(display_name(self._cw, 'use_email', 'object'), emailofstr)
def render_entity_relations(self, entity):
for i, email in enumerate(entity.related_emails(self.skipeids)):
@@ -55,7 +55,7 @@
class EmailAddressShortPrimaryView(EmailAddressPrimaryView):
__select__ = implements('EmailAddress')
- id = 'shortprimary'
+ __regid__ = 'shortprimary'
title = None # hidden view
def render_entity_attributes(self, entity):
@@ -68,7 +68,7 @@
__select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
if entity.reverse_primary_email:
self.w(u'<b>')
if entity.alias:
@@ -85,11 +85,11 @@
"""A one line view that builds a user clickable URL for an email with
'mailto:'"""
- id = 'mailto'
+ __regid__ = 'mailto'
__select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
if entity.reverse_primary_email:
self.w(u'<b>')
if entity.alias:
@@ -108,8 +108,15 @@
self.w(u'</b>')
+class EmailAddressInContextView(baseviews.InContextView):
+ __select__ = implements('EmailAddress')
+
+ def cell_call(self, row, col, **kwargs):
+ self.wview('mailto', self.cw_rset, row=row, col=col, **kwargs)
+
+
class EmailAddressTextView(baseviews.TextView):
__select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
- self.w(self.rset.get_entity(row, col).display_address())
+ self.w(self.cw_rset.get_entity(row, col).display_address())
--- a/web/views/embedding.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/embedding.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,15 +12,15 @@
import re
from urlparse import urljoin
from urllib2 import urlopen, Request, HTTPError
+from urllib import quote as urlquote # XXX should use view.url_quote method
from logilab.mtconverter import guess_encoding
-from cubicweb import urlquote # XXX should use view.url_quote method
from cubicweb.selectors import (one_line_rset, score_entity,
match_search_state, implements)
from cubicweb.interfaces import IEmbedable
from cubicweb.view import NOINDEX, NOFOLLOW
-from cubicweb.common.uilib import soup2xhtml
+from cubicweb.uilib import soup2xhtml
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
from cubicweb.web.views import basetemplates
@@ -29,13 +29,13 @@
class ExternalTemplate(basetemplates.TheMainTemplate):
"""template embeding an external web pages into CubicWeb web interface
"""
- id = 'external'
+ __regid__ = 'external'
def call(self, body):
# XXX fallback to HTML 4 mode when embeding ?
self.set_request_content_type()
- self.req.search_state = ('normal',)
- self.template_header(self.content_type, None, self.req._('external page'),
+ self._cw.search_state = ('normal',)
+ self.template_header(self.content_type, None, self._cw._('external page'),
[NOINDEX, NOFOLLOW])
self.content_header()
self.w(body)
@@ -44,22 +44,22 @@
class EmbedController(Controller):
- id = 'embed'
+ __regid__ = 'embed'
template = 'external'
def publish(self, rset=None):
- req = self.req
+ req = self._cw
if 'custom_css' in req.form:
req.add_css(req.form['custom_css'])
embedded_url = req.form['url']
- allowed = self.config['embed-allowed']
+ allowed = self._cw.vreg.config['embed-allowed']
_ = req._
if allowed is None or not allowed.match(embedded_url):
body = '<h2>%s</h2><h3>%s</h3>' % (
_('error while embedding page'),
_('embedding this url is forbidden'))
else:
- prefix = req.build_url(self.id, url='')
+ prefix = req.build_url(self.__regid__, url='')
authorization = req.get_header('Authorization')
if authorization:
headers = {'Authorization' : authorization}
@@ -68,13 +68,13 @@
try:
body = embed_external_page(embedded_url, prefix,
headers, req.form.get('custom_css'))
- body = soup2xhtml(body, self.req.encoding)
+ body = soup2xhtml(body, self._cw.encoding)
except HTTPError, err:
body = '<h2>%s</h2><h3>%s</h3>' % (
_('error while embedding page'), err)
self.process_rql(req.form.get('rql'))
- return self.vreg['views'].main_template(req, self.template,
- rset=self.rset, body=body)
+ return self._cw.vreg['views'].main_template(req, self.template,
+ rset=self.cw_rset, body=body)
def entity_has_embedable_url(entity):
@@ -82,7 +82,7 @@
url = entity.embeded_url()
if not url or not url.strip():
return 0
- allowed = entity.config['embed-allowed']
+ allowed = entity._cw.vreg.config['embed-allowed']
if allowed is None or not allowed.match(url):
return 0
return 1
@@ -92,20 +92,19 @@
"""display an 'embed' link on entity implementing `embeded_url` method
if the returned url match embeding configuration
"""
- id = 'embed'
+ __regid__ = 'embed'
__select__ = (one_line_rset() & match_search_state('normal')
& implements(IEmbedable)
& score_entity(entity_has_embedable_url))
title = _('embed')
- controller = 'embed'
def url(self, row=0):
- entity = self.rset.get_entity(row, 0)
- url = urljoin(self.req.base_url(), entity.embeded_url())
- if self.req.form.has_key('rql'):
- return self.build_url(url=url, rql=self.req.form['rql'])
- return self.build_url(url=url)
+ entity = self.cw_rset.get_entity(row, 0)
+ url = urljoin(self._cw.base_url(), entity.embeded_url())
+ if self._cw.form.has_key('rql'):
+ return self._cw.build_url('embed', url=url, rql=self._cw.form['rql'])
+ return self._cw.build_url('embed', url=url)
--- a/web/views/error.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/error.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,17 +11,17 @@
from cubicweb.view import StartupView
class FourOhFour(StartupView):
- id = '404'
+ __regid__ = '404'
def call(self):
- _ = self.req._
+ _ = self._cw._
self.w(u"<h1>%s</h1>" % _('this resource does not exist'))
class ErrorOccured(StartupView):
- id = '500'
+ __regid__ = '500'
def call(self):
- _ = self.req._
+ _ = self._cw._
self.w(u"<h1>%s</h1>" %
_('an error occured, the request cannot be fulfilled'))
--- a/web/views/facets.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/facets.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,7 +12,7 @@
from logilab.mtconverter import xml_escape
from cubicweb.appobject import objectify_selector
-from cubicweb.selectors import (non_final_entity, two_lines_rset,
+from cubicweb.selectors import (non_final_entity, multi_lines_rset,
match_context_prop, yes, relation_possible)
from cubicweb.web.box import BoxTemplate
from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
@@ -29,8 +29,8 @@
class FilterBox(BoxTemplate):
"""filter results of a query"""
- id = 'filter_box'
- __select__ = (((non_final_entity() & two_lines_rset())
+ __regid__ = 'filter_box'
+ __select__ = (((non_final_entity() & multi_lines_rset())
| contextview_selector()
) & match_context_prop())
context = 'left'
@@ -55,13 +55,13 @@
if context:
rset, vid, divid, paginate = context
else:
- rset = self.rset
+ rset = self.cw_rset
vid, divid = None, 'pageContent'
paginate = view and view.paginable
return rset, vid, divid, paginate
def call(self, view=None):
- req = self.req
+ req = self._cw
req.add_js( self.needs_js )
req.add_css( self.needs_css)
if self.roundcorners:
@@ -77,7 +77,7 @@
mainvar, baserql = prepare_facets_rqlst(rqlst, rset.args)
widgets = []
for facet in self.get_facets(rset, mainvar):
- if facet.propval('visible'):
+ if facet.cw_propval('visible'):
wdg = facet.get_widget()
if wdg is not None:
widgets.append(wdg)
@@ -89,7 +89,7 @@
w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">' % (
divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()]))))
w(u'<fieldset>')
- hiddens = {'facets': ','.join(wdg.facet.id for wdg in widgets),
+ hiddens = {'facets': ','.join(wdg.facet.__regid__ for wdg in widgets),
'baserql': baserql}
for param in ('subvid', 'vtitle'):
if param in req.form:
@@ -102,44 +102,44 @@
rqlst.recover()
def display_bookmark_link(self, rset):
- eschema = self.schema.eschema('Bookmark')
- if eschema.has_perm(self.req, 'add'):
+ eschema = self._cw.vreg.schema.eschema('Bookmark')
+ if eschema.has_perm(self._cw, 'add'):
bk_path = 'view?rql=%s' % rset.printable_rql()
- bk_title = self.req._('my custom search')
- linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
- bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
- bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
+ bk_title = self._cw._('my custom search')
+ linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid
+ bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
+ bk_base_url = self._cw.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
xml_escape(bk_base_url),
xml_escape(bk_add_url),
- self.req._('bookmark this search'))
+ self._cw._('bookmark this search'))
self.w(self.bk_linkbox_template % bk_link)
def get_facets(self, rset, mainvar):
- return self.vreg['facets'].possible_vobjects(self.req, rset=rset,
- context='facetbox',
- filtered_variable=mainvar)
+ return self._cw.vreg['facets'].poss_visible_objects(self._cw, rset=rset,
+ context='facetbox',
+ filtered_variable=mainvar)
# facets ######################################################################
class CreatedByFacet(RelationFacet):
- id = 'created_by-facet'
+ __regid__ = 'created_by-facet'
rtype = 'created_by'
target_attr = 'login'
class InGroupFacet(RelationFacet):
- id = 'in_group-facet'
+ __regid__ = 'in_group-facet'
rtype = 'in_group'
target_attr = 'name'
class InStateFacet(RelationFacet):
- id = 'in_state-facet'
+ __regid__ = 'in_state-facet'
rtype = 'in_state'
target_attr = 'name'
# inherit from RelationFacet to benefit from its possible_values implementation
class ETypeFacet(RelationFacet):
- id = 'etype-facet'
+ __regid__ = 'etype-facet'
__select__ = yes()
order = 1
rtype = 'is'
@@ -147,17 +147,17 @@
@property
def title(self):
- return self.req._('entity type')
+ return self._cw._('entity type')
def vocabulary(self):
"""return vocabulary for this facet, eg a list of 2-uple (label, value)
"""
- etypes = self.rset.column_types(0)
- return sorted((self.req._(etype), etype) for etype in etypes)
+ etypes = self.cw_rset.column_types(0)
+ return sorted((self._cw._(etype), etype) for etype in etypes)
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
- value = self.req.form.get(self.id)
+ value = self._cw.form.get(self.__regid__)
if not value:
return
self.rqlst.add_type_restriction(self.filtered_variable, value)
@@ -180,13 +180,13 @@
class HasTextFacet(AbstractFacet):
__select__ = relation_possible('has_text', 'subject') & match_context_prop()
- id = 'has_text-facet'
+ __regid__ = 'has_text-facet'
rtype = 'has_text'
role = 'subject'
order = 0
@property
def title(self):
- return self.req._('has_text')
+ return self._cw._('has_text')
def get_widget(self):
"""return the widget instance to use to display this facet
@@ -198,7 +198,7 @@
def add_rql_restrictions(self):
"""add restriction for this facet into the rql syntax tree"""
- value = self.req.form.get(self.id)
+ value = self._cw.form.get(self.__regid__)
if not value:
return
self.rqlst.add_constant_restriction(self.filtered_variable, 'has_text', value, 'String')
--- a/web/views/formrenderers.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/formrenderers.py Mon Feb 08 13:35:07 2010 +0100
@@ -1,31 +1,38 @@
"""form renderers, responsible to layout a form to html
:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
+
from logilab.common import dictattr
from logilab.mtconverter import xml_escape
from simplejson import dumps
-from cubicweb.common import tags
+from cubicweb import tags
from cubicweb.appobject import AppObject
-from cubicweb.selectors import entity_implements, yes
-from cubicweb.web import eid_param
-from cubicweb.web import formwidgets as fwdgs
-from cubicweb.web.widgets import checkbox
-from cubicweb.web.formfields import HiddenInitialValueField
+from cubicweb.selectors import implements, yes
+from cubicweb.web import eid_param, formwidgets as fwdgs
+
+
+def checkbox(name, value, attrs='', checked=None):
+ if checked is None:
+ checked = value
+ checked = checked and 'checked="checked"' or ''
+ return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
+ name, value, checked, attrs)
def field_label(form, field):
# XXX with 3.6 we can now properly rely on 'if field.role is not None' and
# stop having a tuple for label
if isinstance(field.label, tuple): # i.e. needs contextual translation
- return form.req.pgettext(*field.label)
- return form.req._(field.label)
+ return form._cw.pgettext(*field.label)
+ return form._cw._(field.label)
@@ -42,13 +49,12 @@
+---------+
"""
__registry__ = 'formrenderers'
- id = 'default'
+ __regid__ = 'default'
- _options = ('display_fields', 'display_label', 'display_help',
+ _options = ('display_label', 'display_help',
'display_progress_div', 'table_class', 'button_bar_class',
# add entity since it may be given to select the renderer
'entity')
- display_fields = None # None -> all fields
display_label = True
display_help = True
display_progress_div = True
@@ -56,7 +62,7 @@
button_bar_class = u'formButtonBar'
def __init__(self, req=None, rset=None, row=None, col=None, **kwargs):
- super(FormRenderer, self).__init__(req, rset, row, col)
+ super(FormRenderer, self).__init__(req, rset=rset, row=row, col=col)
if self._set_options(kwargs):
raise ValueError('unconsumed arguments %s' % kwargs)
@@ -77,10 +83,10 @@
w = data.append
w(self.open_form(form, values))
if self.display_progress_div:
- w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+ w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
w(u'<fieldset>')
w(tags.input(type=u'hidden', name=u'__form_id',
- value=values.get('formvid', form.id)))
+ value=values.get('formvid', form.__regid__)))
if form.redirect_path:
w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
self.render_fields(w, form, values)
@@ -96,7 +102,7 @@
if field.label is None:
return u''
label = field_label(form, field)
- attrs = {'for': form.context[field]['id']}
+ attrs = {'for': field.dom_id(form)}
if field.required:
attrs['class'] = 'required'
return tags.label(label, **attrs)
@@ -107,11 +113,11 @@
if callable(descr):
descr = descr(form)
if descr:
- help.append('<div class="helper">%s</div>' % self.req._(descr))
- example = field.example_format(self.req)
+ help.append('<div class="helper">%s</div>' % self._cw._(descr))
+ example = field.example_format(self._cw)
if example:
help.append('<div class="helper">(%s: %s)</div>'
- % (self.req._('sample format'), example))
+ % (self._cw._('sample format'), example))
return u' '.join(help)
# specific methods (mostly to ease overriding) #############################
@@ -121,14 +127,12 @@
This method should be called once inlined field errors has been consumed
"""
- req = self.req
+ req = self._cw
errex = form.form_valerror
# get extra errors
if errex is not None:
errormsg = req._('please correct the following errors:')
- displayed = form.form_displayed_errors
- errors = sorted((field, err) for field, err in errex.errors.items()
- if not field in displayed)
+ errors = form.remaining_errors()
if errors:
if len(errors) > 1:
templstr = '<li>%s</li>\n'
@@ -145,12 +149,12 @@
return u''
def open_form(self, form, values):
- if form.form_needs_multipart:
+ if form.needs_multipart:
enctype = 'multipart/form-data'
else:
enctype = 'application/x-www-form-urlencoded'
if form.action is None:
- action = self.req.build_url('edit')
+ action = self._cw.build_url('edit')
else:
action = form.action
tag = ('<form action="%s" method="post" enctype="%s"' % (
@@ -167,14 +171,6 @@
tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
return tag + '>'
- def display_field(self, form, field):
- if isinstance(field, HiddenInitialValueField):
- field = field.visible_field
- return (self.display_fields is None
- or field.name in form.internal_fields
- or (field.name, field.role) in self.display_fields
- or (field.name, field.role) in form.internal_fields)
-
def render_fields(self, w, form, values):
fields = self._render_hidden_fields(w, form)
if fields:
@@ -189,9 +185,7 @@
def _render_hidden_fields(self, w, form):
fields = form.fields[:]
for field in form.fields:
- if not self.display_field(form, field):
- fields.remove(field)
- elif not field.is_visible():
+ if not field.is_visible():
w(field.render(form, self))
fields.remove(field)
return fields
@@ -212,18 +206,19 @@
continue
w(u'<fieldset class="%s">' % (fieldset or u'default'))
if fieldset:
- w(u'<legend>%s</legend>' % self.req._(fieldset))
+ w(u'<legend>%s</legend>' % self._cw._(fieldset))
w(u'<table class="%s">' % self.table_class)
for field in fields:
w(u'<tr class="%s_%s_row">' % (field.name, field.role))
- if self.display_label:
+ if self.display_label and field.label is not None:
w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- error = form.form_field_error(field)
+ w('<td')
+ if field.label is None:
+ w(' colspan="2"')
+ error = form.field_error(field)
if error:
- w(u'<td class="error">')
- w(error)
- else:
- w(u'<td>')
+ w(u' class="error"')
+ w(u'>')
w(field.render(form, self))
if self.display_help:
w(self.render_help(form, field))
@@ -240,29 +235,18 @@
w(u'<td>%s</td>\n' % button.render(form))
w(u'</tr></table>')
+ def render_error(self, w, err):
+ """return validation error for widget's field, if any"""
+ w(u'<span class="error">%s</span>' % err)
+
+
class BaseFormRenderer(FormRenderer):
"""use form_renderer_id = 'base' if you want base FormRenderer layout even
when selected for an entity
"""
- id = 'base'
-
-
-class EntityBaseFormRenderer(BaseFormRenderer):
- """use form_renderer_id = 'base' if you want base FormRenderer layout even
- when selected for an entity
- """
- __select__ = entity_implements('Any')
+ __regid__ = 'base'
- def display_field(self, form, field):
- if not super(EntityBaseFormRenderer, self).display_field(form, field):
- if isinstance(field, HiddenInitialValueField):
- field = field.visible_field
- ismeta = form.edited_entity.e_schema.is_metadata(field.name)
- return ismeta is not None and (
- ismeta[0] in self.display_fields or
- (ismeta[0], 'subject') in self.display_fields)
- return True
class HTableFormRenderer(FormRenderer):
@@ -274,7 +258,7 @@
| field1 input | field2 input | buttons
+--------------+--------------+---------+
"""
- id = 'htable'
+ __regid__ = 'htable'
display_help = False
def _render_fields(self, fields, w, form):
@@ -290,10 +274,10 @@
w(u'</tr>')
w(u'<tr>')
for field in fields:
- error = form.form_field_error(field)
+ error = form.field_error(field)
if error:
w(u'<td class="error">')
- w(error)
+ self.render_error(w, error)
else:
w(u'<td>')
w(field.render(form, self))
@@ -311,7 +295,7 @@
class EntityCompositeFormRenderer(FormRenderer):
"""specific renderer for multiple entities edition form (muledit)"""
- id = 'composite'
+ __regid__ = 'composite'
_main_display_fields = None
@@ -319,14 +303,13 @@
if form.parent_form is None:
w(u'<table class="listing">')
subfields = [field for field in form.forms[0].fields
- if self.display_field(form, field)
- and field.is_visible()]
+ if field.is_visible()]
if subfields:
# main form, display table headers
w(u'<tr class="header">')
w(u'<th align="left">%s</th>' %
tags.input(type='checkbox',
- title=self.req._('toggle check boxes'),
+ title=self._cw._('toggle check boxes'),
onclick="setCheckboxesState('eid', this.checked)"))
for field in subfields:
w(u'<th>%s</th>' % field_label(form, field))
@@ -345,15 +328,15 @@
qeid = eid_param('eid', entity.eid)
cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % \
xml_escape(dumps(entity.eid))
- w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
+ w(u'<tr class="%s">' % (entity.cw_row % 2 and u'even' or u'odd'))
# XXX turn this into a widget used on the eid field
w(u'<td>%s</td>' % checkbox('eid', entity.eid,
checked=qeid in values))
for field in fields:
- error = form.form_field_error(field)
+ error = form.field_error(field)
if error:
w(u'<td class="error">')
- w(error)
+ self.render_error(w, error)
else:
w(u'<td>')
if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox,
@@ -369,14 +352,13 @@
self._main_display_fields = fields
-class EntityFormRenderer(EntityBaseFormRenderer):
+class EntityFormRenderer(BaseFormRenderer):
"""specific renderer for entity edition form (edition)"""
- id = 'default'
+ __regid__ = 'default'
# needs some additional points in some case (XXX explain cases)
- __select__ = EntityBaseFormRenderer.__select__ & yes()
+ __select__ = implements('Any') & yes()
- _options = FormRenderer._options + ('display_relations_form', 'main_form_title')
- display_relations_form = True
+ _options = FormRenderer._options + ('main_form_title',)
main_form_title = _('main informations')
def render(self, form, values):
@@ -387,16 +369,10 @@
attrs_fs_label = ''
if self.main_form_title:
attrs_fs_label += ('<div class="iformTitle"><span>%s</span></div>'
- % self.req._(self.main_form_title))
+ % self._cw._(self.main_form_title))
attrs_fs_label += '<div class="formBody">'
return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values)
- def render_fields(self, w, form, values):
- super(EntityFormRenderer, self).render_fields(w, form, values)
- self.inline_entities_form(w, form)
- if form.edited_entity.has_eid() and self.display_relations_form:
- self.relations_form(w, form)
-
def _render_fields(self, fields, w, form):
if not form.edited_entity.has_eid() or form.edited_entity.has_perm('update'):
super(EntityFormRenderer, self)._render_fields(fields, w, form)
@@ -416,96 +392,12 @@
else:
super(EntityFormRenderer, self).render_buttons(w, form)
- def relations_form(self, w, form):
- srels_by_cat = form.srelations_by_category('generic', 'add', strict=True)
- if not srels_by_cat:
- return u''
- req = self.req
- _ = req._
- __ = _
- label = u'%s :' % __('This %s' % form.edited_entity.e_schema).capitalize()
- eid = form.edited_entity.eid
- w(u'<fieldset class="subentity">')
- w(u'<legend class="iformTitle">%s</legend>' % label)
- w(u'<table id="relatedEntities">')
- for rschema, target, related in form.relations_table():
- # already linked entities
- if related:
- w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
- w(u'<td>')
- w(u'<ul>')
- for viewparams in related:
- w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
- % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
- if not form.force_display and form.maxrelitems < len(related):
- link = (u'<span class="invisible">'
- '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
- '</span>' % self.req._('view all'))
- w(u'<li class="invisible">%s</li>' % link)
- w(u'</ul>')
- w(u'</td>')
- w(u'</tr>')
- pendings = list(form.restore_pending_inserts())
- if not pendings:
- w(u'<tr><th> </th><td> </td></tr>')
- else:
- for row in pendings:
- # soon to be linked to entities
- w(u'<tr id="tr%s">' % row[1])
- w(u'<th>%s</th>' % row[3])
- w(u'<td>')
- w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
- (_('cancel this insert'), row[2]))
- w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], xml_escape(row[5])))
- w(u'</td>')
- w(u'</tr>')
- w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
- w(u'<th class="labelCol">')
- w(u'<select id="relationSelector_%s" tabindex="%s" '
- 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
- w(u'<option value="">%s</option>' % _('select a relation'))
- for i18nrtype, rschema, target in srels_by_cat:
- # more entities to link to
- w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
- w(u'</select>')
- w(u'</th>')
- w(u'<td id="unrelatedDivs_%s"></td>' % eid)
- w(u'</tr>')
- w(u'</table>')
- w(u'</fieldset>')
-
- # NOTE: should_* and display_* method extracted and moved to the form to
- # ease overriding
-
- def inline_entities_form(self, w, form):
- """create a form to edit entity's inlined relations"""
- if not hasattr(form, 'inlined_form_views'):
- return
- keysinorder = []
- formviews = form.inlined_form_views()
- for formview in formviews:
- if not (formview.rtype, formview.role) in keysinorder:
- keysinorder.append( (formview.rtype, formview.role) )
- for key in keysinorder:
- self.inline_relation_form(w, form, [fv for fv in formviews
- if (fv.rtype, fv.role) == key])
-
- def inline_relation_form(self, w, form, formviews):
- i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
- formviews[0].rtype, formviews[0].role)
- w(u'<div id="inline%sslot">' % formviews[0].rtype)
- for formview in formviews:
- w(formview.render(i18nctx=i18nctx, row=formview.row, col=formview.col))
- w(u'</div>')
-
class EntityInlinedFormRenderer(EntityFormRenderer):
"""specific renderer for entity inlined edition form
(inline-[creation|edition])
"""
- id = 'inline'
+ __regid__ = 'inline'
def render(self, form, values):
form.add_media()
@@ -517,11 +409,10 @@
w(u'<div id="div-%(divid)s">' % values)
else:
w(u'<div id="notice-%s" class="notice">%s</div>' % (
- values['divid'], self.req._('click on the box to cancel the deletion')))
+ values['divid'], self._cw._('click on the box to cancel the deletion')))
w(u'<div class="iformBody">')
eschema = form.edited_entity.e_schema
- ctx = values.pop('i18nctx')
- values['removemsg'] = self.req.pgettext(ctx, 'remove this %s' % eschema)
+ values['removemsg'] = self._cw._('remove-inlined-entity-form')
w(u'<div class="iformTitle"><span>%(title)s</span> '
'#<span class="icounter">%(counter)s</span> '
'[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
@@ -541,6 +432,5 @@
if fields:
self._render_fields(fields, w, form)
self.render_child_forms(w, form, values)
- self.inline_entities_form(w, form)
w(u'</fieldset>')
--- a/web/views/forms.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/forms.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,14 +9,16 @@
from warnings import warn
+from logilab.common.decorators import iclassmethod
from logilab.common.compat import any
from logilab.common.deprecation import deprecated
+from cubicweb import typed_eid
from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
-from cubicweb.web import form, formwidgets as fwdgs
+from cubicweb.web import uicfg, form, formwidgets as fwdgs
from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.formfields import HiddenInitialValueField, StringField
+from cubicweb.web.formfields import StringField, relvoc_unrelated, guess_field
class FieldsForm(form.Form):
@@ -54,7 +56,7 @@
* `fieldsets_in_order`: fieldset name sequence, to control order
"""
- id = 'base'
+ __regid__ = 'base'
internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
@@ -76,20 +78,20 @@
def __init__(self, req, rset=None, row=None, col=None,
submitmsg=None, mainform=True,
**kwargs):
- super(FieldsForm, self).__init__(req, rset, row=row, col=col)
+ super(FieldsForm, self).__init__(req, rset=rset, row=row, col=col)
self.fields = list(self.__class__._fields_)
for key, val in kwargs.items():
if key in NAV_FORM_PARAMETERS:
- self.form_add_hidden(key, val)
+ self.add_hidden(key, val)
elif hasattr(self.__class__, key) and not key[0] == '_':
setattr(self, key, val)
else:
- self.extra_kwargs[key] = val
+ self.cw_extra_kwargs[key] = val
# skip other parameters, usually given for selection
# (else write a custom class to handle them)
if mainform:
- self.form_add_hidden('__errorurl', self.session_key())
- self.form_add_hidden('__domid', self.domid)
+ self.add_hidden('__errorurl', self.session_key())
+ self.add_hidden('__domid', self.domid)
self.restore_previous_post(self.session_key())
# XXX why do we need two different variables (mainform and copy_nav_params ?)
@@ -98,22 +100,21 @@
if not param in kwargs:
value = req.form.get(param)
if value:
- self.form_add_hidden(param, value)
+ self.add_hidden(param, value)
if submitmsg is not None:
- self.form_add_hidden('__message', submitmsg)
- self.context = None
+ self.add_hidden('__message', submitmsg)
if 'domid' in kwargs:# session key changed
self.restore_previous_post(self.session_key())
@property
- def form_needs_multipart(self):
+ def needs_multipart(self):
"""true if the form needs enctype=multipart/form-data"""
return any(field.needs_multipart for field in self.fields)
- def form_add_hidden(self, name, value=None, **kwargs):
+ def add_hidden(self, name, value=None, **kwargs):
"""add an hidden field to the form"""
kwargs.setdefault('widget', fwdgs.HiddenInput)
- field = StringField(name=name, initial=value, **kwargs)
+ field = StringField(name=name, value=value, **kwargs)
if 'id' in kwargs:
# by default, hidden input don't set id attribute. If one is
# explicitly specified, ensure it will be set
@@ -124,131 +125,49 @@
def add_media(self):
"""adds media (CSS & JS) required by this widget"""
if self.needs_js:
- self.req.add_js(self.needs_js)
+ self._cw.add_js(self.needs_js)
if self.needs_css:
- self.req.add_css(self.needs_css)
+ self._cw.add_css(self.needs_css)
- def render(self, formvalues=None, rendervalues=None, renderer=None):
+ def render(self, formvalues=None, rendervalues=None, renderer=None, **kwargs):
"""render this form, using the renderer given in args or the default
FormRenderer()
"""
- self.build_context(formvalues or {})
+ if rendervalues is not None:
+ warn('[3.6] rendervalues argument is deprecated, all named arguments will be given instead',
+ DeprecationWarning, stacklevel=2)
+ kwargs = rendervalues
+ self.build_context(formvalues)
if renderer is None:
- renderer = self.form_default_renderer()
- return renderer.render(self, rendervalues or {})
+ renderer = self.default_renderer()
+ return renderer.render(self, kwargs)
- def form_default_renderer(self):
- return self.vreg['formrenderers'].select(self.form_renderer_id,
- self.req, rset=self.rset,
- row=self.row, col=self.col)
+ def default_renderer(self):
+ return self._cw.vreg['formrenderers'].select(
+ self.form_renderer_id, self._cw,
+ rset=self.cw_rset, row=self.cw_row, col=self.cw_col)
- def build_context(self, rendervalues=None):
+ formvalues = None
+ def build_context(self, formvalues=None):
"""build form context values (the .context attribute which is a
dictionary with field instance as key associated to a dictionary
containing field 'name' (qualified), 'id', 'value' (for display, always
a string).
-
- rendervalues is an optional dictionary containing extra form values
- given to render()
"""
- if self.context is not None:
+ if self.formvalues is not None:
return # already built
- self.context = context = {}
- # ensure rendervalues is a dict
- if rendervalues is None:
- rendervalues = {}
+ self.formvalues = formvalues or {}
# use a copy in case fields are modified while context is build (eg
# __linkto handling for instance)
for field in self.fields[:]:
for field in field.actual_fields(self):
field.form_init(self)
- value = self.form_field_display_value(field, rendervalues)
- context[field] = {'value': value,
- 'name': self.form_field_name(field),
- 'id': self.form_field_id(field),
- }
-
- def form_field_display_value(self, field, rendervalues, load_bytes=False):
- """return field's *string* value to use for display
-
- looks in
- 1. previously submitted form values if any (eg on validation error)
- 2. req.form
- 3. extra kw args given to render_form
- 4. field's typed value
-
- values found in 1. and 2. are expected te be already some 'display'
- value while those found in 3. and 4. are expected to be correctly typed.
- """
- value = self._req_display_value(field)
- if value is None:
- if field.name in rendervalues:
- value = rendervalues[field.name]
- elif field.name in self.extra_kwargs:
- value = self.extra_kwargs[field.name]
- else:
- value = self.form_field_value(field, load_bytes)
- if callable(value):
- value = value(self)
- if value != INTERNAL_FIELD_VALUE:
- value = field.format_value(self.req, value)
- return value
-
- def _req_display_value(self, field):
- qname = self.form_field_name(field)
- if qname in self.form_previous_values:
- return self.form_previous_values[qname]
- if qname in self.req.form:
- return self.req.form[qname]
- if field.name in self.req.form:
- return self.req.form[field.name]
- return None
- def form_field_value(self, field, load_bytes=False):
- """return field's *typed* value"""
- myattr = '%s_%s_default' % (field.role, field.name)
- if hasattr(self, myattr):
- return getattr(self, myattr)()
- value = field.initial
- if callable(value):
- value = value(self)
- return value
-
- def form_field_error(self, field):
- """return validation error for widget's field, if any"""
- if self._field_has_error(field):
- self.form_displayed_errors.add(field.name)
- return u'<span class="error">%s</span>' % self.form_valerror.errors[field.name]
- return u''
-
- def form_field_format(self, field):
- """return MIME type used for the given (text or bytes) field"""
- return self.req.property_value('ui.default-text-format')
+ @deprecated('[3.6] use .add_hidden(name, value, **kwargs)')
+ def form_add_hidden(self, name, value=None, **kwargs):
+ return self.add_hidden(name, value, **kwargs)
- def form_field_encoding(self, field):
- """return encoding used for the given (text) field"""
- return self.req.encoding
-
- def form_field_name(self, field):
- """return qualified name for the given field"""
- return field.name
-
- def form_field_id(self, field):
- """return dom id for the given field"""
- return field.id
-
- def form_field_vocabulary(self, field, limit=None):
- """return vocabulary for the given field. Should be overriden in
- specific forms using fields which requires some vocabulary
- """
- raise NotImplementedError
-
- def _field_has_error(self, field):
- """return true if the field has some error in given validation exception
- """
- return self.form_valerror and field.name in self.form_valerror.errors
-
- @deprecated('use .render(formvalues, rendervalues)')
+ @deprecated('[3.6] use .render(formvalues, **rendervalues)')
def form_render(self, **values):
"""render this form, using the renderer given in args or the default
FormRenderer()
@@ -256,41 +175,68 @@
self.build_context(values)
renderer = values.pop('renderer', None)
if renderer is None:
- renderer = self.form_default_renderer()
+ renderer = self.default_renderer()
return renderer.render(self, values)
+_AFF = uicfg.autoform_field
+_AFF_KWARGS = uicfg.autoform_field_kwargs
class EntityFieldsForm(FieldsForm):
- id = 'base'
+ __regid__ = 'base'
__select__ = (match_kwargs('entity')
| (one_line_rset() & non_final_entity()))
internal_fields = FieldsForm.internal_fields + ('__type', 'eid', '__maineid')
domid = 'entityForm'
- def __init__(self, req, rset=None, row=None, col=None, *args, **kwargs):
- # entity was either explicitly specified or we have a one line rset
- if 'entity' in kwargs:
+ @iclassmethod
+ def field_by_name(cls_or_self, name, role=None, eschema=None):
+ """return field with the given name and role. If field is not explicitly
+ defined for the form but `eclass` is specified, guess_field will be
+ called.
+ """
+ try:
+ return super(EntityFieldsForm, cls_or_self).field_by_name(name, role)
+ except form.FieldNotFound:
+ if eschema is None or role is None or not name in eschema.schema:
+ raise
+ rschema = eschema.schema.rschema(name)
+ # XXX use a sample target type. Document this.
+ tschemas = rschema.targets(eschema, role)
+ fieldcls = _AFF.etype_get(eschema, rschema, role, tschemas[0])
+ kwargs = _AFF_KWARGS.etype_get(eschema, rschema, role, tschemas[0])
+ if kwargs is None:
+ kwargs = {}
+ if fieldcls:
+ if not isinstance(fieldcls, type):
+ return fieldcls # already and instance
+ return fieldcls(name=name, role=role, eidparam=True, **kwargs)
+ field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
+ if field is None:
+ raise
+ return field
+
+ def __init__(self, _cw, rset=None, row=None, col=None, **kwargs):
+ try:
self.edited_entity = kwargs.pop('entity')
- else:
- self.edited_entity = rset.get_entity(row or 0, col or 0)
- self.edited_entity.complete()
+ except KeyError:
+ self.edited_entity = rset.complete_entity(row or 0, col or 0)
msg = kwargs.pop('submitmsg', None)
- super(EntityFieldsForm, self).__init__(req, rset, row, col, *args, **kwargs)
- self.form_add_hidden('__type', eidparam=True)
- self.form_add_hidden('eid')
+ super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs)
+ self.add_hidden('__type', self.edited_entity.__regid__, eidparam=True)
+ self.add_hidden('eid', self.edited_entity.eid)
if kwargs.get('mainform', True): # mainform default to true in parent
- self.form_add_hidden(u'__maineid', self.edited_entity.eid)
+ self.add_hidden(u'__maineid', self.edited_entity.eid)
# If we need to directly attach the new object to another one
- if self.req.list_form_param('__linkto'):
- for linkto in self.req.list_form_param('__linkto'):
- self.form_add_hidden('__linkto', linkto)
+ if self._cw.list_form_param('__linkto'):
+ for linkto in self._cw.list_form_param('__linkto'):
+ self.add_hidden('__linkto', linkto)
if msg:
- msg = '%s %s' % (msg, self.req._('and linked'))
+ msg = '%s %s' % (msg, self._cw._('and linked'))
else:
- msg = self.req._('entity linked')
+ msg = self._cw._('entity linked')
if msg:
- self.form_add_hidden('__message', msg)
+ self.add_hidden('__message', msg)
def session_key(self):
"""return the key that may be used to store / retreive data about a
@@ -300,240 +246,63 @@
return self.force_session_key
# XXX if this is a json request, suppose we should redirect to the
# entity primary view
- if self.req.json_request and self.edited_entity.has_eid():
+ if self._cw.json_request and self.edited_entity.has_eid():
return '%s#%s' % (self.edited_entity.absolute_url(), self.domid)
- return '%s#%s' % (self.req.url(), self.domid)
-
- def _field_has_error(self, field):
- """return true if the field has some error in given validation exception
- """
- return super(EntityFieldsForm, self)._field_has_error(field) \
- and self.form_valerror.eid == self.edited_entity.eid
-
- def _relation_vocabulary(self, rtype, targettype, role,
- limit=None, done=None):
- """return unrelated entities for a given relation and target entity type
- for use in vocabulary
- """
- if done is None:
- done = set()
- rset = self.edited_entity.unrelated(rtype, targettype, role, limit)
- res = []
- for entity in rset.entities():
- if entity.eid in done:
- continue
- done.add(entity.eid)
- res.append((entity.view('combobox'), entity.eid))
- return res
-
- def _req_display_value(self, field):
- value = super(EntityFieldsForm, self)._req_display_value(field)
- if value is None:
- value = self.edited_entity.linked_to(field.name, field.role)
- if value:
- searchedvalues = ['%s:%s:%s' % (field.name, eid, field.role)
- for eid in value]
- # remove associated __linkto hidden fields
- for field in self.root_form.fields_by_name('__linkto'):
- if field.initial in searchedvalues:
- self.root_form.remove_field(field)
- else:
- value = None
- return value
+ return '%s#%s' % (self._cw.url(), self.domid)
- def _form_field_default_value(self, field, load_bytes):
- defaultattr = 'default_%s' % field.name
- if hasattr(self.edited_entity, defaultattr):
- # XXX bw compat, default_<field name> on the entity
- warn('found %s on %s, should be set on a specific form'
- % (defaultattr, self.edited_entity.id), DeprecationWarning)
- value = getattr(self.edited_entity, defaultattr)
- if callable(value):
- value = value()
- else:
- value = super(EntityFieldsForm, self).form_field_value(field,
- load_bytes)
- return value
-
- def form_default_renderer(self):
- return self.vreg['formrenderers'].select(
- self.form_renderer_id, self.req, rset=self.rset, row=self.row,
- col=self.col, entity=self.edited_entity)
-
- def build_context(self, values=None):
- """overriden to add edit[s|o] hidden fields and to ensure schema fields
- have eidparam set to True
-
- edit[s|o] hidden fields are used to indicate the value for the
- associated field before the (potential) modification made when
- submitting the form.
- """
- if self.context is not None:
- return
- eschema = self.edited_entity.e_schema
- for field in self.fields[:]:
- for field in field.actual_fields(self):
- fieldname = field.name
- if fieldname != 'eid' and (
- (fieldname in eschema.subjrels or
- fieldname in eschema.objrels)):
- field.eidparam = True
- self.fields.append(HiddenInitialValueField(field))
- return super(EntityFieldsForm, self).build_context(values)
-
- def form_field_value(self, field, load_bytes=False):
- """return field's *typed* value
+ def build_context(self, formvalues=None):
+ if self.formvalues is not None:
+ return # already built
+ super(EntityFieldsForm, self).build_context(formvalues)
+ edited = set()
+ for field in self.fields:
+ if field.eidparam:
+ edited.add(field.role_name())
+ self.add_hidden('_cw_edited_fields', u','.join(edited),
+ eidparam=True)
- overriden to deal with
- * special eid / __type / edits- / edito- fields
- * lookup for values on edited entities
- """
- attr = field.name
- entity = self.edited_entity
- if attr == 'eid':
- return entity.eid
- if not field.eidparam:
- return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
- if attr.startswith('edits-') or attr.startswith('edito-'):
- # edit[s|o]- fieds must have the actual value stored on the entity
- assert hasattr(field, 'visible_field')
- vfield = field.visible_field
- assert vfield.eidparam
- if entity.has_eid():
- return self.form_field_value(vfield)
- return INTERNAL_FIELD_VALUE
- if attr == '__type':
- return entity.id
- if self.schema.rschema(attr).final:
- attrtype = entity.e_schema.destination(attr)
- if attrtype == 'Password':
- return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
- if attrtype == 'Bytes':
- if entity.has_eid():
- if load_bytes:
- return getattr(entity, attr)
- # XXX value should reflect if some file is already attached
- return True
- return False
- if entity.has_eid() or attr in entity:
- value = getattr(entity, attr)
- else:
- value = self._form_field_default_value(field, load_bytes)
- return value
- # non final relation field
- if entity.has_eid() or entity.relation_cached(attr, field.role):
- value = [r[0] for r in entity.related(attr, field.role)]
- else:
- value = self._form_field_default_value(field, load_bytes)
- return value
+ def default_renderer(self):
+ return self._cw.vreg['formrenderers'].select(
+ self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row,
+ col=self.cw_col, entity=self.edited_entity)
- def form_field_format(self, field):
- """return MIME type used for the given (text or bytes) field"""
- entity = self.edited_entity
- if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
- entity.has_eid() or '%s_format' % field.name in entity):
- return self.edited_entity.attr_metadata(field.name, 'format')
- return self.req.property_value('ui.default-text-format')
-
- def form_field_encoding(self, field):
- """return encoding used for the given (text) field"""
- entity = self.edited_entity
- if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
- entity.has_eid() or '%s_encoding' % field.name in entity):
- return self.edited_entity.attr_metadata(field.name, 'encoding')
- return super(EntityFieldsForm, self).form_field_encoding(field)
-
- def form_field_name(self, field):
- """return qualified name for the given field"""
- if field.eidparam:
- return eid_param(field.name, self.edited_entity.eid)
- return field.name
+ # controller side method (eg POST reception handling)
- def form_field_id(self, field):
- """return dom id for the given field"""
- if field.eidparam:
- return eid_param(field.id, self.edited_entity.eid)
- return field.id
-
- # XXX all this vocabulary handling should be on the field, no?
-
- def form_field_vocabulary(self, field, limit=None):
- """return vocabulary for the given field"""
- role, rtype = field.role, field.name
- method = '%s_%s_vocabulary' % (role, rtype)
+ def actual_eid(self, eid):
+ # should be either an int (existant entity) or a variable (to be
+ # created entity)
+ assert eid or eid == 0, repr(eid) # 0 is a valid eid
try:
- vocabfunc = getattr(self, method)
- except AttributeError:
+ return typed_eid(eid)
+ except ValueError:
try:
- # XXX bw compat, <role>_<rtype>_vocabulary on the entity
- vocabfunc = getattr(self.edited_entity, method)
- except AttributeError:
- vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
- else:
- warn('found %s on %s, should be set on a specific form'
- % (method, self.edited_entity.id), DeprecationWarning)
- # NOTE: it is the responsibility of `vocabfunc` to sort the result
- # (direclty through RQL or via a python sort). This is also
- # important because `vocabfunc` might return a list with
- # couples (label, None) which act as separators. In these
- # cases, it doesn't make sense to sort results afterwards.
- return vocabfunc(rtype, limit)
+ return self._cw.data['eidmap'][eid]
+ except KeyError:
+ self._cw.data['eidmap'][eid] = None
+ return None
- def subject_relation_vocabulary(self, rtype, limit=None):
- """defaut vocabulary method for the given relation, looking for
- relation's object entities (i.e. self is the subject)
- """
- entity = self.edited_entity
- if isinstance(rtype, basestring):
- rtype = entity.schema.rschema(rtype)
- done = None
- assert not rtype.final, rtype
- if entity.has_eid():
- done = set(e.eid for e in getattr(entity, str(rtype)))
- result = []
- rsetsize = None
- for objtype in rtype.objects(entity.e_schema):
- if limit is not None:
- rsetsize = limit - len(result)
- result += self._relation_vocabulary(rtype, objtype, 'subject',
- rsetsize, done)
- if limit is not None and len(result) >= limit:
- break
- return result
-
- def object_relation_vocabulary(self, rtype, limit=None):
- """defaut vocabulary method for the given relation, looking for
- relation's subject entities (i.e. self is the object)
- """
- entity = self.edited_entity
- if isinstance(rtype, basestring):
- rtype = entity.schema.rschema(rtype)
- done = None
- if entity.has_eid():
- done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype))
- result = []
- rsetsize = None
- for subjtype in rtype.subjects(entity.e_schema):
- if limit is not None:
- rsetsize = limit - len(result)
- result += self._relation_vocabulary(rtype, subjtype, 'object',
- rsetsize, done)
- if limit is not None and len(result) >= limit:
- break
- return result
-
- def srelations_by_category(self, categories=None, permission=None,
- strict=False):
+ def editable_relations(self):
return ()
def should_display_add_new_relation_link(self, rschema, existant, card):
return False
+ @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function')
+ def subject_relation_vocabulary(self, rtype, limit=None):
+ """defaut vocabulary method for the given relation, looking for
+ relation's object entities (i.e. self is the subject)
+ """
+ return relvoc_unrelated(self.edited_entity, rtype, 'subject', limit=None)
+
+ @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function')
+ def object_relation_vocabulary(self, rtype, limit=None):
+ return relvoc_unrelated(self.edited_entity, rtype, 'object', limit=None)
+
class CompositeFormMixIn(object):
"""form composed of sub-forms"""
- id = 'composite'
- form_renderer_id = id
+ __regid__ = 'composite'
+ form_renderer_id = __regid__
def __init__(self, *args, **kwargs):
super(CompositeFormMixIn, self).__init__(*args, **kwargs)
@@ -544,10 +313,10 @@
subform.parent_form = self
self.forms.append(subform)
- def build_context(self, rendervalues=None):
- super(CompositeFormMixIn, self).build_context(rendervalues)
+ def build_context(self, formvalues=None):
+ super(CompositeFormMixIn, self).build_context(formvalues)
for form in self.forms:
- form.build_context(rendervalues)
+ form.build_context(formvalues)
class CompositeForm(CompositeFormMixIn, FieldsForm):
--- a/web/views/ibreadcrumbs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/ibreadcrumbs.py Mon Feb 08 13:35:07 2010 +0100
@@ -12,18 +12,18 @@
from cubicweb.interfaces import IBreadCrumbs
from cubicweb.selectors import (one_line_rset, implements, one_etype_rset,
- two_lines_rset, any_rset)
+ multi_lines_rset, any_rset)
from cubicweb.view import EntityView, Component
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
from cubicweb.entity import Entity
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
class BreadCrumbEntityVComponent(Component):
- id = 'breadcrumbs'
+ __regid__ = 'breadcrumbs'
__select__ = one_line_rset() & implements(IBreadCrumbs, accept_none=False)
- property_defs = {
+ cw_property_defs = {
_('visible'): dict(type='Boolean', default=True,
help=_('display the component or not')),
}
@@ -33,7 +33,7 @@
link_template = u'<a href="%s">%s</a>'
def call(self, view=None, first_separator=True):
- entity = self.rset.get_entity(0, 0)
+ entity = self.cw_rset.get_entity(0, 0)
path = entity.breadcrumbs(view)
if path:
self.open_breadcrumbs()
@@ -51,7 +51,7 @@
def render_breadcrumbs(self, contextentity, path):
root = path.pop(0)
if isinstance(root, Entity):
- self.w(self.link_template % (self.req.build_url(root.id),
+ self.w(self.link_template % (self._cw.build_url(root.__regid__),
root.dc_type('plural')))
self.w(self.separator)
self.wpath_part(root, contextentity, not path)
@@ -68,23 +68,23 @@
self.w(part.view('breadcrumbs'))
elif isinstance(part, tuple):
url, title = part
- textsize = self.req.property_value('navigation.short-line-size')
+ textsize = self._cw.property_value('navigation.short-line-size')
self.w(self.link_template % (
xml_escape(url), xml_escape(uilib.cut(title, textsize))))
else:
- textsize = self.req.property_value('navigation.short-line-size')
+ textsize = self._cw.property_value('navigation.short-line-size')
self.w(uilib.cut(unicode(part), textsize))
class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent):
- __select__ = two_lines_rset() & one_etype_rset() & \
+ __select__ = multi_lines_rset() & one_etype_rset() & \
implements(IBreadCrumbs, accept_none=False)
def render_breadcrumbs(self, contextentity, path):
# XXX hack: only display etype name or first non entity path part
root = path.pop(0)
if isinstance(root, Entity):
- self.w(u'<a href="%s">%s</a>' % (self.req.build_url(root.id),
+ self.w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
root.dc_type('plural')))
else:
self.wpath_part(root, contextentity, not path)
@@ -97,15 +97,15 @@
self.w(u'<span id="breadcrumbs" class="pathbar">')
if first_separator:
self.w(self.separator)
- self.w(self.req._('search'))
+ self.w(self._cw._('search'))
self.w(u'</span>')
class BreadCrumbView(EntityView):
- id = 'breadcrumbs'
+ __regid__ = 'breadcrumbs'
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
desc = xml_escape(uilib.cut(entity.dc_description(), 50))
# XXX remember camember : tags.a autoescapes !
self.w(tags.a(entity.view('breadcrumbtext'),
@@ -113,9 +113,9 @@
class BreadCrumbTextView(EntityView):
- id = 'breadcrumbtext'
+ __regid__ = 'breadcrumbtext'
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
- textsize = self.req.property_value('navigation.short-line-size')
+ entity = self.cw_rset.get_entity(row, col)
+ textsize = self._cw.property_value('navigation.short-line-size')
self.w(uilib.cut(entity.dc_title(), textsize))
--- a/web/views/idownloadable.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/idownloadable.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,7 +14,7 @@
from cubicweb.selectors import (one_line_rset, score_entity,
implements, match_context_prop)
from cubicweb.interfaces import IDownloadable
-from cubicweb.common.mttransforms import ENGINE
+from cubicweb.mttransforms import ENGINE
from cubicweb.web.box import EntityBoxTemplate
from cubicweb.web.views import primary, baseviews
@@ -26,7 +26,7 @@
return 1
def download_box(w, entity, title=None, label=None, footer=u''):
- req = entity.req
+ req = entity._cw
w(u'<div class="sideBox">')
if title is None:
title = req._('download')
@@ -42,7 +42,7 @@
class DownloadBox(EntityBoxTemplate):
- id = 'download_box'
+ __regid__ = 'download_box'
# no download box for images
# XXX primary_view selector ?
__select__ = (one_line_rset() & implements(IDownloadable) &
@@ -50,7 +50,7 @@
order = 10
def cell_call(self, row, col, title=None, label=None, **kwargs):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
download_box(self.w, entity, title, label)
@@ -58,7 +58,7 @@
"""this view is replacing the deprecated 'download' controller and allow
downloading of entities providing the necessary interface
"""
- id = 'download'
+ __regid__ = 'download'
__select__ = one_line_rset() & implements(IDownloadable)
templatable = False
@@ -68,30 +68,30 @@
def set_request_content_type(self):
"""overriden to set the correct filetype and filename"""
- entity = self.complete_entity(0)
+ entity = self.cw_rset.complete_entity(0, 0)
encoding = entity.download_encoding()
if encoding in BINARY_ENCODINGS:
contenttype = 'application/%s' % encoding
encoding = None
else:
contenttype = entity.download_content_type()
- self.req.set_content_type(contenttype or self.content_type,
+ self._cw.set_content_type(contenttype or self.content_type,
filename=entity.download_file_name(),
encoding=encoding)
def call(self):
- self.w(self.complete_entity(0).download_data())
+ self.w(self.cw_rset.complete_entity(0, 0).download_data())
class DownloadLinkView(EntityView):
"""view displaying a link to download the file"""
- id = 'downloadlink'
+ __regid__ = 'downloadlink'
__select__ = implements(IDownloadable)
title = None # should not be listed in possible views
def cell_call(self, row, col, title=None, **kwargs):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
url = xml_escape(entity.download_url())
self.w(u'<a href="%s">%s</a>' % (url, xml_escape(title or entity.dc_title())))
@@ -104,16 +104,17 @@
self.w(u'<div class="content">')
contenttype = entity.download_content_type()
if contenttype.startswith('image/'):
- self.wview('image', entity.rset, row=entity.row)
+ self.wview('image', entity.cw_rset, row=entity.cw_row)
else:
- self.wview('downloadlink', entity.rset, title=self.req._('download'), row=entity.row)
+ self.wview('downloadlink', entity.cw_rset, title=self._cw._('download'), row=entity.cw_row)
try:
if ENGINE.has_input(contenttype):
self.w(entity.printable_value('data'))
except TransformError:
pass
except Exception, ex:
- msg = self.req._("can't display data, unexpected error: %s") % xml_escape(str(ex))
+ msg = self._cw._("can't display data, unexpected error: %s") \
+ % xml_escape(str(ex))
self.w('<div class="error">%s</div>' % msg)
self.w(u'</div>')
@@ -123,33 +124,33 @@
def cell_call(self, row, col, title=None, **kwargs):
"""the oneline view is a link to download the file"""
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
url = xml_escape(entity.absolute_url())
name = xml_escape(title or entity.download_file_name())
durl = xml_escape(entity.download_url())
self.w(u'<a href="%s">%s</a> [<a href="%s">%s</a>]' %
- (url, name, durl, self.req._('download')))
+ (url, name, durl, self._cw._('download')))
class ImageView(EntityView):
- id = 'image'
+ __regid__ = 'image'
__select__ = implements(IDownloadable) & score_entity(is_image)
title = _('image')
def call(self):
- rset = self.rset
+ rset = self.cw_rset
for i in xrange(len(rset)):
self.w(u'<div class="efile">')
- self.wview(self.id, rset, row=i, col=0)
+ self.wview(self.__regid__, rset, row=i, col=0)
self.w(u'</div>')
def cell_call(self, row, col, width=None, height=None, link=False):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
#if entity.data_format.startswith('image/'):
imgtag = u'<img src="%s" alt="%s" ' % (
xml_escape(entity.download_url()),
- (self.req._('download %s') % xml_escape(entity.download_file_name())))
+ (self._cw._('download %s') % xml_escape(entity.download_file_name())))
if width:
imgtag += u'width="%i" ' % width
if height:
--- a/web/views/igeocodable.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/igeocodable.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,22 +14,22 @@
from cubicweb.selectors import implements
class GeocodingJsonView(EntityView):
- id = 'geocoding-json'
+ __regid__ = 'geocoding-json'
+ __select__ = implements(IGeocodable)
+
binary = True
templatable = False
content_type = 'application/json'
- __select__ = implements(IGeocodable)
-
def call(self):
# remove entities that don't define latitude and longitude
- self.rset = self.rset.filtered_rset(lambda e: e.latitude and e.longitude)
- zoomlevel = self.req.form.pop('zoomlevel', 8)
- extraparams = self.req.form.copy()
+ self.cw_rset = self.cw_rset.filtered_rset(lambda e: e.latitude and e.longitude)
+ zoomlevel = self._cw.form.pop('zoomlevel', 8)
+ extraparams = self._cw.form.copy()
extraparams.pop('vid', None)
extraparams.pop('rql', None)
markers = [self.build_marker_data(rowidx, extraparams)
- for rowidx in xrange(len(self.rset))]
+ for rowidx in xrange(len(self.cw_rset))]
center = {
'latitude': sum(marker['latitude'] for marker in markers) / len(markers),
'longitude': sum(marker['longitude'] for marker in markers) / len(markers),
@@ -42,12 +42,12 @@
self.w(simplejson.dumps(geodata))
def build_marker_data(self, row, extraparams):
- entity = self.entity(row, 0)
+ entity = self.cw_rset.get_entity(row, 0)
icon = None
if hasattr(entity, 'marker_icon'):
icon = entity.marker_icon()
else:
- icon = (self.req.external_resource('GMARKER_ICON'), (20, 34), (4, 34), None)
+ icon = (self._cw.external_resource('GMARKER_ICON'), (20, 34), (4, 34), None)
return {'latitude': entity.latitude, 'longitude': entity.longitude,
'title': entity.dc_long_title(),
#icon defines : (icon._url, icon.size, icon.iconAncho', icon.shadow)
@@ -57,46 +57,45 @@
class GoogleMapBubbleView(EntityView):
- id = 'gmap-bubble'
-
+ __regid__ = 'gmap-bubble'
__select__ = implements(IGeocodable)
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(u'<div>%s</div>' % entity.view('oneline'))
# FIXME: we should call something like address-view if available
class GoogleMapsView(EntityView):
- id = 'gmap-view'
+ __regid__ = 'gmap-view'
+ __select__ = implements(IGeocodable)
- __select__ = implements(IGeocodable)
paginable = False
def call(self, gmap_key, width=400, height=400, uselabel=True, urlparams=None):
- self.req.demote_to_html()
+ self._cw.demote_to_html()
# remove entities that don't define latitude and longitude
- self.rset = self.rset.filtered_rset(lambda e: e.latitude and e.longitude)
- self.req.add_js('http://maps.google.com/maps?sensor=false&file=api&v=2&key=%s' % gmap_key,
+ self.cw_rset = self.cw_rset.filtered_rset(lambda e: e.latitude and e.longitude)
+ self._cw.add_js('http://maps.google.com/maps?sensor=false&file=api&v=2&key=%s' % gmap_key,
localfile=False)
- self.req.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
- rql = self.rset.printable_rql()
+ self._cw.add_js( ('cubicweb.widgets.js', 'cubicweb.gmap.js', 'gmap.utility.labeledmarker.js') )
+ rql = self.cw_rset.printable_rql()
if urlparams is None:
- loadurl = self.build_url(rql=rql, vid='geocoding-json')
+ loadurl = self._cw.build_url(rql=rql, vid='geocoding-json')
else:
- loadurl = self.build_url(rql=rql, vid='geocoding-json', **urlparams)
+ loadurl = self._cw.build_url(rql=rql, vid='geocoding-json', **urlparams)
self.w(u'<div style="width: %spx; height: %spx;" class="widget gmap" '
u'cubicweb:wdgtype="GMapWidget" cubicweb:loadtype="auto" '
u'cubicweb:loadurl="%s" cubicweb:uselabel="%s"> </div>' % (width, height, loadurl, uselabel))
class GoogeMapsLegend(EntityView):
- id = 'gmap-legend'
+ __regid__ = 'gmap-legend'
def call(self):
self.w(u'<ol>')
- for rowidx in xrange(len(self.rset)):
+ for rowidx in xrange(len(self.cw_rset)):
self.w(u'<li>')
- self.wview('listitem', self.rset, row=rowidx, col=0)
+ self.wview('listitem', self.cw_rset, row=rowidx, col=0)
self.w(u'</li>')
self.w(u'</ol>')
--- a/web/views/iprogress.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/iprogress.py Mon Feb 08 13:35:07 2010 +0100
@@ -34,7 +34,7 @@
header_for_COLNAME methods allow to customize header's label
"""
- id = 'progress_table_view'
+ __regid__ = 'progress_table_view'
title = _('task progression')
__select__ = implements(IMileStone)
@@ -45,21 +45,21 @@
def call(self, columns=None):
"""displays all versions in a table"""
- self.req.add_css('cubicweb.iprogress.css')
- _ = self.req._
+ self._cw.add_css('cubicweb.iprogress.css')
+ _ = self._cw._
self.columns = columns or self.columns
- ecls = self.vreg['etypes'].etype_class(self.rset.description[0][0])
+ ecls = self._cw.vreg['etypes'].etype_class(self.cw_rset.description[0][0])
self.w(u'<table class="progress">')
self.table_header(ecls)
self.w(u'<tbody>')
- for row in xrange(self.rset.rowcount):
+ for row in xrange(self.cw_rset.rowcount):
self.cell_call(row=row, col=0)
self.w(u'</tbody>')
self.w(u'</table>')
def cell_call(self, row, col):
- _ = self.req._
- entity = self.entity(row, col)
+ _ = self._cw._
+ entity = self.cw_rset.get_entity(row, col)
infos = {}
for col in self.columns:
meth = getattr(self, 'build_%s_cell' % col, None)
@@ -83,16 +83,16 @@
def header_for_project(self, ecls):
"""use entity's parent type as label"""
- return display_name(self.req, ecls.parent_type)
+ return display_name(self._cw, ecls.parent_type)
def header_for_milestone(self, ecls):
"""use entity's type as label"""
- return display_name(self.req, ecls.id)
+ return display_name(self._cw, ecls.__regid__)
def table_header(self, ecls):
"""builds the table's header"""
self.w(u'<thead><tr>')
- _ = self.req._
+ _ = self._cw._
for column in self.columns:
meth = getattr(self, 'header_for_%s' % column, None)
if meth:
@@ -109,7 +109,7 @@
project = entity.get_main_task()
if project:
return project.view('incontext')
- return self.req._('no related project')
+ return self._cw._('no related project')
def build_milestone_cell(self, entity):
"""``milestone`` column cell renderer"""
@@ -117,16 +117,16 @@
def build_state_cell(self, entity):
"""``state`` column cell renderer"""
- return xml_escape(self.req._(entity.state))
+ return xml_escape(self._cw._(entity.state))
def build_eta_date_cell(self, entity):
"""``eta_date`` column cell renderer"""
if entity.finished():
- return self.format_date(entity.completion_date())
- formated_date = self.format_date(entity.initial_prevision_date())
+ return self._cw.format_date(entity.completion_date())
+ formated_date = self._cw.format_date(entity.initial_prevision_date())
if entity.in_progress():
- eta_date = self.format_date(entity.eta_date())
- _ = self.req._
+ eta_date = self._cw.format_date(entity.eta_date())
+ _ = self._cw._
if formated_date:
formated_date += u' (%s %s)' % (_('expected:'), eta_date)
else:
@@ -139,7 +139,7 @@
def build_cost_cell(self, entity):
"""``cost`` column cell renderer"""
- _ = self.req._
+ _ = self._cw._
pinfo = entity.progress_info()
totalcost = pinfo.get('estimatedcorrected', pinfo['estimated'])
missing = pinfo.get('notestimatedcorrected', pinfo.get('notestimated', 0))
@@ -156,20 +156,18 @@
def build_progress_cell(self, entity):
"""``progress`` column cell renderer"""
- progress = u'<div class="progress_data">%s (%.2f%%)</div>' % (
- entity.done, entity.progress())
- return progress + entity.view('progressbar')
+ return entity.view('progressbar')
class InContextProgressTableView(ProgressTableView):
"""this views redirects to ``progress_table_view`` but removes
the ``project`` column
"""
- id = 'ic_progress_table_view'
+ __regid__ = 'ic_progress_table_view'
def call(self, columns=None):
- view = self.vreg['views'].select('progress_table_view', self.req,
- rset=self.rset)
+ view = self._cw.vreg['views'].select('progress_table_view', self._cw,
+ rset=self.cw_rset)
columns = list(columns or view.columns)
try:
columns.remove('project')
@@ -180,13 +178,14 @@
class ProgressBarView(EntityView):
"""displays a progress bar"""
- id = 'progressbar'
+ __regid__ = 'progressbar'
title = _('progress bar')
__select__ = implements(IProgress)
def cell_call(self, row, col):
- self.req.add_css('cubicweb.iprogress.css')
- entity = self.entity(row, col)
+ self._cw.add_css('cubicweb.iprogress.css')
+ self._cw.add_js('cubicweb.iprogress.js')
+ entity = self.cw_rset.get_entity(row, col)
widget = ProgressBarWidget(entity.done, entity.todo,
entity.revised_cost)
self.w(widget.render())
--- a/web/views/isioc.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/isioc.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,14 +14,14 @@
from cubicweb.interfaces import ISiocItem, ISiocContainer
class SIOCView(EntityView):
- id = 'sioc'
+ __regid__ = 'sioc'
__select__ = EntityView.__select__ & implements(ISiocItem, ISiocContainer)
title = _('sioc')
templatable = False
content_type = 'text/xml'
def call(self):
- self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+ self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
self.w(u'''<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
@@ -30,21 +30,21 @@
xmlns:sioc="http://rdfs.org/sioc/ns#"
xmlns:sioctype="http://rdfs.org/sioc/types#"
xmlns:dcterms="http://purl.org/dc/terms/">\n''')
- for i in xrange(self.rset.rowcount):
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u'</rdf:RDF>\n')
def cell_call(self, row, col):
- self.wview('sioc_element', self.rset, row=row, col=col)
+ self.wview('sioc_element', self.cw_rset, row=row, col=col)
class SIOCContainerView(EntityView):
- id = 'sioc_element'
+ __regid__ = 'sioc_element'
__select__ = EntityView.__select__ & implements(ISiocContainer)
templatable = False
content_type = 'text/xml'
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
sioct = xml_escape(entity.isioc_type())
self.w(u'<sioc:%s rdf:about="%s">\n'
% (sioct, xml_escape(entity.absolute_url())))
@@ -59,13 +59,13 @@
class SIOCItemView(EntityView):
- id = 'sioc_element'
+ __regid__ = 'sioc_element'
__select__ = EntityView.__select__ & implements(ISiocItem)
templatable = False
content_type = 'text/xml'
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
sioct = xml_escape(entity.isioc_type())
self.w(u'<sioc:%s rdf:about="%s">\n'
% (sioct, xml_escape(entity.absolute_url())))
--- a/web/views/magicsearch.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/magicsearch.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,11 +11,12 @@
import re
from logging import getLogger
+from warnings import warn
from rql import RQLSyntaxError, BadRQLQuery, parse
from rql.nodes import Relation
-from cubicweb import Unauthorized
+from cubicweb import Unauthorized, typed_eid
from cubicweb.view import Component
LOGGER = getLogger('cubicweb.magicsearch')
@@ -135,20 +136,20 @@
class BaseQueryProcessor(Component):
__abstract__ = True
- id = 'magicsearch_processor'
+ __regid__ = 'magicsearch_processor'
# set something if you want explicit component search facility for the
# component
name = None
- def process_query(self, uquery, req):
- args = self.preprocess_query(uquery, req)
+ def process_query(self, uquery):
+ args = self.preprocess_query(uquery)
try:
- return req.execute(*args)
+ return self._cw.execute(*args)
finally:
# rollback necessary to avoid leaving the connection in a bad state
- req.cnx.rollback()
+ self._cw.cnx.rollback()
- def preprocess_query(self, uquery, req):
+ def preprocess_query(self, uquery):
raise NotImplementedError()
@@ -160,7 +161,7 @@
"""
name = 'rql'
priority = 0
- def preprocess_query(self, uquery, req):
+ def preprocess_query(self, uquery):
return uquery,
@@ -169,11 +170,12 @@
and attributes
"""
priority = 2
- def preprocess_query(self, uquery, req):
+ def preprocess_query(self, uquery):
rqlst = parse(uquery, print_errors=False)
- schema = self.vreg.schema
+ schema = self._cw.vreg.schema
# rql syntax tree will be modified in place if necessary
- translate_rql_tree(rqlst, trmap(self.config, schema, req.lang), schema)
+ translate_rql_tree(rqlst, trmap(self._cw.vreg.config, schema, self._cw.lang),
+ schema)
return rqlst.as_string(),
@@ -184,10 +186,9 @@
"""
priority = 4
- def preprocess_query(self, uquery, req):
+ def preprocess_query(self, uquery):
"""try to get rql from an unicode query string"""
args = None
- self.req = req
try:
# Process as if there was a quoted part
args = self._quoted_words_query(uquery)
@@ -210,7 +211,7 @@
"""
etype = word.capitalize()
try:
- return trmap(self.config, self.vreg.schema, self.req.lang)[etype]
+ return trmap(self._cw.vreg.config, self._cw.vreg.schema, self._cw.lang)[etype]
except KeyError:
raise BadRQLQuery('%s is not a valid entity name' % etype)
@@ -222,7 +223,7 @@
# Need to convert from unicode to string (could be whatever)
rtype = word.lower()
# Find the entity name as stored in the DB
- translations = trmap(self.config, self.vreg.schema, self.req.lang)
+ translations = trmap(self._cw.vreg.config, self._cw.vreg.schema, self._cw.lang)
try:
translations = translations[rtype]
except KeyError:
@@ -239,7 +240,7 @@
"""
# if this is an integer, then directly go to eid
try:
- eid = int(word)
+ eid = typed_eid(word)
return 'Any X WHERE X eid %(x)s', {'x': eid}, 'x'
except ValueError:
etype = self._get_entity_type(word)
@@ -249,9 +250,9 @@
searchop = ''
if '%' in searchstr:
if rtype:
- possible_etypes = self.schema.rschema(rtype).objects(etype)
+ possible_etypes = self._cw.vreg.schema.rschema(rtype).objects(etype)
else:
- possible_etypes = [self.schema.eschema(etype)]
+ possible_etypes = [self._cw.vreg.schema.eschema(etype)]
if searchattr or len(possible_etypes) == 1:
searchattr = searchattr or possible_etypes[0].main_attribute()
searchop = 'LIKE '
@@ -275,10 +276,10 @@
"""Specific process for three words query (case (3) of preprocess_rql)
"""
etype = self._get_entity_type(word1)
- eschema = self.schema.eschema(etype)
+ eschema = self._cw.vreg.schema.eschema(etype)
rtype = self._get_attribute_name(word2, eschema)
# expand shortcut if rtype is a non final relation
- if not self.schema.rschema(rtype).final:
+ if not self._cw.vreg.schema.rschema(rtype).final:
return self._expand_shortcut(etype, rtype, word3)
if '%' in word3:
searchop = 'LIKE '
@@ -336,28 +337,28 @@
priority = 10
name = 'text'
- def preprocess_query(self, uquery, req):
+ def preprocess_query(self, uquery):
"""suppose it's a plain text query"""
return 'Any X WHERE X has_text %(text)s', {'text': uquery}
class MagicSearchComponent(Component):
- id = 'magicsearch'
+ __regid__ = 'magicsearch'
def __init__(self, req, rset=None):
- super(MagicSearchComponent, self).__init__(req, rset)
+ super(MagicSearchComponent, self).__init__(req, rset=rset)
processors = []
self.by_name = {}
- for processorcls in self.vreg['components']['magicsearch_processor']:
+ for processorcls in self._cw.vreg['components']['magicsearch_processor']:
# instantiation needed
- processor = processorcls()
+ processor = processorcls(self._cw)
processors.append(processor)
if processor.name is not None:
assert not processor.name in self.by_name
self.by_name[processor.name.lower()] = processor
self.processors = sorted(processors, key=lambda x: x.priority)
- def process_query(self, uquery, req):
+ def process_query(self, uquery):
assert isinstance(uquery, unicode)
try:
procname, query = uquery.split(':', 1)
@@ -368,7 +369,15 @@
unauthorized = None
for proc in self.processors:
try:
- return proc.process_query(uquery, req)
+ try:
+ return proc.process_query(uquery)
+ except TypeError, exc: # cw 3.5 compat
+ print "EXC", exc
+ warn("[3.6] %s.%s.process_query() should now accept uquery "
+ "as unique argument, use self._cw instead of req"
+ % (proc.__module__, proc.__class__.__name__),
+ DeprecationWarning)
+ return proc.process_query(uquery, self._cw)
# FIXME : we don't want to catch any exception type here !
except (RQLSyntaxError, BadRQLQuery):
pass
@@ -381,6 +390,6 @@
if unauthorized:
raise unauthorized
else:
- # let exception propagate
- return proc.process_query(uquery, req)
- raise BadRQLQuery(req._('sorry, the server is unable to handle this query'))
+ # explicitly specified processor: don't try to catch the exception
+ return proc.process_query(uquery)
+ raise BadRQLQuery(self._cw._('sorry, the server is unable to handle this query'))
--- a/web/views/management.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/management.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView
-from cubicweb.common.uilib import html_traceback, rest_traceback
+from cubicweb.uilib import html_traceback, rest_traceback
from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field
@@ -26,7 +26,7 @@
def schema_definition(self, eschema, link=True, access_types=None):
w = self.w
- _ = self.req._
+ _ = self._cw._
if not access_types:
access_types = eschema.ACTIONS
w(u'<table class="schemaInfo">')
@@ -34,7 +34,7 @@
_("permission"), _('granted to groups'), _('rql expressions')))
for access_type in access_types:
w(u'<tr>')
- w(u'<td>%s</td>' % self.req.__('%s_perm' % access_type))
+ w(u'<td>%s</td>' % self._cw.__('%s_perm' % access_type))
groups = eschema.get_groups(access_type)
l = []
groups = [(_(group), group) for group in groups]
@@ -43,7 +43,7 @@
# XXX we should get a group entity and call its absolute_url
# method
l.append(u'<a href="%s" class="%s">%s</a><br/>' % (
- self.build_url('cwgroup/%s' % group), group, trad))
+ self._cw.build_url('cwgroup/%s' % group), group, trad))
else:
l.append(u'<div class="%s">%s</div>' % (group, trad))
w(u'<td>%s</td>' % u''.join(l))
@@ -67,21 +67,21 @@
class SecurityManagementView(EntityView, SecurityViewMixIn):
"""display security information for a given entity"""
- id = 'security'
+ __regid__ = 'security'
__select__ = EntityView.__select__ & authenticated_user()
title = _('security')
def call(self):
- self.w(u'<div id="progress">%s</div>' % self.req._('validating...'))
+ self.w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
super(SecurityManagementView, self).call()
def cell_call(self, row, col):
- self.req.add_js('cubicweb.edition.js')
- self.req.add_css('cubicweb.acl.css')
- entity = self.rset.get_entity(row, col)
+ self._cw.add_js('cubicweb.edition.js')
+ self._cw.add_css('cubicweb.acl.css')
+ entity = self.cw_rset.get_entity(row, col)
w = self.w
- _ = self.req._
+ _ = self._cw._
w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
% (entity.dc_type().capitalize(),
xml_escape(entity.absolute_url()),
@@ -91,7 +91,7 @@
self.schema_definition(entity.e_schema)
self.w('<h2>%s</h2>' % _('manage security'))
# ownership information
- if self.schema.rschema('owned_by').has_perm(self.req, 'add',
+ if self._cw.vreg.schema.rschema('owned_by').has_perm(self._cw, 'add',
fromeid=entity.eid):
self.owned_by_edit_form(entity)
else:
@@ -99,30 +99,30 @@
# cwpermissions
if 'require_permission' in entity.e_schema.subject_relations():
w('<h3>%s</h3>' % _('permissions for this entity'))
- reqpermschema = self.schema.rschema('require_permission')
+ reqpermschema = self._cw.vreg.schema.rschema('require_permission')
self.require_permission_information(entity, reqpermschema)
- if reqpermschema.has_perm(self.req, 'add', fromeid=entity.eid):
+ if reqpermschema.has_perm(self._cw, 'add', fromeid=entity.eid):
self.require_permission_edit_form(entity)
def owned_by_edit_form(self, entity):
- self.w('<h3>%s</h3>' % self.req._('ownership'))
- msg = self.req._('ownerships have been changed')
- form = self.vreg['forms'].select('base', self.req, entity=entity,
+ self.w('<h3>%s</h3>' % self._cw._('ownership'))
+ msg = self._cw._('ownerships have been changed')
+ form = self._cw.vreg['forms'].select('base', self._cw, entity=entity,
form_renderer_id='base', submitmsg=msg,
form_buttons=[wdgs.SubmitButton()],
domid='ownership%s' % entity.eid,
__redirectvid='security',
__redirectpath=entity.rest_path())
- field = guess_field(entity.e_schema, self.schema.rschema('owned_by'))
+ field = guess_field(entity.e_schema, self._cw.vreg.schema.rschema('owned_by'))
form.append_field(field)
- self.w(form.render(rendervalues=dict(display_progress_div=False)))
+ self.w(form.render(display_progress_div=False))
def owned_by_information(self, entity):
ownersrset = entity.related('owned_by')
if ownersrset:
- self.w('<h3>%s</h3>' % self.req._('ownership'))
+ self.w('<h3>%s</h3>' % self._cw._('ownership'))
self.w(u'<div class="ownerInfo">')
- self.w(self.req._('this entity is currently owned by') + ' ')
+ self.w(self._cw._('this entity is currently owned by') + ' ')
self.wview('csv', entity.related('owned_by'), 'null')
self.w(u'</div>')
# else we don't know if this is because entity has no owner or becayse
@@ -131,10 +131,10 @@
def require_permission_information(self, entity, reqpermschema):
if entity.require_permission:
w = self.w
- _ = self.req._
- if reqpermschema.has_perm(self.req, 'delete', fromeid=entity.eid):
- delurl = self.build_url('edit', __redirectvid='security',
- __redirectpath=entity.rest_path())
+ _ = self._cw._
+ if reqpermschema.has_perm(self._cw, 'delete', fromeid=entity.eid):
+ delurl = self._cw.build_url('edit', __redirectvid='security',
+ __redirectpath=entity.rest_path())
delurl = delurl.replace('%', '%%')
# don't give __delete value to build_url else it will be urlquoted
# and this will replace %s by %25s
@@ -157,54 +157,54 @@
w(u'</tr>\n')
w(u'</table>')
else:
- self.w(self.req._('no associated permissions'))
+ self.w(self._cw._('no associated permissions'))
def require_permission_edit_form(self, entity):
- newperm = self.vreg['etypes'].etype_class('CWPermission')(self.req)
- newperm.eid = self.req.varmaker.next()
- self.w(u'<p>%s</p>' % self.req._('add a new permission'))
- form = self.vreg['forms'].select('base', self.req, entity=newperm,
+ newperm = self._cw.vreg['etypes'].etype_class('CWPermission')(self._cw)
+ newperm.eid = self._cw.varmaker.next()
+ self.w(u'<p>%s</p>' % self._cw._('add a new permission'))
+ form = self._cw.vreg['forms'].select('base', self._cw, entity=newperm,
form_buttons=[wdgs.SubmitButton()],
domid='reqperm%s' % entity.eid,
__redirectvid='security',
__redirectpath=entity.rest_path())
- form.form_add_hidden('require_permission', entity.eid, role='object',
- eidparam=True)
+ form.add_hidden('require_permission', entity.eid, role='object',
+ eidparam=True)
permnames = getattr(entity, '__permissions__', None)
cwpermschema = newperm.e_schema
if permnames is not None:
- field = guess_field(cwpermschema, self.schema.rschema('name'),
+ field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('name'),
widget=wdgs.Select({'size': 1}),
choices=permnames)
else:
- field = guess_field(cwpermschema, self.schema.rschema('name'))
+ field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('name'))
form.append_field(field)
- field = guess_field(cwpermschema, self.schema.rschema('label'))
+ field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('label'))
form.append_field(field)
- field = guess_field(cwpermschema, self.schema.rschema('require_group'))
+ field = guess_field(cwpermschema, self._cw.vreg.schema.rschema('require_group'))
form.append_field(field)
- renderer = self.vreg['formrenderers'].select(
- 'htable', self.req, rset=None, display_progress_div=False)
+ renderer = self._cw.vreg['formrenderers'].select(
+ 'htable', self._cw, rset=None, display_progress_div=False)
self.w(form.render(renderer=renderer))
class ErrorView(AnyRsetView):
"""default view when no result has been found"""
__select__ = yes()
- id = 'error'
+ __regid__ = 'error'
def page_title(self):
"""returns a title according to the result set - used for the
title in the HTML header
"""
- return self.req._('an error occured')
+ return self._cw._('an error occured')
def call(self):
- req = self.req.reset_headers()
+ req = self._cw.reset_headers()
w = self.w
ex = req.data.get('ex')#_("unable to find exception information"))
excinfo = req.data.get('excinfo')
- title = self.req._('an error occured')
+ title = self._cw._('an error occured')
w(u'<h2>%s</h2>' % title)
if 'errmsg' in req.data:
ex = req.data['errmsg']
@@ -212,7 +212,7 @@
else:
exclass = ex.__class__.__name__
ex = exc_message(ex, req.encoding)
- if excinfo is not None and self.config['print-traceback']:
+ if excinfo is not None and self._cw.vreg.config['print-traceback']:
if exclass is None:
w(u'<div class="tb">%s</div>'
% xml_escape(ex).replace("\n","<br />"))
@@ -226,26 +226,26 @@
# if excinfo is not None, it's probably not a bug
if excinfo is None:
return
- vcconf = self.config.vc_config()
+ vcconf = self._cw.vreg.config.vc_config()
w(u"<div>")
- eversion = vcconf.get('cubicweb', self.req._('no version information'))
+ eversion = vcconf.get('cubicweb', self._cw._('no version information'))
# NOTE: tuple wrapping needed since eversion is itself a tuple
w(u"<b>CubicWeb version:</b> %s<br/>\n" % (eversion,))
cversions = []
- for cube in self.config.cubes():
- cubeversion = vcconf.get(cube, self.req._('no version information'))
+ for cube in self._cw.vreg.config.cubes():
+ cubeversion = vcconf.get(cube, self._cw._('no version information'))
w(u"<b>Package %s version:</b> %s<br/>\n" % (cube, cubeversion))
cversions.append((cube, cubeversion))
w(u"</div>")
# creates a bug submission link if submit-mail is set
- if self.config['submit-mail']:
- form = self.vreg['forms'].select('base', self.req, rset=None,
+ if self._cw.vreg.config['submit-mail']:
+ form = self._cw.vreg['forms'].select('base', self._cw, rset=None,
mainform=False)
binfo = text_error_description(ex, excinfo, req, eversion, cversions)
- form.form_add_hidden('description', binfo,
- # we must use a text area to keep line breaks
- widget=wdgs.TextArea({'class': 'hidden'}))
- form.form_add_hidden('__bugreporting', '1')
+ form.add_hidden('description', binfo,
+ # we must use a text area to keep line breaks
+ widget=wdgs.TextArea({'class': 'hidden'}))
+ form.add_hidden('__bugreporting', '1')
form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
form.action = req.build_url('reportbug')
w(form.render())
@@ -274,27 +274,27 @@
class ProcessInformationView(StartupView):
- id = 'info'
+ __regid__ = 'info'
__select__ = none_rset() & match_user_groups('users', 'managers')
title = _('server information')
def call(self, **kwargs):
"""display server information"""
- vcconf = self.config.vc_config()
- req = self.req
+ vcconf = self._cw.vreg.config.vc_config()
+ req = self._cw
_ = req._
# display main information
self.w(u'<h3>%s</h3>' % _('Application'))
self.w(u'<table border="1">')
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
'CubicWeb', vcconf.get('cubicweb', _('no version information'))))
- for pkg in self.config.cubes():
+ for pkg in self._cw.vreg.config.cubes():
pkgversion = vcconf.get(pkg, _('no version information'))
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
pkg, pkgversion))
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
- _('home'), self.config.apphome))
+ _('home'), self._cw.vreg.config.apphome))
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
_('base url'), req.base_url()))
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>' % (
--- a/web/views/massmailing.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/massmailing.py Mon Feb 08 13:35:07 2010 +0100
@@ -19,7 +19,7 @@
class SendEmailAction(action.Action):
- id = 'sendemail'
+ __regid__ = 'sendemail'
# XXX should check email is set as well
__select__ = (action.Action.__select__ & implements(IEmailable)
& match_user_groups('managers', 'users'))
@@ -29,18 +29,25 @@
def url(self):
params = {'vid': 'massmailing', '__force_display': 1}
- if self.req.form.has_key('rql'):
- params['rql'] = self.req.form['rql']
- return self.build_url(self.req.relative_path(includeparams=False),
- **params)
+ if self._cw.form.has_key('rql'):
+ params['rql'] = self._cw.form['rql']
+ return self._cw.build_url(self._cw.relative_path(includeparams=False),
+ **params)
+def recipient_vocabulary(form, field):
+ vocab = [(entity.get_email(), entity.eid) for entity in form.cw_rset.entities()]
+ return [(label, value) for label, value in vocab if label]
+
class MassMailingForm(forms.FieldsForm):
- id = 'massmailing'
+ __regid__ = 'massmailing'
sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
- label=_('From:'))
- recipient = ff.StringField(widget=CheckBox(), label=_('Recipients:'))
+ label=_('From:'),
+ value=lambda f: '%s <%s>' % (f._cw.user.dc_title(), f._cw.user.get_email()))
+ recipient = ff.StringField(widget=CheckBox(), label=_('Recipients:'),
+ choices=recipient_vocabulary,
+ value= lambda f: [entity.eid for entity in f.cw_rset.entities() if entity.get_email()])
subject = ff.StringField(label=_('Subject:'), max_length=256)
mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
inputid='mailbody'))
@@ -49,25 +56,17 @@
_('send email'), 'SEND_EMAIL_ICON'),
ImgButton('cancelbutton', "javascript: history.back()",
stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
- form_renderer_id = id
+ form_renderer_id = __regid__
- def form_field_vocabulary(self, field):
- if field.name == 'recipient':
- vocab = [(entity.get_email(), entity.eid) for entity in self.rset.entities()]
- return [(label, value) for label, value in vocab if label]
- return super(MassMailingForm, self).form_field_vocabulary(field)
-
- def form_field_value(self, field, values):
- if field.name == 'recipient':
- return [entity.eid for entity in self.rset.entities() if entity.get_email()]
- elif field.name == 'mailbody':
- field.widget.attrs['cubicweb:variables'] = ','.join(self.get_allowed_substitutions())
- return super(MassMailingForm, self).form_field_value(field, values)
+ def __init__(self, *args, **kwargs):
+ super(MassMailingForm, self).__init__(*args, **kwargs)
+ field = self.field_by_name('mailbody')
+ field.widget.attrs['cubicweb:variables'] = ','.join(self.get_allowed_substitutions())
def get_allowed_substitutions(self):
attrs = []
- for coltype in self.rset.column_types(0):
- eclass = self.vreg['etypes'].etype_class(coltype)
+ for coltype in self.cw_rset.column_types(0):
+ eclass = self._cw.vreg['etypes'].etype_class(coltype)
attrs.append(eclass.allowed_massmail_keys())
return sorted(reduce(operator.and_, attrs))
@@ -75,13 +74,13 @@
insertLink = u'<a href="javascript: insertText(\'%%(%s)s\', \'emailarea\');">%%(%s)s</a>'
substs = (u'<div class="substitution">%s</div>' % (insertLink % (subst, subst))
for subst in self.get_allowed_substitutions())
- helpmsg = self.req._('You can use any of the following substitutions in your text')
+ helpmsg = self._cw._('You can use any of the following substitutions in your text')
return u'<div id="substitutions"><span>%s</span>%s</div>' % (
helpmsg, u'\n'.join(substs))
class MassMailingFormRenderer(formrenderers.FormRenderer):
- id = 'massmailing'
+ __regid__ = 'massmailing'
button_bar_class = u'toolbar'
def _render_fields(self, fields, w, form):
@@ -117,14 +116,14 @@
pass
class MassMailingFormView(form.FormViewMixIn, EntityView):
- id = 'massmailing'
+ __regid__ = 'massmailing'
__select__ = implements(IEmailable) & match_user_groups('managers', 'users')
def call(self):
- req = self.req
+ req = self._cw
req.add_js('cubicweb.widgets.js', 'cubicweb.massmailing.js')
req.add_css('cubicweb.mailform.css')
from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
- form = self.vreg['forms'].select('massmailing', self.req, rset=self.rset,
- action='sendmail', domid='sendmail')
- self.w(form.render(formvalues=dict(sender=from_addr)))
+ form = self._cw.vreg['forms'].select('massmailing', self._cw, rset=self.cw_rset,
+ action='sendmail', domid='sendmail')
+ self.w(form.render())
--- a/web/views/navigation.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/navigation.py Mon Feb 08 13:35:07 2010 +0100
@@ -17,7 +17,7 @@
from cubicweb.selectors import (paginated_rset, sorted_rset,
primary_view, match_context_prop,
one_line_rset, implements)
-from cubicweb.common.uilib import cut
+from cubicweb.uilib import cut
from cubicweb.web.component import EntityVComponent, NavigationComponent
@@ -26,8 +26,8 @@
def call(self):
"""displays a resultset by page"""
w = self.w
- req = self.req
- rset = self.rset
+ req = self._cw
+ rset = self.cw_rset
page_size = self.page_size
start = 0
blocklist = []
@@ -59,14 +59,14 @@
nb_chars = 5
def display_func(self, rset, col, attrname):
- req = self.req
+ req = self._cw
if attrname is not None:
def index_display(row):
if not rset[row][col]: # outer join
return u''
entity = rset.get_entity(row, col)
return entity.printable_value(attrname, format='text/plain')
- elif self.schema.eschema(rset.description[0][col]).final:
+ elif self._cw.vreg.schema.eschema(rset.description[0][col]).final:
def index_display(row):
return unicode(rset[row][col])
else:
@@ -82,9 +82,9 @@
[ana - cro] | [cro - ghe] | ... | [tim - zou]
"""
w = self.w
- rset = self.rset
+ rset = self.cw_rset
page_size = self.page_size
- rschema = self.schema.rschema
+ rschema = self._cw.vreg.schema.rschema
# attrname = the name of attribute according to which the sort
# is done if any
for sorterm in rset.syntax_tree().children[0].orderby:
@@ -124,10 +124,10 @@
# nothing usable found, use the first column
index_display = self.display_func(rset, 0, None)
blocklist = []
- params = dict(self.req.form)
+ params = dict(self._cw.form)
self.clean_params(params)
start = 0
- basepath = self.req.relative_path(includeparams=False)
+ basepath = self._cw.relative_path(includeparams=False)
while start < rset.rowcount:
stop = min(start + page_size - 1, rset.rowcount - 1)
cell = self.format_link_content(index_display(start), index_display(stop))
@@ -149,7 +149,7 @@
class NextPrevNavigationComponent(EntityVComponent):
- id = 'prevnext'
+ __regid__ = 'prevnext'
# register msg not generated since no entity implements IPrevNext in cubicweb
# itself
title = _('contentnavigation_prevnext')
@@ -159,23 +159,23 @@
context = 'navbottom'
order = 10
def call(self, view=None):
- entity = self.entity(0)
+ entity = self.cw_rset.get_entity(0,0)
previous = entity.previous_entity()
next = entity.next_entity()
if previous or next:
- textsize = self.req.property_value('navigation.short-line-size')
+ textsize = self._cw.property_value('navigation.short-line-size')
self.w(u'<div class="prevnext">')
if previous:
self.w(u'<div class="previousEntity left">')
self.w(self.previous_link(previous, textsize))
self.w(u'</div>')
- self.req.html_headers.add_raw('<link rel="prev" href="%s" />'
+ self._cw.html_headers.add_raw('<link rel="prev" href="%s" />'
% xml_escape(previous.absolute_url()))
if next:
self.w(u'<div class="nextEntity right">')
self.w(self.next_link(next, textsize))
self.w(u'</div>')
- self.req.html_headers.add_raw('<link rel="next" href="%s" />'
+ self._cw.html_headers.add_raw('<link rel="next" href="%s" />'
% xml_escape(next.absolute_url()))
self.w(u'</div>')
self.w(u'<div class="clear"></div>')
@@ -183,13 +183,13 @@
def previous_link(self, previous, textsize):
return u'<a href="%s" title="%s"><< %s</a>' % (
xml_escape(previous.absolute_url()),
- self.req._('i18nprevnext_previous'),
+ self._cw._('i18nprevnext_previous'),
xml_escape(cut(previous.dc_title(), textsize)))
def next_link(self, next, textsize):
return u'<a href="%s" title="%s">%s >></a>' % (
xml_escape(next.absolute_url()),
- self.req._('i18nprevnext_next'),
+ self._cw._('i18nprevnext_next'),
xml_escape(cut(next.dc_title(), textsize)))
@@ -197,10 +197,12 @@
"""write pages index in w stream (default to view.w) and then limit the result
set (default to view.rset) to the currently displayed page
"""
- req = view.req
+ req = view._cw
if rset is None:
- rset = view.rset
- nav = req.vreg['components'].select_object(
+ rset = view.cw_rset
+ if w is None:
+ w = view.w
+ nav = req.vreg['components'].select_or_none(
'navigation', req, rset=rset, page_size=page_size)
if nav:
if w is None:
@@ -212,7 +214,7 @@
nav.clean_params(params)
# make a link to see them all
if show_all_option:
- url = xml_escape(view.build_url(__force_display=1, **params))
+ url = xml_escape(req.build_url(__force_display=1, **params))
w(u'<span><a href="%s">%s</a></span>\n'
% (url, req._('show %s results') % len(rset)))
rset.limit(offset=start, limit=stop-start, inplace=True)
@@ -222,8 +224,8 @@
"""paginate results if the view is paginable and we're not explictly told to
display everything (by setting __force_display in req.form)
"""
- if view.paginable and not view.req.form.get('__force_display'):
- do_paginate(view, rset, w or view.w, show_all_option, page_size)
+ if view.paginable and not view._cw.form.get('__force_display'):
+ do_paginate(view, rset, w, show_all_option, page_size)
# monkey patch base View class to add a .paginate([...])
# method to be called to write pages index in the view and then limit the result
--- a/web/views/old_calendar.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/old_calendar.py Mon Feb 08 13:35:07 2010 +0100
@@ -9,9 +9,10 @@
from datetime import date, time, timedelta
from logilab.mtconverter import xml_escape
+from logilab.common.date import (ONEDAY, ONEWEEK, days_in_month, previous_month,
+ next_month, first_day, last_day, date_range)
from cubicweb.interfaces import ICalendarViews
-from cubicweb.utils import ONEDAY, ONEWEEK, date_range, first_day, last_day, previous_month, next_month, days_in_month
from cubicweb.selectors import implements
from cubicweb.view import EntityView
@@ -44,16 +45,16 @@
next1 = next_month(date, smallshift)
prev2 = previous_month(date, bigshift)
next2 = next_month(date, bigshift)
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
return self.NAV_HEADER % (
- xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
- month=prev2.month)),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
- month=prev1.month)),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
- month=next1.month)),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
- month=next2.month)))
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev2.year,
+ month=prev2.month)),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev1.year,
+ month=prev1.month)),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next1.year,
+ month=next1.month)),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next2.year,
+ month=next2.month)))
# Calendar building methods ##############################################
@@ -69,7 +70,7 @@
"""method responsible for building *one* HTML calendar"""
# FIXME iterates between [first_day-first_day.day_of_week ;
# last_day+6-last_day.day_of_week]
- umonth = self.format_date(first_day, '%B %Y') # localized month name
+ umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
rows = []
current_row = [NO_CELL] * first_day.weekday()
for daynum in xrange(0, days_in_month(first_day)):
@@ -86,13 +87,13 @@
rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
current_row = []
current_row.extend([NO_CELL] * (6-day.weekday()))
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
if day.weekday() != 6:
rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
- url = self.build_url(rql=rql, vid='calendarmonth',
- year=first_day.year, month=first_day.month)
+ url = self._cw.build_url(rql=rql, vid='calendarmonth',
+ year=first_day.year, month=first_day.month)
monthlink = u'<a href="%s">%s</a>' % (xml_escape(url), umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
+ return CALENDAR(self._cw) % (monthlink, '\n'.join(rows))
def _mk_schedule(self, begin, end, itemvid='calendaritem'):
"""private method that gathers information from resultset
@@ -106,12 +107,12 @@
day2 : { hour : [views] } ... }
"""
# put this here since all sub views are calling this method
- self.req.add_css('cubicweb.calendar.css')
+ self._cw.add_css('cubicweb.calendar.css')
schedule = {}
- for row in xrange(len(self.rset.rows)):
- entity = self.entity(row)
+ for row in xrange(len(self.cw_rset.rows)):
+ entity = self.cw_rset.get_entity(row,0)
infos = u'<div class="event">'
- infos += self.view(itemvid, self.rset, row=row)
+ infos += self._cw.view(itemvid, self.cw_rset, row=row)
infos += u'</div>'
for date_ in entity.matching_dates(begin, end):
day = date(date_.year, date_.month, date_.day)
@@ -162,13 +163,13 @@
class YearCalendarView(_CalendarView):
- id = 'calendaryear'
+ __regid__ = 'calendaryear'
title = _('calendar (year)')
def call(self, year=None, month=None):
"""this view renders a 3x3 calendars' table"""
- year = year or int(self.req.form.get('year', date.today().year))
- month = month or int(self.req.form.get('month', date.today().month))
+ year = year or int(self._cw.form.get('year', date.today().year))
+ month = month or int(self._cw.form.get('month', date.today().month))
center_date = date(year, month, 1)
begin, end = self.get_date_range(day=center_date)
schedule = self._mk_schedule(begin, end)
@@ -181,12 +182,12 @@
"""this view renders three semesters as three rows of six columns,
one column per month
"""
- id = 'calendarsemester'
+ __regid__ = 'calendarsemester'
title = _('calendar (semester)')
def call(self, year=None, month=None):
- year = year or int(self.req.form.get('year', date.today().year))
- month = month or int(self.req.form.get('month', date.today().month))
+ year = year or int(self._cw.form.get('year', date.today().year))
+ month = month or int(self._cw.form.get('month', date.today().month))
begin = previous_month(date(year, month, 1), 2)
end = next_month(date(year, month, 1), 3)
schedule = self._mk_schedule(begin, end)
@@ -198,15 +199,15 @@
def build_calendars(self, schedule, begin, end):
self.w(u'<tr>')
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
for cur_month in date_range(begin, end, incmonth=1):
- umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
- url = self.build_url(rql=rql, vid=self.id,
- year=cur_month.year, month=cur_month.month)
+ umonth = u'%s %s' % (self._cw.format_date(cur_month, '%B'), cur_month.year)
+ url = self._cw.build_url(rql=rql, vid=self.__regid__,
+ year=cur_month.year, month=cur_month.month)
self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (xml_escape(url),
umonth))
self.w(u'</tr>')
- _ = self.req._
+ _ = self._cw._
for day_num in xrange(31):
self.w(u'<tr>')
for cur_month in date_range(begin, end, incmonth=1):
@@ -229,12 +230,12 @@
class MonthCalendarView(_CalendarView):
"""this view renders a 3x1 calendars' table"""
- id = 'calendarmonth'
+ __regid__ = 'calendarmonth'
title = _('calendar (month)')
def call(self, year=None, month=None):
- year = year or int(self.req.form.get('year', date.today().year))
- month = month or int(self.req.form.get('month', date.today().month))
+ year = year or int(self._cw.form.get('year', date.today().year))
+ month = month or int(self._cw.form.get('month', date.today().month))
center_date = date(year, month, 1)
begin, end = self.get_date_range(day=center_date, shift=1)
schedule = self._mk_schedule(begin, end)
@@ -246,12 +247,12 @@
class WeekCalendarView(_CalendarView):
"""this view renders a calendar for week events"""
- id = 'calendarweek'
+ __regid__ = 'calendarweek'
title = _('calendar (week)')
def call(self, year=None, week=None):
- year = year or int(self.req.form.get('year', date.today().year))
- week = week or int(self.req.form.get('week', date.today().isocalendar()[1]))
+ year = year or int(self._cw.form.get('year', date.today().year))
+ week = week or int(self._cw.form.get('week', date.today().isocalendar()[1]))
day0 = date(year, 1, 1)
first_day_of_week = day0 - day0.weekday()*ONEDAY + ONEWEEK
begin, end = first_day_of_week- ONEWEEK, first_day_of_week + 2*ONEWEEK
@@ -266,12 +267,12 @@
self.w(self.nav_header(first_day_of_week))
def build_calendar(self, schedule, weeks):
- rql = self.rset.printable_rql()
- _ = self.req._
+ rql = self.cw_rset.printable_rql()
+ _ = self._cw._
for monday, sunday in weeks:
- umonth = self.format_date(monday, '%B %Y')
- url = self.build_url(rql=rql, vid='calendarmonth',
- year=monday.year, month=monday.month)
+ umonth = self._cw.format_date(monday, '%B %Y')
+ url = self._cw.build_url(rql=rql, vid='calendarmonth',
+ year=monday.year, month=monday.month)
monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
% (_('week'), monday.isocalendar()[1], monthlink))
@@ -293,25 +294,25 @@
prev2 = date - ONEWEEK * bigshift
next1 = date + ONEWEEK * smallshift
next2 = date + ONEWEEK * bigshift
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
return self.NAV_HEADER % (
- xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
- xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev2.year, week=prev2.isocalendar()[1])),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=prev1.year, week=prev1.isocalendar()[1])),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next1.year, week=next1.isocalendar()[1])),
+ xml_escape(self._cw.build_url(rql=rql, vid=self.__regid__, year=next2.year, week=next2.isocalendar()[1])))
class AMPMYearCalendarView(YearCalendarView):
- id = 'ampmcalendaryear'
+ __regid__ = 'ampmcalendaryear'
title = _('am/pm calendar (year)')
def build_calendar(self, schedule, first_day):
"""method responsible for building *one* HTML calendar"""
- umonth = self.format_date(first_day, '%B %Y') # localized month name
+ umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
rows = [] # each row is: (am,pm), (am,pm) ... week_title
current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
for daynum in xrange(0, days_in_month(first_day)):
# build cells day
day = first_day + timedelta(daynum)
@@ -324,7 +325,7 @@
AMPM_EMPTY % ("pmCell", "pm")))
# store & reset current row on Sundays
if day.weekday() == 6:
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
@@ -332,7 +333,7 @@
rows.append(current_row)
current_row = []
current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
weeklink = '<a href="%s">%s</a>' % (xml_escape(url), day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
@@ -348,29 +349,29 @@
formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
# tigh everything together
- url = self.build_url(rql=rql, vid='ampmcalendarmonth',
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
year=first_day.year, month=first_day.month)
monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
+ return CALENDAR(self._cw) % (monthlink, '\n'.join(formatted_rows))
class AMPMSemesterCalendarView(SemesterCalendarView):
"""this view renders a 3x1 calendars' table"""
- id = 'ampmcalendarsemester'
+ __regid__ = 'ampmcalendarsemester'
title = _('am/pm calendar (semester)')
def build_calendars(self, schedule, begin, end):
self.w(u'<tr>')
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
for cur_month in date_range(begin, end, incmonth=1):
- umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
- url = self.build_url(rql=rql, vid=self.id,
+ umonth = u'%s %s' % (self._cw.format_date(cur_month, '%B'), cur_month.year)
+ url = self._cw.build_url(rql=rql, vid=self.__regid__,
year=cur_month.year, month=cur_month.month)
self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (xml_escape(url),
umonth))
self.w(u'</tr>')
- _ = self.req._
+ _ = self._cw._
for day_num in xrange(31):
self.w(u'<tr>')
for cur_month in date_range(begin, end, incmonth=1):
@@ -394,15 +395,15 @@
class AMPMMonthCalendarView(MonthCalendarView):
"""this view renders a 3x1 calendars' table"""
- id = 'ampmcalendarmonth'
+ __regid__ = 'ampmcalendarmonth'
title = _('am/pm calendar (month)')
def build_calendar(self, schedule, first_day):
"""method responsible for building *one* HTML calendar"""
- umonth = self.format_date(first_day, '%B %Y') # localized month name
+ umonth = self._cw.format_date(first_day, '%B %Y') # localized month name
rows = [] # each row is: (am,pm), (am,pm) ... week_title
current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.weekday()
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
for daynum in xrange(0, days_in_month(first_day)):
# build cells day
day = first_day + timedelta(daynum)
@@ -415,16 +416,16 @@
AMPM_EMPTY % ("pmCell", "pm")))
# store & reset current row on Sundays
if day.weekday() == 6:
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.isocalendar()[1])
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
+ year=day.year, week=day.isocalendar()[1])
weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
rows.append(current_row)
current_row = []
current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.isocalendar()[1])
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarweek',
+ year=day.year, week=day.isocalendar()[1])
weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
@@ -440,27 +441,27 @@
formatted_rows.append('<tr class="amRow"><td> </td>%s</tr>'% '\n'.join(am_row))
formatted_rows.append('<tr class="pmRow"><td> </td>%s</tr>'% '\n'.join(pm_row))
# tigh everything together
- url = self.build_url(rql=rql, vid='ampmcalendarmonth',
- year=first_day.year, month=first_day.month)
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
+ year=first_day.year, month=first_day.month)
monthlink = '<a href="%s">%s</a>' % (xml_escape(url),
umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
+ return CALENDAR(self._cw) % (monthlink, '\n'.join(formatted_rows))
class AMPMWeekCalendarView(WeekCalendarView):
"""this view renders a 3x1 calendars' table"""
- id = 'ampmcalendarweek'
+ __regid__ = 'ampmcalendarweek'
title = _('am/pm calendar (week)')
def build_calendar(self, schedule, weeks):
- rql = self.rset.printable_rql()
+ rql = self.cw_rset.printable_rql()
w = self.w
- _ = self.req._
+ _ = self._cw._
for monday, sunday in weeks:
- umonth = self.format_date(monday, '%B %Y')
- url = self.build_url(rql=rql, vid='ampmcalendarmonth',
- year=monday.year, month=monday.month)
+ umonth = self._cw.format_date(monday, '%B %Y')
+ url = self._cw.build_url(rql=rql, vid='ampmcalendarmonth',
+ year=monday.year, month=monday.month)
monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
w(u'<tr>%s</tr>' % (
WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
@@ -474,7 +475,7 @@
hours.sort()
w(AMPM_DAYWEEK % (
len(hours), _(WEEKDAYS[day.weekday()]),
- self.format_date(day)))
+ self._cw.format_date(day)))
w(AMPM_WEEK_CELL % (
hours[0].hour, hours[0].minute,
'\n'.join(events[hours[0]])))
@@ -486,7 +487,7 @@
else:
w(AMPM_DAYWEEK_EMPTY % (
_(WEEKDAYS[day.weekday()]),
- self.format_date(day)))
+ self._cw.format_date(day)))
w(WEEK_EMPTY_CELL)
w(u'</tr>')
--- a/web/views/owl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/owl.py Mon Feb 08 13:35:07 2010 +0100
@@ -58,28 +58,27 @@
class OWLView(StartupView):
"""This view export in owl format schema database. It is the TBOX"""
- id = 'owl'
+ __regid__ = 'owl'
title = _('owl')
templatable = False
content_type = 'application/xml' # 'text/xml'
def call(self, writeprefix=True):
- skipmeta = int(self.req.form.get('skipmeta', True))
+ skipmeta = int(self._cw.form.get('skipmeta', True))
if writeprefix:
- self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
+ self.w(OWL_OPENING_ROOT % {'appid': self._cw.vreg.schema.name})
self.visit_schema(skiptypes=skipmeta and schema.SKIP_TYPES or ())
if writeprefix:
self.w(OWL_CLOSING_ROOT)
- def should_display_rschema(self, rschema):
+ def should_display_rschema(self, eschema, rschema, role):
return not rschema in self.skiptypes and (
- rschema.has_local_role('read') or
- rschema.has_perm(self.req, 'read'))
+ rschema.may_have_permission('read', self._cw, eschema, role))
def visit_schema(self, skiptypes):
"""get a layout for a whole schema"""
self.skiptypes = skiptypes
- entities = sorted(eschema for eschema in self.schema.entities()
+ entities = sorted(eschema for eschema in self._cw.vreg.schema.entities()
if not eschema.final or eschema in skiptypes)
self.w(u'<!-- classes definition -->')
for eschema in entities:
@@ -94,13 +93,10 @@
self.w(u'<owl:Class rdf:ID="%s">'% eschema)
self.w(u'<!-- relations -->')
for rschema, targetschemas, role in eschema.relation_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, role):
continue
for oeschema in targetschemas:
- if role == 'subject':
- card = rschema.rproperty(eschema, oeschema, 'cardinality')[0]
- else:
- card = rschema.rproperty(oeschema, eschema, 'cardinality')[1]
+ card = rschema.role_rdef(eschema, oeschema, role).role_cardinality(role)
cardtag = OWL_CARD_MAP[card]
if cardtag:
self.w(u'''<rdfs:subClassOf>
@@ -112,7 +108,7 @@
self.w(u'<!-- attributes -->')
for rschema, aschema in eschema.attribute_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, 'subject'):
continue
self.w(u'''<rdfs:subClassOf>
<owl:Restriction>
@@ -125,7 +121,7 @@
def visit_property_schema(self, eschema):
"""get a layout for property entity OWL schema"""
for rschema, targetschemas, role in eschema.relation_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, role):
continue
for oeschema in targetschemas:
self.w(u'''<owl:ObjectProperty rdf:ID="%s">
@@ -135,7 +131,7 @@
def visit_property_object_schema(self, eschema):
for rschema, aschema in eschema.attribute_definitions():
- if not self.should_display_rschema(rschema):
+ if not self.should_display_rschema(eschema, rschema, 'subject'):
continue
self.w(u'''<owl:DatatypeProperty rdf:ID="%s">
<rdfs:domain rdf:resource="#%s"/>
@@ -145,36 +141,37 @@
class OWLABOXView(EntityView):
'''This view represents a part of the ABOX for a given entity.'''
- id = 'owlabox'
+ __regid__ = 'owlabox'
title = _('owlabox')
templatable = False
content_type = 'application/xml' # 'text/xml'
def call(self):
- self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
- for i in xrange(self.rset.rowcount):
+ self.w(OWL_OPENING_ROOT % {'appid': self._cw.vreg.schema.name})
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(OWL_CLOSING_ROOT)
def cell_call(self, row, col):
- self.wview('owlaboxitem', self.rset, row=row, col=col)
+ self.wview('owlaboxitem', self.cw_rset, row=row, col=col)
class OWLABOXItemView(EntityView):
'''This view represents a part of the ABOX for a given entity.'''
- id = 'owlaboxitem'
+ __regid__ = 'owlaboxitem'
templatable = False
content_type = 'application/xml' # 'text/xml'
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
eschema = entity.e_schema
self.w(u'<%s rdf:ID="%s">' % (eschema, entity.eid))
self.w(u'<!--attributes-->')
for rschema, aschema in eschema.attribute_definitions():
if rschema.meta:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ rdef = rschema.rdef(eschema, aschema)
+ if not rdef.may_have_permission('read', self._cw):
continue
aname = rschema.type
if aname == 'eid':
@@ -189,23 +186,28 @@
for rschema, targetschemas, role in eschema.relation_definitions():
if rschema.meta:
continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ for tschema in targetschemas:
+ rdef = rschema.role_rdef(eschema, tschema, role)
+ if rdef.may_have_permission('read', self._cw):
+ break
+ else:
+ # no read perms to any relation of this type. Skip.
continue
if role == 'object':
attr = 'reverse_%s' % rschema.type
else:
attr = rschema.type
for x in getattr(entity, attr):
- self.w(u'<%s>%s %s</%s>' % (attr, x.id, x.eid, attr))
+ self.w(u'<%s>%s %s</%s>' % (attr, x.__regid__, x.eid, attr))
self.w(u'</%s>'% eschema)
class DownloadOWLSchemaAction(Action):
- id = 'download_as_owl'
+ __regid__ = 'download_as_owl'
__select__ = none_rset() & match_view('schema')
category = 'mainactions'
title = _('download schema as owl')
def url(self):
- return self.build_url('view', vid='owl')
+ return self._cw.build_url('view', vid='owl')
--- a/web/views/plots.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/plots.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,19 +13,15 @@
from simplejson import dumps
from logilab.common import flatten
+from logilab.common.date import datetime2ticks
from logilab.mtconverter import xml_escape
-from cubicweb.utils import make_uid, UStringIO, datetime2ticks
+from cubicweb.utils import make_uid, UStringIO
from cubicweb.appobject import objectify_selector
+from cubicweb.selectors import multi_columns_rset
from cubicweb.web.views import baseviews
@objectify_selector
-def at_least_two_columns(cls, req, rset=None, *args, **kwargs):
- if not rset:
- return 0
- return len(rset.rows[0]) >= 2
-
-@objectify_selector
def all_columns_are_numbers(cls, req, rset=None, *args, **kwargs):
"""accept result set with at least one line and two columns of result
all columns after second must be of numerical types"""
@@ -128,28 +124,28 @@
class PlotView(baseviews.AnyRsetView):
- id = 'plot'
+ __regid__ = 'plot'
title = _('generic plot')
- __select__ = at_least_two_columns() & all_columns_are_numbers()
+ __select__ = multi_columns_rset() & all_columns_are_numbers()
timemode = False
def call(self, width=500, height=400):
# prepare data
- rqlst = self.rset.syntax_tree()
+ rqlst = self.cw_rset.syntax_tree()
# XXX try to make it work with unions
varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:]
- abscissa = [row[0] for row in self.rset]
+ abscissa = [row[0] for row in self.cw_rset]
plots = []
- nbcols = len(self.rset.rows[0])
+ nbcols = len(self.cw_rset.rows[0])
for col in xrange(1, nbcols):
- data = [row[col] for row in self.rset]
+ data = [row[col] for row in self.cw_rset]
plots.append(filterout_nulls(abscissa, data))
plotwidget = FlotPlotWidget(varnames, plots, timemode=self.timemode)
- plotwidget.render(self.req, width, height, w=self.w)
+ plotwidget.render(self._cw, width, height, w=self.w)
class TimeSeriePlotView(PlotView):
- __select__ = at_least_two_columns() & columns_are_date_then_numbers()
+ __select__ = multi_columns_rset() & columns_are_date_then_numbers()
timemode = True
@@ -177,26 +173,26 @@
self.w(u'<img src="%s" />' % xml_escape(piechart.url))
class PieChartView(baseviews.AnyRsetView):
- id = 'piechart'
+ __regid__ = 'piechart'
pieclass = Pie
- __select__ = at_least_two_columns() & second_column_is_number()
+ __select__ = multi_columns_rset() & second_column_is_number()
def _guess_vid(self, row):
- etype = self.rset.description[row][0]
- if self.schema.eschema(etype).final:
+ etype = self.cw_rset.description[row][0]
+ if self._cw.vreg.schema.eschema(etype).final:
return 'final'
return 'textincontext'
def call(self, title=None, width=None, height=None):
labels = []
values = []
- for rowidx, (_, value) in enumerate(self.rset):
+ for rowidx, (_, value) in enumerate(self.cw_rset):
if value is not None:
vid = self._guess_vid(rowidx)
- label = '%s: %s' % (self.view(vid, self.rset, row=rowidx, col=0),
+ label = '%s: %s' % (self.view(vid, self.cw_rset, row=rowidx, col=0),
value)
- labels.append(label.encode(self.req.encoding))
+ labels.append(label.encode(self._cw.encoding))
values.append(value)
pie = PieChartWidget(labels, values, pieclass=self.pieclass,
title=title)
@@ -206,5 +202,5 @@
class PieChart3DView(PieChartView):
- id = 'piechart3D'
+ __regid__ = 'piechart3D'
pieclass = Pie3D
--- a/web/views/primary.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/primary.py Mon Feb 08 13:35:07 2010 +0100
@@ -21,7 +21,7 @@
class PrimaryView(EntityView):
"""the full view of an non final entity"""
- id = 'primary'
+ __regid__ = 'primary'
title = _('primary')
show_attr_label = True
show_rel_label = True
@@ -39,15 +39,15 @@
return []
def cell_call(self, row, col):
- self.row = row
- self.maxrelated = self.req.property_value('navigation.related-limit')
- entity = self.complete_entity(row, col)
+ self.cw_row = row
+ self.cw_col = col
+ self.maxrelated = self._cw.property_value('navigation.related-limit')
+ entity = self.cw_rset.complete_entity(row, col)
self.render_entity(entity)
def render_entity(self, entity):
self.render_entity_title(entity)
- # XXX uncomment this in 3.6
- #self.render_entity_toolbox(entity)
+ self.render_entity_toolbox(entity)
# entity's attributes and relations, excluding meta data
# if the entity isn't meta itself
if self.is_primary():
@@ -77,10 +77,10 @@
def content_navigation_components(self, context):
self.w(u'<div class="%s">' % context)
- for comp in self.vreg['contentnavigation'].possible_vobjects(
- self.req, rset=self.rset, row=self.row, view=self, context=context):
+ for comp in self._cw.vreg['contentnavigation'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, row=self.cw_row, view=self, context=context):
try:
- comp.render(w=self.w, row=self.row, view=self)
+ comp.render(w=self.w, row=self.cw_row, view=self)
except NotImplementedError:
warn('component %s doesnt implement cell_call, please update'
% comp.__class__, DeprecationWarning)
@@ -94,7 +94,7 @@
if self.is_primary():
self.w(u'<h1>%s</h1>' % title)
else:
- atitle = self.req._('follow this link for more information on this %s') % entity.dc_type()
+ atitle = self._cw._('follow this link for more information on this %s') % entity.dc_type()
self.w(u'<h4><a href="%s" title="%s">%s</a></h4>'
% (entity.absolute_url(), atitle, title))
@@ -126,7 +126,7 @@
else:
rset = self._relation_rset(entity, rschema, role, dispctrl)
if rset:
- value = self.view(vid, rset)
+ value = self._cw.view(vid, rset)
else:
value = None
if self.skip_none and (value is None or value == ''):
@@ -170,7 +170,7 @@
self.w(u'</div>')
else:
try:
- box.render(w=self.w, row=self.row)
+ box.render(w=self.w, row=self.cw_row)
except NotImplementedError:
# much probably a context insensitive box, which only implements
# .call() and not cell_call()
@@ -182,11 +182,11 @@
rset = self._relation_rset(entity, rschema, role, dispctrl)
if not rset:
continue
- label = display_name(self.req, rschema.type, role)
+ label = display_name(self._cw, rschema.type, role)
vid = dispctrl.get('vid', 'sidebox')
sideboxes.append( (label, rset, vid, dispctrl) )
- sideboxes += self.vreg['boxes'].possible_vobjects(
- self.req, rset=self.rset, row=self.row, view=self,
+ sideboxes += self._cw.vreg['boxes'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, row=self.cw_row, view=self,
context='incontext')
# XXX since we've two sorted list, it may be worth using bisect
def get_order(x):
@@ -195,7 +195,7 @@
# default to 1000 so view boxes occurs after component boxes
return x[-1].get('order', 1000)
# x is a component box
- return x.propval('order')
+ return x.cw_propval('order')
return sorted(sideboxes, key=get_order)
def _section_def(self, entity, where):
@@ -229,7 +229,7 @@
def _render_relation(self, dispctrl, rset, defaultvid):
self.w(u'<div class="section">')
if dispctrl.get('showlabel', self.show_rel_label):
- self.w(u'<h4>%s</h4>' % self.req._(dispctrl['label']))
+ self.w(u'<h4>%s</h4>' % self._cw._(dispctrl['label']))
self.wview(dispctrl.get('vid', defaultvid), rset,
initargs={'dispctrl': dispctrl})
self.w(u'</div>')
@@ -241,41 +241,40 @@
else:
showlabel = dispctrl.get('showlabel', self.show_rel_label)
if dispctrl.get('label'):
- label = self.req._(dispctrl.get('label'))
+ label = self._cw._(dispctrl.get('label'))
else:
- label = display_name(self.req, rschema.type, role)
+ label = display_name(self._cw, rschema.type, role)
self.field(label, value, show_label=showlabel, tr=False, table=table)
class RelatedView(EntityView):
- id = 'autolimited'
+ __regid__ = 'autolimited'
def call(self, **kwargs):
# nb: rset retreived using entity.related with limit + 1 if any
# because of that, we known that rset.printable_rql() will return
# rql with no limit set anyway (since it's handled manually)
- if 'dispctrl' in self.extra_kwargs:
- limit = self.extra_kwargs['dispctrl'].get('limit')
+ if 'dispctrl' in self.cw_extra_kwargs:
+ limit = self.cw_extra_kwargs['dispctrl'].get('limit')
else:
limit = None
- # if not too many entities, show them all in a list
- if limit is None or self.rset.rowcount <= limit:
- if self.rset.rowcount == 1:
- self.wview('incontext', self.rset, row=0)
- elif 1 < self.rset.rowcount <= 5:
- self.wview('csv', self.rset)
+ if limit is None or self.cw_rset.rowcount <= limit:
+ if self.cw_rset.rowcount == 1:
+ self.wview('incontext', self.cw_rset, row=0)
+ elif 1 < self.cw_rset.rowcount <= 5:
+ self.wview('csv', self.cw_rset)
else:
self.w(u'<div>')
- self.wview('simplelist', self.rset)
+ self.wview('simplelist', self.cw_rset)
self.w(u'</div>')
# else show links to display related entities
else:
- rql = self.rset.printable_rql()
- self.rset.limit(limit) # remove extra entity
+ rql = self.cw_rset.printable_rql()
+ self.cw_rset.limit(limit) # remove extra entity
self.w(u'<div>')
- self.wview('simplelist', self.rset)
- self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
- self.req._('see them all')))
+ self.wview('simplelist', self.cw_rset)
+ self.w(u'[<a href="%s">%s</a>]' % (self._cw.build_url(rql=rql),
+ self._cw._('see them all')))
self.w(u'</div>')
@@ -283,11 +282,11 @@
"""use this view for attributes whose value is an url and that you want
to display as clickable link
"""
- id = 'urlattr'
+ __regid__ = 'urlattr'
__select__ = EntityView.__select__ & match_kwargs('rtype')
def cell_call(self, row, col, rtype, **kwargs):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
url = entity.printable_value(rtype)
if url:
self.w(u'<a href="%s">%s</a>' % (url, url))
@@ -310,5 +309,5 @@
for attr in ('name', 'final'):
_pvs.tag_attribute(('CWEType', attr), 'hidden')
-for attr in ('name', 'final', 'symetric', 'inlined'):
+for attr in ('name', 'final', 'symmetric', 'inlined'):
_pvs.tag_attribute(('CWRType', attr), 'hidden')
--- a/web/views/pyviews.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/pyviews.py Mon Feb 08 13:35:07 2010 +0100
@@ -11,12 +11,12 @@
from cubicweb.selectors import match_kwargs
class PyValTableView(View):
- id = 'pyvaltable'
+ __regid__ = 'pyvaltable'
__select__ = match_kwargs('pyvalue')
def call(self, pyvalue, headers=None):
if headers is None:
- headers = self.req.form.get('headers')
+ headers = self._cw.form.get('headers')
self.w(u'<table class="listing">\n')
if headers:
self.w(u'<tr>')
@@ -32,7 +32,7 @@
class PyValListView(View):
- id = 'pyvallist'
+ __regid__ = 'pyvallist'
__select__ = match_kwargs('pyvalue')
def call(self, pyvalue):
--- a/web/views/schema.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/schema.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,22 +14,19 @@
from cubicweb.selectors import (implements, yes, match_user_groups,
has_related_entities)
-from cubicweb.schema import META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES
+from cubicweb.schema import (META_RTYPES, SCHEMA_TYPES, SYSTEM_RTYPES,
+ WORKFLOW_TYPES, INTERNAL_TYPES)
from cubicweb.schemaviewer import SchemaViewer
from cubicweb.view import EntityView, StartupView
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.web import action, facet, uicfg
from cubicweb.web.views import TmpFileViewMixin
from cubicweb.web.views import primary, baseviews, tabs, management
ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
-SKIP_TYPES = ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES
-SKIP_TYPES.update(set(('Transition', 'State', 'TrInfo', 'Workflow',
- 'WorkflowTransition', 'BaseTransition',
- 'SubWorkflowExitPoint',
- 'CWUser', 'CWGroup',
- 'CWCache', 'CWProperty', 'CWPermission',
- 'ExternalUri')))
+SKIP_TYPES = (ALWAYS_SKIP_TYPES | META_RTYPES | SYSTEM_RTYPES | WORKFLOW_TYPES
+ | INTERNAL_TYPES)
+SKIP_TYPES.update(set(('CWUser', 'CWGroup')))
def skip_types(req):
if int(req.form.get('skipmeta', True)):
@@ -44,53 +41,53 @@
# global schema view ###########################################################
class SchemaView(tabs.TabsMixin, StartupView):
- id = 'schema'
+ __regid__ = 'schema'
title = _('instance schema')
tabs = [_('schema-text'), _('schema-image')]
default_tab = 'schema-text'
def call(self):
"""display schema information"""
- self.req.add_js('cubicweb.ajax.js')
- self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
+ self._cw.add_js('cubicweb.ajax.js')
+ self._cw.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
self.render_tabs(self.tabs, self.default_tab)
class SchemaTabImageView(StartupView):
- id = 'schema-image'
+ __regid__ = 'schema-image'
def call(self):
self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
u'meta-data, but you can also display a <a href="%s">complete '
u'schema with meta-data</a>.</div>')
- % xml_escape(self.build_url('view', vid='schemagraph', skipmeta=0)))
+ % xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=0)))
self.w(u'<img src="%s" alt="%s"/>\n' % (
- xml_escape(self.req.build_url('view', vid='schemagraph', skipmeta=1)),
- self.req._("graphical representation of the instance'schema")))
+ xml_escape(self._cw.build_url('view', vid='schemagraph', skipmeta=1)),
+ self._cw._("graphical representation of the instance'schema")))
class SchemaTabTextView(StartupView):
- id = 'schema-text'
+ __regid__ = 'schema-text'
def call(self):
- rset = self.req.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
+ rset = self._cw.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
'X final FALSE')
self.wview('table', rset, displayfilter=True)
class ManagerSchemaPermissionsView(StartupView, management.SecurityViewMixIn):
- id = 'schema-security'
+ __regid__ = 'schema-security'
__select__ = StartupView.__select__ & match_user_groups('managers')
def call(self, display_relations=True):
- self.req.add_css('cubicweb.acl.css')
- skiptypes = skip_types(self.req)
+ self._cw.add_css('cubicweb.acl.css')
+ skiptypes = skip_types(self._cw)
formparams = {}
- formparams['sec'] = self.id
+ formparams['sec'] = self.__regid__
if not skiptypes:
formparams['skipmeta'] = u'0'
- schema = self.schema
+ schema = self._cw.vreg.schema
# compute entities
entities = sorted(eschema for eschema in schema.entities()
if not (eschema.final or eschema in skiptypes))
@@ -103,20 +100,20 @@
else:
relations = []
# index
- _ = self.req._
+ _ = self._cw._
self.w(u'<div id="schema_security"><a id="index" href="index"/>')
self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
self.w(u'<h4>%s</h4>' % _('Entities').capitalize())
ents = []
for eschema in sorted(entities):
- url = xml_escape(self.build_url('schema', **formparams))
+ url = xml_escape(self._cw.build_url('schema', **formparams))
ents.append(u'<a class="grey" href="%s#%s">%s</a> (%s)' % (
url, eschema.type, eschema.type, _(eschema.type)))
self.w(u', '.join(ents))
self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
rels = []
for rschema in sorted(relations):
- url = xml_escape(self.build_url('schema', **formparams))
+ url = xml_escape(self._cw.build_url('schema', **formparams))
rels.append(u'<a class="grey" href="%s#%s">%s</a> (%s), ' % (
url , rschema.type, rschema.type, _(rschema.type)))
self.w(u', '.join(ents))
@@ -128,18 +125,18 @@
self.w(u'</div>')
def display_entities(self, entities, formparams):
- _ = self.req._
+ _ = self._cw._
self.w(u'<a id="entities" href="entities"/>')
self.w(u'<h2 class="schema">%s</h2>' % _('permissions for entities').capitalize())
for eschema in entities:
self.w(u'<a id="%s" href="%s"/>' % (eschema.type, eschema.type))
self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
- url = xml_escape(self.build_url('schema', **formparams) + '#index')
+ url = xml_escape(self._cw.build_url('schema', **formparams) + '#index')
self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
- url, self.req.external_resource('UP_ICON'), _('up')))
+ url, self._cw.external_resource('UP_ICON'), _('up')))
self.w(u'</h3>')
self.w(u'<div style="margin: 0px 1.5em">')
- self.schema_definition(eschema, link=False)
+ self._cw.vreg.schema_definition(eschema, link=False)
# display entity attributes only if they have some permissions modified
modified_attrs = []
for attr, etype in eschema.attribute_definitions():
@@ -151,19 +148,19 @@
self.w(u'<div style="margin: 0px 6em">')
for attr in modified_attrs:
self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attr.type, _(attr.type)))
- self.schema_definition(attr, link=False)
+ self._cw.vreg.schema_definition(attr, link=False)
self.w(u'</div>')
def display_relations(self, relations, formparams):
- _ = self.req._
+ _ = self._cw._
self.w(u'<a id="relations" href="relations"/>')
self.w(u'<h2 class="schema">%s </h2>' % _('permissions for relations').capitalize())
for rschema in relations:
self.w(u'<a id="%s" href="%s"/>' % (rschema.type, rschema.type))
self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
- url = xml_escape(self.build_url('schema', **formparams) + '#index')
+ url = xml_escape(self._cw.build_url('schema', **formparams) + '#index')
self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
- url, self.req.external_resource('UP_ICON'), _('up')))
+ url, self._cw.external_resource('UP_ICON'), _('up')))
self.w(u'</h3>')
self.w(u'<div style="margin: 0px 1.5em">')
subjects = [str(subj) for subj in rschema.subjects()]
@@ -175,17 +172,17 @@
_('object_plural:'),
', '.join(str(obj) for obj in rschema.objects()),
', '.join(_(str(obj)) for obj in rschema.objects())))
- self.schema_definition(rschema, link=False)
+ self._cw.vreg.schema_definition(rschema, link=False)
self.w(u'</div>')
class SchemaUreportsView(StartupView):
- id = 'schema-block'
+ __regid__ = 'schema-block'
def call(self):
- viewer = SchemaViewer(self.req)
- layout = viewer.visit_schema(self.schema, display_relations=True,
- skiptypes=skip_types(self.req))
+ viewer = SchemaViewer(self._cw)
+ layout = viewer.visit_schema(self._cw.vreg.schema, display_relations=True,
+ skiptypes=skip_types(self._cw))
self.w(uilib.ureport_as_html(layout))
@@ -207,7 +204,7 @@
__select__ = implements('CWEType')
def cell_call(self, row, col, **kwargs):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
final = entity.final
if final:
self.w(u'<em class="finalentity">')
@@ -231,13 +228,13 @@
class CWETypeSTextView(EntityView):
- id = 'cwetype-schema-text'
+ __regid__ = 'cwetype-schema-text'
__select__ = EntityView.__select__ & implements('CWEType')
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
self.w(u'<h2>%s</h2>' % _('Attributes'))
- rset = self.req.execute('Any N,F,D,I,J,DE,A '
+ rset = self._cw.execute('Any N,F,D,I,J,DE,A '
'ORDERBY AA WHERE A is CWAttribute, '
'A ordernum AA, A defaultval D, '
'A description DE, '
@@ -248,7 +245,7 @@
{'x': entity.eid})
self.wview('editable-table', rset, 'null', displayfilter=True)
self.w(u'<h2>%s</h2>' % _('Relations'))
- rset = self.req.execute(
+ rset = self._cw.execute(
'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
'WHERE A is CWRelation, A description D, A composite K?, '
'A relation_type R, R name RN, A to_entity TT, TT name TTN, '
@@ -256,7 +253,7 @@
{'x': entity.eid})
self.wview('editable-table', rset, 'null', displayfilter=True,
displaycols=range(6), mainindex=5)
- rset = self.req.execute(
+ rset = self._cw.execute(
'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
'WHERE A is CWRelation, A description D, A composite K?, '
'A relation_type R, R name RN, A from_entity TT, TT name TTN, '
@@ -267,56 +264,56 @@
class CWETypeSImageView(EntityView):
- id = 'cwetype-schema-image'
+ __regid__ = 'cwetype-schema-image'
__select__ = EntityView.__select__ & implements('CWEType')
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
url = entity.absolute_url(vid='schemagraph')
self.w(u'<img src="%s" alt="%s"/>' % (
xml_escape(url),
- xml_escape(self.req._('graphical schema for %s') % entity.name)))
+ xml_escape(self._cw._('graphical schema for %s') % entity.name)))
class CWETypeSPermView(EntityView):
- id = 'cwetype-schema-permissions'
+ __regid__ = 'cwetype-schema-permissions'
__select__ = EntityView.__select__ & implements('CWEType')
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
- _ = self.req._
+ entity = self.cw_rset.get_entity(row, col)
+ _ = self._cw._
self.w(u'<h2>%s</h2>' % _('Add permissions'))
- rset = self.req.execute('Any P WHERE X add_permission P, '
+ rset = self._cw.execute('Any P WHERE X add_permission P, '
'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Read permissions'))
- rset = self.req.execute('Any P WHERE X read_permission P, '
+ rset = self._cw.execute('Any P WHERE X read_permission P, '
'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Update permissions'))
- rset = self.req.execute('Any P WHERE X update_permission P, '
+ rset = self._cw.execute('Any P WHERE X update_permission P, '
'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Delete permissions'))
- rset = self.req.execute('Any P WHERE X delete_permission P, '
+ rset = self._cw.execute('Any P WHERE X delete_permission P, '
'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
class CWETypeSWorkflowView(EntityView):
- id = 'cwetype-workflow'
+ __regid__ = 'cwetype-workflow'
__select__ = (EntityView.__select__ & implements('CWEType') &
has_related_entities('workflow_of', 'object'))
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
if entity.default_workflow:
wf = entity.default_workflow[0]
- self.w(u'<h1>%s (%s)</h1>' % (wf.name, self.req._('default')))
+ self.w(u'<h1>%s (%s)</h1>' % (wf.name, self._cw._('default')))
self.wf_image(wf)
for altwf in entity.reverse_workflow_of:
if altwf.eid == wf.eid:
@@ -327,7 +324,8 @@
def wf_image(self, wf):
self.w(u'<img src="%s" alt="%s"/>' % (
xml_escape(wf.absolute_url(vid='wfgraph')),
- xml_escape(self.req._('graphical representation of %s') % wf.name)))
+ xml_escape(self._cw._('graphical representation of %s') % wf.name)))
+
# CWRType ######################################################################
@@ -338,12 +336,12 @@
def render_entity_attributes(self, entity):
super(CWRTypeSchemaView, self).render_entity_attributes(entity)
- rschema = self.vreg.schema.rschema(entity.name)
- viewer = SchemaViewer(self.req)
+ rschema = self._cw.vreg.schema.rschema(entity.name)
+ viewer = SchemaViewer(self._cw)
layout = viewer.visit_relationschema(rschema, title=False)
self.w(uilib.ureport_as_html(layout))
if not rschema.final:
- msg = self.req._('graphical schema for %s') % entity.name
+ msg = self._cw._('graphical schema for %s') % entity.name
self.w(tags.img(src=entity.absolute_url(vid='schemagraph'),
alt=msg))
@@ -352,18 +350,16 @@
class RestrictedSchemaVisitorMixIn(object):
def __init__(self, req, *args, **kwargs):
- self.req = req
+ self._cw = req
super(RestrictedSchemaVisitorMixIn, self).__init__(*args, **kwargs)
def should_display_schema(self, rschema):
return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
- and (rschema.has_local_role('read')
- or rschema.has_perm(self.req, 'read')))
+ and rschema.may_have_permission('read', self._cw))
- def should_display_attr(self, rschema):
- return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(rschema)
- and (rschema.has_local_role('read')
- or rschema.has_perm(self.req, 'read')))
+ def should_display_attr(self, eschema, rschema):
+ return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(eschema, rschema)
+ and eschema.rdef(rschema).may_have_permission('read', self._cw))
class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
@@ -379,28 +375,28 @@
class SchemaImageView(TmpFileViewMixin, StartupView):
- id = 'schemagraph'
+ __regid__ = 'schemagraph'
content_type = 'image/png'
def _generate(self, tmpfile):
"""display global schema information"""
- print 'skipedtypes', skip_types(self.req)
- visitor = FullSchemaVisitor(self.req, self.schema,
- skiptypes=skip_types(self.req))
+ print 'skipedtypes', skip_types(self._cw)
+ visitor = FullSchemaVisitor(self._cw, self._cw.vreg.schema,
+ skiptypes=skip_types(self._cw))
s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
class CWETypeSchemaImageView(TmpFileViewMixin, EntityView):
- id = 'schemagraph'
+ __regid__ = 'schemagraph'
__select__ = implements('CWEType')
content_type = 'image/png'
def _generate(self, tmpfile):
"""display schema information for an entity"""
- entity = self.rset.get_entity(self.row, self.col)
- eschema = self.vreg.schema.eschema(entity.name)
- visitor = OneHopESchemaVisitor(self.req, eschema,
- skiptypes=skip_types(self.req))
+ entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
+ eschema = self._cw.vreg.schema.eschema(entity.name)
+ visitor = OneHopESchemaVisitor(self._cw, eschema,
+ skiptypes=skip_types(self._cw))
s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
@@ -409,21 +405,21 @@
def _generate(self, tmpfile):
"""display schema information for an entity"""
- entity = self.rset.get_entity(self.row, self.col)
- rschema = self.vreg.schema.rschema(entity.name)
- visitor = OneHopRSchemaVisitor(self.req, rschema)
+ entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
+ rschema = self._cw.vreg.schema.rschema(entity.name)
+ visitor = OneHopRSchemaVisitor(self._cw, rschema)
s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
# misc: facets, actions ########################################################
class CWFinalFacet(facet.AttributeFacet):
- id = 'cwfinal-facet'
+ __regid__ = 'cwfinal-facet'
__select__ = facet.AttributeFacet.__select__ & implements('CWEType', 'CWRType')
rtype = 'final'
class ViewSchemaAction(action.Action):
- id = 'schema'
+ __regid__ = 'schema'
__select__ = yes()
title = _("site schema")
@@ -431,4 +427,4 @@
order = 30
def url(self):
- return self.build_url(self.id)
+ return self._cw.build_url(self.__regid__)
--- a/web/views/sessions.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/sessions.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,8 +15,8 @@
class InMemoryRepositorySessionManager(AbstractSessionManager):
"""manage session data associated to a session identifier"""
- def __init__(self):
- AbstractSessionManager.__init__(self)
+ def __init__(self, *args, **kwargs):
+ AbstractSessionManager.__init__(self, *args, **kwargs)
# XXX require a RepositoryAuthenticationManager which violates
# authenticate interface by returning a session instead of a user
#assert isinstance(self.authmanager, RepositoryAuthenticationManager)
--- a/web/views/sparql.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/sparql.py Mon Feb 08 13:35:07 2010 +0100
@@ -23,38 +23,38 @@
Sparql2rqlTranslator = None
class SparqlForm(forms.FieldsForm):
- id = 'sparql'
+ __regid__ = 'sparql'
sparql = formfields.StringField(help=_('type here a sparql query'))
resultvid = formfields.StringField(choices=((_('table'), 'table'),
(_('sparql xml'), 'sparqlxml')),
widget=fwdgs.Radio,
- initial='table')
+ value='table')
form_buttons = [fwdgs.SubmitButton()]
@property
def action(self):
- return self.req.url()
+ return self._cw.url()
class SparqlFormView(form.FormViewMixIn, StartupView):
- id = 'sparql'
+ __regid__ = 'sparql'
def call(self):
- form = self.vreg.select('forms', 'sparql', self.req)
+ form = self._cw.vreg.select('forms', 'sparql', self._cw)
self.w(form.render())
- sparql = self.req.form.get('sparql')
- vid = self.req.form.get('resultvid', 'table')
+ sparql = self._cw.form.get('sparql')
+ vid = self._cw.form.get('resultvid', 'table')
if sparql:
try:
- qinfo = Sparql2rqlTranslator(self.schema).translate(sparql)
+ qinfo = Sparql2rqlTranslator(self._cw.vreg.schema).translate(sparql)
except rql.TypeResolverException, ex:
- self.w(self.req._('can not resolve entity types:') + u' ' + unicode('ex'))
+ self.w(self._cw._('can not resolve entity types:') + u' ' + unicode('ex'))
except UnsupportedQuery:
- self.w(self.req._('we are not yet ready to handle this query'))
+ self.w(self._cw._('we are not yet ready to handle this query'))
except xy.UnsupportedVocabulary, ex:
- self.w(self.req._('unknown vocabulary:') + u' ' + unicode('ex'))
+ self.w(self._cw._('unknown vocabulary:') + u' ' + unicode('ex'))
if vid == 'sparqlxml':
- url = self.build_url('view', rql=qinfo.finalize(), vid=vid)
+ url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
raise Redirect(url)
- rset = self.req.execute(qinfo.finalize())
+ rset = self._cw.execute(qinfo.finalize())
self.wview(vid, rset, 'null')
@@ -81,16 +81,16 @@
class SparqlResultXmlView(AnyRsetView):
"""The spec can be found here: http://www.w3.org/TR/rdf-sparql-XMLres/
"""
- id = 'sparqlxml'
+ __regid__ = 'sparqlxml'
content_type = 'application/sparql-results+xml'
templatable = False
def call(self):
# XXX handle UNION
- rqlst = self.rset.syntax_tree().children[0]
+ rqlst = self.cw_rset.syntax_tree().children[0]
varnames = [var.name for var in rqlst.selection]
results = E.results()
- for rowidx in xrange(len(self.rset)):
+ for rowidx in xrange(len(self.cw_rset)):
result = E.result()
for colidx, varname in enumerate(varnames):
result.append(self.cell_binding(rowidx, colidx, varname))
@@ -101,21 +101,21 @@
self.w(etree.tostring(sparql, encoding=unicode, pretty_print=True))
def cell_binding(self, row, col, varname):
- celltype = self.rset.description[row][col]
- if self.schema.eschema(celltype).final:
- cellcontent = self.view('cell', self.rset, row=row, col=col)
+ celltype = self.cw_rset.description[row][col]
+ if self._cw.vreg.schema.eschema(celltype).final:
+ cellcontent = self.view('cell', self.cw_rset, row=row, col=col)
return E.binding(E.literal(cellcontent,
datatype=xmlschema(celltype)),
name=varname)
else:
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
return E.binding(E.uri(entity.absolute_url()), name=varname)
def set_request_content_type(self):
"""overriden to set the correct filetype and filename"""
- self.req.set_content_type(self.content_type,
+ self._cw.set_content_type(self.content_type,
filename='sparql.xml',
- encoding=self.req.encoding)
+ encoding=self._cw.encoding)
def registration_callback(vreg):
if Sparql2rqlTranslator is not None:
--- a/web/views/startup.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/startup.py Mon Feb 08 13:35:07 2010 +0100
@@ -18,27 +18,17 @@
from cubicweb.web import ajax_replace_url, uicfg, httpcache
class ManageView(StartupView):
- id = 'manage'
+ __regid__ = 'manage'
title = _('manage')
http_cache_manager = httpcache.EtagHTTPCacheManager
add_etype_links = ()
- @classmethod
- def vreg_initialization_completed(cls):
- for eschema in cls.schema.entities():
- if eschema.schema_entity():
- uicfg.indexview_etype_section.setdefault(eschema, 'schema')
- elif eschema.is_subobject(strict=True):
- uicfg.indexview_etype_section.setdefault(eschema, 'subobject')
- else:
- uicfg.indexview_etype_section.setdefault(eschema, 'application')
-
def display_folders(self):
return False
def call(self, **kwargs):
"""The default view representing the instance's management"""
- self.req.add_css('cubicweb.manageview.css')
+ self._cw.add_css('cubicweb.manageview.css')
self.w(u'<div>\n')
if not self.display_folders():
self._main_index()
@@ -53,10 +43,10 @@
self.w(u'</div>\n')
def _main_index(self):
- req = self.req
+ req = self._cw
manager = req.user.matching_groups('managers')
- if not manager and 'Card' in self.schema:
- rset = self.req.execute('Card X WHERE X wikiid "index"')
+ if not manager and 'Card' in self._cw.vreg.schema:
+ rset = self._cw.execute('Card X WHERE X wikiid "index"')
else:
rset = None
if rset:
@@ -65,56 +55,56 @@
self.entities()
self.w(u'<div class="hr"> </div>')
self.startup_views()
- if manager and 'Card' in self.schema:
+ if manager and 'Card' in self._cw.vreg.schema:
self.w(u'<div class="hr"> </div>')
if rset:
href = rset.get_entity(0, 0).absolute_url(vid='edition')
- label = self.req._('edit the index page')
+ label = self._cw._('edit the index page')
else:
href = req.build_url('view', vid='creation', etype='Card', wikiid='index')
- label = self.req._('create an index page')
+ label = self._cw._('create an index page')
self.w(u'<br/><a href="%s">%s</a>\n' % (xml_escape(href), label))
def folders(self):
- self.w(u'<h4>%s</h4>\n' % self.req._('Browse by category'))
- self.vreg['views'].select('tree', self.req).render(w=self.w)
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Browse by category'))
+ self._cw.vreg['views'].select('tree', self._cw).render(w=self.w)
def create_links(self):
self.w(u'<ul class="createLink">')
for etype in self.add_etype_links:
eschema = self.schema.eschema(etype)
- if eschema.has_perm(self.req, 'add'):
+ if eschema.has_perm(self._cw, 'add'):
self.w(u'<li><a href="%s">%s</a></li>' % (
- self.req.build_url('add/%s' % eschema),
- self.req.__('add a %s' % eschema).capitalize()))
+ self._cw.build_url('add/%s' % eschema),
+ self._cw.__('add a %s' % eschema).capitalize()))
self.w(u'</ul>')
def startup_views(self):
- self.w(u'<h4>%s</h4>\n' % self.req._('Startup views'))
+ self.w(u'<h4>%s</h4>\n' % self._cw._('Startup views'))
self.startupviews_table()
def startupviews_table(self):
- for v in self.vreg['views'].possible_views(self.req, None):
- if v.category != 'startupview' or v.id in ('index', 'tree', 'manage'):
+ for v in self._cw.vreg['views'].possible_views(self._cw, None):
+ if v.category != 'startupview' or v.__regid__ in ('index', 'tree', 'manage'):
continue
self.w('<p><a href="%s">%s</a></p>' % (
- xml_escape(v.url()), xml_escape(self.req._(v.title).capitalize())))
+ xml_escape(v.url()), xml_escape(self._cw._(v.title).capitalize())))
def entities(self):
- schema = self.schema
- self.w(u'<h4>%s</h4>\n' % self.req._('The repository holds the following entities'))
- manager = self.req.user.matching_groups('managers')
+ schema = self._cw.vreg.schema
+ self.w(u'<h4>%s</h4>\n' % self._cw._('The repository holds the following entities'))
+ manager = self._cw.user.matching_groups('managers')
self.w(u'<table class="startup">')
if manager:
- self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('application entities'))
+ self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('application entities'))
self.entity_types_table(eschema for eschema in schema.entities()
if uicfg.indexview_etype_section.get(eschema) == 'application')
if manager:
- self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('system entities'))
+ self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('system entities'))
self.entity_types_table(eschema for eschema in schema.entities()
if uicfg.indexview_etype_section.get(eschema) == 'system')
if 'CWAttribute' in schema: # check schema support
- self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self.req._('schema entities'))
+ self.w(u'<tr><th colspan="4">%s</th></tr>\n' % self._cw._('schema entities'))
self.entity_types_table(eschema for eschema in schema.entities()
if uicfg.indexview_etype_section.get(eschema) == 'schema')
self.w(u'</table>')
@@ -138,15 +128,18 @@
"""return a list of formatted links to get a list of entities of
a each entity's types
"""
- req = self.req
+ req = self._cw
for eschema in eschemas:
- if eschema.final or (not eschema.has_perm(req, 'read') and
- not eschema.has_local_role('read')):
+ if eschema.final or not eschema.may_have_permission('read', req):
continue
etype = eschema.type
- label = display_name(req, etype, 'plural')
nb = req.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
- url = self.build_url(etype)
+ if nb > 1:
+ label = display_name(req, etype, 'plural')
+ else:
+ label = display_name(req, etype)
+ nb = req.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
+ url = self._cw.build_url(etype)
etypelink = u' <a href="%s">%s</a> (%d)' % (
xml_escape(url), label, nb)
yield (label, etypelink, self.add_entity_link(eschema, req))
@@ -157,35 +150,13 @@
return u''
return u'[<a href="%s" title="%s">+</a>]' % (
xml_escape(self.create_url(eschema.type)),
- self.req.__('add a %s' % eschema))
+ self._cw.__('add a %s' % eschema))
class IndexView(ManageView):
- id = 'index'
+ __regid__ = 'index'
title = _('view_index')
def display_folders(self):
- return 'Folder' in self.schema and self.req.execute('Any COUNT(X) WHERE X is Folder')[0][0]
-
-
-class RegistryView(StartupView):
- id = 'registry'
- title = _('registry')
- __select__ = StartupView.__select__ & match_user_groups('managers')
+ return 'Folder' in self._cw.vreg.schema and self._cw.execute('Any COUNT(X) WHERE X is Folder')[0][0]
- def call(self, **kwargs):
- """The default view representing the instance's management"""
- self.w(u'<h1>%s</h1>' % _("Registry's content"))
- keys = sorted(self.vreg)
- self.w(u'<p>%s</p>\n' % ' - '.join('<a href="/_registry#%s">%s</a>' % (key, key) for key in keys))
- for key in keys:
- self.w(u'<h2><a name="%s">%s</a></h2>' % (key,key))
- items = self.vreg[key].items()
- if items:
- self.w(u'<table><tbody>')
- for key, value in sorted(items):
- self.w(u'<tr><td>%s</td><td>%s</td></tr>' % (key, xml_escape(repr(value))))
- self.w(u'</tbody></table>\n')
- else:
- self.w(u'<p>Empty</p>\n')
-
--- a/web/views/tableview.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/tableview.py Mon Feb 08 13:35:07 2010 +0100
@@ -15,28 +15,28 @@
from cubicweb.selectors import nonempty_rset, match_form_params
from cubicweb.utils import make_uid
from cubicweb.view import EntityView, AnyRsetView
-from cubicweb.common import tags
-from cubicweb.common.uilib import toggle_action, limitsize, htmlescape
+from cubicweb import tags
+from cubicweb.uilib import toggle_action, limitsize, htmlescape
from cubicweb.web import jsonize
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
PopupBoxMenu, BoxLink)
from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
class TableView(AnyRsetView):
- id = 'table'
+ __regid__ = 'table'
title = _('table')
finalview = 'final'
def form_filter(self, divid, displaycols, displayactions, displayfilter,
hidden=True):
- rqlst = self.rset.syntax_tree()
+ rqlst = self.cw_rset.syntax_tree()
# union not yet supported
if len(rqlst.children) != 1:
return ()
rqlst.save_state()
- mainvar, baserql = prepare_facets_rqlst(rqlst, self.rset.args)
- wdgs = [facet.get_widget() for facet in self.vreg['facets'].possible_vobjects(
- self.req, rset=self.rset, context='tablefilter',
+ mainvar, baserql = prepare_facets_rqlst(rqlst, self.cw_rset.args)
+ wdgs = [facet.get_widget() for facet in self._cw.vreg['facets'].poss_visible_objects(
+ self._cw, rset=self.cw_rset, context='tablefilter',
filtered_variable=mainvar)]
wdgs = [wdg for wdg in wdgs if wdg is not None]
rqlst.recover()
@@ -52,8 +52,8 @@
"""display a form to filter table's content. This should only
occurs when a context eid is given
"""
- self.req.add_css('cubicweb.facets.css')
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
+ self._cw.add_css('cubicweb.facets.css')
+ self._cw.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
# drop False / None values from vidargs
vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
@@ -61,7 +61,7 @@
self.w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
self.w(u'<input type="hidden" name="divid" value="%s" />' % divid)
self.w(u'<input type="hidden" name="fromformfilter" value="1" />')
- filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets),
+ filter_hiddens(self.w, facets=','.join(wdg.facet.__regid__ for wdg in fwidgets),
baserql=baserql)
self.w(u'<table class="filter">\n')
self.w(u'<tr>\n')
@@ -78,8 +78,8 @@
"""returns the index of the first non-attribute variable among the RQL
selected variables
"""
- eschema = self.vreg.schema.eschema
- for i, etype in enumerate(self.rset.description[0]):
+ eschema = self._cw.vreg.schema.eschema
+ for i, etype in enumerate(self.cw_rset.description[0]):
try:
if not eschema(etype).final:
return i
@@ -89,10 +89,10 @@
def displaycols(self, displaycols):
if displaycols is None:
- if 'displaycols' in self.req.form:
- displaycols = [int(idx) for idx in self.req.form['displaycols']]
+ if 'displaycols' in self._cw.form:
+ displaycols = [int(idx) for idx in self._cw.form['displaycols']]
else:
- displaycols = range(len(self.rset.syntax_tree().children[0].selection))
+ displaycols = range(len(self.cw_rset.syntax_tree().children[0].selection))
return displaycols
def call(self, title=None, subvid=None, displayfilter=None, headers=None,
@@ -105,7 +105,7 @@
:param displayfilter: filter that selects rows to display
:param headers: columns' titles
"""
- req = self.req
+ req = self._cw
req.add_js('jquery.tablesorter.js')
req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
# compute label first since the filter form may remove some necessary
@@ -116,7 +116,7 @@
hidden = True
if not subvid and 'subvid' in req.form:
subvid = req.form.pop('subvid')
- divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.rset))
+ divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.cw_rset))
actions = list(actions)
if mainindex is None:
displayfilter, displayactions = False, False
@@ -145,7 +145,7 @@
actions += self.show_hide_actions(divid, True)
self.w(u'<div id="%s"' % divid)
if displayactions:
- actionsbycat = self.vreg['actions'].possible_actions(req, self.rset)
+ actionsbycat = self._cw.vreg['actions'].possible_actions(req, self.cw_rset)
for action in actionsbycat.get('mainactions', ()):
for action in action.actual_actions():
actions.append( (action.url(), req._(action.title),
@@ -166,13 +166,12 @@
if not fromformfilter:
self.w(u'</div>\n')
-
def show_hide_actions(self, divid, currentlydisplayed=False):
showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
for what in ('Form', 'Show', 'Hide', 'Actions'))
showhide = 'javascript:' + showhide
- showlabel = self.req._('show filter form')
- hidelabel = self.req._('hide filter form')
+ showlabel = self._cw._('show filter form')
+ hidelabel = self._cw._('hide filter form')
if currentlydisplayed:
return [(showhide, showlabel, 'hidden', '%sShow' % divid),
(showhide, hidelabel, None, '%sHide' % divid)]
@@ -181,8 +180,8 @@
def render_actions(self, divid, actions):
box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
- label = tags.img(src=self.req.external_resource('PUCE_DOWN'),
- alt=xml_escape(self.req._('action(s) on this selection')))
+ label = tags.img(src=self._cw.external_resource('PUCE_DOWN'),
+ alt=xml_escape(self._cw._('action(s) on this selection')))
menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
ident='%sActions' % divid)
box.append(menu)
@@ -194,6 +193,7 @@
def get_columns(self, computed_labels, displaycols, headers, subvid,
cellvids, cellattrs, mainindex):
columns = []
+ eschema = self._cw.vreg.schema.eschema
for colindex, label in enumerate(computed_labels):
if colindex not in displaycols:
continue
@@ -201,14 +201,14 @@
if headers is not None:
label = headers[displaycols.index(colindex)]
if colindex == mainindex:
- label += ' (%s)' % self.rset.rowcount
+ label += ' (%s)' % self.cw_rset.rowcount
column = TableColumn(label, colindex)
- coltype = self.rset.description[0][colindex]
+ coltype = self.cw_rset.description[0][colindex]
# compute column cell view (if coltype is None, it's a left outer
# join, use the default non final subvid)
if cellvids and colindex in cellvids:
column.append_renderer(cellvids[colindex], colindex)
- elif coltype is not None and self.schema.eschema(coltype).final:
+ elif coltype is not None and eschema(coltype).final:
column.append_renderer(self.finalview, colindex)
else:
column.append_renderer(subvid or 'incontext', colindex)
@@ -221,10 +221,10 @@
def render_cell(self, cellvid, row, col, w):
- self.view('cell', self.rset, row=row, col=col, cellvid=cellvid, w=w)
+ self._cw.view('cell', self.cw_rset, row=row, col=col, cellvid=cellvid, w=w)
def get_rows(self):
- return self.rset
+ return self.cw_rset
@htmlescape
@jsonize
@@ -233,46 +233,45 @@
# XXX it might be interesting to try to limit value's
# length as much as possible (e.g. by returning the 10
# first characters of a string)
- val = self.rset[row][col]
+ val = self.cw_rset[row][col]
if val is None:
return u''
- etype = self.rset.description[row][col]
- if self.schema.eschema(etype).final:
- entity, rtype = self.rset.related_entity(row, col)
+ etype = self.cw_rset.description[row][col]
+ if self._cw.vreg.schema.eschema(etype).final:
+ entity, rtype = self.cw_rset.related_entity(row, col)
if entity is None:
return val # remove_html_tags() ?
return entity.sortvalue(rtype)
- entity = self.rset.get_entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
return entity.sortvalue()
class EditableTableView(TableView):
- id = 'editable-table'
+ __regid__ = 'editable-table'
finalview = 'editable-final'
title = _('editable-table')
class CellView(EntityView):
+ __regid__ = 'cell'
__select__ = nonempty_rset()
- id = 'cell'
-
def cell_call(self, row, col, cellvid=None):
"""
:param row, col: indexes locating the cell value in view's result set
:param cellvid: cell view (defaults to 'outofcontext')
"""
- etype, val = self.rset.description[row][col], self.rset[row][col]
- if val is not None and not self.schema.eschema(etype).final:
- e = self.rset.get_entity(row, col)
+ etype, val = self.cw_rset.description[row][col], self.cw_rset[row][col]
+ if val is not None and not self._cw.vreg.schema.eschema(etype).final:
+ e = self.cw_rset.get_entity(row, col)
e.view(cellvid or 'outofcontext', w=self.w)
elif val is None:
# This is usually caused by a left outer join and in that case,
# regular views will most certainly fail if they don't have
# a real eid
- self.wview('final', self.rset, row=row, col=col)
+ self.wview('final', self.cw_rset, row=row, col=col)
else:
- self.wview(cellvid or 'final', self.rset, 'null', row=row, col=col)
+ self.wview(cellvid or 'final', self.cw_rset, 'null', row=row, col=col)
class InitialTableView(TableView):
@@ -287,7 +286,7 @@
* the actual query (`actualrql` form parameter) whose results will be
displayed with default restrictions set
"""
- id = 'initialtable'
+ __regid__ = 'initialtable'
__select__ = nonempty_rset() & match_form_params('actualrql')
# should not be displayed in possible view since it expects some specific
# parameters
@@ -296,17 +295,17 @@
def call(self, title=None, subvid=None, headers=None, divid=None,
displaycols=None, displayactions=None, mainindex=None):
"""Dumps a table displaying a composite query"""
- actrql = self.req.form['actualrql']
- self.ensure_ro_rql(actrql)
+ actrql = self._cw.form['actualrql']
+ self._cw.ensure_ro_rql(actrql)
displaycols = self.displaycols(displaycols)
- if displayactions is None and 'displayactions' in self.req.form:
+ if displayactions is None and 'displayactions' in self._cw.form:
displayactions = True
- if divid is None and 'divid' in self.req.form:
- divid = self.req.form['divid']
+ if divid is None and 'divid' in self._cw.form:
+ divid = self._cw.form['divid']
self.w(u'<div class="section">')
- if not title and 'title' in self.req.form:
+ if not title and 'title' in self._cw.form:
# pop title so it's not displayed by the table view as well
- title = self.req.form.pop('title')
+ title = self._cw.form.pop('title')
if title:
self.w(u'<h2>%s</h2>\n' % title)
if mainindex is None:
@@ -315,15 +314,15 @@
actions = self.form_filter(divid, displaycols, displayactions, True)
else:
actions = ()
- if not subvid and 'subvid' in self.req.form:
- subvid = self.req.form.pop('subvid')
- self.view('table', self.req.execute(actrql),
- 'noresult', w=self.w, displayfilter=False, subvid=subvid,
- displayactions=displayactions, displaycols=displaycols,
- actions=actions, headers=headers, divid=divid)
+ if not subvid and 'subvid' in self._cw.form:
+ subvid = self._cw.form.pop('subvid')
+ self._cw.view('table', self._cw.execute(actrql),
+ 'noresult', w=self.w, displayfilter=False, subvid=subvid,
+ displayactions=displayactions, displaycols=displaycols,
+ actions=actions, headers=headers, divid=divid)
self.w(u'</div>\n')
class EditableInitialTableTableView(InitialTableView):
- id = 'editable-initialtable'
+ __regid__ = 'editable-initialtable'
finalview = 'editable-final'
--- a/web/views/tabs.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/tabs.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,7 +13,7 @@
from cubicweb import NoSelectableObject, role
from cubicweb.selectors import partial_has_related_entities
from cubicweb.view import EntityView
-from cubicweb.common import tags, uilib
+from cubicweb import tags, uilib
from cubicweb.utils import make_uid
from cubicweb.web.views import primary
@@ -23,7 +23,7 @@
"""
def _prepare_bindings(self, vid, reloadable):
- self.req.add_onload(u"""
+ self._cw.add_onload(u"""
jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
});""" % {'event': 'load_%s' % vid, 'vid': vid,
@@ -35,7 +35,7 @@
first version only support lazy viewing for an entity at a time
"""
w = w or self.w
- self.req.add_js('cubicweb.lazy.js')
+ self._cw.add_js('cubicweb.lazy.js')
urlparams = {'vid' : vid, 'fname' : 'view'}
if rql:
urlparams['rql'] = rql
@@ -44,10 +44,10 @@
elif rset:
urlparams['rql'] = rset.printable_rql()
w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
- tabid or vid, xml_escape(self.build_url('json', **urlparams))))
+ tabid or vid, xml_escape(self._cw.build_url('json', **urlparams))))
if show_spinbox:
w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
- % (tabid or vid, self.req._('loading')))
+ % (tabid or vid, self._cw._('loading')))
w(u'</div>')
self._prepare_bindings(tabid or vid, reloadable)
@@ -55,8 +55,8 @@
"""trigger an event that will force immediate loading of the view
on dom readyness
"""
- self.req.add_js('cubicweb.lazy.js')
- self.req.add_onload("trigger_load('%s');" % vid)
+ self._cw.add_js('cubicweb.lazy.js')
+ self._cw.add_onload("trigger_load('%s');" % vid)
class TabsMixin(LazyViewMixin):
@@ -65,17 +65,17 @@
@property
def cookie_name(self):
- return str('%s_active_tab' % self.config.appid)
+ return str('%s_active_tab' % self._cw.vreg.config.appid)
def active_tab(self, default):
- if 'tab' in self.req.form:
- return self.req.form['tab']
- cookies = self.req.get_cookie()
+ if 'tab' in self._cw.form:
+ return self._cw.form['tab']
+ cookies = self._cw.get_cookie()
cookiename = self.cookie_name
activetab = cookies.get(cookiename)
if activetab is None:
cookies[cookiename] = default
- self.req.set_cookie(cookies, cookiename)
+ self._cw.set_cookie(cookies, cookiename)
return default
return activetab.value
@@ -83,17 +83,17 @@
selected_tabs = []
may_be_active_tab = self.active_tab(default_tab)
active_tab = default_tab
- viewsvreg = self.vreg['views']
+ viewsvreg = self._cw.vreg['views']
for tab in tabs:
try:
tabid, tabkwargs = tab
tabkwargs = tabkwargs.copy()
except ValueError:
tabid, tabkwargs = tab, {}
- tabkwargs.setdefault('rset', self.rset)
+ tabkwargs.setdefault('rset', self.cw_rset)
vid = tabkwargs.get('vid', tabid)
try:
- viewsvreg.select(vid, self.req, **tabkwargs)
+ viewsvreg.select(vid, self._cw, **tabkwargs)
selected_tabs.append((tabid, tabkwargs))
except NoSelectableObject:
continue
@@ -104,11 +104,11 @@
def render_tabs(self, tabs, default, entity=None):
# delegate to the default tab if there is more than one entity
# in the result set (tabs are pretty useless there)
- if entity and len(self.rset) > 1:
+ if entity and len(self.cw_rset) > 1:
entity.view(default, w=self.w)
return
- self.req.add_css('ui.tabs.css')
- self.req.add_js(('ui.core.js', 'ui.tabs.js',
+ self._cw.add_css('ui.tabs.css')
+ self._cw.add_js(('ui.core.js', 'ui.tabs.js',
'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
# prune tabs : not all are to be shown
tabs, active_tab = self.prune_tabs(tabs, default)
@@ -122,7 +122,7 @@
w(u'<li>')
w(u'<a href="#%s">' % tabid)
w(u'<span onclick="set_tab(\'%s\', \'%s\')">' % (tabid, self.cookie_name))
- w(tabkwargs.pop('label', self.req._(tabid)))
+ w(tabkwargs.pop('label', self._cw._(tabid)))
w(u'</span>')
w(u'</a>')
w(u'</li>')
@@ -134,13 +134,13 @@
w(u'<div id="%s">' % tabid)
tabkwargs.setdefault('tabid', tabid)
tabkwargs.setdefault('vid', tabid)
- tabkwargs.setdefault('rset', self.rset)
+ tabkwargs.setdefault('rset', self.cw_rset)
self.lazyview(**tabkwargs)
w(u'</div>')
# call the set_tab() JS function *after* each tab is generated
# because the callback binding needs to be done before
# XXX make work history: true
- self.req.add_onload(u"""
+ self._cw.add_onload(u"""
jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
set_tab('%(vid)s', '%(cookiename)s');
""" % {'tabindex' : active_tab_idx,
@@ -157,7 +157,7 @@
class ProjectScreenshotsView(EntityRelationView):
'''display project's screenshots'''
- id = title = _('projectscreenshots')
+ __regid__ = title = _('projectscreenshots')
__select__ = EntityRelationView.__select__ & implements('Project')
rtype = 'screenshot'
role = 'subject'
@@ -171,10 +171,10 @@
vid = 'list'
def cell_call(self, row, col):
- rset = self.entity(row, col).related(self.rtype, role(self))
+ rset = self.cw_rset.get_entity(row, col).related(self.rtype, role(self))
self.w(u'<div class="mainInfo">')
if self.title:
- self.w(tags.h1(self.req._(self.title)))
+ self.w(tags.h1(self._cw._(self.title)))
self.wview(self.vid, rset, 'noresult')
self.w(u'</div>')
@@ -186,7 +186,7 @@
default_tab = 'main_tab'
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.render_entity_title(entity)
# XXX uncomment this in 3.6
#self.render_entity_toolbox(entity)
@@ -194,7 +194,7 @@
TabedPrimaryView = TabbedPrimaryView # XXX deprecate that typo!
class PrimaryTab(primary.PrimaryView):
- id = 'main_tab'
+ __regid__ = 'main_tab'
title = None
def is_primary(self):
--- a/web/views/timeline.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/timeline.py Mon Feb 08 13:35:07 2010 +0100
@@ -24,7 +24,7 @@
NOTE: work in progress (image_url, bubbleUrl and so on
should be properties of entity classes or subviews)
"""
- id = 'timeline-json'
+ __regid__ = 'timeline-json'
binary = True
templatable = False
content_type = 'application/json'
@@ -34,7 +34,7 @@
def call(self):
events = []
- for entity in self.rset.entities():
+ for entity in self.cw_rset.entities():
event = self.build_event(entity)
if event is not None:
events.append(event)
@@ -86,9 +86,9 @@
'cubicweb.timeline-ext.js', 'cubicweb.ajax.js')
def render_url(self, loadurl, tlunit=None):
- tlunit = tlunit or self.req.form.get('tlunit')
- self.req.add_js(self.jsfiles)
- self.req.add_css('timeline-bundle.css')
+ tlunit = tlunit or self._cw.form.get('tlunit')
+ self._cw.add_js(self.jsfiles)
+ self._cw.add_css('timeline-bundle.css')
if tlunit:
additional = u' cubicweb:tlunit="%s"' % tlunit
else:
@@ -102,14 +102,14 @@
class TimelineView(TimelineViewMixIn, EntityView):
"""builds a cubicweb timeline widget node"""
- id = 'timeline'
+ __regid__ = 'timeline'
title = _('timeline')
__select__ = implements(ICalendarable)
paginable = False
def call(self, tlunit=None):
- self.req.html_headers.define_var('Timeline_urlPrefix', self.req.datadir_url)
- rql = self.rset.printable_rql()
- loadurl = self.build_url(rql=rql, vid='timeline-json')
+ self._cw.html_headers.define_var('Timeline_urlPrefix', self._cw.datadir_url)
+ rql = self.cw_rset.printable_rql()
+ loadurl = self._cw.build_url(rql=rql, vid='timeline-json')
self.render_url(loadurl, tlunit)
@@ -117,7 +117,7 @@
"""similar to `TimelineView` but loads data from a static
JSON file instead of one after a RQL query.
"""
- id = 'static-timeline'
+ __regid__ = 'static-timeline'
def call(self, loadurl, tlunit=None, wdgclass=None):
self.widget_class = wdgclass or self.widget_class
--- a/web/views/timetable.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/timetable.py Mon Feb 08 13:35:07 2010 +0100
@@ -7,10 +7,10 @@
"""
from logilab.mtconverter import xml_escape
+from logilab.common.date import date_range, todatetime
from cubicweb.interfaces import ITimetableViews
from cubicweb.selectors import implements
-from cubicweb.utils import date_range, todatetime
from cubicweb.view import AnyRsetView
@@ -25,7 +25,7 @@
ALL_USERS = object()
class TimeTableView(AnyRsetView):
- id = 'timetable'
+ __regid__ = 'timetable'
title = _('timetable')
__select__ = implements(ITimetableViews)
paginable = False
@@ -33,15 +33,15 @@
def call(self, title=None):
"""Dumps a timetable from a resultset composed of a note (anything
with start/stop) and a user (anything)"""
- self.req.add_css('cubicweb.timetable.css')
+ self._cw.add_css('cubicweb.timetable.css')
dates = {}
users = []
users_max = {}
# XXX: try refactoring with calendar.py:OneMonthCal
- for row in xrange(self.rset.rowcount):
- task = self.rset.get_entity(row, 0)
- if len(self.rset[row]) > 1:
- user = self.rset.get_entity(row, 1)
+ for row in xrange(self.cw_rset.rowcount):
+ task = self.cw_rset.get_entity(row, 0)
+ if len(self.cw_rset[row]) > 1:
+ user = self.cw_rset.get_entity(row, 1)
else:
user = ALL_USERS
the_dates = []
@@ -172,7 +172,7 @@
odd = not odd
if not empty_line:
- self.w(u'<th class="ttdate">%s</th>' % self.format_date(date) )
+ self.w(u'<th class="ttdate">%s</th>' % self._cw.format_date(date) )
else:
self.w(u'<th>...</th>' )
previous_is_empty = True
--- a/web/views/treeview.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/treeview.py Mon Feb 08 13:35:07 2010 +0100
@@ -20,21 +20,21 @@
return str('%s-treestate' % treeid)
class TreeView(EntityView):
- id = 'treeview'
+ __regid__ = 'treeview'
itemvid = 'treeitemview'
subvid = 'oneline'
css_classes = 'treeview widget'
title = _('tree view')
def _init_params(self, subvid, treeid, initial_load, initial_thru_ajax, morekwargs):
- form = self.req.form
+ form = self._cw.form
if subvid is None:
subvid = form.pop('treesubvid', self.subvid) # consume it
if treeid is None:
treeid = form.pop('treeid', None)
if treeid is None:
treeid = 'throw_away' + make_uid('uid')
- if 'morekwargs' in self.req.form:
+ if 'morekwargs' in self._cw.form:
ajaxargs = json.loads(form.pop('morekwargs'))
# got unicode & python keywords must be strings
morekwargs.update(dict((str(k), v)
@@ -44,9 +44,9 @@
return subvid, treeid, toplevel_thru_ajax, toplevel
def _init_headers(self, treeid, toplevel_thru_ajax):
- self.req.add_css('jquery.treeview.css')
- self.req.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
- self.req.html_headers.add_onload(u"""
+ self._cw.add_css('jquery.treeview.css')
+ self._cw.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
+ self._cw.html_headers.add_onload(u"""
jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid,
jsoncall=toplevel_thru_ajax)
@@ -59,12 +59,13 @@
self._init_headers(treeid, toplevel_thru_ajax)
ulid = ' id="tree-%s"' % treeid
self.w(u'<ul%s class="%s">' % (ulid, self.css_classes))
- for i, entity in enumerate(sorted(self.rset.entities(), key=lambda x: x.dc_title())):
- if i+1 < len(self.rset):
+ for i, entity in enumerate(sorted(self.cw_rset.entities(),
+ key=lambda x: x.dc_title())):
+ if i+1 < len(self.cw_rset):
morekwargs['is_last'] = False
else:
morekwargs['is_last'] = True
- entity.view(self.itemvid, vid=subvid, parentvid=self.id,
+ entity.view(self.itemvid, vid=subvid, parentvid=self.__regid__,
treeid=treeid, w=self.w, **morekwargs)
self.w(u'</ul>')
@@ -79,7 +80,7 @@
class FileTreeView(TreeView):
"""specific version of the treeview to display file trees
"""
- id = 'filetree'
+ __regid__ = 'filetree'
css_classes = 'treeview widget filetree'
title = _('file tree view')
@@ -93,10 +94,10 @@
This view adds an enclosing <span> with some specific CSS classes
around the oneline view. This is needed by the jquery treeview plugin.
"""
- id = 'filetree-oneline'
+ __regid__ = 'filetree-oneline'
def cell_call(self, row, col):
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
if ITree.is_implemented_by(entity.__class__) and not entity.is_leaf():
self.w(u'<div class="folder">%s</div>\n' % entity.view('oneline'))
else:
@@ -106,12 +107,12 @@
class DefaultTreeViewItemView(EntityView):
"""default treeitem view for entities which don't implement ITree"""
- id = 'treeitemview'
+ __regid__ = 'treeitemview'
def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs):
assert treeid is not None
- entity = self.entity(row, col)
- itemview = self.view(vid, self.rset, row=row, col=col)
+ entity = self.cw_rset.get_entity(row, col)
+ itemview = self._cw.view(vid, self.cw_rset, row=row, col=col)
last_class = morekwargs['is_last'] and ' class="last"' or ''
self.w(u'<li%s>%s</li>' % (last_class, itemview))
@@ -121,12 +122,12 @@
(each item should be expandable if it's not a tree leaf)
"""
- id = 'treeitemview'
+ __regid__ = 'treeitemview'
+ __select__ = implements(ITree)
default_branch_state_is_open = False
- __select__ = implements(ITree)
def open_state(self, eeid, treeid):
- cookies = self.req.get_cookie()
+ cookies = self._cw.get_cookie()
treestate = cookies.get(treecookiename(treeid))
if treestate:
return str(eeid) in treestate.value.split(';')
@@ -135,7 +136,7 @@
def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview',
is_last=False, **morekwargs):
w = self.w
- entity = self.entity(row, col)
+ entity = self.cw_rset.get_entity(row, col)
liclasses = []
is_open = self.open_state(entity.eid, treeid)
is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf()
@@ -145,12 +146,12 @@
w(u'<li class="%s">' % u' '.join(liclasses))
else:
rql = entity.children_rql() % {'x': entity.eid}
- url = xml_escape(self.build_url('json', rql=rql, vid=parentvid,
- pageid=self.req.pageid,
- treeid=treeid,
- fname='view',
- treesubvid=vid,
- morekwargs=json.dumps(morekwargs)))
+ url = xml_escape(self._cw.build_url('json', rql=rql, vid=parentvid,
+ pageid=self._cw.pageid,
+ treeid=treeid,
+ fname='view',
+ treesubvid=vid,
+ morekwargs=json.dumps(morekwargs)))
divclasses = ['hitarea']
if is_open:
liclasses.append('collapsable')
@@ -181,7 +182,7 @@
if not is_open:
w(u'<ul class="placeholder"><li>place holder</li></ul>')
# the local node info
- self.wview(vid, self.rset, row=row, col=col, **morekwargs)
+ self.wview(vid, self.cw_rset, row=row, col=col, **morekwargs)
if is_open and not is_leaf: # => rql is defined
self.wview(parentvid, entity.children(entities=False), subvid=vid,
treeid=treeid, initial_load=False, **morekwargs)
--- a/web/views/urlpublishing.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/urlpublishing.py Mon Feb 08 13:35:07 2010 +0100
@@ -28,8 +28,7 @@
from rql import TypeResolverException
from cubicweb import RegistryException, typed_eid
-from cubicweb.web import NotFound, Redirect
-from cubicweb.web.component import Component, Component
+from cubicweb.web import NotFound, Redirect, component
class PathDontMatch(Exception):
@@ -37,7 +36,7 @@
a path
"""
-class URLPublisherComponent(Component):
+class URLPublisherComponent(component.Component):
"""associate url's path to view identifier / rql queries,
by applying a chain of urlpathevaluator components.
@@ -50,13 +49,15 @@
lower priority. The first evaluator returning a result or raising
something else than `PathDontMatch` will stop the handlers chain.
"""
- id = 'urlpublisher'
+ __regid__ = 'urlpublisher'
+ vreg = None # XXX necessary until property for deprecation warning is on appobject
- def __init__(self, default_method='view'):
+ def __init__(self, vreg, default_method='view'):
super(URLPublisherComponent, self).__init__()
+ self.vreg = vreg
self.default_method = default_method
evaluators = []
- for evaluatorcls in self.vreg['components']['urlpathevaluator']:
+ for evaluatorcls in vreg['components']['urlpathevaluator']:
# instantiation needed
evaluator = evaluatorcls(self)
evaluators.append(evaluator)
@@ -74,7 +75,7 @@
:type path: str
:param path: the path of the resource to publish
- :rtype: tuple(str, `cubicweb.common.utils.ResultSet` or None)
+ :rtype: tuple(str, `cubicweb.utils.ResultSet` or None)
:return: the publishing method identifier and an optional result set
:raise NotFound: if no handler is able to decode the given path
@@ -98,13 +99,14 @@
return pmid, rset
-class URLPathEvaluator(Component):
+class URLPathEvaluator(component.Component):
__abstract__ = True
- id = 'urlpathevaluator'
+ __regid__ = 'urlpathevaluator'
+ vreg = None # XXX necessary until property for deprecation warning is on appobject
def __init__(self, urlpublisher):
self.urlpublisher = urlpublisher
-
+ self.vreg = urlpublisher.vreg
class RawPathEvaluator(URLPathEvaluator):
"""handle path of the form::
@@ -197,7 +199,7 @@
evaluators = sorted(self.vreg['urlrewriting'].all_objects(),
key=lambda x: x.priority, reverse=True)
for rewritercls in evaluators:
- rewriter = rewritercls()
+ rewriter = rewritercls(req)
try:
# XXX we might want to chain url rewrites
return rewriter.rewrite(req, uri)
@@ -217,6 +219,7 @@
raise PathDontMatch()
# remove last part and see if this is something like an actions
# if so, call
+ # XXX bad smell: refactor to simpler code
try:
actionsreg = self.vreg['actions']
requested = parts.pop(-1)
@@ -232,7 +235,7 @@
continue
else:
try:
- action = actionsreg.select_best(actions, req, rset=rset)
+ action = actionsreg._select_best(actions, req, rset=rset)
except RegistryException:
continue
else:
--- a/web/views/urlrewrite.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/urlrewrite.py Mon Feb 08 13:35:07 2010 +0100
@@ -68,7 +68,7 @@
If the input uri is a regexp, group substitution is allowed
"""
- id = 'simple'
+ __regid__ = 'simple'
rules = [
('/_', dict(vid='manage')),
@@ -190,7 +190,7 @@
"""Here, the rules dict maps regexps or plain strings to
callbacks that will be called with (input, uri, req, schema)
"""
- id = 'schemabased'
+ __regid__ = 'schemabased'
rules = [
# rgxp : callback
(rgx('/search/(.+)'), build_rset(rql=r'Any X WHERE X has_text %(text)s',
@@ -209,9 +209,9 @@
continue
if isinstance(inputurl, basestring):
if inputurl == uri:
- return callback(inputurl, uri, req, self.schema)
+ return callback(inputurl, uri, req, self._cw.vreg.schema)
elif inputurl.match(uri): # it's a regexp
- return callback(inputurl, uri, req, self.schema)
+ return callback(inputurl, uri, req, self._cw.vreg.schema)
else:
self.debug("no schemabased rewrite rule found for %s", uri)
raise KeyError(uri)
--- a/web/views/vcard.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/vcard.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,7 +16,7 @@
class VCardCWUserView(EntityView):
"""export a person information as a vcard"""
- id = 'vcard'
+ __regid__ = 'vcard'
title = _('vcard')
templatable = False
content_type = 'text/x-vcard'
@@ -24,11 +24,11 @@
def set_request_content_type(self):
"""overriden to set a .vcf filename"""
- self.req.set_content_type(self.content_type, filename='vcard.vcf')
+ self._cw.set_content_type(self.content_type, filename='vcard.vcf')
def cell_call(self, row, col):
self.vcard_header()
- self.vcard_content(self.complete_entity(row, col))
+ self.vcard_content(self.cw_rset.complete_entity(row, col))
self.vcard_footer()
def vcard_header(self):
--- a/web/views/wdoc.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/wdoc.py Mon Feb 08 13:35:07 2010 +0100
@@ -13,12 +13,12 @@
from datetime import date
from logilab.common.changelog import ChangeLog
+from logilab.common.date import strptime, todate
from logilab.mtconverter import CHARSET_DECL_RGX
from cubicweb.selectors import match_form_params, yes
from cubicweb.view import StartupView
-from cubicweb.utils import strptime, todate
-from cubicweb.common.uilib import rest_publish
+from cubicweb.uilib import rest_publish
from cubicweb.web import NotFound, action
_ = unicode
@@ -86,20 +86,21 @@
class InlineHelpView(StartupView):
__select__ = match_form_params('fid')
- id = 'wdoc'
+ __regid__ = 'wdoc'
title = _('site documentation')
def call(self):
- fid = self.req.form['fid']
- for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
- self.config.available_languages()):
+ fid = self._cw.form['fid']
+ vreg = self._cw.vreg
+ for lang in chain((self._cw.lang, vreg.property_value('ui.language')),
+ vreg.config.available_languages()):
rid = '%s_%s.rst' % (fid, lang)
- resourcedir = self.config.locate_doc_file(rid)
+ resourcedir = vreg.config.locate_doc_file(rid)
if resourcedir:
break
else:
raise NotFound
- self.tocindex = build_toc(self.config)
+ self.tocindex = build_toc(vreg.config)
try:
node = self.tocindex[fid]
except KeyError:
@@ -107,7 +108,7 @@
else:
self.navigation_links(node)
self.w(u'<div class="hr"></div>')
- self.w(u'<h1>%s</h1>' % (title_for_lang(node, self.req.lang)))
+ self.w(u'<h1>%s</h1>' % (title_for_lang(node, self._cw.lang)))
data = open(join(resourcedir, rid)).read()
self.w(rest_publish(self, data))
if node is not None:
@@ -116,7 +117,7 @@
self.navigation_links(node)
def navigation_links(self, node):
- req = self.req
+ req = self._cw
parent = node.parent
if parent is None:
return
@@ -138,10 +139,10 @@
def navsection(self, node, navtype):
htmlclass, imgpath, msgid = self.navinfo[navtype]
self.w(u'<span class="%s">' % htmlclass)
- self.w(u'%s : ' % self.req._(msgid))
+ self.w(u'%s : ' % self._cw._(msgid))
self.w(u'<a href="%s">%s</a>' % (
- self.req.build_url('doc/'+node.attrib['resource']),
- title_for_lang(node, self.req.lang)))
+ self._cw.build_url('doc/'+node.attrib['resource']),
+ title_for_lang(node, self._cw.lang)))
self.w(u'</span>\n')
def subsections_links(self, node, first=True):
@@ -153,8 +154,8 @@
self.w(u'<ul class="docsum">')
for child in sub:
self.w(u'<li><a href="%s">%s</a>' % (
- self.req.build_url('doc/'+child.attrib['resource']),
- title_for_lang(child, self.req.lang)))
+ self._cw.build_url('doc/'+child.attrib['resource']),
+ title_for_lang(child, self._cw.lang)))
self.subsections_links(child, False)
self.w(u'</li>')
self.w(u'</ul>\n')
@@ -162,18 +163,18 @@
class InlineHelpImageView(StartupView):
- id = 'wdocimages'
+ __regid__ = 'wdocimages'
__select__ = match_form_params('fid')
binary = True
templatable = False
content_type = 'image/png'
def call(self):
- fid = self.req.form['fid']
- for lang in chain((self.req.lang, self.vreg.property_value('ui.language')),
- self.config.available_languages()):
+ fid = self._cw.form['fid']
+ for lang in chain((self._cw.lang, self._cw.vreg.property_value('ui.language')),
+ self._cw.vreg.config.available_languages()):
rid = join('images', '%s_%s.png' % (fid, lang))
- resourcedir = self.config.locate_doc_file(rid)
+ resourcedir = self._cw.vreg.config.locate_doc_file(rid)
if resourcedir:
break
else:
@@ -182,18 +183,18 @@
class ChangeLogView(StartupView):
- id = 'changelog'
+ __regid__ = 'changelog'
title = _('What\'s new?')
maxentries = 25
def call(self):
- rid = 'ChangeLog_%s' % (self.req.lang)
+ rid = 'ChangeLog_%s' % (self._cw.lang)
allentries = []
- title = self.req._(self.title)
+ title = self._cw._(self.title)
restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), '']
w = restdata.append
today = date.today()
- for fpath in self.config.locate_all_files(rid):
+ for fpath in self._cw.vreg.config.locate_all_files(rid):
cl = ChangeLog(fpath)
encoding = 'utf-8'
# additional content may be found in title
@@ -223,7 +224,7 @@
i = 0
for edate, messages in reversed(allentries):
if latestdate != edate:
- fdate = self.format_date(edate)
+ fdate = self._cw.format_date(edate)
w(u'\n%s' % fdate)
w('~' * len(fdate))
latestdate = edate
@@ -237,7 +238,7 @@
class HelpAction(action.Action):
- id = 'help'
+ __regid__ = 'help'
__select__ = yes()
category = 'footer'
@@ -245,10 +246,10 @@
title = _('Help')
def url(self):
- return self.req.build_url('doc/main')
+ return self._cw.build_url('doc/main')
class ChangeLogAction(action.Action):
- id = 'changelog'
+ __regid__ = 'changelog'
__select__ = yes()
category = 'footer'
@@ -256,11 +257,11 @@
title = ChangeLogView.title
def url(self):
- return self.req.build_url('changelog')
+ return self._cw.build_url('changelog')
class AboutAction(action.Action):
- id = 'about'
+ __regid__ = 'about'
__select__ = yes()
category = 'footer'
@@ -268,5 +269,5 @@
title = _('about this site')
def url(self):
- return self.req.build_url('doc/about')
+ return self._cw.build_url('doc/about')
--- a/web/views/workflow.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/workflow.py Mon Feb 08 13:35:07 2010 +0100
@@ -17,7 +17,7 @@
from cubicweb import Unauthorized, view
from cubicweb.selectors import (implements, has_related_entities, one_line_rset,
relation_possible, match_form_params,
- entity_implements, score_entity)
+ implements, score_entity)
from cubicweb.interfaces import IWorkflowable
from cubicweb.view import EntityView
from cubicweb.schema import display_name
@@ -25,11 +25,30 @@
from cubicweb.web import formfields as ff, formwidgets as fwdgs
from cubicweb.web.views import TmpFileViewMixin, forms, primary, autoform
+_pvs = uicfg.primaryview_section
+_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
+_pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
+_pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
+
+_abaa = uicfg.actionbox_appearsin_addmenu
+_abaa.tag_subject_of(('BaseTransition', 'condition', 'RQLExpression'), False)
+_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
+_abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
+ False)
+_abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
+_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
+_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
+
+_afs = uicfg.autoform_section
+_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden')
+_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden')
+_afs.tag_object_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
+
# IWorkflowable views #########################################################
class ChangeStateForm(forms.CompositeEntityForm):
- id = 'changestate'
+ __regid__ = 'changestate'
form_renderer_id = 'base' # don't want EntityFormRenderer
form_buttons = [fwdgs.SubmitButton(),
@@ -37,58 +56,59 @@
class ChangeStateFormView(form.FormViewMixIn, view.EntityView):
- id = 'statuschange'
+ __regid__ = 'statuschange'
title = _('status change')
__select__ = (one_line_rset() & implements(IWorkflowable)
& match_form_params('treid'))
def cell_call(self, row, col):
- entity = self.rset.get_entity(row, col)
- transition = self.req.entity_from_eid(self.req.form['treid'])
+ entity = self.cw_rset.get_entity(row, col)
+ transition = self._cw.entity_from_eid(self._cw.form['treid'])
form = self.get_form(entity, transition)
- self.w(form.error_message())
- self.w(u'<h4>%s %s</h4>\n' % (self.req._(transition.name),
+ self.w(u'<h4>%s %s</h4>\n' % (self._cw._(transition.name),
entity.view('oneline')))
msg = self.req._('status will change from %(st1)s to %(st2)s') % {
'st1': entity.printable_state,
- 'st2': self.req._(transition.destination().name)}
+ 'st2': self._cw._(transition.destination().name)}
self.w(u'<p>%s</p>\n' % msg)
- self.w(form.render(formvalues=dict(wf_info_for=entity.eid,
- by_transition=transition.eid)))
+ self.w(form.render())
def redirectpath(self, entity):
return entity.rest_path()
def get_form(self, entity, transition, **kwargs):
# XXX used to specify both rset/row/col and entity in case implements
- # selector (and not entity_implements) is used on custom form
- form = self.vreg['forms'].select(
- 'changestate', self.req, entity=entity, transition=transition,
+ # selector (and not implements) is used on custom form
+ form = self._cw.vreg['forms'].select(
+ 'changestate', self._cw, entity=entity, transition=transition,
redirect_path=self.redirectpath(entity), **kwargs)
- trinfo = self.vreg['etypes'].etype_class('TrInfo')(self.req)
- self.initialize_varmaker()
- trinfo.eid = self.varmaker.next()
- subform = self.vreg['forms'].select('edition', self.req, entity=trinfo,
- mainform=False)
- subform.field_by_name('by_transition').widget = fwdgs.HiddenInput()
+ trinfo = self._cw.vreg['etypes'].etype_class('TrInfo')(self._cw)
+ trinfo.eid = self._cw.varmaker.next()
+ subform = self._cw.vreg['forms'].select('edition', self._cw, entity=trinfo,
+ mainform=False)
+ subform.field_by_name('wf_info_for', 'subject').value = entity.eid
+ trfield = subform.field_by_name('by_transition', 'subject')
+ trfield.widget = fwdgs.HiddenInput()
+ trfield.value = transition.eid
form.add_subform(subform)
return form
class WFHistoryView(EntityView):
- id = 'wfhistory'
+ __regid__ = 'wfhistory'
__select__ = relation_possible('wf_info_for', role='object') & \
score_entity(lambda x: x.workflow_history)
+
title = _('Workflow history')
def cell_call(self, row, col, view=None):
- _ = self.req._
- eid = self.rset[row][col]
+ _ = self._cw._
+ eid = self.cw_rset[row][col]
sel = 'Any FS,TS,WF,D'
rql = ' ORDERBY D DESC WHERE WF wf_info_for X,'\
'WF from_state FS, WF to_state TS, WF comment C,'\
'WF creation_date D'
- if self.vreg.schema.eschema('CWUser').has_perm(self.req, 'read'):
+ if self._cw.vreg.schema.eschema('CWUser').has_perm(self._cw, 'read'):
sel += ',U,C'
rql += ', WF owned_by U?'
displaycols = range(5)
@@ -100,7 +120,7 @@
headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
rql = '%s %s, X eid %%(x)s' % (sel, rql)
try:
- rset = self.req.execute(rql, {'x': eid}, 'x')
+ rset = self._cw.execute(rql, {'x': eid}, 'x')
except Unauthorized:
return
if rset:
@@ -110,20 +130,20 @@
class WFHistoryVComponent(component.EntityVComponent):
"""display the workflow history for entities supporting it"""
- id = 'wfhistory'
+ __regid__ = 'wfhistory'
__select__ = WFHistoryView.__select__ & component.EntityVComponent.__select__
context = 'navcontentbottom'
title = _('Workflow history')
def cell_call(self, row, col, view=None):
- self.wview('wfhistory', self.rset, row=row, col=col, view=view)
+ self.wview('wfhistory', self.cw_rset, row=row, col=col, view=view)
# workflow actions #############################################################
class WorkflowActions(action.Action):
"""fill 'workflow' sub-menu of the actions box"""
- id = 'workflow'
+ __regid__ = 'workflow'
__select__ = (action.Action.__select__ & one_line_rset() &
relation_possible('in_state'))
@@ -131,25 +151,25 @@
order = 10
def fill_menu(self, box, menu):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
- menu.label = u'%s: %s' % (self.req._('state'), entity.printable_state)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ menu.label = u'%s: %s' % (self._cw._('state'), entity.printable_state)
menu.append_anyway = True
super(WorkflowActions, self).fill_menu(box, menu)
def actual_actions(self):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
hastr = False
for tr in entity.possible_transitions():
url = entity.absolute_url(vid='statuschange', treid=tr.eid)
- yield self.build_action(self.req._(tr.name), url)
+ yield self.build_action(self._cw._(tr.name), url)
hastr = True
# don't propose to see wf if user can't pass any transition
if hastr:
wfurl = entity.current_workflow.absolute_url()
- yield self.build_action(self.req._('view workflow'), wfurl)
+ yield self.build_action(self._cw._('view workflow'), wfurl)
if entity.workflow_history:
wfurl = entity.absolute_url(vid='wfhistory')
- yield self.build_action(self.req._('view history'), wfurl)
+ yield self.build_action(self._cw._('view history'), wfurl)
# workflow entity types views ##################################################
@@ -176,34 +196,34 @@
self.w(entity.view('reledit', rtype='description'))
self.w(u'<img src="%s" alt="%s"/>' % (
xml_escape(entity.absolute_url(vid='wfgraph')),
- xml_escape(self.req._('graphical workflow for %s') % entity.name)))
+ xml_escape(self._cw._('graphical workflow for %s') % entity.name)))
class CellView(view.EntityView):
- id = 'cell'
+ __regid__ = 'cell'
__select__ = implements('TrInfo')
def cell_call(self, row, col, cellvid=None):
- self.w(self.rset.get_entity(row, col).view('reledit', rtype='comment'))
+ self.w(self.cw_rset.get_entity(row, col).view('reledit', rtype='comment'))
class StateInContextView(view.EntityView):
"""convenience trick, State's incontext view should not be clickable"""
- id = 'incontext'
+ __regid__ = 'incontext'
__select__ = implements('State')
def cell_call(self, row, col):
- self.w(xml_escape(self.view('textincontext', self.rset,
- row=row, col=col)))
+ self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
+ row=row, col=col)))
# workflow entity types edition ################################################
_afs = uicfg.autoform_section
-_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'generated')
-_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'generated')
-_afs.tag_object_of(('State', 'allowed_transition', '*'), 'primary')
-_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'primary')
+_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden')
+_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden')
+_afs.tag_object_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
+_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
def workflow_items_for_relation(req, wfeid, wfrelation, targetrelation):
wf = req.entity_from_eid(wfeid)
@@ -214,12 +234,12 @@
class TransitionEditionForm(autoform.AutomaticEntityForm):
- __select__ = entity_implements('Transition')
+ __select__ = implements('Transition')
def workflow_states_for_relation(self, targetrelation):
eids = self.edited_entity.linked_to('transition_of', 'subject')
if eids:
- return workflow_items_for_relation(self.req, eids[0], 'state_of',
+ return workflow_items_for_relation(self._cw, eids[0], 'state_of',
targetrelation)
return []
@@ -235,13 +255,13 @@
class StateEditionForm(autoform.AutomaticEntityForm):
- __select__ = entity_implements('State')
+ __select__ = implements('State')
def subject_allowed_transition_vocabulary(self, rtype, limit=None):
if not self.edited_entity.has_eid():
eids = self.edited_entity.linked_to('state_of', 'subject')
if eids:
- return workflow_items_for_relation(self.req, eids[0], 'transition_of',
+ return workflow_items_for_relation(self._cw, eids[0], 'transition_of',
'allowed_transition')
return []
@@ -302,15 +322,15 @@
class WorkflowImageView(TmpFileViewMixin, view.EntityView):
- id = 'wfgraph'
+ __regid__ = 'wfgraph'
+ __select__ = implements('Workflow')
content_type = 'image/png'
- __select__ = implements('Workflow')
def _generate(self, tmpfile):
"""display schema information for an entity"""
- entity = self.rset.get_entity(self.row, self.col)
+ entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
visitor = WorkflowVisitor(entity)
- prophdlr = WorkflowDotPropsHandler(self.req)
+ prophdlr = WorkflowDotPropsHandler(self._cw)
generator = GraphGenerator(DotBackend('workflow', 'LR',
ratio='compress', size='30,12'))
return generator.generate(visitor, prophdlr, tmpfile)
--- a/web/views/xbel.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/xbel.py Mon Feb 08 13:35:07 2010 +0100
@@ -16,32 +16,32 @@
class XbelView(XMLView):
- id = 'xbel'
+ __regid__ = 'xbel'
title = _('xbel')
templatable = False
content_type = 'text/xml' #application/xbel+xml
def cell_call(self, row, col):
- self.wview('xbelitem', self.rset, row=row, col=col)
+ self.wview('xbelitem', self.cw_rset, row=row, col=col)
def call(self):
"""display a list of entities by calling their <item_vid> view"""
title = self.page_title()
- url = self.build_url(rql=self.req.form.get('rql', ''))
- self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+ url = self._cw.build_url(rql=self._cw.form.get('rql', ''))
+ self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
self.w(u'<!DOCTYPE xbel PUBLIC "+//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML" "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd">')
self.w(u'<xbel version="1.0">')
- self.w(u'<title>%s</title>' % self.req._('bookmarks'))
- for i in xrange(self.rset.rowcount):
+ self.w(u'<title>%s</title>' % self._cw._('bookmarks'))
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u"</xbel>")
class XbelItemView(EntityView):
- id = 'xbelitem'
+ __regid__ = 'xbelitem'
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(u'<bookmark href="%s">' % xml_escape(self.url(entity)))
self.w(u' <title>%s</title>' % xml_escape(entity.dc_title()))
self.w(u'</bookmark>')
--- a/web/views/xmlrss.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/views/xmlrss.py Mon Feb 08 13:35:07 2010 +0100
@@ -14,7 +14,7 @@
from cubicweb.selectors import non_final_entity, one_line_rset, appobject_selectable
from cubicweb.view import EntityView, AnyRsetView, Component
-from cubicweb.common.uilib import simple_sgml_tag
+from cubicweb.uilib import simple_sgml_tag
from cubicweb.web import httpcache, box
@@ -22,7 +22,7 @@
class XMLView(EntityView):
"""xml view for entities"""
- id = 'xml'
+ __regid__ = 'xml'
title = _('xml')
templatable = False
content_type = 'text/xml'
@@ -30,23 +30,23 @@
item_vid = 'xmlitem'
def cell_call(self, row, col):
- self.wview(self.item_vid, self.rset, row=row, col=col)
+ self.wview(self.item_vid, self.cw_rset, row=row, col=col)
def call(self):
"""display a list of entities by calling their <item_vid> view"""
- self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
- self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset)))
- for i in xrange(self.rset.rowcount):
+ self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
+ self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.cw_rset)))
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u'</%s>\n' % self.xml_root)
class XMLItemView(EntityView):
- id = 'xmlitem'
+ __regid__ = 'xmlitem'
def cell_call(self, row, col):
""" element as an item for an xml feed """
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(u'<%s>\n' % (entity.e_schema))
for rschema, attrschema in entity.e_schema.attribute_definitions():
attr = rschema.type
@@ -67,7 +67,7 @@
class XMLRsetView(AnyRsetView):
"""dumps raw rset as xml"""
- id = 'rsetxml'
+ __regid__ = 'rsetxml'
title = _('xml export')
templatable = False
content_type = 'text/xml'
@@ -75,12 +75,12 @@
def call(self):
w = self.w
- rset, descr = self.rset, self.rset.description
- eschema = self.schema.eschema
+ rset, descr = self.cw_rset, self.cw_rset.description
+ eschema = self._cw.vreg.schema.eschema
labels = self.columns_labels(tr=False)
- w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+ w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
- for rowindex, row in enumerate(self.rset):
+ for rowindex, row in enumerate(self.cw_rset):
w(u' <row>\n')
for colindex, val in enumerate(row):
etype = descr[rowindex][colindex]
@@ -92,11 +92,11 @@
if val is not None and not eschema(etype).final:
attrs['eid'] = val
# csvrow.append(val) # val is eid in that case
- val = self.view('textincontext', rset,
- row=rowindex, col=colindex)
+ val = self._cw.view('textincontext', rset,
+ row=rowindex, col=colindex)
else:
- val = self.view('final', rset, row=rowindex,
- col=colindex, format='text/plain')
+ val = self._cw.view('final', rset, row=rowindex,
+ col=colindex, format='text/plain')
w(simple_sgml_tag(tag, val, **attrs))
w(u' </row>\n')
w(u'</%s>\n' % self.xml_root)
@@ -105,24 +105,24 @@
# RSS stuff ###################################################################
class RSSFeedURL(Component):
- id = 'rss_feed_url'
+ __regid__ = 'rss_feed_url'
__select__ = non_final_entity()
def feed_url(self):
- return self.build_url(rql=self.limited_rql(), vid='rss')
+ return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
class RSSEntityFeedURL(Component):
- id = 'rss_feed_url'
+ __regid__ = 'rss_feed_url'
__select__ = non_final_entity() & one_line_rset()
def feed_url(self):
- return self.entity(0, 0).rss_feed_url()
+ return self.cw_rset.get_entity(0, 0).rss_feed_url()
class RSSIconBox(box.BoxTemplate):
"""just display the RSS icon on uniform result set"""
- id = 'rss'
+ __regid__ = 'rss'
__select__ = (box.BoxTemplate.__select__
& appobject_selectable('components', 'rss_feed_url'))
@@ -131,18 +131,18 @@
def call(self, **kwargs):
try:
- rss = self.req.external_resource('RSS_LOGO')
+ rss = self._cw.external_resource('RSS_LOGO')
except KeyError:
self.error('missing RSS_LOGO external resource')
return
- urlgetter = self.vreg['components'].select('rss_feed_url', self.req,
- rset=self.rset)
+ urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
+ rset=self.cw_rset)
url = urlgetter.feed_url()
self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
class RSSView(XMLView):
- id = 'rss'
+ __regid__ = 'rss'
title = _('rss')
templatable = False
content_type = 'text/xml'
@@ -150,7 +150,7 @@
cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
def _open(self):
- req = self.req
+ req = self._cw
self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
self.w(u'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
self.w(u' <channel>\n')
@@ -160,7 +160,7 @@
% xml_escape(req.form.get('vtitle', '')))
params = req.form.copy()
params.pop('vid', None)
- self.w(u' <link>%s</link>\n' % xml_escape(self.build_url(**params)))
+ self.w(u' <link>%s</link>\n' % xml_escape(self._cw.build_url(**params)))
def _close(self):
self.w(u' </channel>\n')
@@ -169,21 +169,21 @@
def call(self):
"""display a list of entities by calling their <item_vid> view"""
self._open()
- for i in xrange(self.rset.rowcount):
+ for i in xrange(self.cw_rset.rowcount):
self.cell_call(i, 0)
self._close()
def cell_call(self, row, col):
- self.wview('rssitem', self.rset, row=row, col=col)
+ self.wview('rssitem', self.cw_rset, row=row, col=col)
class RSSItemView(EntityView):
- id = 'rssitem'
+ __regid__ = 'rssitem'
date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
add_div_section = False
def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
+ entity = self.cw_rset.complete_entity(row, col)
self.w(u'<item>\n')
self.w(u'<guid isPermaLink="true">%s</guid>\n'
% xml_escape(entity.absolute_url()))
--- a/web/webctl.py Mon Feb 08 13:33:09 2010 +0100
+++ b/web/webctl.py Mon Feb 08 13:35:07 2010 +0100
@@ -8,8 +8,7 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb import underline_title
-from cubicweb.toolsutils import CommandHandler
+from cubicweb.toolsutils import CommandHandler, underline_title
from logilab.common.shellutils import ASK
class WebCreateHandler(CommandHandler):
--- a/web/widgets.py Mon Feb 08 13:33:09 2010 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,930 +0,0 @@
-"""widgets for entity edition
-
-those are in cubicweb.common since we need to know available widgets at schema
-serialization time
-
-:organization: Logilab
-:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-__docformat__ = "restructuredtext en"
-
-from datetime import datetime
-
-from logilab.mtconverter import xml_escape
-
-from yams.constraints import SizeConstraint, StaticVocabularyConstraint
-
-from cubicweb.common.uilib import toggle_action
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
-
-def _format_attrs(kwattrs):
- """kwattrs is the dictionary of the html attributes available for
- the edited element
- """
- # sort for predictability (required for tests)
- return u' '.join(sorted(u'%s="%s"' % item for item in kwattrs.iteritems()))
-
-def _value_from_values(values):
- # take care, value may be 0, 0.0...
- if values:
- value = values[0]
- if value is None:
- value = u''
- else:
- value = u''
- return value
-
-def _eclass_eschema(eschema_or_eclass):
- try:
- return eschema_or_eclass, eschema_or_eclass.e_schema
- except AttributeError:
- return None, eschema_or_eclass
-
-def checkbox(name, value, attrs='', checked=None):
- if checked is None:
- checked = value
- checked = checked and 'checked="checked"' or ''
- return u'<input type="checkbox" name="%s" value="%s" %s %s />' % (
- name, value, checked, attrs)
-
-def widget(vreg, subjschema, rschema, objschema, role='object'):
- """get a widget to edit the given relation"""
- if rschema == 'eid':
- # return HiddenWidget(vreg, subjschema, rschema, objschema)
- return EidWidget(vreg, _eclass_eschema(subjschema)[1], rschema, objschema)
- return widget_factory(vreg, subjschema, rschema, objschema, role=role)
-
-
-class Widget(object):
- """abstract widget class"""
- need_multipart = False
- # generate the "id" attribute with the same value as the "name" (html) attribute
- autoid = True
- html_attributes = set(('id', 'class', 'tabindex', 'accesskey', 'onchange', 'onkeypress'))
- cubicwebns_attributes = set()
-
- def __init__(self, vreg, subjschema, rschema, objschema,
- role='subject', description=None,
- **kwattrs):
- self.vreg = vreg
- self.rschema = rschema
- self.subjtype = subjschema
- self.objtype = objschema
- self.role = role
- self.name = rschema.type
- self.description = description
- self.attrs = kwattrs
- # XXX accesskey may not be unique
- kwattrs['accesskey'] = self.name[0]
-
- def copy(self):
- """shallow copy (useful when you need to modify self.attrs
- because widget instances are cached)
- """
- # brute force copy (subclasses don't have the
- # same __init__ prototype)
- widget = self.__new__(self.__class__)
- widget.__dict__ = dict(self.__dict__)
- widget.attrs = dict(widget.attrs)
- return widget
-
- @staticmethod
- def size_constraint_attrs(attrs, maxsize):
- """set html attributes in the attrs dict to consider maxsize"""
- pass
-
- def format_attrs(self):
- """return a string with html attributes available for the edit input"""
- # sort for predictability (required for tests)
- attrs = []
- for name, value in self.attrs.iteritems():
- # namespace attributes have priority over standard xhtml ones
- if name in self.cubicwebns_attributes:
- attrs.append(u'cubicweb:%s="%s"' % (name, value))
- elif name in self.html_attributes:
- attrs.append(u'%s="%s"' % (name, value))
- return u' '.join(sorted(attrs))
-
- def required(self, entity):
- """indicates if the widget needs a value to be filled in"""
- card = self.rschema.cardinality(self.subjtype, self.objtype, self.role)
- return card in '1+'
-
- def input_id(self, entity):
- try:
- return self.rname
- except AttributeError:
- return eid_param(self.name, entity.eid)
-
- def render_label(self, entity, label=None):
- """render widget's label"""
- label = label or self.rschema.display_name(entity.req, self.role)
- forid = self.input_id(entity)
- if forid:
- forattr = ' for="%s"' % forid
- else:
- forattr = ''
- if self.required(entity):
- label = u'<label class="required"%s>%s</label>' % (forattr, label)
- else:
- label = u'<label%s>%s</label>' % (forattr, label)
- return label
-
- def render_error(self, entity):
- """return validation error for widget's field of the given entity, if
- any
- """
- errex = entity.req.data.get('formerrors')
- if errex and errex.eid == entity.eid and self.name in errex.errors:
- entity.req.data['displayederrors'].add(self.name)
- return u'<span class="error">%s</span>' % errex.errors[self.name]
- return u''
-
- def render_help(self, entity):
- """render a help message about the (edited) field"""
- req = entity.req
- help = [u'<div class="helper">']
- descr = self.description or self.rschema.rproperty(self.subjtype, self.objtype, 'description')
- if descr:
- help.append(u'<span>%s</span>' % req._(descr))
- example = self.render_example(req)
- if example:
- help.append(u'<span>(%s: %s)</span>'
- % (req._('sample format'), example))
- help.append(u'</div>')
- return u' '.join(help)
-
- def render_example(self, req):
- return u''
-
- def render(self, entity):
- """render the widget for a simple view"""
- if not entity.has_eid():
- return u''
- return entity.printable_value(self.name)
-
- def edit_render(self, entity, tabindex=None,
- includehelp=False, useid=None, **kwargs):
- """render the widget for edition"""
- # this is necessary to handle multiple edition
- self.rname = eid_param(self.name, entity.eid)
- if useid:
- self.attrs['id'] = useid
- elif self.autoid:
- self.attrs['id'] = self.rname
- if tabindex is not None:
- self.attrs['tabindex'] = tabindex
- else:
- self.attrs['tabindex'] = entity.req.next_tabindex()
- output = self._edit_render(entity, **kwargs)
- if includehelp:
- output += self.render_help(entity)
- return output
-
- def _edit_render(self, entity):
- """do the actual job to render the widget for edition"""
- raise NotImplementedError
-
- def current_values(self, entity):
- """return the value of the field associated to this widget on the given
- entity. always return a list of values, which'll have size equal to 1
- if the field is monovalued (like all attribute fields, but not all non
- final relation fields
- """
- if self.rschema.final:
- return entity.attribute_values(self.name)
- elif entity.has_eid():
- return [row[0] for row in entity.related(self.name, self.role)]
- return ()
-
- def current_value(self, entity):
- return _value_from_values(self.current_values(entity))
-
- def current_display_values(self, entity):
- """same as .current_values but consider values stored in session in case
- of validation error
- """
- values = entity.req.data.get('formvalues')
- if values is None:
- return self.current_values(entity)
- cdvalues = values.get(self.rname)
- if cdvalues is None:
- return self.current_values(entity)
- if not isinstance(cdvalues, (list, tuple)):
- cdvalues = (cdvalues,)
- return cdvalues
-
- def current_display_value(self, entity):
- """same as .current_value but consider values stored in session in case
- of validation error
- """
- return _value_from_values(self.current_display_values(entity))
-
- def hidden_input(self, entity, qvalue):
- """return an hidden field which
- 1. indicates that a field is edited
- 2. hold the old value to easily detect if the field has been modified
-
- `qvalue` is the html quoted old value
- """
- if self.role == 'subject':
- editmark = 'edits'
- else:
- editmark = 'edito'
- if qvalue is None or not entity.has_eid():
- qvalue = INTERNAL_FIELD_VALUE
- return u'<input type="hidden" name="%s-%s" value="%s"/>\n' % (
- editmark, self.rname, qvalue)
-
-class InputWidget(Widget):
- """abstract class for input generating a <input> tag"""
- input_type = None
- html_attributes = Widget.html_attributes | set(('type', 'name', 'value'))
-
- def _edit_render(self, entity):
- value = self.current_value(entity)
- dvalue = self.current_display_value(entity)
- if isinstance(value, basestring):
- value = xml_escape(value)
- if isinstance(dvalue, basestring):
- dvalue = xml_escape(dvalue)
- return u'%s<input type="%s" name="%s" value="%s" %s/>' % (
- self.hidden_input(entity, value), self.input_type,
- self.rname, dvalue, self.format_attrs())
-
-class HiddenWidget(InputWidget):
- input_type = 'hidden'
- autoid = False
- def __init__(self, vreg, subjschema, rschema, objschema,
- role='subject', **kwattrs):
- InputWidget.__init__(self, vreg, subjschema, rschema, objschema,
- role='subject',
- **kwattrs)
- # disable access key
- del self.attrs['accesskey']
-
- def current_value(self, entity):
- value = InputWidget.current_value(self, entity)
- return value or INTERNAL_FIELD_VALUE
-
- def current_display_value(self, entity):
- value = InputWidget.current_display_value(self, entity)
- return value or INTERNAL_FIELD_VALUE
-
- def render_label(self, entity, label=None):
- """render widget's label"""
- return u''
-
- def render_help(self, entity):
- return u''
-
- def hidden_input(self, entity, value):
- """no hidden input for hidden input"""
- return ''
-
-
-class EidWidget(HiddenWidget):
-
- def _edit_render(self, entity):
- return u'<input type="hidden" name="eid" value="%s" />' % entity.eid
-
-
-class StringWidget(InputWidget):
- input_type = 'text'
- html_attributes = InputWidget.html_attributes | set(('size', 'maxlength'))
- @staticmethod
- def size_constraint_attrs(attrs, maxsize):
- """set html attributes in the attrs dict to consider maxsize"""
- attrs['size'] = min(maxsize, 40)
- attrs['maxlength'] = maxsize
-
-
-class AutoCompletionWidget(StringWidget):
- cubicwebns_attributes = (StringWidget.cubicwebns_attributes |
- set(('accesskey', 'size', 'maxlength')))
- attrs = ()
-
- wdgtype = 'SuggestField'
-
- def current_value(self, entity):
- value = StringWidget.current_value(self, entity)
- return value or INTERNAL_FIELD_VALUE
-
- def _get_url(self, entity):
- return entity.req.build_url('json', fname=entity.autocomplete_initfuncs[self.rschema],
- pageid=entity.req.pageid, mode='remote')
-
- def _edit_render(self, entity):
- req = entity.req
- req.add_js( ('cubicweb.widgets.js', 'jquery.autocomplete.js') )
- req.add_css('jquery.autocomplete.css')
- value = self.current_value(entity)
- dvalue = self.current_display_value(entity)
- if isinstance(value, basestring):
- value = xml_escape(value)
- if isinstance(dvalue, basestring):
- dvalue = xml_escape(dvalue)
- iid = self.attrs.pop('id')
- if self.required(entity):
- cssclass = u' required'
- else:
- cssclass = u''
- dataurl = self._get_url(entity)
- return (u'%(hidden)s<input type="text" name="%(iid)s" value="%(value)s" cubicweb:dataurl="%(url)s" class="widget%(required)s" id="%(iid)s" '
- u'tabindex="%(tabindex)s" cubicweb:loadtype="auto" cubicweb:wdgtype="%(wdgtype)s" %(attrs)s />' % {
- 'iid': iid,
- 'hidden': self.hidden_input(entity, value),
- 'wdgtype': self.wdgtype,
- 'url': xml_escape(dataurl),
- 'tabindex': self.attrs.pop('tabindex'),
- 'value': dvalue,
- 'attrs': self.format_attrs(),
- 'required' : cssclass,
- })
-
-class StaticFileAutoCompletionWidget(AutoCompletionWidget):
- wdgtype = 'StaticFileSuggestField'
-
- def _get_url(self, entity):
- return entity.req.datadir_url + entity.autocomplete_initfuncs[self.rschema]
-
-class RestrictedAutoCompletionWidget(AutoCompletionWidget):
- wdgtype = 'RestrictedSuggestField'
-
-
-class PasswordWidget(InputWidget):
- input_type = 'password'
-
- def required(self, entity):
- if InputWidget.required(self, entity) and not entity.has_eid():
- return True
- return False
-
- def current_values(self, entity):
- # on existant entity, show password field has non empty (we don't have
- # the actual value
- if entity.has_eid():
- return (INTERNAL_FIELD_VALUE,)
- return super(PasswordWidget, self).current_values(entity)
-
- def _edit_render(self, entity):
- html = super(PasswordWidget, self)._edit_render(entity)
- name = eid_param(self.name + '-confirm', entity.eid)
- return u'%s<br/>\n<input type="%s" name="%s" id="%s" tabindex="%s"/> <span class="emphasis">(%s)</span>' % (
- html, self.input_type, name, name, entity.req.next_tabindex(),
- entity.req._('confirm password'))
-
-
-class TextWidget(Widget):
- html_attributes = Widget.html_attributes | set(('rows', 'cols'))
-
- @staticmethod
- def size_constraint_attrs(attrs, maxsize):
- """set html attributes in the attrs dict to consider maxsize"""
- if 256 < maxsize < 513:
- attrs['cols'], attrs['rows'] = 60, 5
- else:
- attrs['cols'], attrs['rows'] = 80, 10
-
- def render(self, entity):
- if not entity.has_eid():
- return u''
- return entity.printable_value(self.name)
-
- def _edit_render(self, entity, with_format=True):
- req = entity.req
- editor = self._edit_render_textarea(entity, with_format)
- value = self.current_value(entity)
- if isinstance(value, basestring):
- value = xml_escape(value)
- return u'%s%s' % (self.hidden_input(entity, value), editor)
-
- def _edit_render_textarea(self, entity, with_format):
- self.attrs.setdefault('cols', 80)
- self.attrs.setdefault('rows', 20)
- dvalue = self.current_display_value(entity)
- if isinstance(dvalue, basestring):
- dvalue = xml_escape(dvalue)
- if entity.use_fckeditor(self.name):
- entity.req.fckeditor_config()
- if with_format:
- if entity.has_eid():
- format = entity.attr_metadata(self.name, 'format')
- else:
- format = ''
- frname = eid_param(self.name + '_format', entity.eid)
- hidden = u'<input type="hidden" name="edits-%s" value="%s"/>\n'\
- '<input type="hidden" name="%s" value="text/html"/>\n' % (
- frname, format, frname)
- return u'%s<textarea cubicweb:type="wysiwyg" onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
- hidden, self.rname, self.format_attrs(), dvalue)
- if with_format and entity.e_schema.has_metadata(self.name, 'format'):
- fmtwdg = entity.get_widget(self.name + '_format')
- fmtwdgstr = fmtwdg.edit_render(entity, tabindex=self.attrs['tabindex'])
- self.attrs['tabindex'] = entity.req.next_tabindex()
- else:
- fmtwdgstr = ''
- return u'%s<br/><textarea onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
- fmtwdgstr, self.rname, self.format_attrs(), dvalue)
-
-
-class CheckBoxWidget(Widget):
- html_attributes = Widget.html_attributes | set(('checked', ))
- def _edit_render(self, entity):
- value = self.current_value(entity)
- dvalue = self.current_display_value(entity)
- return self.hidden_input(entity, value) + checkbox(self.rname, 'checked', self.format_attrs(), dvalue)
-
- def render(self, entity):
- if not entity.has_eid():
- return u''
- if getattr(entity, self.name):
- return entity.req._('yes')
- return entity.req._('no')
-
-
-class YesNoRadioWidget(CheckBoxWidget):
- html_attributes = Widget.html_attributes | set(('disabled',))
- def _edit_render(self, entity):
- value = self.current_value(entity)
- dvalue = self.current_display_value(entity)
- attrs1 = self.format_attrs()
- del self.attrs['id'] # avoid duplicate id for xhtml compliance
- attrs2 = self.format_attrs()
- if dvalue:
- attrs1 += ' checked="checked"'
- else:
- attrs2 += ' checked="checked"'
- wdgs = [self.hidden_input(entity, value),
- u'<input type="radio" name="%s" value="1" %s/>%s<br/>' % (self.rname, attrs1, entity.req._('yes')),
- u'<input type="radio" name="%s" value="" %s/>%s<br/>' % (self.rname, attrs2, entity.req._('no'))]
- return '\n'.join(wdgs)
-
-
-class FileWidget(Widget):
- need_multipart = True
- def _file_wdg(self, entity):
- wdgs = [u'<input type="file" name="%s" %s/>' % (self.rname, self.format_attrs())]
- req = entity.req
- if (entity.e_schema.has_metadata(self.name, 'format')
- or entity.e_schema.has_metadata(self.name, 'encoding')):
- divid = '%s-%s-advanced' % (self.name, entity.eid)
- wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
- (xml_escape(toggle_action(divid)),
- req._('show advanced fields'),
- xml_escape(req.build_url('data/puce_down.png')),
- req._('show advanced fields')))
- wdgs.append(u'<div id="%s" class="hidden">' % divid)
- for extraattr in ('_format', '_encoding'):
- if entity.e_schema.has_subject_relation('%s%s' % (self.name, extraattr)):
- ewdg = entity.get_widget(self.name + extraattr)
- wdgs.append(ewdg.render_label(entity))
- wdgs.append(ewdg.edit_render(entity, includehelp=True))
- wdgs.append(u'<br/>')
- wdgs.append(u'</div>')
- if entity.has_eid():
- if not self.required(entity):
- # trick to be able to delete an uploaded file
- wdgs.append(u'<br/>')
- wdgs.append(checkbox(eid_param('__%s_detach' % self.rname, entity.eid), False))
- wdgs.append(req._('detach attached file %s' % entity.dc_title()))
- else:
- wdgs.append(u'<br/>')
- wdgs.append(req._('currently attached file: %s' % entity.dc_title()))
- return '\n'.join(wdgs)
-
- def _edit_render(self, entity):
- return self.hidden_input(entity, None) + self._file_wdg(entity)
-
-
-class TextFileWidget(FileWidget):
- def _edit_msg(self, entity):
- if entity.has_eid() and not self.required(entity):
- msg = entity.req._(
- 'You can either submit a new file using the browse button above'
- ', or choose to remove already uploaded file by checking the '
- '"detach attached file" check-box, or edit file content online '
- 'with the widget below.')
- else:
- msg = entity.req._(
- 'You can either submit a new file using the browse button above'
- ', or edit file content online with the widget below.')
- return msg
-
- def _edit_render(self, entity):
- wdgs = [self._file_wdg(entity)]
- if entity.attr_metadata(self.name, 'format') in ('text/plain', 'text/html', 'text/rest'):
- msg = self._edit_msg(entity)
- wdgs.append(u'<p><b>%s</b></p>' % msg)
- twdg = TextWidget(self.vreg, self.subjtype, self.rschema, self.objtype)
- twdg.rname = self.rname
- data = getattr(entity, self.name)
- if data:
- encoding = entity.attr_metadata(self.name, 'encoding')
- try:
- entity[self.name] = unicode(data.getvalue(), encoding)
- except UnicodeError:
- pass
- else:
- wdgs.append(twdg.edit_render(entity, with_format=False))
- entity[self.name] = data # restore Binary value
- wdgs.append(u'<br/>')
- return '\n'.join(wdgs)
-
-
-class ComboBoxWidget(Widget):
- html_attributes = Widget.html_attributes | set(('multiple', 'size'))
-
- def __init__(self, vreg, subjschema, rschema, objschema,
- multiple=False, **kwattrs):
- super(ComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
- **kwattrs)
- if multiple:
- self.attrs['multiple'] = 'multiple'
- if not 'size' in self.attrs:
- self.attrs['size'] = '5'
- # disable access key (dunno why but this is not allowed by xhtml 1.0)
- del self.attrs['accesskey']
-
- def vocabulary(self, entity):
- raise NotImplementedError()
-
- def form_value(self, entity, value, values):
- if value in values:
- flag = 'selected="selected"'
- else:
- flag = ''
- return value, flag
-
- def _edit_render(self, entity):
- values = self.current_values(entity)
- if values:
- res = [self.hidden_input(entity, v) for v in values]
- else:
- res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
- dvalues = self.current_display_values(entity)
- res.append(u'<select name="%s" %s>' % (self.rname, self.format_attrs()))
- for label, value in self.vocabulary(entity):
- if value is None:
- # handle separator
- res.append(u'<optgroup label="%s"/>' % (label or ''))
- else:
- value, flag = self.form_value(entity, value, dvalues)
- res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
- res.append(u'</select>')
- return '\n'.join(res)
-
-
-class StaticComboBoxWidget(ComboBoxWidget):
-
- def __init__(self, vreg, subjschema, rschema, objschema,
- vocabfunc, multiple=False, sort=False, **kwattrs):
- super(StaticComboBoxWidget, self).__init__(vreg, subjschema, rschema, objschema,
- multiple, **kwattrs)
- self.sort = sort
- self.vocabfunc = vocabfunc
-
- def vocabulary(self, entity):
- choices = self.vocabfunc(entity=entity)
- if self.sort:
- choices = sorted(choices)
- if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'):
- return zip((entity.req._(v) for v in choices), choices)
- return zip(choices, choices)
-
-
-class EntityLinkComboBoxWidget(ComboBoxWidget):
- """to be used be specific forms"""
-
- def current_values(self, entity):
- if entity.has_eid():
- return [r[0] for r in entity.related(self.name, self.role)]
- defaultmeth = 'default_%s_%s' % (self.role, self.name)
- if hasattr(entity, defaultmeth):
- return getattr(entity, defaultmeth)()
- return ()
-
- def vocabulary(self, entity):
- return [('', INTERNAL_FIELD_VALUE)] + entity.vocabulary(self.rschema, self.role)
-
-
-class RawDynamicComboBoxWidget(EntityLinkComboBoxWidget):
-
- def vocabulary(self, entity, limit=None):
- req = entity.req
- # first see if its specified by __linkto form parameters
- linkedto = entity.linked_to(self.name, self.role)
- if linkedto:
- entities = (req.entity_from_eid(eid) for eid in linkedto)
- return [(entity.view('combobox'), entity.eid) for entity in entities]
- # it isn't, check if the entity provides a method to get correct values
- if not self.required(entity):
- res = [('', INTERNAL_FIELD_VALUE)]
- else:
- res = []
- # vocabulary doesn't include current values, add them
- if entity.has_eid():
- rset = entity.related(self.name, self.role)
- relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()]
- else:
- relatedvocab = []
- return res + entity.vocabulary(self.rschema, self.role) + relatedvocab
-
-
-class DynamicComboBoxWidget(RawDynamicComboBoxWidget):
-
- def vocabulary(self, entity, limit=None):
- return sorted(super(DynamicComboBoxWidget, self).vocabulary(entity, limit))
-
-
-class AddComboBoxWidget(DynamicComboBoxWidget):
- def _edit_render(self, entity):
- req = entity.req
- req.add_js( ('cubicweb.ajax.js', 'jquery.js', 'cubicweb.widgets.js') )
- values = self.current_values(entity)
- if values:
- res = [self.hidden_input(entity, v) for v in values]
- else:
- res = [self.hidden_input(entity, INTERNAL_FIELD_VALUE)]
- dvalues = self.current_display_values(entity)
- etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
- res.append(u'<select class="widget" cubicweb:etype_to="%s" cubicweb:etype_from="%s" cubicweb:loadtype="auto" cubicweb:wdgtype="AddComboBox" name="%s" %s>'
- % (entity.e_schema, etype_from, self.rname, self.format_attrs()))
- for label, value in self.vocabulary(entity):
- if value is None:
- # handle separator
- res.append(u'<optgroup label="%s"/>' % (label or ''))
- else:
- value, flag = self.form_value(entity, value, dvalues)
- res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
- res.append(u'</select>')
- res.append(u'<div id="newvalue">')
- res.append(u'<input type="text" id="newopt" />')
- res.append(u'<a href="javascript:noop()" id="add_newopt"> </a></div>')
- return '\n'.join(res)
-
-
-class IntegerWidget(StringWidget):
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 5
- kwattrs['maxlength'] = 15
- StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
- def render_example(self, req):
- return '23'
-
-
-class FloatWidget(StringWidget):
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 5
- kwattrs['maxlength'] = 15
- StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
- def render_example(self, req):
- formatstr = req.property_value('ui.float-format')
- return formatstr % 1.23
-
- def current_values(self, entity):
- values = entity.attribute_values(self.name)
- if values:
- formatstr = entity.req.property_value('ui.float-format')
- value = values[0]
- if value is not None:
- value = float(value)
- else:
- return ()
- return [formatstr % value]
- return ()
-
-
-class DecimalWidget(StringWidget):
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 5
- kwattrs['maxlength'] = 15
- StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
- def render_example(self, req):
- return '345.0300'
-
-
-class DateWidget(StringWidget):
- format_key = 'ui.date-format'
- monthnames = ('january', 'february', 'march', 'april',
- 'may', 'june', 'july', 'august',
- 'september', 'october', 'november', 'december')
- daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
- 'friday', 'saturday', 'sunday')
-
- @classmethod
- def add_localized_infos(cls, req):
- """inserts JS variables defining localized months and days"""
- # import here to avoid dependancy from cubicweb-common to simplejson
- _ = req._
- monthnames = [_(mname) for mname in cls.monthnames]
- daynames = [_(dname) for dname in cls.daynames]
- req.html_headers.define_var('MONTHNAMES', monthnames)
- req.html_headers.define_var('DAYNAMES', daynames)
-
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs.setdefault('size', 10)
- kwattrs.setdefault('maxlength', 10)
- StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
- def current_values(self, entity):
- values = entity.attribute_values(self.name)
- if values and hasattr(values[0], 'strftime'):
- formatstr = entity.req.property_value(self.format_key)
- return [values[0].strftime(str(formatstr))]
- return values
-
- def render_example(self, req):
- formatstr = req.property_value(self.format_key)
- return datetime.now().strftime(str(formatstr))
-
-
- def _edit_render(self, entity):
- wdg = super(DateWidget, self)._edit_render(entity)
- cal_button = self.render_calendar_popup(entity)
- return wdg+cal_button
-
- def render_help(self, entity):
- """calendar popup widget"""
- req = entity.req
- help = [ u'<div class="helper">' ]
- descr = self.rschema.rproperty(self.subjtype, self.objtype, 'description')
- if descr:
- help.append('<span>%s</span>' % req._(descr))
- example = self.render_example(req)
- if example:
- help.append('<span>(%s: %s)</span>'
- % (req._('sample format'), example))
- help.append(u'</div>')
- return u' '.join(help)
-
- def render_calendar_popup(self, entity):
- """calendar popup widget"""
- req = entity.req
- self.add_localized_infos(req)
- req.add_js(('cubicweb.ajax.js', 'cubicweb.calendar.js',))
- req.add_css(('cubicweb.calendar_popup.css',))
- inputid = self.attrs.get('id', self.rname)
- helperid = "%shelper" % inputid
- _today = datetime.now()
- year = int(req.form.get('year', _today.year))
- month = int(req.form.get('month', _today.month))
-
- return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
-<img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
- % (helperid, inputid, year, month,
- req.external_resource('CALENDAR_ICON'), req._('calendar'), helperid) )
-
-class DateTimeWidget(DateWidget):
- format_key = 'ui.datetime-format'
-
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 16
- kwattrs['maxlength'] = 16
- DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
- def render_example(self, req):
- formatstr1 = req.property_value('ui.datetime-format')
- formatstr2 = req.property_value('ui.date-format')
- return req._('%(fmt1)s, or without time: %(fmt2)s') % {
- 'fmt1': datetime.now().strftime(str(formatstr1)),
- 'fmt2': datetime.now().strftime(str(formatstr2)),
- }
-
-
-class TimeWidget(StringWidget):
- format_key = 'ui.time-format'
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 5
- kwattrs['maxlength'] = 5
- StringWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-
-class EmailWidget(StringWidget):
-
- def render(self, entity):
- email = getattr(entity, self.name)
- if not email:
- return u''
- return u'<a href="mailto:%s">%s</a>' % (email, email)
-
-class URLWidget(StringWidget):
-
- def render(self, entity):
- url = getattr(entity, self.name)
- if not url:
- return u''
- url = xml_escape(url)
- return u'<a href="%s">%s</a>' % (url, url)
-
-class EmbededURLWidget(StringWidget):
-
- def render(self, entity):
- url = getattr(entity, self.name)
- if not url:
- return u''
- aurl = xml_escape(entity.build_url('embed', url=url))
- return u'<a href="%s">%s</a>' % (aurl, url)
-
-
-
-def widget_factory(vreg, subjschema, rschema, objschema, role='subject',
- **kwargs):
- """return the most adapated widget to edit the relation
- 'subjschema rschema objschema' according to information found in the schema
- """
- if role == 'subject':
- eclass, subjschema = _eclass_eschema(subjschema)
- else:
- eclass, objschema = _eclass_eschema(objschema)
- if eclass is not None and rschema in getattr(eclass, 'widgets', ()):
- wcls = WIDGETS[eclass.widgets[rschema]]
- elif not rschema.final:
- card = rschema.rproperty(subjschema, objschema, 'cardinality')
- if role == 'object':
- multiple = card[1] in '+*'
- else: #if role == 'subject':
- multiple = card[0] in '+*'
- return DynamicComboBoxWidget(vreg, subjschema, rschema, objschema,
- role=role, multiple=multiple)
- else:
- wcls = None
- factory = FACTORIES.get(objschema, _default_widget_factory)
- return factory(vreg, subjschema, rschema, objschema, wcls=wcls,
- role=role, **kwargs)
-
-
-# factories to find the most adapated widget according to a type and other constraints
-
-def _string_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
- w = None
- for c in rschema.rproperty(subjschema, objschema, 'constraints'):
- if isinstance(c, StaticVocabularyConstraint):
- # may have been set by a previous SizeConstraint but doesn't make sense
- # here (even doesn't have the same meaning on a combobox actually)
- kwargs.pop('size', None)
- return (wcls or StaticComboBoxWidget)(vreg, subjschema, rschema, objschema,
- vocabfunc=c.vocabulary, **kwargs)
- if isinstance(c, SizeConstraint) and c.max is not None:
- # don't return here since a StaticVocabularyConstraint may
- # follow
- if wcls is None:
- if c.max < 257:
- _wcls = StringWidget
- else:
- _wcls = TextWidget
- else:
- _wcls = wcls
- _wcls.size_constraint_attrs(kwargs, c.max)
- w = _wcls(vreg, subjschema, rschema, objschema, **kwargs)
- if w is None:
- w = (wcls or TextWidget)(vreg, subjschema, rschema, objschema, **kwargs)
- return w
-
-def _default_widget_factory(vreg, subjschema, rschema, objschema, wcls=None, **kwargs):
- if wcls is None:
- wcls = _WFACTORIES[objschema]
- return wcls(vreg, subjschema, rschema, objschema, **kwargs)
-
-FACTORIES = {
- 'String' : _string_widget_factory,
- 'Boolean': _default_widget_factory,
- 'Bytes': _default_widget_factory,
- 'Date': _default_widget_factory,
- 'Datetime': _default_widget_factory,
- 'Float': _default_widget_factory,
- 'Decimal': _default_widget_factory,
- 'Int': _default_widget_factory,
- 'Password': _default_widget_factory,
- 'Time': _default_widget_factory,
- }
-
-# default widget by entity's type
-_WFACTORIES = {
- 'Boolean': YesNoRadioWidget,
- 'Bytes': FileWidget,
- 'Date': DateWidget,
- 'Datetime': DateTimeWidget,
- 'Int': IntegerWidget,
- 'Float': FloatWidget,
- 'Decimal': DecimalWidget,
- 'Password': PasswordWidget,
- 'String' : StringWidget,
- 'Time': TimeWidget,
- }
-
-# widgets registry
-WIDGETS = {}
-def register(widget_list):
- for obj in widget_list:
- if isinstance(obj, type) and issubclass(obj, Widget):
- if obj is Widget or obj is ComboBoxWidget:
- continue
- WIDGETS[obj.__name__] = obj
-
-register(globals().values())
--- a/wsgi/handler.py Mon Feb 08 13:33:09 2010 +0100
+++ b/wsgi/handler.py Mon Feb 08 13:35:07 2010 +0100
@@ -97,7 +97,7 @@
# assert self.base_url[-1] == '/'
# self.https_url = config['https-url']
# assert not self.https_url or self.https_url[-1] == '/'
- self.url_rewriter = self.appli.vreg.select_object('components', 'urlrewriter')
+ self.url_rewriter = self.appli.vreg['components'].select_or_none('urlrewriter')
def _render(self, req):
"""this function performs the actual rendering