backport stable branch, take care a lot of conflicts occured, this may be the revision you're looking for...
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/appobject.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,338 @@
+"""Base class for dynamically loaded objects manipulated in the web interface
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from warnings import warn
+
+from mx.DateTime import now, oneSecond
+from simplejson import dumps
+
+from logilab.common.decorators import classproperty
+from logilab.common.deprecation import obsolete
+
+from rql.nodes import VariableRef, SubQuery
+from rql.stmts import Union, Select
+
+from cubicweb import Unauthorized
+from cubicweb.vregistry import VObject, AndSelector
+from cubicweb.selectors import yes
+from cubicweb.utils import UStringIO, ustrftime
+
+
+class Cache(dict):
+ def __init__(self):
+ super(Cache, self).__init__()
+ self.cache_creation_date = None
+ self.latest_cache_lookup = now()
+
+CACHE_REGISTRY = {}
+
+class AppRsetObject(VObject):
+ """This is the base class for CubicWeb application objects
+ which are selected according to a request and result set.
+
+ Classes are kept in the vregistry and instantiation is done at selection
+ time.
+
+ At registration time, the following attributes are set on the class:
+ :vreg:
+ the application's registry
+ :schema:
+ the application's schema
+ :config:
+ the application's configuration
+
+ At instantiation time, the following attributes are set on the instance:
+ :req:
+ current request
+ :rset:
+ result set on which the object is applied
+ """
+ __select__ = yes()
+
+ @classmethod
+ def registered(cls, vreg):
+ super(AppRsetObject, cls).registered(vreg)
+ cls.vreg = vreg
+ cls.schema = vreg.schema
+ cls.config = vreg.config
+ cls.register_properties()
+ return cls
+
+ @classmethod
+ def selected(cls, *args, **kwargs):
+ """by default web app objects are usually instantiated on
+ selection according to a request, a result set, and optional
+ row and col
+ """
+ assert len(args) <= 2
+# for key in ('req', 'rset'):
+# if key in kwargs:
+# args += (kwargs.pop(key),)
+ instance = cls(*args)
+ instance.row = kwargs.pop('row', None)
+ instance.col = kwargs.pop('col', None)
+ instance.extra_kwargs = kwargs
+ return instance
+
+ # Eproperties definition:
+ # key: id of the property (the actual EProperty 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)
+
+ @classmethod
+ def propkey(cls, propid):
+ return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
+
+ @classproperty
+ @obsolete('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):
+ super(AppRsetObject, self).__init__()
+ self.req = req
+ self.rset = rset
+
+ @property
+ def cursor(self): # XXX deprecate in favor of req.cursor?
+ msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
+ warn(msg, DeprecationWarning, stacklevel=2)
+ return self.req.cursor
+
+ 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()
+ CACHE_REGISTRY[cachename] = cache
+ _now = now()
+ if _now > cache.latest_cache_lookup + oneSecond:
+ ecache = self.req.execute('Any C,T WHERE C is ECache, 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.empty()
+ 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
+ """
+ # 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.select_component('navigation', self.req, 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
+
+ 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
+
+ # url generation methods ##################################################
+
+ controller = 'view'
+
+ def build_url(self, method=None, **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.
+ """
+ # 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)
+
+ # various resources accessors #############################################
+
+ def etype_rset(self, etype, size=1):
+ """return a fake result set for a particular entity type"""
+ msg = '.etype_rset is deprecated, use req.etype_rset'
+ warn(msg, DeprecationWarning, stacklevel=2)
+ return self.req.etype_rset(etype, size=1)
+
+ def eid_rset(self, eid, etype=None):
+ """return a result set for the given eid"""
+ msg = '.eid_rset is deprecated, use req.eid_rset'
+ warn(msg, DeprecationWarning, stacklevel=2)
+ return self.req.eid_rset(eid, etype)
+
+ 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)
+
+ 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
+
+ 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.req.add_js('cubicweb.ajax.js')
+ if nonify:
+ # XXX < 2.48.3 bw compat
+ warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
+ _cb = cb
+ def cb(*args):
+ _cb(*args)
+ cbname = self.req.register_onetime_callback(cb, *args)
+ msg = dumps(msg or '')
+ return "javascript:userCallbackThenReloadPage('%s', %s)" % (
+ cbname, msg)
+
+ # formating methods #######################################################
+
+ def tal_render(self, template, variables):
+ """render a precompiled page template with variables in the given
+ dictionary as context
+ """
+ from cubicweb.common.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()
+
+ def format_date(self, date, date_format=None, time=False):
+ """return a string for a mx date time according to application'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''
+
+ def format_time(self, time):
+ """return a string for a mx date time according to application's
+ configuration
+ """
+ if time:
+ return ustrftime(time, self.req.property_value('ui.time-format'))
+ return u''
+
+ def format_float(self, num):
+ """return a string for floating point number according to application's
+ configuration
+ """
+ if num:
+ return self.req.property_value('ui.float-format') % num
+ return u''
+
+ # 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'))
+
+
+class AppObject(AppRsetObject):
+ """base class for application objects which are not selected
+ according to a result set, only by their identifier.
+
+ Those objects may not have req, rset and cursor set.
+ """
+
+ @classmethod
+ def selected(cls, *args, **kwargs):
+ """by default web app objects are usually instantiated on
+ selection
+ """
+ return cls(*args, **kwargs)
+
+ def __init__(self, req=None, rset=None, **kwargs):
+ self.req = req
+ self.rset = rset
+ self.__dict__.update(kwargs)
--- a/common/appobject.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/appobject.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,463 +1,3 @@
-"""Base class for dynamically loaded objects manipulated in the web interface
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
from warnings import warn
-
-from mx.DateTime import now, oneSecond
-from simplejson import dumps
-
-from logilab.common.deprecation import obsolete
-
-from rql.nodes import VariableRef, SubQuery
-from rql.stmts import Union, Select
-
-from cubicweb import Unauthorized
-from cubicweb.vregistry import VObject
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.uilib import html_escape, ustrftime
-from cubicweb.common.registerers import yes_registerer, priority_registerer
-from cubicweb.common.selectors import yes
-
-_MARKER = object()
-
-
-class Cache(dict):
- def __init__(self):
- super(Cache, self).__init__()
- self.cache_creation_date = None
- self.latest_cache_lookup = now()
-
-CACHE_REGISTRY = {}
-
-class AppRsetObject(VObject):
- """This is the base class for CubicWeb application objects
- which are selected according to a request and result set.
-
- Classes are kept in the vregistry and instantiation is done at selection
- time.
-
- At registration time, the following attributes are set on the class:
- :vreg:
- the application's registry
- :schema:
- the application's schema
- :config:
- the application's configuration
-
- At instantiation time, the following attributes are set on the instance:
- :req:
- current request
- :rset:
- result set on which the object is applied
- """
-
- @classmethod
- def registered(cls, vreg):
- cls.vreg = vreg
- cls.schema = vreg.schema
- cls.config = vreg.config
- cls.register_properties()
- return cls
-
- @classmethod
- def selected(cls, req, rset, row=None, col=None, **kwargs):
- """by default web app objects are usually instantiated on
- selection according to a request, a result set, and optional
- row and col
- """
- instance = cls(req, rset)
- instance.row = row
- instance.col = col
- return instance
-
- # Eproperties definition:
- # key: id of the property (the actual EProperty 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)
-
- @classmethod
- def propkey(cls, propid):
- return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
-
-
- def __init__(self, req, rset):
- super(AppRsetObject, self).__init__()
- self.req = req
- self.rset = rset
-
- @property
- def cursor(self): # XXX deprecate in favor of req.cursor?
- msg = '.cursor is deprecated, use req.execute (or req.cursor if necessary)'
- warn(msg, DeprecationWarning, stacklevel=2)
- return self.req.cursor
-
- 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()
- CACHE_REGISTRY[cachename] = cache
- _now = now()
- if _now > cache.latest_cache_lookup + oneSecond:
- ecache = self.req.execute('Any C,T WHERE C is ECache, 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.empty()
- 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
- """
- # 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.select_component('navigation', self.req, 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
-
- 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
-
- # url generation methods ##################################################
-
- controller = 'view'
-
- def build_url(self, method=None, **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.
- """
- # 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)
-
- # various resources accessors #############################################
-
- def etype_rset(self, etype, size=1):
- """return a fake result set for a particular entity type"""
- msg = '.etype_rset is deprecated, use req.etype_rset'
- warn(msg, DeprecationWarning, stacklevel=2)
- return self.req.etype_rset(etype, size=1)
-
- def eid_rset(self, eid, etype=None):
- """return a result set for the given eid"""
- msg = '.eid_rset is deprecated, use req.eid_rset'
- warn(msg, DeprecationWarning, stacklevel=2)
- return self.req.eid_rset(eid, etype)
-
- 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)
-
- 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
-
- 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.req.add_js('cubicweb.ajax.js')
- if nonify:
- # XXX < 2.48.3 bw compat
- warn('nonify argument is deprecated', DeprecationWarning, stacklevel=2)
- _cb = cb
- def cb(*args):
- _cb(*args)
- cbname = self.req.register_onetime_callback(cb, *args)
- msg = dumps(msg or '')
- return "javascript:userCallbackThenReloadPage('%s', %s)" % (
- cbname, msg)
-
- # formating methods #######################################################
-
- def tal_render(self, template, variables):
- """render a precompiled page template with variables in the given
- dictionary as context
- """
- from cubicweb.common.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()
-
- def format_date(self, date, date_format=None, time=False):
- """return a string for a mx date time according to application'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''
-
- def format_time(self, time):
- """return a string for a mx date time according to application's
- configuration
- """
- if time:
- return ustrftime(time, self.req.property_value('ui.time-format'))
- return u''
-
- def format_float(self, num):
- """return a string for floating point number according to application's
- configuration
- """
- if num:
- return self.req.property_value('ui.float-format') % num
- return u''
-
- # 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'))
-
- # .accepts handling utilities #############################################
-
- accepts = ('Any',)
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- """apply the following rules:
- * if row is None, return the sum of values returned by the method
- for each entity's type in the result set. If any score is 0,
- return 0.
- * if row is specified, return the value returned by the method with
- the entity's type of this row
- """
- if row is None:
- score = 0
- for etype in rset.column_types(0):
- accepted = cls.accept(req.user, etype)
- if not accepted:
- return 0
- score += accepted
- return score
- return cls.accept(req.user, rset.description[row][col or 0])
-
- @classmethod
- def accept(cls, user, etype):
- """score etype, returning better score on exact match"""
- if 'Any' in cls.accepts:
- return 1
- eschema = cls.schema.eschema(etype)
- matching_types = [e.type for e in eschema.ancestors()]
- matching_types.append(etype)
- for index, basetype in enumerate(matching_types):
- if basetype in cls.accepts:
- return 2 + index
- return 0
-
- # .rtype handling utilities ##############################################
-
- @classmethod
- def relation_possible(cls, etype):
- """tell if a relation with etype entity is possible according to
- mixed class'.etype, .rtype and .target attributes
-
- XXX should probably be moved out to a function
- """
- schema = cls.schema
- rtype = cls.rtype
- eschema = schema.eschema(etype)
- if hasattr(cls, 'role'):
- role = cls.role
- elif cls.target == 'subject':
- role = 'object'
- else:
- role = 'subject'
- # check if this relation is possible according to the schema
- try:
- if role == 'object':
- rschema = eschema.object_relation(rtype)
- else:
- rschema = eschema.subject_relation(rtype)
- except KeyError:
- return False
- if hasattr(cls, 'etype'):
- letype = cls.etype
- try:
- if role == 'object':
- return etype in rschema.objects(letype)
- else:
- return etype in rschema.subjects(letype)
- except KeyError, ex:
- return False
- return True
-
-
- # XXX deprecated (since 2.43) ##########################
-
- @obsolete('use req.datadir_url')
- def datadir_url(self):
- """return url of the application's data directory"""
- return self.req.datadir_url
-
- @obsolete('use req.external_resource()')
- def external_resource(self, rid, default=_MARKER):
- return self.req.external_resource(rid, default)
-
-
-class AppObject(AppRsetObject):
- """base class for application objects which are not selected
- according to a result set, only by their identifier.
-
- Those objects may not have req, rset and cursor set.
- """
-
- @classmethod
- def selected(cls, *args, **kwargs):
- """by default web app objects are usually instantiated on
- selection
- """
- return cls(*args, **kwargs)
-
- def __init__(self, req=None, rset=None, **kwargs):
- self.req = req
- self.rset = rset
- self.__dict__.update(kwargs)
-
-
-class ReloadableMixIn(object):
- """simple mixin for reloadable parts of UI"""
-
- 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.req.add_js('cubicweb.ajax.js')
- if nonify:
- _cb = cb
- def cb(*args):
- _cb(*args)
- cbname = self.req.register_onetime_callback(cb, *args)
- return self.build_js(cbname, html_escape(msg or ''))
-
- def build_update_js_call(self, cbname, msg):
- rql = html_escape(self.rset.printable_rql())
- return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
- cbname, self.id, rql, msg, self.__registry__, self.div_id())
-
- def build_reload_js_call(self, cbname, msg):
- return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
-
- build_js = build_update_js_call # expect updatable component by default
-
- def div_id(self):
- return ''
-
-
-class ComponentMixIn(ReloadableMixIn):
- """simple mixin for component object"""
- __registry__ = 'components'
- __registerer__ = yes_registerer
- __selectors__ = (yes,)
- __select__ = classmethod(*__selectors__)
-
- def div_class(self):
- return '%s %s' % (self.propval('htmlclass'), self.id)
-
- def div_id(self):
- return '%sComponent' % self.id
-
-
-class Component(ComponentMixIn, AppObject):
- """base class for non displayable components
- """
-
-class SingletonComponent(Component):
- """base class for non displayable unique components
- """
- __registerer__ = priority_registerer
+warn('moved to cubicweb.appobject', DeprecationWarning, stacklevel=2)
+from cubicweb.appobject import *
--- a/common/entity.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/entity.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,1106 +1,4 @@
-"""Base class for entity objects manipulated in clients
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.common import interface
-from logilab.common.compat import all
-from logilab.common.decorators import cached
-from logilab.mtconverter import TransformData, TransformError
-from rql.utils import rqlvar_maker
-
-from cubicweb import Unauthorized
-from cubicweb.vregistry import autoselectors
-from cubicweb.rset import ResultSet
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.registerers import id_registerer
-from cubicweb.common.selectors import yes
-from cubicweb.common.uilib import printable_value, html_escape, soup2xhtml
-from cubicweb.common.mixins import MI_REL_TRIGGERS
-from cubicweb.common.mttransforms import ENGINE
-from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
-
-_marker = object()
-
-def greater_card(rschema, subjtypes, objtypes, index):
- for subjtype in subjtypes:
- for objtype in objtypes:
- card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
- if card in '+*':
- return card
- return '1'
-
-
-class RelationTags(object):
-
- MODE_TAGS = frozenset(('link', 'create'))
- CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
- 'inlineview'))
-
- def __init__(self, eclass, tagdefs):
- # XXX if a rtag is redefined in a subclass,
- # the rtag of the base class overwrite the rtag of the subclass
- self.eclass = eclass
- self._tagdefs = {}
- for relation, tags in tagdefs.iteritems():
- # tags must become a set
- if isinstance(tags, basestring):
- tags = set((tags,))
- elif not isinstance(tags, set):
- tags = set(tags)
- # relation must become a 3-uple (rtype, targettype, role)
- if isinstance(relation, basestring):
- self._tagdefs[(relation, '*', 'subject')] = tags
- self._tagdefs[(relation, '*', 'object')] = tags
- elif len(relation) == 1: # useful ?
- self._tagdefs[(relation[0], '*', 'subject')] = tags
- self._tagdefs[(relation[0], '*', 'object')] = tags
- elif len(relation) == 2:
- rtype, ttype = relation
- ttype = bw_normalize_etype(ttype) # XXX bw compat
- self._tagdefs[rtype, ttype, 'subject'] = tags
- self._tagdefs[rtype, ttype, 'object'] = tags
- elif len(relation) == 3:
- relation = list(relation) # XXX bw compat
- relation[1] = bw_normalize_etype(relation[1])
- self._tagdefs[tuple(relation)] = tags
- else:
- raise ValueError('bad rtag definition (%r)' % (relation,))
-
-
- def __initialize__(self):
- # eclass.[*]schema are only set when registering
- self.schema = self.eclass.schema
- eschema = self.eschema = self.eclass.e_schema
- rtags = self._tagdefs
- # expand wildcards in rtags and add automatic tags
- for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
- rtype = rschema.type
- star_tags = rtags.pop((rtype, '*', role), set())
- for tschema in tschemas:
- tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
- if role == 'subject':
- X, Y = eschema, tschema
- card = rschema.rproperty(X, Y, 'cardinality')[0]
- composed = rschema.rproperty(X, Y, 'composite') == 'object'
- else:
- X, Y = tschema, eschema
- card = rschema.rproperty(X, Y, 'cardinality')[1]
- composed = rschema.rproperty(X, Y, 'composite') == 'subject'
- # set default category tags if needed
- if not tags & self.CATEGORY_TAGS:
- if card in '1+':
- if not rschema.is_final() and composed:
- category = 'generated'
- elif rschema.is_final() and (
- rschema.type.endswith('_format')
- or rschema.type.endswith('_encoding')):
- category = 'generated'
- else:
- category = 'primary'
- elif rschema.is_final():
- if (rschema.type.endswith('_format')
- or rschema.type.endswith('_encoding')):
- category = 'generated'
- else:
- category = 'secondary'
- else:
- category = 'generic'
- tags.add(category)
- if not tags & self.MODE_TAGS:
- if card in '?1':
- # by default, suppose link mode if cardinality doesn't allow
- # more than one relation
- mode = 'link'
- elif rschema.rproperty(X, Y, 'composite') == role:
- # if self is composed of the target type, create mode
- mode = 'create'
- else:
- # link mode by default
- mode = 'link'
- tags.add(mode)
-
- def _default_target(self, rschema, role='subject'):
- eschema = self.eschema
- if role == 'subject':
- return eschema.subject_relation(rschema).objects(eschema)[0]
- else:
- return eschema.object_relation(rschema).subjects(eschema)[0]
-
- # dict compat
- def __getitem__(self, key):
- if isinstance(key, basestring):
- key = (key,)
- return self.get_tags(*key)
-
- __contains__ = __getitem__
-
- def get_tags(self, rtype, targettype=None, role='subject'):
- rschema = self.schema.rschema(rtype)
- if targettype is None:
- tschema = self._default_target(rschema, role)
- else:
- tschema = self.schema.eschema(targettype)
- return self._tagdefs[(rtype, tschema.type, role)]
-
- __call__ = get_tags
-
- def get_mode(self, rtype, targettype=None, role='subject'):
- # XXX: should we make an assertion on rtype not being final ?
- # assert not rschema.is_final()
- tags = self.get_tags(rtype, targettype, role)
- # do not change the intersection order !
- modes = tags & self.MODE_TAGS
- assert len(modes) == 1
- return modes.pop()
-
- def get_category(self, rtype, targettype=None, role='subject'):
- tags = self.get_tags(rtype, targettype, role)
- categories = tags & self.CATEGORY_TAGS
- assert len(categories) == 1
- return categories.pop()
-
- def is_inlined(self, rtype, targettype=None, role='subject'):
- # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
- return 'inlineview' in self.get_tags(rtype, targettype, role)
-
-
-class metaentity(autoselectors):
- """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
- tagdefs = {}
- widgets = {}
- for base in bases:
- tagdefs.update(getattr(base, '__rtags__', {}))
- widgets.update(getattr(base, 'widgets', {}))
- # update with the class' own rtgas
- tagdefs.update(classdict.get('__rtags__', {}))
- widgets.update(classdict.get('widgets', {}))
- # XXX decide whether or not it's a good idea to replace __rtags__
- # good point: transparent support for inheritance levels >= 2
- # bad point: we loose the information of which tags are specific
- # to this entity class
- classdict['__rtags__'] = tagdefs
- classdict['widgets'] = widgets
- eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
- # adds the "rtags" attribute
- eclass.rtags = RelationTags(eclass, tagdefs)
- return eclass
-
-
-class Entity(AppRsetObject, dict):
- """an entity instance has e_schema automagically set on
- the class and instances has access to their issuing cursor.
-
- A property is set for each attribute and relation on each entity's type
- class. Becare that among attributes, 'eid' is *NEITHER* stored in the
- dict containment (which acts as a cache for other attributes dynamically
- fetched)
-
- :type e_schema: `cubicweb.schema.EntitySchema`
- :ivar e_schema: the entity's schema
-
- :type rest_var: str
- :cvar rest_var: indicates which attribute should be used to build REST urls
- If None is specified, the first non-meta attribute will
- be used
-
- :type skip_copy_for: list
- :cvar skip_copy_for: a list of relations that should be skipped when copying
- this kind of entity. Note that some relations such
- as composite relations or relations that have '?1' as object
- cardinality
- """
- __metaclass__ = metaentity
- __registry__ = 'etypes'
- __registerer__ = id_registerer
- __selectors__ = (yes,)
- widgets = {}
- id = None
- e_schema = None
- eid = None
- rest_attr = None
- skip_copy_for = ()
-
- @classmethod
- def registered(cls, registry):
- """build class using descriptor at registration time"""
- assert cls.id is not None
- super(Entity, cls).registered(registry)
- if cls.id != 'Any':
- cls.__initialize__()
- return cls
-
- MODE_TAGS = set(('link', 'create'))
- CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
- @classmethod
- def __initialize__(cls):
- """initialize a specific entity class by adding descriptors to access
- entity type's attributes and relations
- """
- etype = cls.id
- assert etype != 'Any', etype
- cls.e_schema = eschema = cls.schema.eschema(etype)
- for rschema, _ in eschema.attribute_definitions():
- if rschema.type == 'eid':
- continue
- setattr(cls, rschema.type, Attribute(rschema.type))
- mixins = []
- for rschema, _, x in eschema.relation_definitions():
- if (rschema, x) in MI_REL_TRIGGERS:
- mixin = MI_REL_TRIGGERS[(rschema, x)]
- if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
- mixins.append(mixin)
- for iface in getattr(mixin, '__implements__', ()):
- if not interface.implements(cls, iface):
- interface.extend(cls, iface)
- if x == 'subject':
- setattr(cls, rschema.type, SubjectRelation(rschema))
- else:
- attr = 'reverse_%s' % rschema.type
- setattr(cls, attr, ObjectRelation(rschema))
- if mixins:
- cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
- cls.debug('plugged %s mixins on %s', mixins, etype)
- cls.rtags.__initialize__()
-
- @classmethod
- def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
- settype=True, ordermethod='fetch_order'):
- """return a rql to fetch all entities of the class type"""
- restrictions = restriction or []
- if settype:
- restrictions.append('%s is %s' % (mainvar, cls.id))
- if fetchattrs is None:
- fetchattrs = cls.fetch_attrs
- selection = [mainvar]
- orderby = []
- # start from 26 to avoid possible conflicts with X
- varmaker = rqlvar_maker(index=26)
- cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
- orderby, restrictions, user, ordermethod)
- rql = 'Any %s' % ','.join(selection)
- if orderby:
- rql += ' ORDERBY %s' % ','.join(orderby)
- rql += ' WHERE %s' % ', '.join(restrictions)
- return rql
-
- @classmethod
- def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs,
- selection, orderby, restrictions, user,
- ordermethod='fetch_order', visited=None):
- eschema = cls.e_schema
- if visited is None:
- visited = set((eschema.type,))
- elif eschema.type in visited:
- # avoid infinite recursion
- return
- else:
- visited.add(eschema.type)
- _fetchattrs = []
- for attr in fetchattrs:
- try:
- rschema = eschema.subject_relation(attr)
- except KeyError:
- cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
- attr, cls.id)
- continue
- if not user.matching_groups(rschema.get_groups('read')):
- continue
- var = varmaker.next()
- selection.append(var)
- restriction = '%s %s %s' % (mainvar, attr, var)
- restrictions.append(restriction)
- if not rschema.is_final():
- # XXX this does not handle several destination types
- desttype = rschema.objects(eschema.type)[0]
- card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
- if card not in '?1':
- selection.pop()
- restrictions.pop()
- continue
- if card == '?':
- restrictions[-1] += '?' # left outer join if not mandatory
- destcls = cls.vreg.etype_class(desttype)
- destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
- selection, orderby, restrictions,
- user, ordermethod, visited=visited)
- orderterm = getattr(cls, ordermethod)(attr, var)
- if orderterm:
- orderby.append(orderterm)
- return selection, orderby, restrictions
-
- def __init__(self, req, rset, row=None, col=0):
- AppRsetObject.__init__(self, req, rset)
- dict.__init__(self)
- self.row, self.col = row, col
- self._related_cache = {}
- if rset is not None:
- self.eid = rset[row][col]
- else:
- self.eid = None
- self._is_saved = True
-
- def __repr__(self):
- return '<Entity %s %s %s at %s>' % (
- self.e_schema, self.eid, self.keys(), id(self))
-
- def __nonzero__(self):
- return True
-
- def __hash__(self):
- return id(self)
-
- def pre_add_hook(self):
- """hook called by the repository before doing anything to add the entity
- (before_add entity hooks have not been called yet). This give the
- occasion to do weird stuff such as autocast (File -> Image for instance).
-
- This method must return the actual entity to be added.
- """
- return self
-
- def set_eid(self, eid):
- self.eid = self['eid'] = eid
-
- def has_eid(self):
- """return True if the entity has an attributed eid (False
- meaning that the entity has to be created
- """
- try:
- int(self.eid)
- return True
- except (ValueError, TypeError):
- return False
-
- def is_saved(self):
- """during entity creation, there is some time during which the entity
- has an eid attributed though it's not saved (eg during before_add_entity
- hooks). You can use this method to ensure the entity has an eid *and* is
- saved in its source.
- """
- return self.has_eid() and self._is_saved
-
- @cached
- def metainformation(self):
- res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
- res['source'] = self.req.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)
-
- def check_perm(self, action):
- self.e_schema.check_perm(self.req, action, self.eid)
-
- def has_perm(self, action):
- return self.e_schema.has_perm(self.req, action, self.eid)
-
- def view(self, vid, __registry='views', **kwargs):
- """shortcut to apply a view on this entity"""
- return self.vreg.render(__registry, vid, self.req, rset=self.rset,
- row=self.row, col=self.col, **kwargs)
-
- def absolute_url(self, method=None, **kwargs):
- """return an absolute url to view this entity"""
- # 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':
- kwargs['base_url'] = self.metainformation()['source'].get('base-url')
- if method is None or method == 'view':
- kwargs['_restpath'] = self.rest_path()
- else:
- kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
- return self.build_url(method, **kwargs)
-
- def rest_path(self):
- """returns a REST-like (relative) path for this entity"""
- mainattr, needcheck = self._rest_attr_info()
- etype = str(self.e_schema)
- if mainattr == 'eid':
- value = self.eid
- else:
- value = getattr(self, mainattr)
- if value is None:
- return '%s/eid/%s' % (etype.lower(), self.eid)
- if needcheck:
- # make sure url is not ambiguous
- rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
- if value is not None:
- nbresults = self.req.execute(rql, {'value' : value})[0][0]
- # may an assertion that nbresults is not 0 would be a good idea
- if nbresults != 1: # no ambiguity
- return '%s/eid/%s' % (etype.lower(), self.eid)
- return '%s/%s' % (etype.lower(), self.req.url_quote(value))
-
- @classmethod
- def _rest_attr_info(cls):
- mainattr, needcheck = 'eid', True
- if cls.rest_attr:
- mainattr = cls.rest_attr
- needcheck = not cls.e_schema.has_unique_values(mainattr)
- else:
- for rschema in cls.e_schema.subject_relations():
- if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
- mainattr = str(rschema)
- needcheck = False
- break
- if mainattr == 'eid':
- needcheck = False
- return mainattr, needcheck
-
- @cached
- def formatted_attrs(self):
- """returns the list of attributes which have some format information
- (i.e. rich text strings)
- """
- attrs = []
- for rschema, attrschema in self.e_schema.attribute_definitions():
- if attrschema.type == 'String' and self.has_format(rschema):
- attrs.append(rschema.type)
- return attrs
-
- def format(self, attr):
- """return the mime type format for an attribute (if specified)"""
- return getattr(self, '%s_format' % attr, None)
-
- def text_encoding(self, attr):
- """return the text encoding for an attribute, default to site encoding
- """
- encoding = getattr(self, '%s_encoding' % attr, None)
- return encoding or self.vreg.property_value('ui.encoding')
-
- def has_format(self, attr):
- """return true if this entity's schema has a format field for the given
- attribute
- """
- return self.e_schema.has_subject_relation('%s_format' % attr)
-
- def has_text_encoding(self, attr):
- """return true if this entity's schema has ab encoding field for the
- given attribute
- """
- return self.e_schema.has_subject_relation('%s_encoding' % attr)
-
- def printable_value(self, attr, value=_marker, attrtype=None,
- format='text/html', displaytime=True):
- """return a displayable value (i.e. unicode string) which may contains
- html tags
- """
- attr = str(attr)
- if value is _marker:
- value = getattr(self, attr)
- if isinstance(value, basestring):
- value = value.strip()
- if value is None or value == '': # don't use "not", 0 is an acceptable value
- return u''
- if attrtype is None:
- attrtype = self.e_schema.destination(attr)
- props = self.e_schema.rproperties(attr)
- if attrtype == 'String':
- # internalinalized *and* formatted string such as schema
- # description...
- if props.get('internationalizable'):
- value = self.req._(value)
- attrformat = self.format(attr)
- if attrformat:
- return self.mtc_transform(value, attrformat, format,
- self.req.encoding)
- elif attrtype == 'Bytes':
- attrformat = self.format(attr)
- if attrformat:
- try:
- encoding = getattr(self, '%s_encoding' % attr)
- except AttributeError:
- encoding = self.req.encoding
- return self.mtc_transform(value.getvalue(), attrformat, format,
- encoding)
- return u''
- value = printable_value(self.req, attrtype, value, props, displaytime)
- if format == 'text/html':
- value = html_escape(value)
- return value
-
- def mtc_transform(self, data, format, target_format, encoding,
- _engine=ENGINE):
- trdata = TransformData(data, format, encoding, appobject=self)
- data = _engine.convert(trdata, target_format).decode()
- if format == 'text/html':
- data = soup2xhtml(data, self.req.encoding)
- return data
-
- # entity cloning ##########################################################
-
- def copy_relations(self, ceid):
- """copy relations of the object with the given eid on this object
-
- By default meta and composite relations are skipped.
- Overrides this if you want another behaviour
- """
- assert self.has_eid()
- execute = self.req.execute
- for rschema in self.e_schema.subject_relations():
- if rschema.meta or rschema.is_final():
- continue
- # skip already defined relations
- if getattr(self, rschema.type):
- continue
- if rschema.type in self.skip_copy_for:
- continue
- if rschema.type == 'in_state':
- # if the workflow is defining an initial state (XXX AND we are
- # not in the managers group? not done to be more consistent)
- # don't try to copy in_state
- if execute('Any S WHERE S state_of ET, ET initial_state S,'
- 'ET name %(etype)s', {'etype': str(self.e_schema)}):
- continue
- # skip composite relation
- if self.e_schema.subjrproperty(rschema, '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':
- continue
- rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
- rschema.type, rschema.type)
- execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
- self.clear_related_cache(rschema.type, 'subject')
- for rschema in self.e_schema.object_relations():
- if rschema.meta:
- continue
- # skip already defined relations
- if getattr(self, 'reverse_%s' % rschema.type):
- continue
- # skip composite relation
- if self.e_schema.objrproperty(rschema, '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':
- continue
- rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
- rschema.type, rschema.type)
- execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
- self.clear_related_cache(rschema.type, 'object')
-
- # data fetching methods ###################################################
-
- @cached
- 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)
-
- def to_complete_relations(self):
- """by default complete final relations to when calling .complete()"""
- for rschema in self.e_schema.subject_relations():
- if rschema.is_final():
- continue
- if len(rschema.objects(self.e_schema)) > 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)):
- yield rschema, 'subject'
-
- def to_complete_attributes(self, skip_bytes=True):
- for rschema, attrschema in self.e_schema.attribute_definitions():
- # skip binary data by default
- if skip_bytes and attrschema.type == 'Bytes':
- continue
- attr = rschema.type
- if attr == 'eid':
- continue
- # password retreival is blocked at the repository server level
- if not self.req.user.matching_groups(rschema.get_groups('read')) \
- or attrschema.type == 'Password':
- self[attr] = None
- continue
- yield attr
-
- def complete(self, attributes=None, skip_bytes=True):
- """complete this entity by adding missing attributes (i.e. query the
- repository to fill the entity)
-
- :type skip_bytes: bool
- :param skip_bytes:
- if true, attribute of type Bytes won't be considered
- """
- assert self.has_eid()
- varmaker = rqlvar_maker()
- V = varmaker.next()
- rql = ['WHERE %s eid %%(x)s' % V]
- selected = []
- for attr in (attributes or self.to_complete_attributes(skip_bytes)):
- # if attribute already in entity, nothing to do
- if self.has_key(attr):
- continue
- # case where attribute must be completed, but is not yet in entity
- var = varmaker.next()
- rql.append('%s %s %s' % (V, attr, var))
- selected.append((attr, var))
- # +1 since this doen't include the main variable
- lastattr = len(selected) + 1
- if attributes is None:
- # fetch additional relations (restricted to 0..1 relations)
- for rschema, role in self.to_complete_relations():
- rtype = rschema.type
- if self.relation_cached(rtype, role):
- continue
- var = varmaker.next()
- 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: # '?"
- 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: # '?"
- 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)
- rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
- # handle attributes
- for i in xrange(1, lastattr):
- self[str(selected[i-1][0])] = rset[i]
- # handle relations
- for i in xrange(lastattr, len(rset)):
- rtype, x = selected[i-1][0]
- value = rset[i]
- if value is None:
- rrset = ResultSet([], rql, {'x': self.eid})
- self.req.decorate_rset(rrset)
- else:
- rrset = self.req.eid_rset(value)
- self.set_related_cache(rtype, x, rrset)
-
- def get_value(self, name):
- """get value for the attribute relation <name>, query the repository
- to get the value if necessary.
-
- :type name: str
- :param name: name of the attribute to get
- """
- try:
- value = self[name]
- except KeyError:
- if not self.is_saved():
- return None
- rql = "Any A WHERE X eid %%(x)s, X %s A" % name
- # XXX should we really use unsafe_execute here??
- execute = getattr(self.req, 'unsafe_execute', self.req.execute)
- try:
- rset = execute(rql, {'x': self.eid}, 'x')
- except Unauthorized:
- self[name] = value = None
- else:
- assert rset.rowcount <= 1, (self, rql, rset.rowcount)
- try:
- self[name] = value = rset.rows[0][0]
- except IndexError:
- # probably a multisource error
- self.critical("can't get value for attribute %s of entity with eid %s",
- name, self.eid)
- if self.e_schema.destination(name) == 'String':
- self[name] = value = self.req._('unaccessible')
- else:
- self[name] = value = None
- return value
-
- def related(self, rtype, role='subject', limit=None, entities=False):
- """returns a resultset of related entities
-
- :param role: is the role played by 'self' in the relation ('subject' or 'object')
- :param limit: resultset's maximum size
- :param entities: if True, the entites are returned; if False, a result set is returned
- """
- try:
- return self.related_cache(rtype, role, entities, limit)
- except KeyError:
- pass
- assert self.has_eid()
- rql = self.related_rql(rtype, role)
- rset = self.req.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'):
- rschema = self.schema[rtype]
- if role == 'subject':
- targettypes = rschema.objects(self.e_schema)
- restriction = 'E eid %%(x)s, E %s X' % rtype
- card = greater_card(rschema, (self.e_schema,), targettypes, 0)
- else:
- targettypes = rschema.subjects(self.e_schema)
- restriction = 'E eid %%(x)s, X %s E' % rtype
- card = greater_card(rschema, targettypes, (self.e_schema,), 1)
- if len(targettypes) > 1:
- fetchattrs_list = []
- for ttype in targettypes:
- etypecls = self.vreg.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,
- settype=False)
- else:
- etypecls = self.vreg.etype_class(targettypes[0])
- rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
- # optimisation: remove ORDERBY if cardinality is 1 or ? (though
- # greater_card return 1 for those both cases)
- if card == '1':
- if ' ORDERBY ' in rql:
- rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
- rql.split(' WHERE ', 1)[1])
- elif not ' ORDERBY ' in rql:
- args = tuple(rql.split(' WHERE ', 1))
- rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
- return rql
-
- # generic vocabulary methods ##############################################
-
- 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
- """
- try:
- vocabfunc = getattr(self, '%s_%s_vocabulary' % (role, rtype))
- except AttributeError:
- vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
- # 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)
-
- 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)
- """
- if isinstance(rtype, basestring):
- rtype = self.schema.rschema(rtype)
- done = None
- assert not rtype.is_final(), rtype
- if self.has_eid():
- done = set(e.eid for e in getattr(self, str(rtype)))
- result = []
- rsetsize = None
- for objtype in rtype.objects(self.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)
- """
- if isinstance(rtype, basestring):
- rtype = self.schema.rschema(rtype)
- done = None
- if self.has_eid():
- done = set(e.eid for e in getattr(self, 'reverse_%s' % rtype))
- result = []
- rsetsize = None
- for subjtype in rtype.subjects(self.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 relation_vocabulary(self, rtype, targettype, role,
- limit=None, done=None):
- if done is None:
- done = set()
- req = self.req
- rset = self.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 unrelated_rql(self, rtype, targettype, role, ordermethod=None,
- vocabconstraints=True):
- """build a rql to fetch `targettype` entities unrelated to this entity
- using (rtype, role) relation
- """
- ordermethod = ordermethod or 'fetch_unrelated_order'
- if isinstance(rtype, basestring):
- rtype = self.schema.rschema(rtype)
- if role == 'subject':
- evar, searchedvar = 'S', 'O'
- subjtype, objtype = self.e_schema, targettype
- else:
- searchedvar, evar = 'S', 'O'
- objtype, subjtype = self.e_schema, targettype
- if self.has_eid():
- restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
- else:
- restriction = []
- constraints = rtype.rproperty(subjtype, objtype, 'constraints')
- if vocabconstraints:
- # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
- # will be included as well
- restriction += [cstr.restriction for cstr in constraints
- if isinstance(cstr, RQLVocabularyConstraint)]
- else:
- restriction += [cstr.restriction for cstr in constraints
- if isinstance(cstr, RQLConstraint)]
- etypecls = self.vreg.etype_class(targettype)
- rql = etypecls.fetch_rql(self.req.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)
- return rql
-
- def unrelated(self, rtype, targettype, role='subject', limit=None,
- ordermethod=None):
- """return a result set of target type objects that may be related
- by a given relation, with self as subject or object
- """
- rql = self.unrelated_rql(rtype, targettype, role, ordermethod)
- if limit is not None:
- before, after = rql.split(' WHERE ', 1)
- rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
- if self.has_eid():
- return self.req.execute(rql, {'x': self.eid})
- return self.req.execute(rql)
-
- # relations cache handling ################################################
-
- def relation_cached(self, rtype, role):
- """return true if the given relation is already cached on the instance
- """
- return '%s_%s' % (rtype, role) in self._related_cache
-
- def related_cache(self, rtype, role, entities=True, limit=None):
- """return values for the given relation if it's cached on the instance,
- else raise `KeyError`
- """
- res = self._related_cache['%s_%s' % (rtype, role)][entities]
- if limit:
- if entities:
- res = res[:limit]
- else:
- res = res.limit(limit)
- return res
-
- def set_related_cache(self, rtype, role, rset, col=0):
- """set cached values for the given relation"""
- if rset:
- related = list(rset.entities(col))
- rschema = self.schema.rschema(rtype)
- if role == 'subject':
- rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
- 'cardinality')[1]
- target = 'object'
- else:
- rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
- 'cardinality')[0]
- target = 'subject'
- if rcard in '?1':
- for rentity in related:
- rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self])
- else:
- related = []
- self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
-
- def clear_related_cache(self, rtype=None, role=None):
- """clear cached values for the given relation or the entire cache if
- no relation is given
- """
- if rtype is None:
- self._related_cache = {}
- else:
- assert role
- self._related_cache.pop('%s_%s' % (rtype, role), None)
-
- # raw edition utilities ###################################################
-
- def set_attributes(self, **kwargs):
- assert kwargs
- relations = []
- for key in kwargs:
- relations.append('X %s %%(%s)s' % (key, key))
- # update current local object
- self.update(kwargs)
- # and now update the database
- kwargs['x'] = self.eid
- self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
- kwargs, 'x')
-
- def delete(self):
- assert self.has_eid(), self.eid
- self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
- {'x': self.eid})
-
- # server side utilities ###################################################
-
- def set_defaults(self):
- """set default values according to the schema"""
- self._default_set = set()
- for attr, value in self.e_schema.defaults():
- if not self.has_key(attr):
- self[str(attr)] = value
- self._default_set.add(attr)
-
- def check(self, creation=False):
- """check this entity against its schema. Only final relation
- are checked here, constraint on actual relations are checked in hooks
- """
- # necessary since eid is handled specifically and yams require it to be
- # in the dictionary
- if self.req is None:
- _ = unicode
- else:
- _ = self.req._
- self.e_schema.check(self, creation=creation, _=_)
-
- def fti_containers(self, _done=None):
- if _done is None:
- _done = set()
- _done.add(self.eid)
- containers = tuple(self.e_schema.fulltext_containers())
- if containers:
- yielded = False
- for rschema, target in containers:
- if target == 'object':
- targets = getattr(self, rschema.type)
- else:
- targets = getattr(self, 'reverse_%s' % rschema)
- for entity in targets:
- if entity.eid in _done:
- continue
- for container in entity.fti_containers(_done):
- yield container
- yielded = True
- if not yielded:
- yield self
- else:
- yield self
-
- def get_words(self):
- """used by the full text indexer to get words to index
-
- this method should only be used on the repository side since it depends
- on the indexer package
-
- :rtype: list
- :return: the list of indexable word of this entity
- """
- from indexer.query_objects import tokenize
- words = []
- for rschema in self.e_schema.indexable_attributes():
- try:
- value = self.printable_value(rschema, format='text/plain')
- except TransformError, ex:
- continue
- except:
- self.exception("can't add value of %s to text index for entity %s",
- rschema, self.eid)
- continue
- if value:
- words += tokenize(value)
-
- for rschema, role in self.e_schema.fulltext_relations():
- if role == 'subject':
- for entity in getattr(self, rschema.type):
- words += entity.get_words()
- else: # if role == 'object':
- for entity in getattr(self, 'reverse_%s' % rschema.type):
- words += entity.get_words()
- return words
-
-
-# attribute and relation descriptors ##########################################
-
-class Attribute(object):
- """descriptor that controls schema attribute access"""
-
- def __init__(self, attrname):
- assert attrname != 'eid'
- self._attrname = attrname
-
- def __get__(self, eobj, eclass):
- if eobj is None:
- return self
- return eobj.get_value(self._attrname)
-
- def __set__(self, eobj, value):
- # XXX bw compat
- # would be better to generate UPDATE queries than the current behaviour
- eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)")
- eobj[self._attrname] = value
-
-
-class Relation(object):
- """descriptor that controls schema relation access"""
- _role = None # for pylint
-
- def __init__(self, rschema):
- self._rschema = rschema
- self._rtype = rschema.type
-
- def __get__(self, eobj, eclass):
- if eobj is None:
- raise AttributeError('%s cannot be only be accessed from instances'
- % self._rtype)
- return eobj.related(self._rtype, self._role, entities=True)
-
- def __set__(self, eobj, value):
- raise NotImplementedError
-
-
-class SubjectRelation(Relation):
- """descriptor that controls schema relation access"""
- _role = 'subject'
-
-class ObjectRelation(Relation):
- """descriptor that controls schema relation access"""
- _role = 'object'
-
-from logging import getLogger
-from cubicweb import set_log_methods
-set_log_methods(Entity, getLogger('cubicweb.entity'))
+from warnings import warn
+warn('moved to cubicweb.entity', DeprecationWarning, stacklevel=2)
+from cubicweb.entity import *
+from cubicweb.entity import _marker
--- a/common/html4zope.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision: 1.2 $
-# Date: $Date: 2005-07-04 16:36:50 $
-# Copyright: This module has been placed in the public domain.
-
-"""
-Simple HyperText Markup Language document tree Writer.
-
-The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
-HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
-minimum of formatting information. A cascading style sheet ("default.css" by
-default) is required for proper viewing with a modern graphical browser.
-
-http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
-"""
-
-__docformat__ = 'reStructuredText'
-
-from logilab.mtconverter import html_escape
-
-from docutils import nodes
-from docutils.writers.html4css1 import Writer as CSS1Writer
-from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
-
-default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
-
-class Writer(CSS1Writer):
- """css writer using our html translator"""
- def __init__(self, base_url):
- CSS1Writer.__init__(self)
- self.translator_class = URLBinder(base_url, HTMLTranslator)
-
- def apply_template(self):
- """overriding this is necessary with docutils >= 0.5"""
- return self.visitor.astext()
-
-class URLBinder:
- def __init__(self, url, klass):
- self.base_url = url
- self.translator_class = HTMLTranslator
-
- def __call__(self, document):
- translator = self.translator_class(document)
- translator.base_url = self.base_url
- return translator
-
-class HTMLTranslator(CSS1HTMLTranslator):
- """ReST tree to html translator"""
-
- def astext(self):
- """return the extracted html"""
- return ''.join(self.body)
-
- def visit_title(self, node):
- """Only 6 section levels are supported by HTML."""
- if isinstance(node.parent, nodes.topic):
- self.body.append(
- self.starttag(node, 'p', '', CLASS='topic-title'))
- if node.parent.hasattr('id'):
- self.body.append(
- self.starttag({}, 'a', '', name=node.parent['id']))
- self.context.append('</a></p>\n')
- else:
- self.context.append('</p>\n')
- elif self.section_level == 0:
- # document title
- self.head.append('<title>%s</title>\n'
- % self.encode(node.astext()))
- self.body.append(self.starttag(node, 'h%d' % default_level, '',
- CLASS='title'))
- self.context.append('</h%d>\n' % default_level)
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (
- default_level+self.section_level-1), ''))
- atts = {}
- if node.hasattr('refid'):
- atts['class'] = 'toc-backref'
- atts['href'] = '%s#%s' % (self.base_url, node['refid'])
- self.body.append(self.starttag({}, 'a', '', **atts))
- self.context.append('</a></h%s>\n' % (
- default_level+self.section_level-1))
-
- def visit_subtitle(self, node):
- """format a subtitle"""
- if isinstance(node.parent, nodes.sidebar):
- self.body.append(self.starttag(node, 'p', '',
- CLASS='sidebar-subtitle'))
- self.context.append('</p>\n')
- else:
- self.body.append(
- self.starttag(node, 'h%s' % (default_level+1), '',
- CLASS='subtitle'))
- self.context.append('</h%s>\n' % (default_level+1))
-
- def visit_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
- def depart_document(self, node):
- """syt: i don't want the enclosing <div class="document">"""
-
- def visit_reference(self, node):
- """syt: i want absolute urls"""
- if node.has_key('refuri'):
- href = node['refuri']
- if ( self.settings.cloak_email_addresses
- and href.startswith('mailto:')):
- href = self.cloak_mailto(href)
- self.in_mailto = 1
- else:
- assert node.has_key('refid'), \
- 'References must have "refuri" or "refid" attribute.'
- href = '%s#%s' % (self.base_url, node['refid'])
- atts = {'href': href, 'class': 'reference'}
- if not isinstance(node.parent, nodes.TextElement):
- assert len(node) == 1 and isinstance(node[0], nodes.image)
- atts['class'] += ' image-reference'
- self.body.append(self.starttag(node, 'a', '', **atts))
-
- ## override error messages to avoid XHTML problems ########################
- def visit_problematic(self, node):
- pass
-
- def depart_problematic(self, node):
- pass
-
- def visit_system_message(self, node):
- backref_text = ''
- if len(node['backrefs']):
- backrefs = node['backrefs']
- if len(backrefs) == 1:
- backref_text = '; <em>backlink</em>'
- else:
- i = 1
- backlinks = []
- for backref in backrefs:
- backlinks.append(str(i))
- i += 1
- backref_text = ('; <em>backlinks: %s</em>'
- % ', '.join(backlinks))
- if node.hasattr('line'):
- line = ', line %s' % node['line']
- else:
- line = ''
- a_start = a_end = ''
- error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
- a_start, node['type'], node['level'], a_end,
- self.encode(node['source']), line, backref_text)
- self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
-
- def depart_system_message(self, node):
- pass
--- a/common/mixins.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/mixins.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,14 +2,14 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from logilab.common.decorators import cached
-from cubicweb.common.selectors import implement_interface
+from cubicweb.selectors import implements
from cubicweb.interfaces import IWorkflowable, IEmailable, ITree
@@ -187,7 +187,7 @@
if rset:
return rset.get_entity(0, 0)
return None
-
+
def change_state(self, stateeid, trcomment=None, trcommentformat=None):
"""change the entity's state according to a state defined in given
parameters
@@ -315,8 +315,7 @@
"""a recursive tree view"""
id = 'tree'
item_vid = 'treeitem'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ITree,)
+ __select__ = implements(ITree)
def call(self, done=None, **kwargs):
if done is None:
--- a/common/mttransforms.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/mttransforms.py Mon Mar 02 21:03:54 2009 +0100
@@ -31,16 +31,7 @@
output = 'text/html'
def _convert(self, trdata):
return html_publish(trdata.appobject, trdata.data)
-
-class ept_to_html(Transform):
- inputs = ('text/cubicweb-page-template',)
- output = 'text/html'
- output_encoding = 'utf-8'
- def _convert(self, trdata):
- from cubicweb.common.tal import compile_template
- value = trdata.encode(self.output_encoding)
- return trdata.appobject.tal_render(compile_template(value), {})
-
+
# Instantiate and configure the transformation engine
@@ -49,7 +40,26 @@
ENGINE = TransformEngine()
ENGINE.add_transform(rest_to_html())
ENGINE.add_transform(html_to_html())
-ENGINE.add_transform(ept_to_html())
+
+try:
+ from cubicweb.ext.tal import compile_template
+except ImportError:
+ HAS_TAL = False
+ from cubicweb.schema import FormatConstraint
+ FormatConstraint.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
--- a/common/registerers.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/registerers.py Mon Mar 02 21:03:54 2009 +0100
@@ -5,22 +5,14 @@
to the application's schema or to already registered object
:organization: Logilab
-:copyright: 2006-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.vregistry import registerer
-
-
-def _accepts_interfaces(obj):
- return sorted(getattr(obj, 'accepts_interfaces', ()))
-
-
-class yes_registerer(registerer):
- """register without any other action"""
- def do_it_yourself(self, registered):
- return self.vobject
+from cubicweb.vregistry import registerer, yes_registerer
+from cubicweb.selectors import implements
+from cubicweb.cwvreg import use_interfaces
class priority_registerer(registerer):
"""systematically kick previous registered class and register the
@@ -54,17 +46,6 @@
def equivalent(self, other):
raise NotImplementedError(self, self.vobject)
-
-
-class kick_registerer(registerer):
- """systematically kick previous registered class and don't register the
- wrapped class. This is temporarily used to discard library object registrable
- but that we don't want to use
- """
- def do_it_yourself(self, registered):
- if registered:
- self.kick(registered, registered[-1])
- return
class accepts_registerer(priority_registerer):
@@ -81,25 +62,14 @@
"""
def do_it_yourself(self, registered):
# if object is accepting interface, we have register it now and
- # remove it latter if no object is implementing accepted interfaces
- if _accepts_interfaces(self.vobject):
+ # remove it later if no object is implementing accepted interfaces
+ if use_interfaces(self.vobject):
return self.vobject
- if not 'Any' in self.vobject.accepts:
- for ertype in self.vobject.accepts:
- if ertype in self.schema:
- break
- else:
- self.skip()
- return None
- for required in getattr(self.vobject, 'requires', ()):
- if required not in self.schema:
- self.skip()
- return
self.remove_equivalents(registered)
return self.vobject
def equivalent(self, other):
- if _accepts_interfaces(self.vobject) != _accepts_interfaces(other):
+ if use_interfaces(self.vobject) != use_interfaces(other):
return False
try:
newaccepts = list(other.accepts)
@@ -116,90 +86,6 @@
return False
-class id_registerer(priority_registerer):
- """register according to the "id" attribute of the wrapped class,
- refering to an entity type.
-
- * if the type is not Any and is not defined the application'schema,
- skip the wrapped class
- * if an object previously registered has the same .id attribute,
- kick it out
- * register
- """
- def do_it_yourself(self, registered):
- etype = self.vobject.id
- if etype != 'Any' and not self.schema.has_entity(etype):
- self.skip()
- return
- self.remove_equivalents(registered)
- return self.vobject
-
- def equivalent(self, other):
- return other.id == self.vobject.id
-
-
-class etype_rtype_registerer(registerer):
- """registerer handling optional .etype and .rtype attributes.:
-
- * if .etype is set and is not an entity type defined in the
- application schema, skip the wrapped class
- * if .rtype or .relname is set and is not a relation type defined in
- the application schema, skip the wrapped class
- * register
- """
- def do_it_yourself(self, registered):
- cls = self.vobject
- if hasattr(cls, 'etype'):
- if not self.schema.has_entity(cls.etype):
- return
- rtype = getattr(cls, 'rtype', None)
- if rtype and not self.schema.has_relation(rtype):
- return
- return cls
-
-class etype_rtype_priority_registerer(etype_rtype_registerer):
- """add priority behaviour to the etype_rtype_registerer
- """
- def do_it_yourself(self, registered):
- cls = super(etype_rtype_priority_registerer, self).do_it_yourself(registered)
- if cls:
- registerer = priority_registerer(self.registry, cls)
- cls = registerer.do_it_yourself(registered)
- return cls
-
-class action_registerer(etype_rtype_registerer):
- """'all in one' actions registerer, handling optional .accepts,
- .etype and .rtype attributes:
-
- * if .etype is set and is not an entity type defined in the
- application schema, skip the wrapped class
- * if .rtype or .relname is set and is not a relation type defined in
- the application schema, skip the wrapped class
- * if .accepts is set, delegate to the accepts_registerer
- * register
- """
- def do_it_yourself(self, registered):
- cls = super(action_registerer, self).do_it_yourself(registered)
- if hasattr(cls, 'accepts'):
- registerer = accepts_registerer(self.registry, cls)
- cls = registerer.do_it_yourself(registered)
- return cls
-
-
-class extresources_registerer(priority_registerer):
- """'registerer according to a .need_resources attributes which
- should list necessary resource identifiers for the wrapped object.
- If one of its resources is missing, don't register
- """
- def do_it_yourself(self, registered):
- if not hasattr(self.config, 'has_resource'):
- return
- for resourceid in self.vobject.need_resources:
- if not self.config.has_resource(resourceid):
- return
- return super(extresources_registerer, self).do_it_yourself(registered)
-
-
__all__ = [cls.__name__ for cls in globals().values()
if isinstance(cls, type) and issubclass(cls, registerer)
and not cls is registerer]
--- a/common/rest.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-"""rest publishing functions
-
-contains some functions and setup of docutils for cubicweb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-from itertools import chain
-from logging import getLogger
-from os.path import join
-
-from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
-from docutils.parsers.rst import Parser, states, directives
-from docutils.parsers.rst.roles import register_canonical_role, set_classes
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.common.html4zope import Writer
-
-# We provide our own parser as an attempt to get rid of
-# state machine reinstanciation
-
-import re
-# compile states.Body patterns
-for k, v in states.Body.patterns.items():
- if isinstance(v, str):
- states.Body.patterns[k] = re.compile(v)
-
-# register ReStructured Text mimetype / extensions
-import mimetypes
-mimetypes.add_type('text/rest', '.rest')
-mimetypes.add_type('text/rest', '.rst')
-
-
-LOGGER = getLogger('cubicweb.rest')
-
-def eid_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- try:
- try:
- eid_num, rest = text.split(u':', 1)
- except:
- eid_num, rest = text, '#'+text
- eid_num = int(eid_num)
- if eid_num < 0:
- raise ValueError
- except ValueError:
- msg = inliner.reporter.error(
- 'EID number must be a positive number; "%s" is invalid.'
- % text, line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- # Base URL mainly used by inliner.pep_reference; so this is correct:
- context = inliner.document.settings.context
- refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
- ref = refedentity.absolute_url()
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('eid', eid_reference_role)
-
-
-def card_reference_role(role, rawtext, text, lineno, inliner,
- options={}, content=[]):
- text = text.strip()
- try:
- wikiid, rest = text.split(u':', 1)
- except:
- wikiid, rest = text, text
- context = inliner.document.settings.context
- cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
- {'id': wikiid})
- if cardrset:
- ref = cardrset.get_entity(0, 0).absolute_url()
- else:
- schema = context.schema
- if schema.eschema('Card').has_perm(context.req, 'add'):
- ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
- else:
- ref = '#'
- set_classes(options)
- return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
- **options)], []
-
-register_canonical_role('card', card_reference_role)
-
-
-def winclude_directive(name, arguments, options, content, lineno,
- content_offset, block_text, state, state_machine):
- """Include a reST file as part of the content of this reST file.
-
- same as standard include directive but using config.locate_doc_resource to
- get actual file to include.
-
- Most part of this implementation is copied from `include` directive defined
- in `docutils.parsers.rst.directives.misc`
- """
- context = state.document.settings.context
- source = state_machine.input_lines.source(
- 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')),
- context.config.available_languages()):
- rid = '%s_%s.rst' % (fid, lang)
- resourcedir = context.config.locate_doc_file(rid)
- if resourcedir:
- break
- else:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\nno resource matching %s.'
- % (name, fid),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- path = join(resourcedir, rid)
- encoding = options.get('encoding', state.document.settings.input_encoding)
- try:
- state.document.settings.record_dependencies.add(path)
- include_file = io.FileInput(
- source_path=path, encoding=encoding,
- error_handler=state.document.settings.input_encoding_error_handler,
- handle_io_errors=None)
- except IOError, error:
- severe = state_machine.reporter.severe(
- 'Problems with "%s" directive path:\n%s: %s.'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- try:
- include_text = include_file.read()
- except UnicodeError, error:
- severe = state_machine.reporter.severe(
- 'Problem with "%s" directive:\n%s: %s'
- % (name, error.__class__.__name__, error),
- nodes.literal_block(block_text, block_text), line=lineno)
- return [severe]
- if options.has_key('literal'):
- literal_block = nodes.literal_block(include_text, include_text,
- source=path)
- literal_block.line = 1
- return literal_block
- else:
- include_lines = statemachine.string2lines(include_text,
- convert_whitespace=1)
- state_machine.insert_input(include_lines, path)
- return []
-
-winclude_directive.arguments = (1, 0, 1)
-winclude_directive.options = {'literal': directives.flag,
- 'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
-
-class CubicWebReSTParser(Parser):
- """The (customized) reStructuredText parser."""
-
- def __init__(self):
- self.initial_state = 'Body'
- self.state_classes = states.state_classes
- self.inliner = states.Inliner()
- self.statemachine = states.RSTStateMachine(
- state_classes=self.state_classes,
- initial_state=self.initial_state,
- debug=0)
-
- def parse(self, inputstring, document):
- """Parse `inputstring` and populate `document`, a document tree."""
- self.setup_parse(inputstring, document)
- inputlines = statemachine.string2lines(inputstring,
- convert_whitespace=1)
- self.statemachine.run(inputlines, document, inliner=self.inliner)
- self.finish_parse()
-
-
-_REST_PARSER = CubicWebReSTParser()
-
-def rest_publish(context, data):
- """publish a string formatted as ReStructured Text to HTML
-
- :type context: a cubicweb application object
-
- :type data: str
- :param data: some ReST text
-
- :rtype: unicode
- :return:
- the data formatted as HTML or the original data if an error occured
- """
- req = context.req
- if isinstance(data, unicode):
- encoding = 'unicode'
- else:
- encoding = req.encoding
- settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
- 'warning_stream': StringIO(), 'context': context,
- # dunno what's the max, severe is 4, and we never want a crash
- # (though try/except may be a better option...)
- 'halt_level': 10,
- }
- if context:
- if hasattr(req, 'url'):
- base_url = req.url()
- elif hasattr(context, 'absolute_url'):
- base_url = context.absolute_url()
- else:
- base_url = req.base_url()
- else:
- base_url = None
- try:
- return publish_string(writer=Writer(base_url=base_url),
- parser=_REST_PARSER, source=data,
- settings_overrides=settings)
- except Exception:
- LOGGER.exception('error while publishing ReST text')
- if not isinstance(data, unicode):
- data = unicode(data, encoding, 'replace')
- return html_escape(req._('error while publishing ReST text')
- + '\n\n' + data)
--- a/common/selectors.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/selectors.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,571 +1,4 @@
-"""This file contains some basic selectors required by application objects.
-
-A selector is responsible to score how well an object may be used with a
-given result set (publishing time selection)
-
-If you have trouble with selectors, especially if the objet (typically
-a view or a component) you want to use is not selected and you want to
-know which one(s) of its selectors fail (e.g. returns 0), you can use
-`traced_selection` or even direclty `TRACED_OIDS`.
-
-`TRACED_OIDS` is a tuple of traced object ids. The special value
-'all' may be used to log selectors for all objects.
-
-For instance, say that the following code yields a `NoSelectableObject`
-exception::
-
- self.view('calendar', myrset)
-
-You can log the selectors involved for *calendar* by replacing the line
-above by::
-
- # in Python2.5
- from cubicweb.common.selectors import traced_selection
- with traced_selection():
- self.view('calendar', myrset)
-
- # in Python2.4
- from cubicweb.common import selectors
- selectors.TRACED_OIDS = ('calendar',)
- self.view('calendar', myrset)
- selectors.TRACED_OIDS = ()
-
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import logging
-
-from logilab.common.compat import all
-from logilab.common.deprecation import deprecated_function
-
-from cubicweb import Unauthorized, NoSelectableObject, role
-from cubicweb.cwvreg import DummyCursorError
-from cubicweb.vregistry import chainall, chainfirst, NoSelectableObject
-from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.schema import split_expression
-
-# helpers for debugging selectors
-SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
-TRACED_OIDS = ()
-
-def lltrace(selector):
- # don't wrap selectors if not in development mode
- if CubicWebConfiguration.mode == 'installed':
- return selector
- def traced(cls, *args, **kwargs):
- ret = selector(cls, *args, **kwargs)
- if TRACED_OIDS == 'all' or cls.id in TRACED_OIDS:
- SELECTOR_LOGGER.warning('selector %s returned %s for %s', selector.__name__, ret, cls)
- return ret
- traced.__name__ = selector.__name__
- return traced
-
-class traced_selection(object):
- """selector debugging helper.
-
- Typical usage is :
-
- >>> with traced_selection():
- ... # some code in which you want to debug selectors
- ... # for all objects
-
- or
-
- >>> with traced_selection( ('oid1', 'oid2') ):
- ... # some code in which you want to debug selectors
- ... # for objects with id 'oid1' and 'oid2'
-
- """
- def __init__(self, traced='all'):
- self.traced = traced
-
- def __enter__(self):
- global TRACED_OIDS
- TRACED_OIDS = self.traced
-
- def __exit__(self, exctype, exc, traceback):
- global TRACED_OIDS
- TRACED_OIDS = ()
- return traceback is None
-
-# very basic selectors ########################################################
-
-def yes(cls, *args, **kwargs):
- """accept everything"""
- return 1
-yes_selector = deprecated_function(yes)
-
-@lltrace
-def none_rset(cls, req, rset, *args, **kwargs):
- """accept no result set"""
- if rset is None:
- return 1
- return 0
-norset_selector = deprecated_function(none_rset)
-
-@lltrace
-def any_rset(cls, req, rset, *args, **kwargs):
- """accept result set, whatever the number of result"""
- if rset is not None:
- return 1
- return 0
-rset_selector = deprecated_function(any_rset)
-
-@lltrace
-def nonempty_rset(cls, req, rset, *args, **kwargs):
- """accept any non empty result set"""
- if rset is not None and rset.rowcount:
- return 1
- return 0
-anyrset_selector = deprecated_function(nonempty_rset)
-
-@lltrace
-def empty_rset(cls, req, rset, *args, **kwargs):
- """accept empty result set"""
- if rset is not None and rset.rowcount == 0:
- return 1
- return 0
-emptyrset_selector = deprecated_function(empty_rset)
-
-@lltrace
-def one_line_rset(cls, req, rset, row=None, *args, **kwargs):
- """accept result set with a single line of result"""
- if rset is not None and (row is not None or rset.rowcount == 1):
- return 1
- return 0
-onelinerset_selector = deprecated_function(one_line_rset)
-
-@lltrace
-def two_lines_rset(cls, req, rset, *args, **kwargs):
- """accept result set with *at least* two lines of result"""
- if rset is not None and rset.rowcount > 1:
- return 1
- return 0
-twolinerset_selector = deprecated_function(two_lines_rset)
-
-@lltrace
-def two_cols_rset(cls, req, rset, *args, **kwargs):
- """accept result set with at least one line and two columns of result"""
- if rset is not None and rset.rowcount > 0 and len(rset.rows[0]) > 1:
- return 1
- return 0
-twocolrset_selector = deprecated_function(two_cols_rset)
-
-@lltrace
-def paginated_rset(cls, req, rset, *args, **kwargs):
- """accept result sets with more rows than the page size
- """
- 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 len(rset) <= page_size:
- return 0
- return 1
-largerset_selector = deprecated_function(paginated_rset)
-
-@lltrace
-def sorted_rset(cls, req, rset, row=None, col=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
-sortedrset_selector = deprecated_function(sorted_rset)
-
-@lltrace
-def one_etype_rset(cls, req, rset, *args, **kwargs):
- """accept result set where entities in the first columns are all of the
- same type
- """
- if len(rset.column_types(0)) != 1:
- return 0
- return 1
-oneetyperset_selector = deprecated_function(one_etype_rset)
-
-@lltrace
-def two_etypes_rset(cls, req, rset, **kwargs):
- """accepts resultsets containing several entity types"""
- if rset:
- etypes = rset.column_types(0)
- if len(etypes) > 1:
- return 1
- return 0
-multitype_selector = deprecated_function(two_etypes_rset)
-
-@lltrace
-def match_search_state(cls, req, rset, row=None, col=None, **kwargs):
- """checks if the current search state is in a .search_states attribute of
- the wrapped class
-
- search state should be either 'normal' or 'linksearch' (eg searching for an
- object to create a relation with another)
- """
- try:
- if not req.search_state[0] in cls.search_states:
- return 0
- except AttributeError:
- return 1 # class doesn't care about search state, accept it
- return 1
-searchstate_selector = deprecated_function(match_search_state)
-
-@lltrace
-def anonymous_user(cls, req, *args, **kwargs):
- """accept if user is anonymous"""
- if req.cnx.anonymous_connection:
- return 1
- return 0
-anonymous_selector = deprecated_function(anonymous_user)
-
-@lltrace
-def authenticated_user(cls, req, *args, **kwargs):
- """accept if user is authenticated"""
- return not anonymous_user(cls, req, *args, **kwargs)
-not_anonymous_selector = deprecated_function(authenticated_user)
-
-@lltrace
-def match_form_params(cls, req, *args, **kwargs):
- """check if parameters specified by the form_params attribute on
- the wrapped class are specified in request form parameters
- """
- score = 0
- for param in cls.form_params:
- val = req.form.get(param)
- if not val:
- return 0
- score += 1
- return score + 1
-req_form_params_selector = deprecated_function(match_form_params)
-
-@lltrace
-def match_kwargs(cls, req, *args, **kwargs):
- """check if arguments specified by the expected_kwargs attribute on
- the wrapped class are specified in given named parameters
- """
- values = []
- for arg in cls.expected_kwargs:
- if not arg in kwargs:
- return 0
- return 1
-kwargs_selector = deprecated_function(match_kwargs)
-
-
-# not so basic selectors ######################################################
-
-@lltrace
-def accept_etype(cls, req, *args, **kwargs):
- """check etype presence in request form *and* accepts conformance"""
- if 'etype' not in req.form and 'etype' not in kwargs:
- return 0
- try:
- etype = req.form['etype']
- except KeyError:
- etype = kwargs['etype']
- # value is a list or a tuple if web request form received several
- # values for etype parameter
- assert isinstance(etype, basestring), "got multiple etype parameters in req.form"
- if 'Any' in cls.accepts:
- return 1
- # no Any found, we *need* exact match
- if etype not in cls.accepts:
- return 0
- # exact match must return a greater value than 'Any'-match
- return 2
-etype_form_selector = deprecated_function(accept_etype)
-
-@lltrace
-def _non_final_entity(cls, req, rset, row=None, col=None, **kwargs):
- """accept non final entities
- if row is not specified, use the first one
- if col is not specified, use the first one
- """
- etype = rset.description[row or 0][col or 0]
- if etype is None: # outer join
- return 0
- if cls.schema.eschema(etype).is_final():
- return 0
- return 1
-_nfentity_selector = deprecated_function(_non_final_entity)
-
-@lltrace
-def _rql_condition(cls, req, rset, row=None, col=None, **kwargs):
- """accept single entity result set if the entity match an rql condition
- """
- if cls.condition:
- eid = rset[row or 0][col or 0]
- if 'U' in frozenset(split_expression(cls.condition)):
- rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % cls.condition
- else:
- rql = 'Any X WHERE X eid %%(x)s, %s' % cls.condition
- try:
- return len(req.execute(rql, {'x': eid, 'u': req.user.eid}, 'x'))
- except Unauthorized:
- return 0
-
- return 1
-_rqlcondition_selector = deprecated_function(_rql_condition)
-
-@lltrace
-def _implement_interface(cls, req, rset, row=None, col=None, **kwargs):
- """accept uniform result sets, and apply the following rules:
-
- * wrapped class must have a accepts_interfaces attribute listing the
- accepted ORed interfaces
- * if row is None, return the sum of values returned by the method
- for each entity's class in the result set. If any score is 0,
- return 0.
- * if row is specified, return the value returned by the method with
- the entity's class of this row
- """
- # XXX this selector can be refactored : extract the code testing
- # for entity schema / interface compliance
- score = 0
- # check 'accepts' to give priority to more specific classes
- if row is None:
- for etype in rset.column_types(col or 0):
- eclass = cls.vreg.etype_class(etype)
- escore = 0
- for iface in cls.accepts_interfaces:
- escore += iface.is_implemented_by(eclass)
- if not escore:
- return 0
- score += escore
- accepts = set(getattr(cls, 'accepts', ()))
- # if accepts is defined on the vobject, eclass must match
- if accepts:
- eschema = eclass.e_schema
- etypes = set([eschema] + eschema.ancestors())
- if accepts & etypes:
- score += 2
- elif 'Any' not in accepts:
- return 0
- return score + 1
- etype = rset.description[row][col or 0]
- if etype is None: # outer join
- return 0
- eclass = cls.vreg.etype_class(etype)
- for iface in cls.accepts_interfaces:
- score += iface.is_implemented_by(eclass)
- if score:
- accepts = set(getattr(cls, 'accepts', ()))
- # if accepts is defined on the vobject, eclass must match
- if accepts:
- eschema = eclass.e_schema
- etypes = set([eschema] + eschema.ancestors())
- if accepts & etypes:
- score += 1
- elif 'Any' not in accepts:
- return 0
- score += 1
- return score
-_interface_selector = deprecated_function(_implement_interface)
-
-@lltrace
-def score_entity_selector(cls, req, rset, row=None, col=None, **kwargs):
- if row is None:
- rows = xrange(rset.rowcount)
- else:
- rows = (row,)
- for row in rows:
- try:
- score = cls.score_entity(rset.get_entity(row, col or 0))
- except DummyCursorError:
- # get a dummy cursor error, that means we are currently
- # using a dummy rset to list possible views for an entity
- # type, not for an actual result set. In that case, we
- # don't care of the value, consider the object as selectable
- return 1
- if not score:
- return 0
- return 1
-
-@lltrace
-def accept_rset(cls, req, rset, row=None, col=None, **kwargs):
- """simply delegate to cls.accept_rset method"""
- return cls.accept_rset(req, rset, row=row, col=col)
-accept_rset_selector = deprecated_function(accept_rset)
-
-@lltrace
-def but_etype(cls, req, rset, row=None, col=None, **kwargs):
- """restrict the searchstate_accept_one_selector to exclude entity's type
- refered by the .etype attribute
- """
- if rset.description[row or 0][col or 0] == cls.etype:
- return 0
- return 1
-but_etype_selector = deprecated_function(but_etype)
-
-@lltrace
-def etype_rtype_selector(cls, req, rset, row=None, col=None, **kwargs):
- """only check if the user has read access on the entity's type refered
- by the .etype attribute and on the relations's type refered by the
- .rtype attribute if set.
- """
- 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
-
-@lltrace
-def has_relation(cls, req, rset, row=None, col=None, **kwargs):
- """check if the user has read access on the relations's type refered by the
- .rtype attribute of the class, and if all entities types in the
- result set has this relation.
- """
- if hasattr(cls, 'rtype'):
- rschema = cls.schema.rschema(cls.rtype)
- perm = getattr(cls, 'require_permission', 'read')
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- if row is None:
- for etype in rset.column_types(col or 0):
- if not cls.relation_possible(etype):
- return 0
- elif not cls.relation_possible(rset.description[row][col or 0]):
- return 0
- return 1
-accept_rtype_selector = deprecated_function(has_relation)
-
-@lltrace
-def one_has_relation(cls, req, rset, row=None, col=None, **kwargs):
- """check if the user has read access on the relations's type refered by the
- .rtype attribute of the class, and if at least one entity type in the
- result set has this relation.
- """
- rschema = cls.schema.rschema(cls.rtype)
- perm = getattr(cls, 'require_permission', 'read')
- if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
- return 0
- if row is None:
- for etype in rset.column_types(col or 0):
- if cls.relation_possible(etype):
- return 1
- elif cls.relation_possible(rset.description[row][col or 0]):
- return 1
- return 0
-one_has_relation_selector = deprecated_function(one_has_relation)
-
-@lltrace
-def has_related_entities(cls, req, rset, row=None, col=None, **kwargs):
- return bool(rset.get_entity(row or 0, col or 0).related(cls.rtype, role(cls)))
-
-
-@lltrace
-def match_user_group(cls, req, rset=None, row=None, col=None, **kwargs):
- """select according to user's groups"""
- if not cls.require_groups:
- return 1
- user = req.user
- if user is None:
- return int('guests' in cls.require_groups)
- score = 0
- if 'owners' in cls.require_groups and rset:
- if row is not None:
- eid = rset[row][col or 0]
- if user.owns(eid):
- score = 1
- else:
- score = all(user.owns(r[col or 0]) for r in rset)
- score += user.matching_groups(cls.require_groups)
- if score:
- # add 1 so that an object with one matching group take priority
- # on an object without require_groups
- return score + 1
- return 0
-in_group_selector = deprecated_function(match_user_group)
-
-@lltrace
-def user_can_add_etype(cls, req, rset, row=None, col=None, **kwargs):
- """only check if the user has add access on the entity's type refered
- by the .etype attribute.
- """
- if not cls.schema.eschema(cls.etype).has_perm(req, 'add'):
- return 0
- return 1
-add_etype_selector = deprecated_function(user_can_add_etype)
-
-@lltrace
-def match_context_prop(cls, req, rset, row=None, col=None, context=None,
- **kwargs):
- 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
-contextprop_selector = deprecated_function(match_context_prop)
-
-@lltrace
-def primary_view(cls, req, rset, row=None, col=None, view=None,
- **kwargs):
- if view is not None and not view.is_primary():
- return 0
- return 1
-primaryview_selector = deprecated_function(primary_view)
-
-def appobject_selectable(registry, oid):
- """return a selector that will have a positive score if an object for the
- given registry and object id is selectable for the input context
- """
- @lltrace
- def selector(cls, req, rset, *args, **kwargs):
- try:
- cls.vreg.select_object(registry, oid, req, rset, *args, **kwargs)
- return 1
- except NoSelectableObject:
- return 0
- return selector
-
-
-# compound selectors ##########################################################
-
-non_final_entity = chainall(nonempty_rset, _non_final_entity)
-non_final_entity.__name__ = 'non_final_entity'
-nfentity_selector = deprecated_function(non_final_entity)
-
-implement_interface = chainall(non_final_entity, _implement_interface)
-implement_interface.__name__ = 'implement_interface'
-interface_selector = deprecated_function(implement_interface)
-
-accept = chainall(non_final_entity, accept_rset)
-accept.__name__ = 'accept'
-accept_selector = deprecated_function(accept)
-
-accept_one = chainall(one_line_rset, accept)
-accept_one.__name__ = 'accept_one'
-accept_one_selector = deprecated_function(accept_one)
-
-rql_condition = chainall(non_final_entity, one_line_rset, _rql_condition)
-rql_condition.__name__ = 'rql_condition'
-rqlcondition_selector = deprecated_function(rql_condition)
-
-
-searchstate_accept = chainall(nonempty_rset, match_search_state, accept)
-searchstate_accept.__name__ = 'searchstate_accept'
-searchstate_accept_selector = deprecated_function(searchstate_accept)
-
-searchstate_accept_one = chainall(one_line_rset, match_search_state,
- accept, _rql_condition)
-searchstate_accept_one.__name__ = 'searchstate_accept_one'
-searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
-
-searchstate_accept_one_but_etype = chainall(searchstate_accept_one, but_etype)
-searchstate_accept_one_but_etype.__name__ = 'searchstate_accept_one_but_etype'
-searchstate_accept_one_but_etype_selector = deprecated_function(
- searchstate_accept_one_but_etype)
+from warnings import warn
+warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2)
+from cubicweb.selectors import *
+from cubicweb.selectors import _rql_condition
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/common/tags.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,42 @@
+"""helper classes to generate simple (X)HTML tags
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import simple_sgml_tag
+
+class tag(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __call__(self, __content=None, **attrs):
+ return simple_sgml_tag(self.name, __content, **attrs)
+
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+img = tag('img')
+label = tag('label')
+option = tag('option')
+h1 = tag('h1')
+h2 = tag('h2')
+h3 = tag('h3')
+h4 = tag('h4')
+h5 = tag('h5')
+
+def select(name, id=None, multiple=False, options=[]):
+ attrs = {}
+ if multiple:
+ attrs['multiple'] = 'multiple'
+ if id:
+ attrs['id'] = id
+ html = [u'<select name="%s" %s>' % (
+ name, ' '.join('%s="%s"' % kv for kv in attrs.items()))]
+ html += options
+ html.append(u'</select>')
+ return u'\n'.join(html)
+
--- a/common/tal.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-"""provides simpleTAL extensions for CubicWeb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-import re
-from os.path import exists, isdir, join
-from logging import getLogger
-from StringIO import StringIO
-
-from simpletal import simpleTAL, simpleTALES
-
-from logilab.common.decorators import cached
-
-LOGGER = getLogger('cubicweb.tal')
-
-
-class LoggerAdapter(object):
- def __init__(self, tal_logger):
- self.tal_logger = tal_logger
-
- def debug(self, msg):
- LOGGER.debug(msg)
-
- def warn(self, msg):
- LOGGER.warning(msg)
-
- def __getattr__(self, attrname):
- return getattr(self.tal_logger, attrname)
-
-
-class CubicWebContext(simpleTALES.Context):
- """add facilities to access entity / resultset"""
-
- def __init__(self, options=None, allowPythonPath=1):
- simpleTALES.Context.__init__(self, options, allowPythonPath)
- self.log = LoggerAdapter(self.log)
-
- def update(self, context):
- for varname, value in context.items():
- self.addGlobal(varname, value)
-
- def addRepeat(self, name, var, initialValue):
- simpleTALES.Context.addRepeat(self, name, var, initialValue)
-
-# XXX FIXME need to find a clean to define OPCODE values for extensions
-I18N_CONTENT = 18
-I18N_REPLACE = 19
-RQL_EXECUTE = 20
-# simpleTAL uses the OPCODE values to define priority over commands.
-# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
-# we can't use the same OPCODE for two different commands without changing
-# the simpleTAL implementation. Another solution would be to totally override
-# the REPEAT implementation with the ITER one, but some specific operations
-# (involving len() for instance) are not implemented for ITER, so we prefer
-# to keep both implementations for now, and to fool simpleTAL by using a float
-# number between 3 and 4
-TAL_ITER = 3.1
-
-
-# FIX simpleTAL HTML 4.01 stupidity
-# (simpleTAL never closes tags like INPUT, IMG, HR ...)
-simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
-
-class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
- """extends default compiler by adding i18n:content commands"""
-
- def __init__(self):
- simpleTAL.HTMLTemplateCompiler.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
- self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
- self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
- self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
-
- def setTALPrefix(self, prefix):
- simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
- self.tal_attribute_map['i18n:content'] = I18N_CONTENT
- self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
- self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
- self.tal_attribute_map['tal:iter'] = TAL_ITER
-
- def compile_cmd_i18n_content(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
-
- def compile_cmd_i18n_replace(self, argument):
- # XXX tal:content structure=, text= should we support this ?
- structure_flag = 0
- return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
-
- def compile_cmd_rql(self, argument):
- return (RQL_EXECUTE, (argument, self.endTagSymbol))
-
- def compile_cmd_tal_iter(self, argument):
- original_id, (var_name, expression, end_tag_symbol) = \
- simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
- return (TAL_ITER, (var_name, expression, self.endTagSymbol))
-
- def getTemplate(self):
- return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
-
- def compileCmdAttributes (self, argument):
- """XXX modified to support single attribute
- definition ending by a ';'
-
- backport this to simpleTAL
- """
- # Compile tal:attributes into attribute command
- # Argument: [(attributeName, expression)]
-
- # Break up the list of attribute settings first
- commandArgs = []
- # We only want to match semi-colons that are not escaped
- argumentSplitter = re.compile(r'(?<!;);(?!;)')
- for attributeStmt in argumentSplitter.split(argument):
- if not attributeStmt.strip():
- continue
- # remove any leading space and un-escape any semi-colons
- attributeStmt = attributeStmt.lstrip().replace(';;', ';')
- # Break each attributeStmt into name and expression
- stmtBits = attributeStmt.split(' ')
- if (len (stmtBits) < 2):
- # Error, badly formed attributes command
- msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
- self.log.error(msg)
- raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
- attName = stmtBits[0]
- attExpr = " ".join(stmtBits[1:])
- commandArgs.append((attName, attExpr))
- return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
-
-
-class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
- """provides implementation for interpreting cubicweb extensions"""
- def __init__(self):
- simpleTAL.TemplateInterpreter.__init__(self)
- self.commandHandler[I18N_CONTENT] = self.cmd_i18n
- self.commandHandler[TAL_ITER] = self.cmdRepeat
- # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
-
- def cmd_i18n(self, command, args):
- """i18n:content and i18n:replace implementation"""
- string, replace_flag, structure_flag, end_symbol = args
- if replace_flag:
- self.outputTag = 0
- result = self.context.globals['_'](string)
- self.tagContent = (0, result)
- self.movePCForward = self.symbolTable[end_symbol]
- self.programCounter += 1
-
-
-class CubicWebTemplate(simpleTAL.HTMLTemplate):
- """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
- """
- def expand(self, context, outputFile):
- interpreter = CubicWebTemplateInterpreter()
- interpreter.initialise(context, outputFile)
- simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
- interpreter=interpreter)
-
- def expandInline(self, context, outputFile, interpreter):
- """ Internally used when expanding a template that is part of a context."""
- try:
- interpreter.execute(self)
- except UnicodeError, unierror:
- LOGGER.exception(str(unierror))
- raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
-
-
-def compile_template(template):
- """compiles a TAL template string
- :type template: unicode
- :param template: a TAL-compliant template string
- """
- string_buffer = StringIO(template)
- compiler = CubicWebTemplateCompiler()
- compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
- return compiler.getTemplate()
-
-
-def compile_template_file(filepath):
- """compiles a TAL template file
- :type filepath: str
- :param template: path of the file to compile
- """
- fp = file(filepath)
- file_content = unicode(fp.read()) # template file should be pure ASCII
- fp.close()
- return compile_template(file_content)
-
-
-def evaluatePython (self, expr):
- if not self.allowPythonPath:
- return self.false
- globals = {}
- for name, value in self.globals.items():
- if isinstance (value, simpleTALES.ContextVariable):
- value = value.rawValue()
- globals[name] = value
- globals['path'] = self.pythonPathFuncs.path
- globals['string'] = self.pythonPathFuncs.string
- globals['exists'] = self.pythonPathFuncs.exists
- globals['nocall'] = self.pythonPathFuncs.nocall
- globals['test'] = self.pythonPathFuncs.test
- locals = {}
- for name, value in self.locals.items():
- if (isinstance (value, simpleTALES.ContextVariable)):
- value = value.rawValue()
- locals[name] = value
- # XXX precompile expr will avoid late syntax error
- try:
- result = eval(expr, globals, locals)
- except Exception, ex:
- ex = ex.__class__('in %r: %s' % (expr, ex))
- raise ex, None, sys.exc_info()[-1]
- if (isinstance (result, simpleTALES.ContextVariable)):
- return result.value()
- return result
-
-simpleTALES.Context.evaluatePython = evaluatePython
-
-
-class talbased(object):
- def __init__(self, filename, write=True):
-## if not osp.isfile(filepath):
-## # print "[tal.py] just for tests..."
-## # get parent frame
-## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
-## filepath = osp.join(directory, filepath)
- self.filename = filename
- self.write = write
-
- def __call__(self, viewfunc):
- def wrapped(instance, *args, **kwargs):
- variables = viewfunc(instance, *args, **kwargs)
- html = instance.tal_render(self._compiled_template(instance), variables)
- if self.write:
- instance.w(html)
- else:
- return html
- return wrapped
-
- def _compiled_template(self, instance):
- for fileordirectory in instance.config.vregistry_path():
- filepath = join(fileordirectory, self.filename)
- if isdir(fileordirectory) and exists(filepath):
- return compile_template_file(filepath)
- raise Exception('no such template %s' % self.filename)
- _compiled_template = cached(_compiled_template, 0)
-
--- a/common/test/data/bootstrap_cubes Fri Feb 27 09:59:53 2009 +0100
+++ b/common/test/data/bootstrap_cubes Mon Mar 02 21:03:54 2009 +0100
@@ -1,1 +1,1 @@
-file, tag
+
--- a/common/test/data/entities.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-from cubicweb.entities import AnyEntity, fetch_config
-
-class Personne(AnyEntity):
- """customized class forne Person entities"""
- id = 'Personne'
- fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
- rest_attr = 'nom'
-
-
-class Societe(AnyEntity):
- id = 'Societe'
- fetch_attrs = ('nom',)
-
-class AnotherNote(AnyEntity):
- id = 'AnotherNote'
--- a/common/test/data/schema/Affaire.sql Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-sujet varchar(128)
-ref varchar(12)
--- a/common/test/data/schema/Note.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-from cubicweb.schema import format_constraint
-
-class AnotherNote(EntityType):
- descr_format = String(meta=True, internationalizable=True,
- default='text/rest', constraints=[format_constraint])
- descr = String(fulltextindexed=True,
- description=_('more detailed description'))
- descr2_format = String(meta=True, internationalizable=True,
- default='text/rest', constraints=[format_constraint])
- descr2 = String(fulltextindexed=True,
- description=_('more detailed description'))
-
-
-class SubNote(AnotherNote):
- __specializes_schema__ = True
- descr3 = String()
--- a/common/test/data/schema/Note.sql Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-date varchar(10)
-type char(1)
-para varchar(512)
--- a/common/test/data/schema/Personne.sql Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-nom ivarchar(64) NOT NULL
-prenom ivarchar(64)
-sexe char(1) DEFAULT 'M'
-promo choice('bon','pasbon')
-titre ivarchar(128)
-adel varchar(128)
-ass varchar(128)
-web varchar(128)
-tel integer
-fax integer
-datenaiss datetime
-test boolean
-description text
-salary float
--- a/common/test/data/schema/Societe.sql Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-nom ivarchar(64)
-web varchar(128)
-tel integer
-fax integer
-rncs varchar(32)
-ad1 varchar(128)
-ad2 varchar(128)
-ad3 varchar(128)
-cp varchar(12)
-ville varchar(32)
--- a/common/test/data/schema/relations.rel Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-Personne travaille Societe
-Personne evaluee Note
-EUser evaluee Note
-Societe evaluee Note
-Personne concerne Affaire
-Affaire concerne Societe
-Personne evaluee Personne
-
-Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P
-Personne connait Personne symetric
-
-Tag tags Note
-Tag tags Personne
-
-Affaire liee_a Societe
-Affaire liee_a Personne
--- a/common/test/unittest_entity.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,491 +0,0 @@
-# -*- coding: utf-8 -*-
-"""unit tests for cubicweb.web.views.entities module"""
-
-from cubicweb.devtools.apptest import EnvBasedTC
-
-from mx.DateTime import DateTimeType, now
-from cubicweb import Binary
-
-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)
-
- def test_boolean_value(self):
- e = self.etype_instance('Tag')
- self.failUnless(e)
-
- def test_yams_inheritance(self):
- from entities import AnotherNote
- e = self.etype_instance('SubNote')
- self.assertIsInstance(e, AnotherNote)
- e2 = self.etype_instance('SubNote')
- self.assertIs(e.__class__, e2.__class__)
-
- def test_has_eid(self):
- e = self.etype_instance('Tag')
- self.assertEquals(e.eid, None)
- self.assertEquals(e.has_eid(), False)
- e.eid = 'X'
- self.assertEquals(e.has_eid(), False)
- e.eid = 0
- self.assertEquals(e.has_eid(), True)
- e.eid = 2
- 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')
- 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.copy_relations(oe.eid)
- self.assertEquals(len(e.ecrit_par), 1)
- self.assertEquals(e.ecrit_par[0].eid, p.eid)
- self.assertEquals(len(e.reverse_tags), 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')
- 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.copy_relations(oe.eid)
- self.failIf(e.ecrit_par)
- self.failUnless(oe.ecrit_par)
-
- def test_copy_with_composite(self):
- user = self.user()
- adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
- e = self.entity('Any X WHERE X eid %(x)s', {'x':user.eid}, 'x')
- self.assertEquals(e.use_email[0].address, "toto@logilab.org")
- self.assertEquals(e.use_email[0].eid, adeleid)
- usereid = self.execute('INSERT EUser X: X login "toto", X upassword "toto", X in_group G, X in_state S '
- 'WHERE G name "users", S name "activated"')[0][0]
- e = self.entity('Any X WHERE X eid %(x)s', {'x':usereid}, 'x')
- e.copy_relations(user.eid)
- self.failIf(e.use_email)
- self.failIf(e.primary_email)
-
- def test_copy_with_non_initial_state(self):
- user = self.user()
- eid = self.execute('INSERT EUser X: X login "toto", X upassword %(pwd)s, X in_group G WHERE G name "users"',
- {'pwd': 'toto'})[0][0]
- self.commit()
- self.execute('SET X in_state S WHERE X eid %(x)s, S name "deactivated"', {'x': eid}, 'x')
- self.commit()
- eid2 = self.execute('INSERT EUser X: X login "tutu", X upassword %(pwd)s', {'pwd': 'toto'})[0][0]
- e = self.entity('Any X WHERE X eid %(x)s', {'x': eid2}, 'x')
- e.copy_relations(eid)
- self.commit()
- e.clear_related_cache('in_state', 'subject')
- self.assertEquals(e.state, 'activated')
-
- def test_related_cache_both(self):
- 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(), [])
- email = user.primary_email[0]
- self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
- self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
- groups = user.in_group
- self.assertEquals(sorted(user._related_cache), ['in_group_subject', 'primary_email_subject'])
- for group in groups:
- 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')
- for tag in u'abcd':
- self.add_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)
-
-
- def test_fetch_rql(self):
- user = self.user()
- Personne = self.vreg.etype_class('Personne')
- Societe = self.vreg.etype_class('Societe')
- Note = self.vreg.etype_class('Note')
- peschema = Personne.e_schema
- seschema = Societe.e_schema
- peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '1*')
- peschema.subject_relation('connait').set_rproperty(peschema, peschema, 'cardinality', '11')
- peschema.subject_relation('evaluee').set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
- seschema.subject_relation('evaluee').set_rproperty(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 WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
- pfetch_attrs = Personne.fetch_attrs
- sfetch_attrs = Societe.fetch_attrs
- try:
- # testing unknown attributes
- Personne.fetch_attrs = ('bloug', 'beep')
- self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne')
- # testing one non final relation
- Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
- 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 two non final relations
- Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee')
- self.assertEquals(Personne.fetch_rql(user),
- 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, '
- 'X prenom AB, X travaille AC, AC nom AD, X evaluee AE, AE modification_date AF')
- # testing one non final relation with recursion
- Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
- Societe.fetch_attrs = ('nom', 'evaluee')
- self.assertEquals(Personne.fetch_rql(user),
- 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, X prenom AB, '
- 'X travaille AC, AC nom AD, AC evaluee AE, AE modification_date AF'
- )
- # testing symetric 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.subject_relation('travaille').set_rproperty(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.subject_relation('travaille').set_rproperty(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
- finally:
- Personne.fetch_attrs = pfetch_attrs
- Societe.fetch_attrs = sfetch_attrs
-
- def test_related_rql(self):
- from cubicweb.entities import fetch_config
- Personne = self.vreg.etype_class('Personne')
- Societe = self.vreg.etype_class('Societe')
- Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'prenom', 'sexe'))
- Societe.fetch_attrs, Societe.fetch_order = fetch_config(('nom', 'web'))
- aff = self.add_entity('Affaire', sujet=u'my subject', ref=u'the ref')
- self.assertEquals(aff.related_rql('liee_a'),
- 'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E liee_a X, '
- 'X nom AA, X modification_date AB')
- Societe.fetch_attrs = ('web',)
- self.assertEquals(aff.related_rql('liee_a'),
- 'Any X ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E liee_a X')
-
- def test_entity_unrelated(self):
- p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
- e = self.add_entity('Tag', name=u'x')
- rschema = e.e_schema.subject_relation('tags')
- related = [r.eid for r in e.tags]
- self.failUnlessEqual(related, [])
- unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
- self.failUnless(p.eid in unrelated)
- self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
- e = self.entity('Any X WHERE X is Tag')
- unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
- self.failIf(p.eid in unrelated)
-
- def test_entity_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'di mascio', prenom=u'gwen')
- rschema = e.e_schema.subject_relation('tags')
- self.assertEquals(len(e.vocabulary(rschema, 'subject', limit=1)),
- 1)
-
- def test_new_entity_unrelated(self):
- e = self.etype_instance('EUser')
- rschema = e.e_schema.subject_relation('in_group')
- unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
- # should be default groups but owners, i.e. managers, users, guests
- self.assertEquals(len(unrelated), 3)
-
-
- def test_rtags_expansion(self):
- from cubicweb.entities import AnyEntity
- class Personne(AnyEntity):
- id = 'Personne'
- __rtags__ = {
- ('travaille', 'Societe', 'subject') : set(('primary',)),
- ('evaluee', '*', 'subject') : set(('secondary',)),
- 'ecrit_par' : set(('inlineview',)),
- }
- self.vreg.register_vobject_class(Personne)
- rtags = Personne.rtags
- self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
- self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
- self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
- self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
- self.assertEquals(rtags.get_tags('ecrit_par', 'Note', 'object'), set(('inlineview', 'link')))
- self.assertEquals(rtags.is_inlined('ecrit_par', 'Note', 'object'), True)
- class Personne2(Personne):
- id = 'Personne'
- __rtags__ = {
- ('evaluee', 'Note', 'subject') : set(('inlineview',)),
- }
- self.vreg.register_vobject_class(Personne2)
- rtags = Personne2.rtags
- self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
- self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
- self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
- self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
-
- def test_relations_by_category(self):
- e = self.etype_instance('EUser')
- def rbc(iterable):
- return [(rschema.type, x) for rschema, tschemas, x in iterable]
- self.assertEquals(rbc(e.relations_by_category('primary')),
- [('login', 'subject'), ('upassword', 'subject'),
- ('in_group', 'subject'), ('in_state', 'subject'),
- ('eid', 'subject'),])
- # firstname and surname are put in secondary category in views.entities.EUserEntity
- self.assertListEquals(rbc(e.relations_by_category('secondary')),
- [('firstname', 'subject'), ('surname', 'subject')])
- self.assertListEquals(rbc(e.relations_by_category('generic')),
- [('primary_email', 'subject'),
- ('evaluee', 'subject'),
- ('for_user', 'object')])
- # owned_by is defined both as subject and object relations on EUser
- self.assertListEquals(rbc(e.relations_by_category('generated')),
- [('last_login_time', 'subject'),
- ('created_by', 'subject'),
- ('creation_date', 'subject'),
- ('is', 'subject'),
- ('is_instance_of', 'subject'),
- ('modification_date', 'subject'),
- ('owned_by', 'subject'),
- ('created_by', 'object'),
- ('wf_info_for', 'object'),
- ('owned_by', 'object'),
- ('bookmarked_by', 'object')])
- e = self.etype_instance('Personne')
- self.assertListEquals(rbc(e.relations_by_category('primary')),
- [('nom', 'subject'), ('eid', 'subject')])
- self.assertListEquals(rbc(e.relations_by_category('secondary')),
- [('prenom', 'subject'),
- ('sexe', 'subject'),
- ('promo', 'subject'),
- ('titre', 'subject'),
- ('adel', 'subject'),
- ('ass', 'subject'),
- ('web', 'subject'),
- ('tel', 'subject'),
- ('fax', 'subject'),
- ('datenaiss', 'subject'),
- ('test', 'subject'),
- ('description', 'subject'),
- ('salary', 'subject')])
- self.assertListEquals(rbc(e.relations_by_category('generic')),
- [('concerne', 'subject'),
- ('connait', 'subject'),
- ('evaluee', 'subject'),
- ('travaille', 'subject'),
- ('ecrit_par', 'object'),
- ('evaluee', 'object'),
- ('liee_a', 'object'),
- ('tags', 'object')])
- self.assertListEquals(rbc(e.relations_by_category('generated')),
- [('created_by', 'subject'),
- ('creation_date', 'subject'),
- ('is', 'subject'),
- ('is_instance_of', 'subject'),
- ('modification_date', 'subject'),
- ('owned_by', 'subject')])
-
-
- def test_printable_value_string(self):
- e = self.add_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/egroup/managers">*ReST*</a></p>\n')
- e['content'] = 'du <em>html</em> <ref rql="EUser X">users</ref>'
- e['content_format'] = 'text/html'
- self.assertEquals(e.printable_value('content'),
- 'du <em>html</em> <a href="http://testing.fr/cubicweb/view?rql=EUser%20X">users</a>')
- e['content'] = 'du *texte*'
- e['content_format'] = 'text/plain'
- self.assertEquals(e.printable_value('content'),
- '<p>\ndu *texte*\n</p>')
- e['title'] = 'zou'
- e['content'] = '<h1 tal:content="self/title">titre</h1>'
- e['content_format'] = 'text/cubicweb-page-template'
- self.assertEquals(e.printable_value('content'),
- '<h1>zou</h1>')
-
- #e = self.etype_instance('Task')
- e['content'] = '''\
-a title
-=======
-du :eid:`1:*ReST*`'''
- e['content_format'] = 'text/rest'
- self.assertEquals(e.printable_value('content', format='text/plain'),
- e['content'])
-
- e['content'] = u'<b>yo (zou éà ;)</b>'
- e['content_format'] = 'text/html'
- self.assertEquals(e.printable_value('content', format='text/plain').strip(),
- u'**yo (zou éà ;)**')
-
- def test_printable_value_bytes(self):
- e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
- data_encoding=u'ascii', name=u'toto.py')
- from cubicweb.common 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="mf">1</span>
-</pre></div>
-''')
- else:
- self.assertEquals(e.printable_value('data'),
- '''<pre class="python">
-<span style="color: #C00000;">lambda</span> <span style="color: #000000;">x</span><span style="color: #0000C0;">:</span> <span style="color: #0080C0;">1</span>
-</pre>
-''')
-
- e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
- data_encoding=u'utf-8', 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>',
- content_format=u'text/html')
- tidy = lambda x: x.replace('\n', '')
- self.assertEquals(tidy(e.printable_value('content')),
- '<div>R&D<br/></div>')
- e['content'] = u'yo !! R&D <div> pas fermé'
- self.assertEquals(tidy(e.printable_value('content')),
- u'yo !! R&D <div> pas fermé</div>')
- e['content'] = u'R&D'
- self.assertEquals(tidy(e.printable_value('content')), u'R&D')
- e['content'] = u'R&D;'
- self.assertEquals(tidy(e.printable_value('content')), u'R&D;')
- e['content'] = u'yo !! R&D <div> pas fermé'
- self.assertEquals(tidy(e.printable_value('content')),
- u'yo !! R&D <div> pas fermé</div>')
- e['content'] = u'été <div> été'
- self.assertEquals(tidy(e.printable_value('content')),
- u'été <div> été</div>')
- e['content'] = u'C'est un exemple sérieux'
- self.assertEquals(tidy(e.printable_value('content')),
- u"C'est un exemple sérieux")
- # make sure valid xhtml is left untouched
- e['content'] = u'<div>R&D<br/></div>'
- self.assertEquals(e.printable_value('content'), e['content'])
- e['content'] = u'<div>été</div>'
- self.assertEquals(e.printable_value('content'), e['content'])
- e['content'] = u'été'
- self.assertEquals(e.printable_value('content'), e['content'])
-
-
- def test_entity_formatted_attrs(self):
- e = self.etype_instance('Note')
- self.assertEquals(e.formatted_attrs(), [])
- e = self.etype_instance('File')
- self.assertEquals(e.formatted_attrs(), ['description'])
- e = self.etype_instance('AnotherNote')
- self.assertEquals(e.formatted_attrs(), ['descr', 'descr2'])
-
-
- def test_fulltextindex(self):
- e = self.etype_instance('File')
- e['name'] = 'an html file'
- e['description'] = 'du <em>html</em>'
- e['description_format'] = 'text/html'
- e['data'] = Binary('some <em>data</em>')
- e['data_format'] = 'text/html'
- e['data_encoding'] = 'ascii'
- 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')
- 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):
- self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
- self.commit()
- try:
- eid = self.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]
- trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
- trinfo.complete()
- self.failUnless(trinfo.relation_cached('from_state', 'subject'))
- self.failUnless(trinfo.relation_cached('to_state', 'subject'))
- self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
- # check with a missing relation
- eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
- 'X to_state S2 WHERE '
- 'U login "admin", S2 name "activated"')[0][0]
- trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
- trinfo.complete()
- self.failUnless(isinstance(trinfo.creation_date, DateTimeType))
- self.failUnless(trinfo.relation_cached('from_state', 'subject'))
- self.failUnless(trinfo.relation_cached('to_state', 'subject'))
- self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
- self.assertEquals(trinfo.from_state, [])
- finally:
- self.rollback()
- self.execute('DELETE RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
- self.commit()
-
- def test_request_cache(self):
- req = self.request()
- user = self.entity('EUser X WHERE X login "admin"', req=req)
- state = user.in_state[0]
- samestate = self.entity('State X WHERE X name "activated"', req=req)
- self.failUnless(state is samestate)
-
- def test_rest_path(self):
- note = self.add_entity('Note', type=u'z')
- self.assertEquals(note.rest_path(), 'note/%s' % note.eid)
- # unique attr
- tag = self.add_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')
- self.assertEquals(person.rest_path(), 'personne/doe')
- # ambiguity test
- person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe')
- 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')
- self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid)
- card2 = self.add_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')
- self.assertEquals(person.prenom, u'adrien')
- self.assertEquals(person.nom, u'di mascio')
- person.set_attributes(prenom=u'sylvain', nom=u'thénault')
- person = self.entity('Personne P') # XXX retreival needed ?
- self.assertEquals(person.prenom, u'sylvain')
- self.assertEquals(person.nom, u'thénault')
-
- def test_metainformation(self):
- note = self.add_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)
- metainf['source'] = metainf['source'].copy()
- metainf['source']['base-url'] = 'http://cubicweb2.com/'
- self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/%s' % note.eid)
-
-if __name__ == '__main__':
- from logilab.common.testlib import unittest_main
- unittest_main()
-
--- a/common/test/unittest_migration.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/test/unittest_migration.py Mon Mar 02 21:03:54 2009 +0100
@@ -30,6 +30,8 @@
self.config = MigrTestConfig('data')
from yams.schema import Schema
self.config.load_schema = lambda expand_cubes=False: Schema('test')
+ self.config.__class__.cubicweb_vobject_path = frozenset()
+ self.config.__class__.cube_vobject_path = frozenset()
def test_migration_files_base(self):
self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)),
--- a/common/test/unittest_utils.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-"""unit tests for module cubicweb.common.utils"""
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList
-
-
-class MakeUidTC(TestCase):
- def test_1(self):
- self.assertNotEquals(make_uid('xyz'), make_uid('abcd'))
- self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
-
- def test_2(self):
- d = {}
- while len(d)<10000:
- uid = make_uid('xyz')
- if d.has_key(uid):
- self.fail(len(d))
- d[uid] = 1
-
-
-class UStringIOTC(TestCase):
- def test_boolean_value(self):
- self.assert_(UStringIO())
-
-
-class SizeConstrainedListTC(TestCase):
-
- def test_append(self):
- l = SizeConstrainedList(10)
- for i in xrange(12):
- l.append(i)
- self.assertEquals(l, range(2, 12))
-
- def test_extend(self):
- testdata = [(range(5), range(5)),
- (range(10), range(10)),
- (range(12), range(2, 12)),
- ]
- for extension, expected in testdata:
- l = SizeConstrainedList(10)
- l.extend(extension)
- yield self.assertEquals, l, expected
-
-
-if __name__ == '__main__':
- unittest_main()
--- a/common/uilib.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/uilib.py Mon Mar 02 21:03:54 2009 +0100
@@ -11,29 +11,18 @@
import csv
import decimal
-import locale
import re
from urllib import quote as urlquote
from cStringIO import StringIO
from copy import deepcopy
-import simplejson
from mx.DateTime import DateTimeType, DateTimeDeltaType
from logilab.common.textutils import unormalize
from logilab.mtconverter import html_escape, html_unescape
-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(fmt), encoding)
-
+from cubicweb.utils import ustrftime
def rql_for_eid(eid):
"""return the rql query necessary to fetch entity with the given eid. This
@@ -220,8 +209,13 @@
"""
value = u'<%s' % tag
if attrs:
+ try:
+ attrs['class'] = attrs.pop('klass')
+ except KeyError:
+ pass
value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value)))
- for attr, value in attrs.items())
+ for attr, value in attrs.items()
+ if value is not None)
if content:
value += u'>%s</%s>' % (html_escape(unicode(content)), tag)
else:
@@ -258,6 +252,7 @@
elif vid:
params.append(repr(vid))
if extraparams:
+ import simplejson
params.append(simplejson.dumps(extraparams))
if swap:
params.append('true')
--- a/common/utils.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/utils.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,263 +1,3 @@
-"""Some utilities for CubicWeb server/clients.
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from md5 import md5
-from time import time
-from random import randint, seed
-
-# initialize random seed from current time
-seed()
-
-def make_uid(key):
- """forge a unique identifier"""
- msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
- return md5(msg).hexdigest()
-
-def working_hours(mxdate):
- """
- Predicate returning True is the date's hour is in working hours (8h->20h)
- """
- if mxdate.hour > 7 and mxdate.hour < 21:
- return True
- return False
-
-def date_range(begin, end, incr=1, include=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.
- """
- date = begin
- while date <= end:
- if include is None or include(date):
- yield date
- date += incr
-
-
-def dump_class(cls, clsname):
- """create copy of a class by creating an empty class inheriting
- from the given cls.
-
- Those class will be used as place holder for attribute and relation
- description
- """
- # type doesn't accept unicode name
- # return type.__new__(type, str(clsname), (cls,), {})
- # __autogenerated__ attribute is just a marker
- return type(str(clsname), (cls,), {'__autogenerated__': True})
-
-
-def merge_dicts(dict1, dict2):
- """update a copy of `dict1` with `dict2`"""
- dict1 = dict(dict1)
- dict1.update(dict2)
- return dict1
-
-
-class SizeConstrainedList(list):
- """simple list that makes sure the list does not get bigger
- than a given size.
-
- when the list is full and a new element is added, the first
- element of the list is removed before appending the new one
-
- >>> l = SizeConstrainedList(2)
- >>> l.append(1)
- >>> l.append(2)
- >>> l
- [1, 2]
- >>> l.append(3)
- [2, 3]
- """
- def __init__(self, maxsize):
- self.maxsize = maxsize
-
- def append(self, element):
- if len(self) == self.maxsize:
- del self[0]
- super(SizeConstrainedList, self).append(element)
-
- def extend(self, sequence):
- super(SizeConstrainedList, self).extend(sequence)
- keepafter = len(self) - self.maxsize
- if keepafter > 0:
- del self[:keepafter]
-
- __iadd__ = extend
-
-
-class UStringIO(list):
- """a file wrapper which automatically encode unicode string to an encoding
- specifed in the constructor
- """
-
- def __nonzero__(self):
- return True
-
- def write(self, value):
- assert isinstance(value, unicode), u"unicode required not %s : %s"\
- % (type(value).__name__, repr(value))
- self.append(value)
-
- def getvalue(self):
- return u''.join(self)
-
- def __repr__(self):
- return '<%s at %#x>' % (self.__class__.__name__, id(self))
-
-
-class HTMLHead(UStringIO):
- """wraps HTML header's stream
-
- Request objects use a HTMLHead instance to ease adding of
- javascripts and stylesheets
- """
- js_unload_code = u'jQuery(window).unload(unloadPageData);'
-
- def __init__(self):
- super(HTMLHead, self).__init__()
- self.jsvars = []
- self.jsfiles = []
- self.cssfiles = []
- self.ie_cssfiles = []
- self.post_inlined_scripts = []
- self.pagedata_unload = False
-
-
- def add_raw(self, rawheader):
- self.write(rawheader)
-
- def define_var(self, var, value):
- self.jsvars.append( (var, value) )
-
- def add_post_inline_script(self, content):
- self.post_inlined_scripts.append(content)
-
- def add_onload(self, jscode):
- self.add_post_inline_script(u"""jQuery(document).ready(function () {
- %s
- });""" % jscode)
-
-
- def add_js(self, jsfile):
- """adds `jsfile` to the list of javascripts used in the webpage
-
- This function checks if the file has already been added
- :param jsfile: the script's URL
- """
- if jsfile not in self.jsfiles:
- self.jsfiles.append(jsfile)
-
- def add_css(self, cssfile, media):
- """adds `cssfile` to the list of javascripts used in the webpage
-
- This function checks if the file has already been added
- :param cssfile: the stylesheet's URL
- """
- if (cssfile, media) not in self.cssfiles:
- self.cssfiles.append( (cssfile, media) )
-
- def add_ie_css(self, cssfile, media='all'):
- """registers some IE specific CSS"""
- if (cssfile, media) not in self.ie_cssfiles:
- self.ie_cssfiles.append( (cssfile, media) )
-
- def add_unload_pagedata(self):
- """registers onunload callback to clean page data on server"""
- if not self.pagedata_unload:
- self.post_inlined_scripts.append(self.js_unload_code)
- self.pagedata_unload = True
-
- def getvalue(self):
- """reimplement getvalue to provide a consistent (and somewhat browser
- optimzed cf. http://stevesouders.com/cuzillion) order in external
- resources declaration
- """
- w = self.write
- # 1/ variable declaration if any
- if self.jsvars:
- from simplejson import dumps
- w(u'<script type="text/javascript">\n')
- for var, value in self.jsvars:
- w(u'%s = %s;\n' % (var, dumps(value)))
- w(u'</script>\n')
- # 2/ css files
- for cssfile, media in self.cssfiles:
- w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
- # 3/ ie css if necessary
- if self.ie_cssfiles:
- w(u'<!--[if lt IE 8]>\n')
- for cssfile, media in self.ie_cssfiles:
- w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
- w(u'<![endif]--> \n')
- # 4/ js files
- for jsfile in self.jsfiles:
- w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
- # 5/ post inlined scripts (i.e. scripts depending on other JS files)
- if self.post_inlined_scripts:
- w(u'<script type="text/javascript">\n')
- w(u'\n\n'.join(self.post_inlined_scripts))
- w(u'\n</script>\n')
- return u'<head>\n%s</head>\n' % super(HTMLHead, self).getvalue()
-
-
-class HTMLStream(object):
- """represents a HTML page.
-
- This is used my main templates so that HTML headers can be added
- at any time during the page generation.
-
- HTMLStream uses the (U)StringIO interface to be compliant with
- existing code.
- """
-
- def __init__(self, req):
- # stream for <head>
- self.head = req.html_headers
- # main stream
- self.body = UStringIO()
- self.doctype = u''
- # xmldecl and html opening tag
- self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
- self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
- 'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
- 'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
-
-
- def write(self, data):
- """StringIO interface: this method will be assigned to self.w
- """
- self.body.write(data)
-
- def getvalue(self):
- """writes HTML headers, closes </head> tag and writes HTML body"""
- return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
- self.htmltag,
- self.head.getvalue(),
- self.body.getvalue())
-
-
-class AcceptMixIn(object):
- """Mixin class for vobjects defining the 'accepts' attribute describing
- a set of supported entity type (Any by default).
- """
- # XXX deprecated, no more necessary
-
-
-from logilab.common.deprecation import moved, class_moved
-rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid')
-ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url')
-
-import cubicweb
-Binary = class_moved(cubicweb.Binary)
+from warnings import warn
+warn('moved to cubicweb.utils', DeprecationWarning, stacklevel=2)
+from cubicweb.utils import *
--- a/common/view.py Fri Feb 27 09:59:53 2009 +0100
+++ b/common/view.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,481 +1,3 @@
-"""abstract views and templates classes for CubicWeb web client
-
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-
-from logilab.mtconverter import html_escape
-
-from cubicweb import NotAnEntity, NoSelectableObject
-from cubicweb.common.registerers import accepts_registerer, priority_registerer
-from cubicweb.common.selectors import (chainfirst, match_user_group, accept,
- nonempty_rset, empty_rset, none_rset)
-from cubicweb.common.appobject import AppRsetObject, ComponentMixIn
-from cubicweb.common.utils import UStringIO, HTMLStream
-
-_ = unicode
-
-# robots control
-NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
-NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
-
-CW_XHTML_EXTENSIONS = '''[
- <!ATTLIST html xmlns:cubicweb CDATA #FIXED \'http://www.logilab.org/2008/cubicweb\' >
-
-<!ENTITY % coreattrs
- "id ID #IMPLIED
- class CDATA #IMPLIED
- style CDATA #IMPLIED
- title CDATA #IMPLIED
-
- cubicweb:sortvalue CDATA #IMPLIED
- cubicweb:target CDATA #IMPLIED
- cubicweb:limit CDATA #IMPLIED
- cubicweb:type CDATA #IMPLIED
- cubicweb:loadtype CDATA #IMPLIED
- cubicweb:wdgtype CDATA #IMPLIED
- cubicweb:initfunc CDATA #IMPLIED
- cubicweb:inputid CDATA #IMPLIED
- cubicweb:tindex CDATA #IMPLIED
- cubicweb:inputname CDATA #IMPLIED
- cubicweb:value CDATA #IMPLIED
- cubicweb:required CDATA #IMPLIED
- cubicweb:accesskey CDATA #IMPLIED
- cubicweb:maxlength CDATA #IMPLIED
- cubicweb:variables CDATA #IMPLIED
- cubicweb:displayactions CDATA #IMPLIED
- cubicweb:fallbackvid CDATA #IMPLIED
- cubicweb:vid CDATA #IMPLIED
- cubicweb:rql CDATA #IMPLIED
- cubicweb:actualrql CDATA #IMPLIED
- cubicweb:rooteid CDATA #IMPLIED
- cubicweb:dataurl CDATA #IMPLIED
- cubicweb:size CDATA #IMPLIED
- cubicweb:tlunit CDATA #IMPLIED
- cubicweb:loadurl CDATA #IMPLIED
- cubicweb:uselabel CDATA #IMPLIED
- cubicweb:facetargs CDATA #IMPLIED
- cubicweb:facetName CDATA #IMPLIED
- "> ] '''
-
-TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
-
-STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
-
-class View(AppRsetObject):
- """abstract view class, used as base for every renderable object such
- as views, templates, some components...web
-
- A view is instantiated to render a [part of a] result set. View
- subclasses may be parametred using the following class attributes:
-
- * `templatable` indicates if the view may be embeded in a main
- template or if it has to be rendered standalone (i.e. XML for
- instance)
- * if the view is not templatable, it should set the `content_type` class
- attribute to the correct MIME type (text/xhtml by default)
- * the `category` attribute may be used in the interface to regroup related
- objects together
-
- At instantiation time, the standard `req`, `rset`, and `cursor`
- attributes are added and the `w` attribute will be set at rendering
- time to a write function to use.
- """
- __registry__ = 'views'
-
- templatable = True
- need_navigation = True
- # content_type = 'application/xhtml+xml' # text/xhtml'
- binary = False
- add_to_breadcrumbs = True
- category = 'view'
-
- def __init__(self, req, rset):
- super(View, self).__init__(req, rset)
- self.w = None
-
- @property
- def content_type(self):
- if self.req.xhtml_browser():
- return 'application/xhtml+xml'
- return 'text/html'
-
- def set_stream(self, w=None):
- if self.w is not None:
- return
- if w is None:
- if self.binary:
- self._stream = stream = StringIO()
- else:
- self._stream = stream = UStringIO()
- w = stream.write
- else:
- stream = None
- self.w = w
- return stream
-
- # main view interface #####################################################
-
- def dispatch(self, w=None, **context):
- """called to render a view object for a result set.
-
- This method is a dispatched to an actual method selected
- according to optional row and col parameters, which are locating
- a particular row or cell in the result set:
-
- * if row [and col] are specified, `cell_call` is called
- * if none of them is supplied, the view is considered to apply on
- the whole result set (which may be None in this case), `call` is
- called
- """
- row, col = context.get('row'), context.get('col')
- if row is not None:
- context.setdefault('col', 0)
- view_func = self.cell_call
- else:
- view_func = self.call
- stream = self.set_stream(w)
- # stream = self.set_stream(context)
- view_func(**context)
- # return stream content if we have created it
- if stream is not None:
- return self._stream.getvalue()
-
- # should default .call() method add a <div classs="section"> around each
- # rset item
- add_div_section = True
-
- def call(self, **kwargs):
- """the view is called for an entire result set, by default loop
- other rows of the result set and call the same view on the
- particular row
-
- Views applicable on None result sets have to override this method
- """
- rset = self.rset
- if rset is None:
- raise NotImplementedError, self
- wrap = self.templatable and len(rset) > 1 and self.add_div_section
- for i in xrange(len(rset)):
- if wrap:
- self.w(u'<div class="section">')
- self.wview(self.id, rset, row=i, **kwargs)
- if wrap:
- self.w(u"</div>")
-
- def cell_call(self, row, col, **kwargs):
- """the view is called for a particular result set cell"""
- raise NotImplementedError, self
-
- def linkable(self):
- """return True if the view may be linked in a menu
-
- by default views without title are not meant to be displayed
- """
- if not getattr(self, 'title', None):
- return False
- return True
-
- def is_primary(self):
- return self.id == '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.
- """
- try:
- return self.build_url(vid=self.id, rql=self.req.form['rql'])
- except KeyError:
- return self.build_url(vid=self.id)
-
- def set_request_content_type(self):
- """set the content type returned by this view"""
- self.req.set_content_type(self.content_type)
-
- # view utilities ##########################################################
-
- def view(self, __vid, rset, __fallback_vid=None, **kwargs):
- """shortcut to self.vreg.render method avoiding to pass self.req"""
- try:
- view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
- except NoSelectableObject:
- if __fallback_vid is None:
- raise
- view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
- return view.dispatch(**kwargs)
-
- def wview(self, __vid, rset, __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)
-
- def whead(self, data):
- self.req.html_headers.write(data)
-
- def wdata(self, data):
- """simple helper that escapes `data` and writes into `self.w`"""
- self.w(html_escape(data))
-
- def action(self, actionid, row=0):
- """shortcut to get action object with id `actionid`"""
- return self.vreg.select_action(actionid, self.req, self.rset,
- row=row)
-
- def action_url(self, actionid, label=None, row=0):
- """simple method to be able to display `actionid` as a link anywhere
- """
- action = self.vreg.select_action(actionid, self.req, self.rset,
- row=row)
- if action:
- label = label or self.req._(action.title)
- return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
- return u''
-
- 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 return a meta tag to disable robot indexation of the page
- """
- return [NOINDEX]
-
- def page_title(self):
- """returns a title according to the result set - used for the
- title in the HTML header
- """
- vtitle = self.req.form.get('vtitle')
- if vtitle:
- return self.req._(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
- if rset and rset.rowcount:
- if rset.rowcount == 1:
- try:
- entity = self.complete_entity(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 = 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')
- else :
- clabel = u'#[*] (%s)' % vtitle
- else:
- clabel = vtitle
- return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
-
- def output_url_builder( self, name, url, args ):
- self.w(u'<script language="JavaScript"><!--\n' \
- u'function %s( %s ) {\n' % (name, ','.join(args) ) )
- url_parts = url.split("%s")
- self.w(u' url="%s"' % url_parts[0] )
- for arg, part in zip(args, url_parts[1:]):
- self.w(u'+str(%s)' % arg )
- if part:
- self.w(u'+"%s"' % part)
- self.w('\n document.window.href=url;\n')
- self.w('}\n-->\n</script>\n')
-
- 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)
-
-
-# concrete views base classes #################################################
-
-class EntityView(View):
- """base class for views applying on an entity (i.e. uniform result set)
- """
- __registerer__ = accepts_registerer
- __selectors__ = (accept,)
- accepts = ('Any',)
- category = 'entityview'
-
- def field(self, label, value, row=True, show_label=True, w=None, tr=True):
- """ read-only field """
- if w is None:
- w = self.w
- if row:
- w(u'<div class="row">')
- if show_label:
- if tr:
- label = display_name(self.req, label)
- w(u'<span class="label">%s</span>' % label)
- w(u'<div class="field">%s</div>' % value)
- if row:
- w(u'</div>')
-
-
-class StartupView(View):
- """base class for views which doesn't need a particular result set
- to be displayed (so they can always be displayed !)
- """
- __registerer__ = priority_registerer
- __selectors__ = (match_user_group, none_rset)
- require_groups = ()
- category = 'startupview'
-
- def url(self):
- """return the url associated with this view. We can omit rql here"""
- return self.build_url('view', vid=self.id)
-
- 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 startup views are indexed
- """
- return []
-
-
-class EntityStartupView(EntityView):
- """base class for entity views which may also be applied to None
- result set (usually a default rql is provided by the view class)
- """
- __registerer__ = accepts_registerer
- __selectors__ = (chainfirst(none_rset, accept),)
-
- default_rql = None
-
- def __init__(self, req, rset):
- super(EntityStartupView, self).__init__(req, rset)
- if rset is None:
- # this instance is not in the "entityview" category
- self.category = 'startupview'
-
- def startup_rql(self):
- """return some rql to be executedif the result set is None"""
- return self.default_rql
-
- def call(self, **kwargs):
- """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
- for i in xrange(len(rset)):
- self.wview(self.id, rset, row=i, **kwargs)
-
- def url(self):
- """return the url associated with this view. We can omit rql if we
- are on a result set on which we do not apply.
- """
- if not self.__select__(self.req, self.rset):
- return self.build_url(vid=self.id)
- return super(EntityStartupView, self).url()
-
-
-class AnyRsetView(View):
- """base class for views applying on any non empty result sets"""
- __registerer__ = priority_registerer
- __selectors__ = (nonempty_rset,)
-
- category = 'anyrsetview'
-
- def columns_labels(self, tr=True):
- if tr:
- translate = display_name
- else:
- translate = lambda req, val: val
- rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
- labels = []
- for colindex, attr in enumerate(rqlstdescr):
- # compute column header
- if colindex == 0 or attr == 'Any': # find a better label
- label = ','.join(translate(self.req, et)
- for et in self.rset.column_types(colindex))
- else:
- label = translate(self.req, attr)
- labels.append(label)
- return labels
-
-
-class EmptyRsetView(View):
- """base class for views applying on any empty result sets"""
- __registerer__ = priority_registerer
- __selectors__ = (empty_rset,)
-
-
-# concrete template base classes ##############################################
-
-class Template(View):
- """a template is almost like a view, except that by default a template
- is only used globally (i.e. no result set adaptation)
- """
- __registry__ = 'templates'
- __registerer__ = priority_registerer
- __selectors__ = (match_user_group,)
-
- require_groups = ()
-
- def template(self, oid, **kwargs):
- """shortcut to self.registry.render method on the templates registry"""
- w = kwargs.pop('w', self.w)
- self.vreg.render('templates', oid, self.req, w=w, **kwargs)
-
-
-class MainTemplate(Template):
- """main template are primary access point to render a full HTML page.
- There is usually at least a regular main template and a simple fallback
- one to display error if the first one failed
- """
-
- base_doctype = STRICT_DOCTYPE
-
- @property
- def doctype(self):
- if self.req.xhtml_browser():
- return self.base_doctype % CW_XHTML_EXTENSIONS
- return self.base_doctype % ''
-
- def set_stream(self, w=None, templatable=True):
- if templatable and self.w is not None:
- return
-
- if w is None:
- if self.binary:
- self._stream = stream = StringIO()
- elif not templatable:
- # not templatable means we're using a non-html view, we don't
- # want the HTMLStream stuff to interfere during data generation
- self._stream = stream = UStringIO()
- else:
- self._stream = stream = HTMLStream(self.req)
- w = stream.write
- else:
- stream = None
- self.w = w
- return stream
-
- def write_doctype(self, xmldecl=True):
- assert isinstance(self._stream, HTMLStream)
- self._stream.doctype = self.doctype
- if not xmldecl:
- self._stream.xmldecl = u''
-
-# viewable components base classes ############################################
-
-class VComponent(ComponentMixIn, View):
- """base class for displayable components"""
- property_defs = {
- 'visible': dict(type='Boolean', default=True,
- help=_('display the component or not')),}
-
-class SingletonVComponent(VComponent):
- """base class for displayable unique components"""
- __registerer__ = priority_registerer
+from warnings import warn
+warn('moved to cubicweb.view', DeprecationWarning, stacklevel=2)
+from cubicweb.view import *
--- a/cwconfig.py Fri Feb 27 09:59:53 2009 +0100
+++ b/cwconfig.py Mon Mar 02 21:03:54 2009 +0100
@@ -59,7 +59,7 @@
# XXX generate this according to the configuration (repository/all-in-one/web)
VREGOPTIONS = []
for registry in ('etypes', 'hooks', 'controllers', 'actions', 'components',
- 'views', 'templates', 'boxes', 'contentnavigation', 'urlrewriting',
+ 'views', 'boxes', 'contentnavigation', 'urlrewriting',
'facets'):
VREGOPTIONS.append(('disable-%s'%registry,
{'type' : 'csv', 'default': (),
--- a/cwvreg.py Fri Feb 27 09:59:53 2009 +0100
+++ b/cwvreg.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""extend the generic VRegistry with some cubicweb specific stuff
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -9,6 +9,7 @@
from warnings import warn
from logilab.common.decorators import cached, clear_cache
+from logilab.common.interface import extend
from rql import RQLHelper
@@ -17,11 +18,25 @@
_ = unicode
-class DummyCursorError(Exception): pass
-class RaiseCursor:
- @classmethod
- def execute(cls, rql, args=None, eid_key=None):
- raise DummyCursorError()
+def use_interfaces(obj):
+ from cubicweb.selectors import implements
+ try:
+ # XXX deprecated
+ return sorted(obj.accepts_interfaces)
+ except AttributeError:
+ try:
+ impl = obj.__select__.search_selector(implements)
+ if impl:
+ return sorted(impl.expected_ifaces)
+ except AttributeError:
+ pass # old-style vobject classes with no accepts_interfaces
+ return ()
+
+def expand_parent_classes(iface):
+ res = [iface]
+ for parent in iface.__bases__:
+ res += expand_parent_classes(parent)
+ return res
class CubicWebRegistry(VRegistry):
@@ -47,8 +62,10 @@
def reset(self):
self._registries = {}
self._lastmodifs = {}
- # two special registries, propertydefs which care all the property definitions, and
- # propertyvals which contains values for those properties
+ self._needs_iface = {}
+ # two special registries, propertydefs which care all the property
+ # definitions, and propertyvals which contains values for those
+ # properties
self._registries['propertydefs'] = {}
self._registries['propertyvalues'] = self.eprop_values = {}
for key, propdef in self.config.eproperty_definitions():
@@ -72,43 +89,51 @@
for objects in regcontent.values():
for obj in objects:
obj.schema = schema
+
+ 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
+ """
+ self.register(obj, **kwargs)
+ if not isinstance(ifaces, (tuple, list)):
+ self._needs_iface[obj] = (ifaces,)
+ else:
+ self._needs_iface[obj] = ifaces
+
+ def register(self, obj, **kwargs):
+ if kwargs.get('registryname', obj.__registry__) == 'etypes':
+ kwargs['clear'] = True
+ super(CubicWebRegistry, self).register(obj, **kwargs)
+ # XXX bw compat
+ ifaces = use_interfaces(obj)
+ if ifaces:
+ self._needs_iface[obj] = ifaces
def register_objects(self, path, force_reload=None):
- """overriden to handle type class cache issue"""
- if super(CubicWebRegistry, self).register_objects(path, force_reload):
+ """overriden to remove objects requiring a missing interface"""
+ if super(CubicWebRegistry, self).register_objects(path, force_reload):
# clear etype cache if you don't want to run into deep weirdness
clear_cache(self, 'etype_class')
+ # we may want to keep interface dependent objects (e.g.for i18n
+ # catalog generation)
+ if not self.config.cleanup_interface_sobjects:
+ return
# remove vobjects that don't support any available interface
interfaces = set()
for classes in self.get('etypes', {}).values():
for cls in classes:
- interfaces.update(cls.__implements__)
- if not self.config.cleanup_interface_sobjects:
- return
- for registry, regcontent in self._registries.items():
- if registry in ('propertydefs', 'propertyvalues', 'etypes'):
- continue
- for oid, objects in regcontent.items():
- for obj in reversed(objects[:]):
- if not obj in objects:
- continue # obj has been kicked by a previous one
- accepted = set(getattr(obj, 'accepts_interfaces', ()))
- if accepted:
- for accepted_iface in accepted:
- for found_iface in interfaces:
- if issubclass(found_iface, accepted_iface):
- # consider priority if necessary
- if hasattr(obj.__registerer__, 'remove_all_equivalents'):
- registerer = obj.__registerer__(self, obj)
- registerer.remove_all_equivalents(objects)
- break
- else:
- self.debug('kicking vobject %s (unsupported interface)', obj)
- objects.remove(obj)
- # if objects is empty, remove oid from registry
- if not objects:
- del regcontent[oid]
-
+ for iface in cls.__implements__:
+ interfaces.update(expand_parent_classes(iface))
+ interfaces.update(expand_parent_classes(cls))
+ for obj, ifaces in self._needs_iface.items():
+ ifaces = frozenset(isinstance(iface, basestring) and self.etype_class(iface) or iface
+ for iface in ifaces)
+ if not ifaces & interfaces:
+ self.debug('kicking vobject %s (unsupported interface)', obj)
+ self.unregister(obj)
+
+
+
def eid_rset(self, cursor, 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
@@ -129,6 +154,8 @@
default to a dump of the class registered for 'Any'
"""
etype = str(etype)
+ if etype == 'Any':
+ return self.select(self.registry_objects('etypes', 'Any'), 'Any')
eschema = self.schema.eschema(etype)
baseschemas = [eschema] + eschema.ancestors()
# browse ancestors from most specific to most generic and
@@ -136,12 +163,21 @@
for baseschema in baseschemas:
btype = str(baseschema)
try:
- return self.select(self.registry_objects('etypes', btype), etype)
+ cls = self.select(self.registry_objects('etypes', btype), etype)
+ break
except ObjectNotFound:
pass
- # no entity class for any of the ancestors, fallback to the default one
- return self.select(self.registry_objects('etypes', 'Any'), etype)
-
+ else:
+ # no entity class for any of the ancestors, fallback to the default
+ # one
+ cls = self.select(self.registry_objects('etypes', 'Any'), etype)
+ # add class itself to the list of implemented interfaces, as well as the
+ # Any entity class so we can select according to class using the
+ # `implements` selector
+ extend(cls, cls)
+ extend(cls, self.etype_class('Any'))
+ return cls
+
def render(self, registry, oid, req, **context):
"""select an object in a given registry and render it
@@ -157,12 +193,12 @@
selected = self.select(objclss, req, rset, **context)
return selected.dispatch(**context)
- def main_template(self, req, oid='main', **context):
+ def main_template(self, req, oid='main-template', **context):
"""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('templates', oid, req, **context)
+ res = self.render('views', oid, req, **context)
if isinstance(res, unicode):
return res.encode(req.encoding)
assert isinstance(res, str)
--- a/debian/control Fri Feb 27 09:59:53 2009 +0100
+++ b/debian/control Mon Mar 02 21:03:54 2009 +0100
@@ -9,7 +9,6 @@
Homepage: http://www.cubicweb.org
XS-Python-Version: >= 2.4, << 2.6
-
Package: cubicweb
Architecture: all
XB-Python-Version: ${python:Versions}
--- a/debian/cubicweb-web.postinst Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-#! /bin/sh -e
-
-ln -sf /usr/share/fckeditor/fckeditor.js /usr/share/cubicweb/cubes/shared/data
-
-#DEBHELPER#
-
-exit 0
--- a/devtools/htmlparser.py Fri Feb 27 09:59:53 2009 +0100
+++ b/devtools/htmlparser.py Mon Mar 02 21:03:54 2009 +0100
@@ -6,7 +6,7 @@
from lxml import etree
from lxml.builder import E
-from cubicweb.common.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE, CW_XHTML_EXTENSIONS
+from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE, CW_XHTML_EXTENSIONS
STRICT_DOCTYPE = str(STRICT_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE % CW_XHTML_EXTENSIONS).strip()
--- a/devtools/test/runtests.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
- import sys, os
- main(os.path.dirname(sys.argv[0]) or '.')
--- a/devtools/testlib.py Fri Feb 27 09:59:53 2009 +0100
+++ b/devtools/testlib.py Mon Mar 02 21:03:54 2009 +0100
@@ -158,7 +158,7 @@
self.commit()
@nocoverage
- def _check_html(self, output, view, template='main'):
+ def _check_html(self, output, view, template='main-template'):
"""raises an exception if the HTML is invalid"""
try:
validatorclass = self.vid_validators[view.id]
@@ -175,7 +175,7 @@
return validator.parse_string(output.strip())
- def view(self, vid, rset, req=None, template='main', **kwargs):
+ def view(self, vid, rset, req=None, template='main-template', **kwargs):
"""This method tests the view `vid` on `rset` using `template`
If no error occured while rendering the view, the HTML is analyzed
@@ -197,24 +197,16 @@
self.set_description("testing %s, mod=%s (%s)" % (vid, view.__module__, rset.printable_rql()))
else:
self.set_description("testing %s, mod=%s (no rset)" % (vid, view.__module__))
- viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs)
if template is None: # raw view testing, no template
viewfunc = view.dispatch
- elif template == 'main':
- _select_view_and_rset = TheMainTemplate._select_view_and_rset
- # patch TheMainTemplate.process_rql to avoid recomputing resultset
- def __select_view_and_rset(self, view=view, rset=rset):
- self.rset = rset
- return view, rset
- TheMainTemplate._select_view_and_rset = __select_view_and_rset
- try:
- return self._test_view(viewfunc, view, template, **kwargs)
- finally:
- if template == 'main':
- TheMainTemplate._select_view_and_rset = _select_view_and_rset
+ else:
+ templateview = self.vreg.select_view(template, req, rset, view=view, **kwargs)
+ kwargs['view'] = view
+ viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs)
+ return self._test_view(viewfunc, view, template, kwargs)
- def _test_view(self, viewfunc, view, template='main', **kwargs):
+ def _test_view(self, viewfunc, view, template='main-template', kwargs={}):
"""this method does the actual call to the view
If no error occured while rendering the view, the HTML is analyzed
@@ -332,7 +324,7 @@
backup_rset = rset._prepare_copy(rset.rows, rset.description)
yield InnerTest(self._testname(rset, view.id, 'view'),
self.view, view.id, rset,
- rset.req.reset_headers(), 'main')
+ rset.req.reset_headers(), 'main-template')
# We have to do this because some views modify the
# resultset's syntax tree
rset = backup_rset
--- a/entities/__init__.py Fri Feb 27 09:59:53 2009 +0100
+++ b/entities/__init__.py Mon Mar 02 21:03:54 2009 +0100
@@ -12,8 +12,8 @@
from logilab.common.decorators import cached
from cubicweb import Unauthorized, typed_eid
-from cubicweb.common.utils import dump_class
-from cubicweb.common.entity import Entity
+from cubicweb.entity import Entity
+from cubicweb.utils import dump_class
from cubicweb.schema import FormatConstraint
from cubicweb.interfaces import IBreadCrumbs, IFeed
@@ -96,20 +96,6 @@
if not hasattr(cls, 'default_%s' % formatattr):
setattr(cls, 'default_%s' % formatattr, cls._default_format)
eschema.format_fields[formatattr] = attr
-
- def _default_format(self):
- return self.req.property_value('ui.default-text-format')
-
- def use_fckeditor(self, attr):
- """return True if fckeditor should be used to edit entity's attribute named
- `attr`, according to user preferences
- """
- req = self.req
- if req.property_value('ui.fckeditor') and self.has_format(attr):
- if self.has_eid() or '%s_format' % attr in self:
- return self.format(attr) == 'text/html'
- return req.property_value('ui.default-text-format') == 'text/html'
- return False
# meta data api ###########################################################
@@ -236,12 +222,6 @@
return self.printable_value(rtype, format='text/plain').lower()
return value
- def after_deletion_path(self):
- """return (path, parameters) which should be used as redirect
- information when this entity is being deleted
- """
- return str(self.e_schema).lower(), {}
-
def add_related_schemas(self):
"""this is actually used ui method to generate 'addrelated' actions from
the schema.
@@ -343,6 +323,65 @@
continue
result.append( (rschema.display_name(self.req, target), rschema, target) )
return sorted(result)
+
+ def linked_to(self, rtype, target, remove=True):
+ """if entity should be linked to another using __linkto form param for
+ the given relation/target, return eids of related entities
+
+ This method is consuming matching link-to information from form params
+ if `remove` is True (by default).
+ """
+ try:
+ return self.__linkto[(rtype, target)]
+ except AttributeError:
+ self.__linkto = {}
+ except KeyError:
+ pass
+ linktos = list(self.req.list_form_param('__linkto'))
+ linkedto = []
+ for linkto in linktos[:]:
+ ltrtype, eid, lttarget = linkto.split(':')
+ if rtype == ltrtype and target == lttarget:
+ # delete __linkto from form param to avoid it being added as
+ # hidden input
+ if remove:
+ linktos.remove(linkto)
+ self.req.form['__linkto'] = linktos
+ linkedto.append(typed_eid(eid))
+ self.__linkto[(rtype, target)] = linkedto
+ return linkedto
+
+ # edit controller callbacks ###############################################
+
+ def after_deletion_path(self):
+ """return (path, parameters) which should be used as redirect
+ information when this entity is being deleted
+ """
+ return str(self.e_schema).lower(), {}
+
+ def pre_web_edit(self):
+ """callback called by the web editcontroller when an entity will be
+ created/modified, to let a chance to do some entity specific stuff.
+
+ Do nothing by default.
+ """
+ pass
+
+ # server side helpers #####################################################
+
+ 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 ()
+
+ # XXX deprecates, may be killed once old widgets system is gone ###########
+
+ def _default_format(self):
+ return self.req.property_value('ui.default-text-format')
def attribute_values(self, attrname):
if self.has_eid() or attrname in self:
@@ -372,51 +411,16 @@
values = (values,)
return values
- def linked_to(self, rtype, target, remove=True):
- """if entity should be linked to another using __linkto form param for
- the given relation/target, return eids of related entities
-
- This method is consuming matching link-to information from form params
- if `remove` is True (by default).
+ def use_fckeditor(self, attr):
+ """return True if fckeditor should be used to edit entity's attribute named
+ `attr`, according to user preferences
"""
- try:
- return self.__linkto[(rtype, target)]
- except AttributeError:
- self.__linkto = {}
- except KeyError:
- pass
- linktos = list(self.req.list_form_param('__linkto'))
- linkedto = []
- for linkto in linktos[:]:
- ltrtype, eid, lttarget = linkto.split(':')
- if rtype == ltrtype and target == lttarget:
- # delete __linkto from form param to avoid it being added as
- # hidden input
- if remove:
- linktos.remove(linkto)
- self.req.form['__linkto'] = linktos
- linkedto.append(typed_eid(eid))
- self.__linkto[(rtype, target)] = linkedto
- return linkedto
-
- def pre_web_edit(self):
- """callback called by the web editcontroller when an entity will be
- created/modified, to let a chance to do some entity specific stuff.
-
- Do nothing by default.
- """
- pass
-
- # server side helpers #####################################################
-
- 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 ()
+ req = self.req
+ if req.property_value('ui.fckeditor') and self.has_format(attr):
+ if self.has_eid() or '%s_format' % attr in self:
+ return self.format(attr) == 'text/html'
+ return 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
--- a/entities/lib.py Fri Feb 27 09:59:53 2009 +0100
+++ b/entities/lib.py Mon Mar 02 21:03:54 2009 +0100
@@ -11,7 +11,7 @@
from logilab.common.decorators import cached
-from cubicweb.common.entity import _marker
+from cubicweb.entity import _marker
from cubicweb.entities import AnyEntity, fetch_config
def mangle_email(address):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/entity.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,1054 @@
+"""Base class for entity objects manipulated in clients
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common import interface
+from logilab.common.compat import all
+from logilab.common.decorators import cached
+from logilab.mtconverter import TransformData, TransformError, html_escape
+
+from rql.utils import rqlvar_maker
+
+from cubicweb import Unauthorized
+from cubicweb.rset import ResultSet
+from cubicweb.selectors import yes
+from cubicweb.appobject import AppRsetObject
+from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
+
+try:
+ from cubicweb.common.uilib import printable_value, soup2xhtml
+ from cubicweb.common.mixins import MI_REL_TRIGGERS
+ from cubicweb.common.mttransforms import ENGINE
+except ImportError:
+ # missing -common
+ MI_REL_TRIGGERS = {}
+
+_marker = object()
+
+def greater_card(rschema, subjtypes, objtypes, index):
+ for subjtype in subjtypes:
+ for objtype in objtypes:
+ card = rschema.rproperty(subjtype, objtype, 'cardinality')[index]
+ if card in '+*':
+ return card
+ return '1'
+
+
+class RelationTags(object):
+
+ MODE_TAGS = frozenset(('link', 'create'))
+ CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated',
+ 'inlineview'))
+
+ def __init__(self, eclass, tagdefs):
+ # XXX if a rtag is redefined in a subclass,
+ # the rtag of the base class overwrite the rtag of the subclass
+ self.eclass = eclass
+ self._tagdefs = {}
+ for relation, tags in tagdefs.iteritems():
+ # tags must become a set
+ if isinstance(tags, basestring):
+ tags = set((tags,))
+ elif not isinstance(tags, set):
+ tags = set(tags)
+ # relation must become a 3-uple (rtype, targettype, role)
+ if isinstance(relation, basestring):
+ self._tagdefs[(relation, '*', 'subject')] = tags
+ self._tagdefs[(relation, '*', 'object')] = tags
+ elif len(relation) == 1: # useful ?
+ self._tagdefs[(relation[0], '*', 'subject')] = tags
+ self._tagdefs[(relation[0], '*', 'object')] = tags
+ elif len(relation) == 2:
+ rtype, ttype = relation
+ ttype = bw_normalize_etype(ttype) # XXX bw compat
+ self._tagdefs[rtype, ttype, 'subject'] = tags
+ self._tagdefs[rtype, ttype, 'object'] = tags
+ elif len(relation) == 3:
+ relation = list(relation) # XXX bw compat
+ relation[1] = bw_normalize_etype(relation[1])
+ self._tagdefs[tuple(relation)] = tags
+ else:
+ raise ValueError('bad rtag definition (%r)' % (relation,))
+
+
+ def __initialize__(self):
+ # eclass.[*]schema are only set when registering
+ self.schema = self.eclass.schema
+ eschema = self.eschema = self.eclass.e_schema
+ rtags = self._tagdefs
+ # expand wildcards in rtags and add automatic tags
+ for rschema, tschemas, role in sorted(eschema.relation_definitions(True)):
+ rtype = rschema.type
+ star_tags = rtags.pop((rtype, '*', role), set())
+ for tschema in tschemas:
+ tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags))
+ if role == 'subject':
+ X, Y = eschema, tschema
+ card = rschema.rproperty(X, Y, 'cardinality')[0]
+ composed = rschema.rproperty(X, Y, 'composite') == 'object'
+ else:
+ X, Y = tschema, eschema
+ card = rschema.rproperty(X, Y, 'cardinality')[1]
+ composed = rschema.rproperty(X, Y, 'composite') == 'subject'
+ # set default category tags if needed
+ if not tags & self.CATEGORY_TAGS:
+ if card in '1+':
+ if not rschema.is_final() and composed:
+ category = 'generated'
+ elif rschema.is_final() and (
+ rschema.type.endswith('_format')
+ or rschema.type.endswith('_encoding')):
+ category = 'generated'
+ else:
+ category = 'primary'
+ elif rschema.is_final():
+ if (rschema.type.endswith('_format')
+ or rschema.type.endswith('_encoding')):
+ category = 'generated'
+ else:
+ category = 'secondary'
+ else:
+ category = 'generic'
+ tags.add(category)
+ if not tags & self.MODE_TAGS:
+ if card in '?1':
+ # by default, suppose link mode if cardinality doesn't allow
+ # more than one relation
+ mode = 'link'
+ elif rschema.rproperty(X, Y, 'composite') == role:
+ # if self is composed of the target type, create mode
+ mode = 'create'
+ else:
+ # link mode by default
+ mode = 'link'
+ tags.add(mode)
+
+ def _default_target(self, rschema, role='subject'):
+ eschema = self.eschema
+ if role == 'subject':
+ return eschema.subject_relation(rschema).objects(eschema)[0]
+ else:
+ return eschema.object_relation(rschema).subjects(eschema)[0]
+
+ # dict compat
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ key = (key,)
+ return self.get_tags(*key)
+
+ __contains__ = __getitem__
+
+ def get_tags(self, rtype, targettype=None, role='subject'):
+ rschema = self.schema.rschema(rtype)
+ if targettype is None:
+ tschema = self._default_target(rschema, role)
+ else:
+ tschema = self.schema.eschema(targettype)
+ return self._tagdefs[(rtype, tschema.type, role)]
+
+ __call__ = get_tags
+
+ def get_mode(self, rtype, targettype=None, role='subject'):
+ # XXX: should we make an assertion on rtype not being final ?
+ # assert not rschema.is_final()
+ tags = self.get_tags(rtype, targettype, role)
+ # do not change the intersection order !
+ modes = tags & self.MODE_TAGS
+ assert len(modes) == 1
+ return modes.pop()
+
+ def get_category(self, rtype, targettype=None, role='subject'):
+ tags = self.get_tags(rtype, targettype, role)
+ categories = tags & self.CATEGORY_TAGS
+ assert len(categories) == 1
+ return categories.pop()
+
+ def is_inlined(self, rtype, targettype=None, role='subject'):
+ # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype)
+ return 'inlineview' in self.get_tags(rtype, targettype, role)
+
+
+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
+ tagdefs = {}
+ widgets = {}
+ for base in bases:
+ tagdefs.update(getattr(base, '__rtags__', {}))
+ widgets.update(getattr(base, 'widgets', {}))
+ # update with the class' own rtgas
+ tagdefs.update(classdict.get('__rtags__', {}))
+ widgets.update(classdict.get('widgets', {}))
+ # XXX decide whether or not it's a good idea to replace __rtags__
+ # good point: transparent support for inheritance levels >= 2
+ # bad point: we loose the information of which tags are specific
+ # to this entity class
+ classdict['__rtags__'] = tagdefs
+ classdict['widgets'] = widgets
+ eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict)
+ # adds the "rtags" attribute
+ eclass.rtags = RelationTags(eclass, tagdefs)
+ return eclass
+
+
+class Entity(AppRsetObject, dict):
+ """an entity instance has e_schema automagically set on
+ the class and instances has access to their issuing cursor.
+
+ A property is set for each attribute and relation on each entity's type
+ class. Becare that among attributes, 'eid' is *NEITHER* stored in the
+ dict containment (which acts as a cache for other attributes dynamically
+ fetched)
+
+ :type e_schema: `cubicweb.schema.EntitySchema`
+ :ivar e_schema: the entity's schema
+
+ :type rest_var: str
+ :cvar rest_var: indicates which attribute should be used to build REST urls
+ If None is specified, the first non-meta attribute will
+ be used
+
+ :type skip_copy_for: list
+ :cvar skip_copy_for: a list of relations that should be skipped when copying
+ this kind of entity. Note that some relations such
+ as composite relations or relations that have '?1' as object
+ cardinality
+ """
+ __metaclass__ = metaentity
+ __registry__ = 'etypes'
+ __select__ = yes()
+ widgets = {}
+ id = None
+ e_schema = None
+ eid = None
+ rest_attr = None
+ skip_copy_for = ()
+
+ @classmethod
+ def registered(cls, registry):
+ """build class using descriptor at registration time"""
+ assert cls.id is not None
+ super(Entity, cls).registered(registry)
+ if cls.id != 'Any':
+ cls.__initialize__()
+ return cls
+
+ MODE_TAGS = set(('link', 'create'))
+ CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata'))
+ @classmethod
+ def __initialize__(cls):
+ """initialize a specific entity class by adding descriptors to access
+ entity type's attributes and relations
+ """
+ etype = cls.id
+ assert etype != 'Any', etype
+ cls.e_schema = eschema = cls.schema.eschema(etype)
+ for rschema, _ in eschema.attribute_definitions():
+ if rschema.type == 'eid':
+ continue
+ setattr(cls, rschema.type, Attribute(rschema.type))
+ mixins = []
+ for rschema, _, x in eschema.relation_definitions():
+ if (rschema, x) in MI_REL_TRIGGERS:
+ mixin = MI_REL_TRIGGERS[(rschema, x)]
+ if not (issubclass(cls, mixin) or mixin in mixins): # already mixed ?
+ mixins.append(mixin)
+ for iface in getattr(mixin, '__implements__', ()):
+ if not interface.implements(cls, iface):
+ interface.extend(cls, iface)
+ if x == 'subject':
+ setattr(cls, rschema.type, SubjectRelation(rschema))
+ else:
+ attr = 'reverse_%s' % rschema.type
+ setattr(cls, attr, ObjectRelation(rschema))
+ if mixins:
+ cls.__bases__ = tuple(mixins + [p for p in cls.__bases__ if not p is object])
+ cls.debug('plugged %s mixins on %s', mixins, etype)
+ cls.rtags.__initialize__()
+
+ @classmethod
+ def fetch_rql(cls, user, restriction=None, fetchattrs=None, mainvar='X',
+ settype=True, ordermethod='fetch_order'):
+ """return a rql to fetch all entities of the class type"""
+ restrictions = restriction or []
+ if settype:
+ restrictions.append('%s is %s' % (mainvar, cls.id))
+ if fetchattrs is None:
+ fetchattrs = cls.fetch_attrs
+ selection = [mainvar]
+ orderby = []
+ # start from 26 to avoid possible conflicts with X
+ varmaker = rqlvar_maker(index=26)
+ cls._fetch_restrictions(mainvar, varmaker, fetchattrs, selection,
+ orderby, restrictions, user, ordermethod)
+ rql = 'Any %s' % ','.join(selection)
+ if orderby:
+ rql += ' ORDERBY %s' % ','.join(orderby)
+ rql += ' WHERE %s' % ', '.join(restrictions)
+ return rql
+
+ @classmethod
+ def _fetch_restrictions(cls, mainvar, varmaker, fetchattrs,
+ selection, orderby, restrictions, user,
+ ordermethod='fetch_order', visited=None):
+ eschema = cls.e_schema
+ if visited is None:
+ visited = set((eschema.type,))
+ elif eschema.type in visited:
+ # avoid infinite recursion
+ return
+ else:
+ visited.add(eschema.type)
+ _fetchattrs = []
+ for attr in fetchattrs:
+ try:
+ rschema = eschema.subject_relation(attr)
+ except KeyError:
+ cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
+ attr, cls.id)
+ continue
+ if not user.matching_groups(rschema.get_groups('read')):
+ continue
+ var = varmaker.next()
+ selection.append(var)
+ restriction = '%s %s %s' % (mainvar, attr, var)
+ restrictions.append(restriction)
+ if not rschema.is_final():
+ # XXX this does not handle several destination types
+ desttype = rschema.objects(eschema.type)[0]
+ card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
+ if card not in '?1':
+ selection.pop()
+ restrictions.pop()
+ continue
+ if card == '?':
+ restrictions[-1] += '?' # left outer join if not mandatory
+ destcls = cls.vreg.etype_class(desttype)
+ destcls._fetch_restrictions(var, varmaker, destcls.fetch_attrs,
+ selection, orderby, restrictions,
+ user, ordermethod, visited=visited)
+ orderterm = getattr(cls, ordermethod)(attr, var)
+ if orderterm:
+ orderby.append(orderterm)
+ return selection, orderby, restrictions
+
+ def __init__(self, req, rset, row=None, col=0):
+ AppRsetObject.__init__(self, req, rset)
+ dict.__init__(self)
+ self.row, self.col = row, col
+ self._related_cache = {}
+ if rset is not None:
+ self.eid = rset[row][col]
+ else:
+ self.eid = None
+ self._is_saved = True
+
+ def __repr__(self):
+ return '<Entity %s %s %s at %s>' % (
+ self.e_schema, self.eid, self.keys(), id(self))
+
+ def __nonzero__(self):
+ return True
+
+ def __hash__(self):
+ return id(self)
+
+ def pre_add_hook(self):
+ """hook called by the repository before doing anything to add the entity
+ (before_add entity hooks have not been called yet). This give the
+ occasion to do weird stuff such as autocast (File -> Image for instance).
+
+ This method must return the actual entity to be added.
+ """
+ return self
+
+ def set_eid(self, eid):
+ self.eid = self['eid'] = eid
+
+ def has_eid(self):
+ """return True if the entity has an attributed eid (False
+ meaning that the entity has to be created
+ """
+ try:
+ int(self.eid)
+ return True
+ except (ValueError, TypeError):
+ return False
+
+ def is_saved(self):
+ """during entity creation, there is some time during which the entity
+ has an eid attributed though it's not saved (eg during before_add_entity
+ hooks). You can use this method to ensure the entity has an eid *and* is
+ saved in its source.
+ """
+ return self.has_eid() and self._is_saved
+
+ @cached
+ def metainformation(self):
+ res = dict(zip(('type', 'source', 'extid'), self.req.describe(self.eid)))
+ res['source'] = self.req.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)
+
+ def check_perm(self, action):
+ self.e_schema.check_perm(self.req, action, self.eid)
+
+ def has_perm(self, action):
+ return self.e_schema.has_perm(self.req, action, self.eid)
+
+ def view(self, vid, __registry='views', **kwargs):
+ """shortcut to apply a view on this entity"""
+ return self.vreg.render(__registry, vid, self.req, rset=self.rset,
+ row=self.row, col=self.col, **kwargs)
+
+ def absolute_url(self, method=None, **kwargs):
+ """return an absolute url to view this entity"""
+ # 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':
+ kwargs['base_url'] = self.metainformation()['source'].get('base-url')
+ if method is None or method == 'view':
+ kwargs['_restpath'] = self.rest_path()
+ else:
+ kwargs['rql'] = 'Any X WHERE X eid %s' % self.eid
+ return self.build_url(method, **kwargs)
+
+ def rest_path(self):
+ """returns a REST-like (relative) path for this entity"""
+ mainattr, needcheck = self._rest_attr_info()
+ etype = str(self.e_schema)
+ if mainattr == 'eid':
+ value = self.eid
+ else:
+ value = getattr(self, mainattr)
+ if value is None:
+ return '%s/eid/%s' % (etype.lower(), self.eid)
+ if needcheck:
+ # make sure url is not ambiguous
+ rql = 'Any COUNT(X) WHERE X is %s, X %s %%(value)s' % (etype, mainattr)
+ if value is not None:
+ nbresults = self.req.execute(rql, {'value' : value})[0][0]
+ # may an assertion that nbresults is not 0 would be a good idea
+ if nbresults != 1: # no ambiguity
+ return '%s/eid/%s' % (etype.lower(), self.eid)
+ return '%s/%s' % (etype.lower(), self.req.url_quote(value))
+
+ @classmethod
+ def _rest_attr_info(cls):
+ mainattr, needcheck = 'eid', True
+ if cls.rest_attr:
+ mainattr = cls.rest_attr
+ needcheck = not cls.e_schema.has_unique_values(mainattr)
+ else:
+ for rschema in cls.e_schema.subject_relations():
+ if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
+ mainattr = str(rschema)
+ needcheck = False
+ break
+ if mainattr == 'eid':
+ needcheck = False
+ return mainattr, needcheck
+
+ @cached
+ def formatted_attrs(self):
+ """returns the list of attributes which have some format information
+ (i.e. rich text strings)
+ """
+ attrs = []
+ for rschema, attrschema in self.e_schema.attribute_definitions():
+ if attrschema.type == 'String' and self.has_format(rschema):
+ attrs.append(rschema.type)
+ return attrs
+
+ def format(self, attr):
+ """return the mime type format for an attribute (if specified)"""
+ return getattr(self, '%s_format' % attr, None)
+
+ def text_encoding(self, attr):
+ """return the text encoding for an attribute, default to site encoding
+ """
+ encoding = getattr(self, '%s_encoding' % attr, None)
+ return encoding or self.vreg.property_value('ui.encoding')
+
+ def has_format(self, attr):
+ """return true if this entity's schema has a format field for the given
+ attribute
+ """
+ return self.e_schema.has_subject_relation('%s_format' % attr)
+
+ def has_text_encoding(self, attr):
+ """return true if this entity's schema has ab encoding field for the
+ given attribute
+ """
+ return self.e_schema.has_subject_relation('%s_encoding' % attr)
+
+ def printable_value(self, attr, value=_marker, attrtype=None,
+ format='text/html', displaytime=True):
+ """return a displayable value (i.e. unicode string) which may contains
+ html tags
+ """
+ attr = str(attr)
+ if value is _marker:
+ value = getattr(self, attr)
+ if isinstance(value, basestring):
+ value = value.strip()
+ if value is None or value == '': # don't use "not", 0 is an acceptable value
+ return u''
+ if attrtype is None:
+ attrtype = self.e_schema.destination(attr)
+ props = self.e_schema.rproperties(attr)
+ if attrtype == 'String':
+ # internalinalized *and* formatted string such as schema
+ # description...
+ if props.get('internationalizable'):
+ value = self.req._(value)
+ attrformat = self.format(attr)
+ if attrformat:
+ return self.mtc_transform(value, attrformat, format,
+ self.req.encoding)
+ elif attrtype == 'Bytes':
+ attrformat = self.format(attr)
+ if attrformat:
+ try:
+ encoding = getattr(self, '%s_encoding' % attr)
+ except AttributeError:
+ encoding = self.req.encoding
+ return self.mtc_transform(value.getvalue(), attrformat, format,
+ encoding)
+ return u''
+ value = printable_value(self.req, attrtype, value, props, displaytime)
+ if format == 'text/html':
+ value = html_escape(value)
+ return value
+
+ def mtc_transform(self, data, format, target_format, encoding,
+ _engine=ENGINE):
+ trdata = TransformData(data, format, encoding, appobject=self)
+ data = _engine.convert(trdata, target_format).decode()
+ if format == 'text/html':
+ data = soup2xhtml(data, self.req.encoding)
+ return data
+
+ # entity cloning ##########################################################
+
+ def copy_relations(self, ceid):
+ """copy relations of the object with the given eid on this object
+
+ By default meta and composite relations are skipped.
+ Overrides this if you want another behaviour
+ """
+ assert self.has_eid()
+ execute = self.req.execute
+ for rschema in self.e_schema.subject_relations():
+ if rschema.meta or rschema.is_final():
+ continue
+ # skip already defined relations
+ if getattr(self, rschema.type):
+ continue
+ if rschema.type in self.skip_copy_for:
+ continue
+ if rschema.type == 'in_state':
+ # if the workflow is defining an initial state (XXX AND we are
+ # not in the managers group? not done to be more consistent)
+ # don't try to copy in_state
+ if execute('Any S WHERE S state_of ET, ET initial_state S,'
+ 'ET name %(etype)s', {'etype': str(self.e_schema)}):
+ continue
+ # skip composite relation
+ if self.e_schema.subjrproperty(rschema, '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':
+ continue
+ rql = 'SET X %s V WHERE X eid %%(x)s, Y eid %%(y)s, Y %s V' % (
+ rschema.type, rschema.type)
+ execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+ self.clear_related_cache(rschema.type, 'subject')
+ for rschema in self.e_schema.object_relations():
+ if rschema.meta:
+ continue
+ # skip already defined relations
+ if getattr(self, 'reverse_%s' % rschema.type):
+ continue
+ # skip composite relation
+ if self.e_schema.objrproperty(rschema, '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':
+ continue
+ rql = 'SET V %s X WHERE X eid %%(x)s, Y eid %%(y)s, V %s Y' % (
+ rschema.type, rschema.type)
+ execute(rql, {'x': self.eid, 'y': ceid}, ('x', 'y'))
+ self.clear_related_cache(rschema.type, 'object')
+
+ # data fetching methods ###################################################
+
+ @cached
+ 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)
+
+ def to_complete_relations(self):
+ """by default complete final relations to when calling .complete()"""
+ for rschema in self.e_schema.subject_relations():
+ if rschema.is_final():
+ continue
+ if len(rschema.objects(self.e_schema)) > 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)):
+ yield rschema, 'subject'
+
+ def to_complete_attributes(self, skip_bytes=True):
+ for rschema, attrschema in self.e_schema.attribute_definitions():
+ # skip binary data by default
+ if skip_bytes and attrschema.type == 'Bytes':
+ continue
+ attr = rschema.type
+ if attr == 'eid':
+ continue
+ # password retreival is blocked at the repository server level
+ if not self.req.user.matching_groups(rschema.get_groups('read')) \
+ or attrschema.type == 'Password':
+ self[attr] = None
+ continue
+ yield attr
+
+ def complete(self, attributes=None, skip_bytes=True):
+ """complete this entity by adding missing attributes (i.e. query the
+ repository to fill the entity)
+
+ :type skip_bytes: bool
+ :param skip_bytes:
+ if true, attribute of type Bytes won't be considered
+ """
+ assert self.has_eid()
+ varmaker = rqlvar_maker()
+ V = varmaker.next()
+ rql = ['WHERE %s eid %%(x)s' % V]
+ selected = []
+ for attr in (attributes or self.to_complete_attributes(skip_bytes)):
+ # if attribute already in entity, nothing to do
+ if self.has_key(attr):
+ continue
+ # case where attribute must be completed, but is not yet in entity
+ var = varmaker.next()
+ rql.append('%s %s %s' % (V, attr, var))
+ selected.append((attr, var))
+ # +1 since this doen't include the main variable
+ lastattr = len(selected) + 1
+ if attributes is None:
+ # fetch additional relations (restricted to 0..1 relations)
+ for rschema, role in self.to_complete_relations():
+ rtype = rschema.type
+ if self.relation_cached(rtype, role):
+ continue
+ var = varmaker.next()
+ 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: # '?"
+ 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: # '?"
+ 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)
+ rset = execute(rql, {'x': self.eid}, 'x', build_descr=False)[0]
+ # handle attributes
+ for i in xrange(1, lastattr):
+ self[str(selected[i-1][0])] = rset[i]
+ # handle relations
+ for i in xrange(lastattr, len(rset)):
+ rtype, x = selected[i-1][0]
+ value = rset[i]
+ if value is None:
+ rrset = ResultSet([], rql, {'x': self.eid})
+ self.req.decorate_rset(rrset)
+ else:
+ rrset = self.req.eid_rset(value)
+ self.set_related_cache(rtype, x, rrset)
+
+ def get_value(self, name):
+ """get value for the attribute relation <name>, query the repository
+ to get the value if necessary.
+
+ :type name: str
+ :param name: name of the attribute to get
+ """
+ try:
+ value = self[name]
+ except KeyError:
+ if not self.is_saved():
+ return None
+ rql = "Any A WHERE X eid %%(x)s, X %s A" % name
+ # XXX should we really use unsafe_execute here??
+ execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+ try:
+ rset = execute(rql, {'x': self.eid}, 'x')
+ except Unauthorized:
+ self[name] = value = None
+ else:
+ assert rset.rowcount <= 1, (self, rql, rset.rowcount)
+ try:
+ self[name] = value = rset.rows[0][0]
+ except IndexError:
+ # probably a multisource error
+ self.critical("can't get value for attribute %s of entity with eid %s",
+ name, self.eid)
+ if self.e_schema.destination(name) == 'String':
+ self[name] = value = self.req._('unaccessible')
+ else:
+ self[name] = value = None
+ return value
+
+ def related(self, rtype, role='subject', limit=None, entities=False):
+ """returns a resultset of related entities
+
+ :param role: is the role played by 'self' in the relation ('subject' or 'object')
+ :param limit: resultset's maximum size
+ :param entities: if True, the entites are returned; if False, a result set is returned
+ """
+ try:
+ return self.related_cache(rtype, role, entities, limit)
+ except KeyError:
+ pass
+ assert self.has_eid()
+ rql = self.related_rql(rtype, role)
+ rset = self.req.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'):
+ rschema = self.schema[rtype]
+ if role == 'subject':
+ targettypes = rschema.objects(self.e_schema)
+ restriction = 'E eid %%(x)s, E %s X' % rtype
+ card = greater_card(rschema, (self.e_schema,), targettypes, 0)
+ else:
+ targettypes = rschema.subjects(self.e_schema)
+ restriction = 'E eid %%(x)s, X %s E' % rtype
+ card = greater_card(rschema, targettypes, (self.e_schema,), 1)
+ if len(targettypes) > 1:
+ fetchattrs_list = []
+ for ttype in targettypes:
+ etypecls = self.vreg.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,
+ settype=False)
+ else:
+ etypecls = self.vreg.etype_class(targettypes[0])
+ rql = etypecls.fetch_rql(self.req.user, [restriction], settype=False)
+ # optimisation: remove ORDERBY if cardinality is 1 or ? (though
+ # greater_card return 1 for those both cases)
+ if card == '1':
+ if ' ORDERBY ' in rql:
+ rql = '%s WHERE %s' % (rql.split(' ORDERBY ', 1)[0],
+ rql.split(' WHERE ', 1)[1])
+ elif not ' ORDERBY ' in rql:
+ args = tuple(rql.split(' WHERE ', 1))
+ rql = '%s ORDERBY Z DESC WHERE X modification_date Z, %s' % args
+ return rql
+
+ # generic vocabulary methods ##############################################
+
+ 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
+ """
+ try:
+ vocabfunc = getattr(self, '%s_%s_vocabulary' % (role, rtype))
+ except AttributeError:
+ vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+ # 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)
+
+ def unrelated_rql(self, rtype, targettype, role, ordermethod=None,
+ vocabconstraints=True):
+ """build a rql to fetch `targettype` entities unrelated to this entity
+ using (rtype, role) relation
+ """
+ ordermethod = ordermethod or 'fetch_unrelated_order'
+ if isinstance(rtype, basestring):
+ rtype = self.schema.rschema(rtype)
+ if role == 'subject':
+ evar, searchedvar = 'S', 'O'
+ subjtype, objtype = self.e_schema, targettype
+ else:
+ searchedvar, evar = 'S', 'O'
+ objtype, subjtype = self.e_schema, targettype
+ if self.has_eid():
+ restriction = ['NOT S %s O' % rtype, '%s eid %%(x)s' % evar]
+ else:
+ restriction = []
+ constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+ if vocabconstraints:
+ # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
+ # will be included as well
+ restriction += [cstr.restriction for cstr in constraints
+ if isinstance(cstr, RQLVocabularyConstraint)]
+ else:
+ restriction += [cstr.restriction for cstr in constraints
+ if isinstance(cstr, RQLConstraint)]
+ etypecls = self.vreg.etype_class(targettype)
+ rql = etypecls.fetch_rql(self.req.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)
+ return rql
+
+ def unrelated(self, rtype, targettype, role='subject', limit=None,
+ ordermethod=None):
+ """return a result set of target type objects that may be related
+ by a given relation, with self as subject or object
+ """
+ rql = self.unrelated_rql(rtype, targettype, role, ordermethod)
+ if limit is not None:
+ before, after = rql.split(' WHERE ', 1)
+ rql = '%s LIMIT %s WHERE %s' % (before, limit, after)
+ if self.has_eid():
+ return self.req.execute(rql, {'x': self.eid})
+ return self.req.execute(rql)
+
+ # relations cache handling ################################################
+
+ def relation_cached(self, rtype, role):
+ """return true if the given relation is already cached on the instance
+ """
+ return '%s_%s' % (rtype, role) in self._related_cache
+
+ def related_cache(self, rtype, role, entities=True, limit=None):
+ """return values for the given relation if it's cached on the instance,
+ else raise `KeyError`
+ """
+ res = self._related_cache['%s_%s' % (rtype, role)][entities]
+ if limit:
+ if entities:
+ res = res[:limit]
+ else:
+ res = res.limit(limit)
+ return res
+
+ def set_related_cache(self, rtype, role, rset, col=0):
+ """set cached values for the given relation"""
+ if rset:
+ related = list(rset.entities(col))
+ rschema = self.schema.rschema(rtype)
+ if role == 'subject':
+ rcard = rschema.rproperty(self.e_schema, related[0].e_schema,
+ 'cardinality')[1]
+ target = 'object'
+ else:
+ rcard = rschema.rproperty(related[0].e_schema, self.e_schema,
+ 'cardinality')[0]
+ target = 'subject'
+ if rcard in '?1':
+ for rentity in related:
+ rentity._related_cache['%s_%s' % (rtype, target)] = (self.as_rset(), [self])
+ else:
+ related = []
+ self._related_cache['%s_%s' % (rtype, role)] = (rset, related)
+
+ def clear_related_cache(self, rtype=None, role=None):
+ """clear cached values for the given relation or the entire cache if
+ no relation is given
+ """
+ if rtype is None:
+ self._related_cache = {}
+ else:
+ assert role
+ self._related_cache.pop('%s_%s' % (rtype, role), None)
+
+ # raw edition utilities ###################################################
+
+ def set_attributes(self, **kwargs):
+ assert kwargs
+ relations = []
+ for key in kwargs:
+ relations.append('X %s %%(%s)s' % (key, key))
+ # update current local object
+ self.update(kwargs)
+ # and now update the database
+ kwargs['x'] = self.eid
+ self.req.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
+ kwargs, 'x')
+
+ def delete(self):
+ assert self.has_eid(), self.eid
+ self.req.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
+ {'x': self.eid})
+
+ # server side utilities ###################################################
+
+ def set_defaults(self):
+ """set default values according to the schema"""
+ self._default_set = set()
+ for attr, value in self.e_schema.defaults():
+ if not self.has_key(attr):
+ self[str(attr)] = value
+ self._default_set.add(attr)
+
+ def check(self, creation=False):
+ """check this entity against its schema. Only final relation
+ are checked here, constraint on actual relations are checked in hooks
+ """
+ # necessary since eid is handled specifically and yams require it to be
+ # in the dictionary
+ if self.req is None:
+ _ = unicode
+ else:
+ _ = self.req._
+ self.e_schema.check(self, creation=creation, _=_)
+
+ def fti_containers(self, _done=None):
+ if _done is None:
+ _done = set()
+ _done.add(self.eid)
+ containers = tuple(self.e_schema.fulltext_containers())
+ if containers:
+ yielded = False
+ for rschema, target in containers:
+ if target == 'object':
+ targets = getattr(self, rschema.type)
+ else:
+ targets = getattr(self, 'reverse_%s' % rschema)
+ for entity in targets:
+ if entity.eid in _done:
+ continue
+ for container in entity.fti_containers(_done):
+ yield container
+ yielded = True
+ if not yielded:
+ yield self
+ else:
+ yield self
+
+ def get_words(self):
+ """used by the full text indexer to get words to index
+
+ this method should only be used on the repository side since it depends
+ on the indexer package
+
+ :rtype: list
+ :return: the list of indexable word of this entity
+ """
+ from indexer.query_objects import tokenize
+ words = []
+ for rschema in self.e_schema.indexable_attributes():
+ try:
+ value = self.printable_value(rschema, format='text/plain')
+ except TransformError, ex:
+ continue
+ except:
+ self.exception("can't add value of %s to text index for entity %s",
+ rschema, self.eid)
+ continue
+ if value:
+ words += tokenize(value)
+
+ for rschema, role in self.e_schema.fulltext_relations():
+ if role == 'subject':
+ for entity in getattr(self, rschema.type):
+ words += entity.get_words()
+ else: # if role == 'object':
+ for entity in getattr(self, 'reverse_%s' % rschema.type):
+ words += entity.get_words()
+ return words
+
+
+# attribute and relation descriptors ##########################################
+
+class Attribute(object):
+ """descriptor that controls schema attribute access"""
+
+ def __init__(self, attrname):
+ assert attrname != 'eid'
+ self._attrname = attrname
+
+ def __get__(self, eobj, eclass):
+ if eobj is None:
+ return self
+ return eobj.get_value(self._attrname)
+
+ def __set__(self, eobj, value):
+ # XXX bw compat
+ # would be better to generate UPDATE queries than the current behaviour
+ eobj.warning("deprecated usage, don't use 'entity.attr = val' notation)")
+ eobj[self._attrname] = value
+
+
+class Relation(object):
+ """descriptor that controls schema relation access"""
+ _role = None # for pylint
+
+ def __init__(self, rschema):
+ self._rschema = rschema
+ self._rtype = rschema.type
+
+ def __get__(self, eobj, eclass):
+ if eobj is None:
+ raise AttributeError('%s cannot be only be accessed from instances'
+ % self._rtype)
+ return eobj.related(self._rtype, self._role, entities=True)
+
+ def __set__(self, eobj, value):
+ raise NotImplementedError
+
+
+class SubjectRelation(Relation):
+ """descriptor that controls schema relation access"""
+ _role = 'subject'
+
+class ObjectRelation(Relation):
+ """descriptor that controls schema relation access"""
+ _role = 'object'
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(Entity, getLogger('cubicweb.entity'))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict). The output contains a
+minimum of formatting information. A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_escape
+
+from docutils import nodes
+from docutils.writers.html4css1 import Writer as CSS1Writer
+from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
+import os
+
+default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
+
+class Writer(CSS1Writer):
+ """css writer using our html translator"""
+ def __init__(self, base_url):
+ CSS1Writer.__init__(self)
+ self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+ def apply_template(self):
+ """overriding this is necessary with docutils >= 0.5"""
+ return self.visitor.astext()
+
+class URLBinder:
+ def __init__(self, url, klass):
+ self.base_url = url
+ self.translator_class = HTMLTranslator
+
+ def __call__(self, document):
+ translator = self.translator_class(document)
+ translator.base_url = self.base_url
+ return translator
+
+class HTMLTranslator(CSS1HTMLTranslator):
+ """ReST tree to html translator"""
+
+ def astext(self):
+ """return the extracted html"""
+ return ''.join(self.body)
+
+ def visit_title(self, node):
+ """Only 6 section levels are supported by HTML."""
+ if isinstance(node.parent, nodes.topic):
+ self.body.append(
+ self.starttag(node, 'p', '', CLASS='topic-title'))
+ if node.parent.hasattr('id'):
+ self.body.append(
+ self.starttag({}, 'a', '', name=node.parent['id']))
+ self.context.append('</a></p>\n')
+ else:
+ self.context.append('</p>\n')
+ elif self.section_level == 0:
+ # document title
+ self.head.append('<title>%s</title>\n'
+ % self.encode(node.astext()))
+ self.body.append(self.starttag(node, 'h%d' % default_level, '',
+ CLASS='title'))
+ self.context.append('</h%d>\n' % default_level)
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (
+ default_level+self.section_level-1), ''))
+ atts = {}
+ if node.hasattr('refid'):
+ atts['class'] = 'toc-backref'
+ atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+ self.body.append(self.starttag({}, 'a', '', **atts))
+ self.context.append('</a></h%s>\n' % (
+ default_level+self.section_level-1))
+
+ def visit_subtitle(self, node):
+ """format a subtitle"""
+ if isinstance(node.parent, nodes.sidebar):
+ self.body.append(self.starttag(node, 'p', '',
+ CLASS='sidebar-subtitle'))
+ self.context.append('</p>\n')
+ else:
+ self.body.append(
+ self.starttag(node, 'h%s' % (default_level+1), '',
+ CLASS='subtitle'))
+ self.context.append('</h%s>\n' % (default_level+1))
+
+ def visit_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+ def depart_document(self, node):
+ """syt: i don't want the enclosing <div class="document">"""
+
+ def visit_reference(self, node):
+ """syt: i want absolute urls"""
+ if node.has_key('refuri'):
+ href = node['refuri']
+ if ( self.settings.cloak_email_addresses
+ and href.startswith('mailto:')):
+ href = self.cloak_mailto(href)
+ self.in_mailto = 1
+ else:
+ assert node.has_key('refid'), \
+ 'References must have "refuri" or "refid" attribute.'
+ href = '%s#%s' % (self.base_url, node['refid'])
+ atts = {'href': href, 'class': 'reference'}
+ if not isinstance(node.parent, nodes.TextElement):
+ assert len(node) == 1 and isinstance(node[0], nodes.image)
+ atts['class'] += ' image-reference'
+ self.body.append(self.starttag(node, 'a', '', **atts))
+
+ ## override error messages to avoid XHTML problems ########################
+ def visit_problematic(self, node):
+ pass
+
+ def depart_problematic(self, node):
+ pass
+
+ def visit_system_message(self, node):
+ backref_text = ''
+ if len(node['backrefs']):
+ backrefs = node['backrefs']
+ if len(backrefs) == 1:
+ backref_text = '; <em>backlink</em>'
+ else:
+ i = 1
+ backlinks = []
+ for backref in backrefs:
+ backlinks.append(str(i))
+ i += 1
+ backref_text = ('; <em>backlinks: %s</em>'
+ % ', '.join(backlinks))
+ if node.hasattr('line'):
+ line = ', line %s' % node['line']
+ else:
+ line = ''
+ a_start = a_end = ''
+ error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+ a_start, node['type'], node['level'], a_end,
+ self.encode(node['source']), line, backref_text)
+ self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+ def depart_system_message(self, node):
+ pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/rest.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,223 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.ext.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+ if isinstance(v, str):
+ states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ try:
+ try:
+ eid_num, rest = text.split(u':', 1)
+ except:
+ eid_num, rest = text, '#'+text
+ eid_num = int(eid_num)
+ if eid_num < 0:
+ raise ValueError
+ except ValueError:
+ msg = inliner.reporter.error(
+ 'EID number must be a positive number; "%s" is invalid.'
+ % text, line=lineno)
+ prb = inliner.problematic(rawtext, rawtext, msg)
+ return [prb], [msg]
+ # Base URL mainly used by inliner.pep_reference; so this is correct:
+ context = inliner.document.settings.context
+ refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+ ref = refedentity.absolute_url()
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+ options={}, content=[]):
+ text = text.strip()
+ try:
+ wikiid, rest = text.split(u':', 1)
+ except:
+ wikiid, rest = text, text
+ context = inliner.document.settings.context
+ cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+ {'id': wikiid})
+ if cardrset:
+ ref = cardrset.get_entity(0, 0).absolute_url()
+ else:
+ schema = context.schema
+ if schema.eschema('Card').has_perm(context.req, 'add'):
+ ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+ else:
+ ref = '#'
+ set_classes(options)
+ return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+ **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ """Include a reST file as part of the content of this reST file.
+
+ same as standard include directive but using config.locate_doc_resource to
+ get actual file to include.
+
+ Most part of this implementation is copied from `include` directive defined
+ in `docutils.parsers.rst.directives.misc`
+ """
+ context = state.document.settings.context
+ source = state_machine.input_lines.source(
+ 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')),
+ context.config.available_languages()):
+ rid = '%s_%s.rst' % (fid, lang)
+ resourcedir = context.config.locate_doc_file(rid)
+ if resourcedir:
+ break
+ else:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\nno resource matching %s.'
+ % (name, fid),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ path = join(resourcedir, rid)
+ encoding = options.get('encoding', state.document.settings.input_encoding)
+ try:
+ state.document.settings.record_dependencies.add(path)
+ include_file = io.FileInput(
+ source_path=path, encoding=encoding,
+ error_handler=state.document.settings.input_encoding_error_handler,
+ handle_io_errors=None)
+ except IOError, error:
+ severe = state_machine.reporter.severe(
+ 'Problems with "%s" directive path:\n%s: %s.'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ try:
+ include_text = include_file.read()
+ except UnicodeError, error:
+ severe = state_machine.reporter.severe(
+ 'Problem with "%s" directive:\n%s: %s'
+ % (name, error.__class__.__name__, error),
+ nodes.literal_block(block_text, block_text), line=lineno)
+ return [severe]
+ if options.has_key('literal'):
+ literal_block = nodes.literal_block(include_text, include_text,
+ source=path)
+ literal_block.line = 1
+ return literal_block
+ else:
+ include_lines = statemachine.string2lines(include_text,
+ convert_whitespace=1)
+ state_machine.insert_input(include_lines, path)
+ return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+ 'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+class CubicWebReSTParser(Parser):
+ """The (customized) reStructuredText parser."""
+
+ def __init__(self):
+ self.initial_state = 'Body'
+ self.state_classes = states.state_classes
+ self.inliner = states.Inliner()
+ self.statemachine = states.RSTStateMachine(
+ state_classes=self.state_classes,
+ initial_state=self.initial_state,
+ debug=0)
+
+ def parse(self, inputstring, document):
+ """Parse `inputstring` and populate `document`, a document tree."""
+ self.setup_parse(inputstring, document)
+ inputlines = statemachine.string2lines(inputstring,
+ convert_whitespace=1)
+ self.statemachine.run(inputlines, document, inliner=self.inliner)
+ self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+ """publish a string formatted as ReStructured Text to HTML
+
+ :type context: a cubicweb application object
+
+ :type data: str
+ :param data: some ReST text
+
+ :rtype: unicode
+ :return:
+ the data formatted as HTML or the original data if an error occured
+ """
+ req = context.req
+ if isinstance(data, unicode):
+ encoding = 'unicode'
+ else:
+ encoding = req.encoding
+ settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+ 'warning_stream': StringIO(), 'context': context,
+ # dunno what's the max, severe is 4, and we never want a crash
+ # (though try/except may be a better option...)
+ 'halt_level': 10,
+ }
+ if context:
+ if hasattr(req, 'url'):
+ base_url = req.url()
+ elif hasattr(context, 'absolute_url'):
+ base_url = context.absolute_url()
+ else:
+ base_url = req.base_url()
+ else:
+ base_url = None
+ try:
+ return publish_string(writer=Writer(base_url=base_url),
+ parser=_REST_PARSER, source=data,
+ settings_overrides=settings)
+ except Exception:
+ LOGGER.exception('error while publishing ReST text')
+ if not isinstance(data, unicode):
+ data = unicode(data, encoding, 'replace')
+ return html_escape(req._('error while publishing ReST text')
+ + '\n\n' + data)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/tal.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,256 @@
+"""provides simpleTAL extensions for CubicWeb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+import re
+from os.path import exists, isdir, join
+from logging import getLogger
+from StringIO import StringIO
+
+from simpletal import simpleTAL, simpleTALES
+
+from logilab.common.decorators import cached
+
+LOGGER = getLogger('cubicweb.tal')
+
+
+class LoggerAdapter(object):
+ def __init__(self, tal_logger):
+ self.tal_logger = tal_logger
+
+ def debug(self, msg):
+ LOGGER.debug(msg)
+
+ def warn(self, msg):
+ LOGGER.warning(msg)
+
+ def __getattr__(self, attrname):
+ return getattr(self.tal_logger, attrname)
+
+
+class CubicWebContext(simpleTALES.Context):
+ """add facilities to access entity / resultset"""
+
+ def __init__(self, options=None, allowPythonPath=1):
+ simpleTALES.Context.__init__(self, options, allowPythonPath)
+ self.log = LoggerAdapter(self.log)
+
+ def update(self, context):
+ for varname, value in context.items():
+ self.addGlobal(varname, value)
+
+ def addRepeat(self, name, var, initialValue):
+ simpleTALES.Context.addRepeat(self, name, var, initialValue)
+
+# XXX FIXME need to find a clean to define OPCODE values for extensions
+I18N_CONTENT = 18
+I18N_REPLACE = 19
+RQL_EXECUTE = 20
+# simpleTAL uses the OPCODE values to define priority over commands.
+# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
+# we can't use the same OPCODE for two different commands without changing
+# the simpleTAL implementation. Another solution would be to totally override
+# the REPEAT implementation with the ITER one, but some specific operations
+# (involving len() for instance) are not implemented for ITER, so we prefer
+# to keep both implementations for now, and to fool simpleTAL by using a float
+# number between 3 and 4
+TAL_ITER = 3.1
+
+
+# FIX simpleTAL HTML 4.01 stupidity
+# (simpleTAL never closes tags like INPUT, IMG, HR ...)
+simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
+
+class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
+ """extends default compiler by adding i18n:content commands"""
+
+ def __init__(self):
+ simpleTAL.HTMLTemplateCompiler.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
+ self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
+ self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
+ self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
+
+ def setTALPrefix(self, prefix):
+ simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
+ self.tal_attribute_map['i18n:content'] = I18N_CONTENT
+ self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
+ self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
+ self.tal_attribute_map['tal:iter'] = TAL_ITER
+
+ def compile_cmd_i18n_content(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_i18n_replace(self, argument):
+ # XXX tal:content structure=, text= should we support this ?
+ structure_flag = 0
+ return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
+
+ def compile_cmd_rql(self, argument):
+ return (RQL_EXECUTE, (argument, self.endTagSymbol))
+
+ def compile_cmd_tal_iter(self, argument):
+ original_id, (var_name, expression, end_tag_symbol) = \
+ simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
+ return (TAL_ITER, (var_name, expression, self.endTagSymbol))
+
+ def getTemplate(self):
+ return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
+
+ def compileCmdAttributes (self, argument):
+ """XXX modified to support single attribute
+ definition ending by a ';'
+
+ backport this to simpleTAL
+ """
+ # Compile tal:attributes into attribute command
+ # Argument: [(attributeName, expression)]
+
+ # Break up the list of attribute settings first
+ commandArgs = []
+ # We only want to match semi-colons that are not escaped
+ argumentSplitter = re.compile(r'(?<!;);(?!;)')
+ for attributeStmt in argumentSplitter.split(argument):
+ if not attributeStmt.strip():
+ continue
+ # remove any leading space and un-escape any semi-colons
+ attributeStmt = attributeStmt.lstrip().replace(';;', ';')
+ # Break each attributeStmt into name and expression
+ stmtBits = attributeStmt.split(' ')
+ if (len (stmtBits) < 2):
+ # Error, badly formed attributes command
+ msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument
+ self.log.error(msg)
+ raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
+ attName = stmtBits[0]
+ attExpr = " ".join(stmtBits[1:])
+ commandArgs.append((attName, attExpr))
+ return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
+
+
+class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
+ """provides implementation for interpreting cubicweb extensions"""
+ def __init__(self):
+ simpleTAL.TemplateInterpreter.__init__(self)
+ self.commandHandler[I18N_CONTENT] = self.cmd_i18n
+ self.commandHandler[TAL_ITER] = self.cmdRepeat
+ # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
+
+ def cmd_i18n(self, command, args):
+ """i18n:content and i18n:replace implementation"""
+ string, replace_flag, structure_flag, end_symbol = args
+ if replace_flag:
+ self.outputTag = 0
+ result = self.context.globals['_'](string)
+ self.tagContent = (0, result)
+ self.movePCForward = self.symbolTable[end_symbol]
+ self.programCounter += 1
+
+
+class CubicWebTemplate(simpleTAL.HTMLTemplate):
+ """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
+ """
+ def expand(self, context, outputFile):
+ interpreter = CubicWebTemplateInterpreter()
+ interpreter.initialise(context, outputFile)
+ simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
+ interpreter=interpreter)
+
+ def expandInline(self, context, outputFile, interpreter):
+ """ Internally used when expanding a template that is part of a context."""
+ try:
+ interpreter.execute(self)
+ except UnicodeError, unierror:
+ LOGGER.exception(str(unierror))
+ raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+
+
+def compile_template(template):
+ """compiles a TAL template string
+ :type template: unicode
+ :param template: a TAL-compliant template string
+ """
+ string_buffer = StringIO(template)
+ compiler = CubicWebTemplateCompiler()
+ compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
+ return compiler.getTemplate()
+
+
+def compile_template_file(filepath):
+ """compiles a TAL template file
+ :type filepath: str
+ :param template: path of the file to compile
+ """
+ fp = file(filepath)
+ file_content = unicode(fp.read()) # template file should be pure ASCII
+ fp.close()
+ return compile_template(file_content)
+
+
+def evaluatePython (self, expr):
+ if not self.allowPythonPath:
+ return self.false
+ globals = {}
+ for name, value in self.globals.items():
+ if isinstance (value, simpleTALES.ContextVariable):
+ value = value.rawValue()
+ globals[name] = value
+ globals['path'] = self.pythonPathFuncs.path
+ globals['string'] = self.pythonPathFuncs.string
+ globals['exists'] = self.pythonPathFuncs.exists
+ globals['nocall'] = self.pythonPathFuncs.nocall
+ globals['test'] = self.pythonPathFuncs.test
+ locals = {}
+ for name, value in self.locals.items():
+ if (isinstance (value, simpleTALES.ContextVariable)):
+ value = value.rawValue()
+ locals[name] = value
+ # XXX precompile expr will avoid late syntax error
+ try:
+ result = eval(expr, globals, locals)
+ except Exception, ex:
+ ex = ex.__class__('in %r: %s' % (expr, ex))
+ raise ex, None, sys.exc_info()[-1]
+ if (isinstance (result, simpleTALES.ContextVariable)):
+ return result.value()
+ return result
+
+simpleTALES.Context.evaluatePython = evaluatePython
+
+
+class talbased(object):
+ def __init__(self, filename, write=True):
+## if not osp.isfile(filepath):
+## # print "[tal.py] just for tests..."
+## # get parent frame
+## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
+## filepath = osp.join(directory, filepath)
+ self.filename = filename
+ self.write = write
+
+ def __call__(self, viewfunc):
+ def wrapped(instance, *args, **kwargs):
+ variables = viewfunc(instance, *args, **kwargs)
+ html = instance.tal_render(self._compiled_template(instance), variables)
+ if self.write:
+ instance.w(html)
+ else:
+ return html
+ return wrapped
+
+ def _compiled_template(self, instance):
+ for fileordirectory in instance.config.vregistry_path():
+ filepath = join(fileordirectory, self.filename)
+ if isdir(fileordirectory) and exists(filepath):
+ return compile_template_file(filepath)
+ raise Exception('no such template %s' % self.filename)
+ _compiled_template = cached(_compiled_template, 0)
+
--- a/goa/appobjects/components.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/appobjects/components.py Mon Mar 02 21:03:54 2009 +0100
@@ -12,10 +12,9 @@
from logilab.common.decorators import cached
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.common.selectors import (one_line_rset, match_search_state,
- accept)
from cubicweb.web import Redirect
from cubicweb.web.views import vid_from_rset
from cubicweb.goa.db import rset_from_objs
@@ -31,9 +30,7 @@
"""
id = 'search-associate'
- __selectors__ = (one_line_rset, match_search_state, accept)
- accepts = ('Any',)
- search_states = ('linksearch',)
+ __select__ = one_line_rset() & match_search_state('linksearch') & accept
def cell_call(self, row, col):
entity = self.entity(0, 0)
--- a/goa/appobjects/dbmgmt.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/appobjects/dbmgmt.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
restoration).
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -13,6 +13,7 @@
from logilab.common.decorators import cached
from logilab.mtconverter import html_escape
+from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.common.view import StartupView
from cubicweb.web import Redirect
from cubicweb.goa.dbinit import fix_entities, init_persistent_schema, insert_versions
@@ -39,7 +40,7 @@
which are doing datastore administration requests
"""
id = 'authinfo'
- require_groups = ('managers',)
+ __select__ = none_rset() & match_user_groups('managers')
def call(self):
cookie = self.req.get_cookie()
@@ -61,7 +62,7 @@
step by step to avoid depassing quotas
"""
id = 'contentinit'
- require_groups = ('managers',)
+ __select__ = none_rset() & match_user_groups('managers')
def server_session(self):
ssession = self.config.repo_session(self.req.cnx.sessionid)
@@ -166,7 +167,7 @@
class ContentClear(StartupView):
id = 'contentclear'
- require_groups = ('managers',)
+ __select__ = none_rset() & match_user_groups('managers')
skip_etypes = ('EGroup', 'EUser')
def call(self):
--- a/goa/appobjects/gauthservice.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/appobjects/gauthservice.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,29 +1,18 @@
"""authentication using google authentication service
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.registerers import priority_registerer
from cubicweb.web.views.basecomponents import UserLink
from cubicweb.web.views.actions import LogoutAction
from google.appengine.api import users
-class use_google_auth_registerer(priority_registerer):
- """register object if use-google-auth is true"""
-
- def do_it_yourself(self, registered):
- if not hasattr(self.config, 'has_resource'):
- return
- return super(use_google_auth_registerer, self).do_it_yourself(registered)
-
-
class GAEUserLink(UserLink):
- __registerer__ = use_google_auth_registerer
def anon_user_link(self):
self.w(self.req._('anonymous'))
@@ -31,7 +20,11 @@
% (users.create_login_url(self.req.url()), self.req._('login')))
class GAELogoutAction(LogoutAction):
- __registerer__ = use_google_auth_registerer
def url(self):
return users.create_logout_url(self.req.build_url('logout') )
+
+def registration_callback(vreg):
+ if hasattr(vreg.config, 'has_resource'):
+ vreg.register(GAEUserLink, clear=True)
+ vreg.register(GAELogoutAction, clear=True)
--- a/goa/appobjects/sessions.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/appobjects/sessions.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""persistent sessions stored in big table
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
XXX TODO:
@@ -17,6 +17,7 @@
from cubicweb import UnknownEid, BadConnectionId
from cubicweb.dbapi import Connection, ConnectionProperties, repo_connect
+from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.server.session import Session
from cubicweb.web import InvalidSession
from cubicweb.web.application import AbstractSessionManager
@@ -254,7 +255,7 @@
class SessionsCleaner(StartupView):
id = 'cleansessions'
- require_groups = ('managers',)
+ __select__ = none_rset() & match_user_groups('managers')
def call(self):
# clean web session
@@ -268,4 +269,9 @@
self.w(u'%s repository sessions closed<br/>\n' % nbclosed)
self.w(u'%s remaining sessions<br/>\n' % remaining)
self.w(u'</div>')
+
+def registration_callback(vreg):
+ vreg.register(SessionsCleaner)
+ vreg.register(GAEAuthenticationManager, clear=True)
+ vreg.register(GAEPersistentSessionManager, clear=True)
--- a/goa/db.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/db.py Mon Mar 02 21:03:54 2009 +0100
@@ -25,7 +25,7 @@
* XXX ListProperty
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -37,7 +37,7 @@
from cubicweb import RequestSessionMixIn, Binary, entities
from cubicweb.rset import ResultSet
-from cubicweb.common.entity import metaentity
+from cubicweb.entity import metaentity
from cubicweb.server.utils import crypt_password
from cubicweb.goa import use_mx_for_dates, mx2datetime, MODE
from cubicweb.goa.dbinit import init_relations
--- a/goa/goactl.py Fri Feb 27 09:59:53 2009 +0100
+++ b/goa/goactl.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""cubicweb on appengine plugins for cubicweb-ctl
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -11,7 +11,7 @@
from cubicweb import BadCommandUsage
from cubicweb import CW_SOFTWARE_ROOT
from cubicweb.toolsutils import (Command, register_commands, copy_skeleton,
- create_dir, create_symlink, create_copy)
+ create_dir, create_symlink, create_copy)
from cubicweb.cwconfig import CubicWebConfiguration
from logilab import common as lgc
@@ -47,28 +47,29 @@
'__init__.py',
'__pkginfo__.py',
'_exceptions.py',
+ 'appobject.py',
'dbapi.py',
'cwvreg.py',
'cwconfig.py',
+ 'entity.py',
'interfaces.py',
'rset.py',
'schema.py',
'schemaviewer.py',
+ 'selectors.py',
+ 'utils.py',
'vregistry.py',
+ 'view.py',
- 'common/appobject.py',
- 'common/entity.py',
- 'common/html4zope.py',
'common/mail.py',
'common/migration.py',
'common/mixins.py',
'common/mttransforms.py',
'common/registerers.py',
- 'common/rest.py',
- 'common/selectors.py',
- 'common/view.py',
'common/uilib.py',
- 'common/utils.py',
+
+ 'ext/html4zope.py',
+ 'ext/rest.py',
'server/hookhelper.py',
'server/hooksmanager.py',
--- a/i18n/en.po Fri Feb 27 09:59:53 2009 +0100
+++ b/i18n/en.po Mon Mar 02 21:03:54 2009 +0100
@@ -1841,6 +1841,9 @@
msgid "i18n_login_popup"
msgstr "login"
+msgid "i18n_register_user"
+msgstr "register"
+
msgid "i18nprevnext_next"
msgstr "next"
@@ -2046,6 +2049,9 @@
msgid "login"
msgstr ""
+msgid "login or email"
+msgstr ""
+
msgid "login_action"
msgstr "log in"
--- a/i18n/es.po Fri Feb 27 09:59:53 2009 +0100
+++ b/i18n/es.po Mon Mar 02 21:03:54 2009 +0100
@@ -1924,6 +1924,9 @@
msgid "i18n_login_popup"
msgstr "identificarse"
+msgid "i18n_register_user"
+msgstr "registrarse"
+
msgid "i18nprevnext_next"
msgstr "siguiente"
--- a/i18n/fr.po Fri Feb 27 09:59:53 2009 +0100
+++ b/i18n/fr.po Mon Mar 02 21:03:54 2009 +0100
@@ -1924,6 +1924,9 @@
msgid "i18n_login_popup"
msgstr "s'authentifier"
+msgid "i18n_register_user"
+msgstr "s'enregister"
+
msgid "i18nprevnext_next"
msgstr "suivant"
@@ -2140,6 +2143,9 @@
msgid "login"
msgstr "identifiant"
+msgid "login or email"
+msgstr "identifiant ou email"
+
msgid "login_action"
msgstr "identifiez vous"
--- a/interfaces.py Fri Feb 27 09:59:53 2009 +0100
+++ b/interfaces.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""Specific views for entities implementing IDownloadable
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -193,8 +193,8 @@
"""embed action interface"""
class ICalendarable(Interface):
- """interface for itms that do have a begin date 'start' and an end
-date 'stop'"""
+ """interface for items that do have a begin date 'start' and an end date 'stop'
+ """
class ICalendarViews(Interface):
"""calendar views interface"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.2.0_Any.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,3 @@
+rql('SET X value "main-template" WHERE X is EProperty, '
+ 'X pkey "ui.main-template", X value "main"')
+checkpoint()
--- a/schema.py Fri Feb 27 09:59:53 2009 +0100
+++ b/schema.py Mon Mar 02 21:03:54 2009 +0100
@@ -10,7 +10,7 @@
import re
from logging import getLogger
-from logilab.common.decorators import cached, clear_cache
+from logilab.common.decorators import cached, clear_cache, monkeypatch
from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
@@ -68,6 +68,41 @@
return (etype,)
ybo.RelationDefinition._actual_types = _actual_types
+
+## cubicweb provides a RichString class for convenience
+class RichString(ybo.String):
+ """Convenience RichString attribute type
+ The follwing declaration::
+
+ class Card(EntityType):
+ content = RichString(fulltextindexed=True, default_format='text/rest')
+
+ is equivalent to::
+
+ class Card(EntityType):
+ content_format = String(meta=True, internationalizable=True,
+ default='text/rest', constraints=[format_constraint])
+ content = String(fulltextindexed=True)
+ """
+ def __init__(self, default_format='text/plain', format_constraints=None, **kwargs):
+ self.default_format = default_format
+ self.format_constraints = format_constraints or [format_constraint]
+ super(RichString, self).__init__(**kwargs)
+
+PyFileReader.context['RichString'] = RichString
+
+## need to monkeypatch yams' _add_relation function to handle RichString
+yams_add_relation = ybo._add_relation
+@monkeypatch(ybo)
+def _add_relation(relations, rdef, name=None, insertidx=None):
+ if isinstance(rdef, RichString):
+ default_format = rdef.default_format
+ format_attrdef = ybo.String(meta=True, internationalizable=True,
+ default=rdef.default_format, maxsize=50,
+ constraints=rdef.format_constraints)
+ yams_add_relation(relations, format_attrdef, name+'_format', insertidx)
+ yams_add_relation(relations, rdef, name, insertidx)
+
def display_name(req, key, form=''):
"""return a internationalized string for the key (schema entity or relation
name) in a given form
@@ -805,7 +840,34 @@
PyFileReader.context['RRQLExpression'] = RRQLExpression
-
+# workflow extensions #########################################################
+
+class workflowable_definition(ybo.metadefinition):
+ """extends default EntityType's metaclass to add workflow relations
+ (i.e. in_state and wf_info_for).
+ This is the default metaclass for WorkflowableEntityType
+ """
+ def __new__(mcs, name, bases, classdict):
+ abstract = classdict.pop('abstract', False)
+ defclass = super(workflowable_definition, mcs).__new__(mcs, name, bases, classdict)
+ if not abstract:
+ existing_rels = set(rdef.name for rdef in defclass.__relations__)
+ if 'in_state' not in existing_rels and 'wf_info_for' not in existing_rels:
+ in_state = ybo.SubjectRelation('State', cardinality='1*',
+ # XXX automatize this
+ constraints=[RQLConstraint('S is ET, O state_of ET')],
+ description=_('account state'))
+ yams_add_relation(defclass.__relations__, in_state, 'in_state')
+ wf_info_for = ybo.ObjectRelation('TrInfo', cardinality='1*', composite='object')
+ yams_add_relation(defclass.__relations__, wf_info_for, 'wf_info_for')
+ return defclass
+
+class WorkflowableEntityType(ybo.EntityType):
+ __metaclass__ = workflowable_definition
+ abstract = True
+
+PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
+
# schema loading ##############################################################
class CubicWebRelationFileReader(RelationFileReader):
@@ -877,6 +939,7 @@
def _load_definition_files(self, cubes):
for filepath in (self.include_schema_files('bootstrap')
+ self.include_schema_files('base')
+ + self.include_schema_files('workflow')
+ self.include_schema_files('Bookmark')
+ self.include_schema_files('Card')):
self.info('loading %s', filepath)
@@ -892,8 +955,8 @@
PERM_USE_TEMPLATE_FORMAT = _('use_template_format')
class FormatConstraint(StaticVocabularyConstraint):
- need_perm_formats = (_('text/cubicweb-page-template'),
- )
+ need_perm_formats = [_('text/cubicweb-page-template')]
+
regular_formats = (_('text/rest'),
_('text/html'),
_('text/plain'),
@@ -913,7 +976,7 @@
def vocabulary(self, entity=None):
if entity and entity.req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
- return self.regular_formats + self.need_perm_formats
+ return self.regular_formats + tuple(self.need_perm_formats)
return self.regular_formats
def __str__(self):
--- a/schemas/Card.py Fri Feb 27 09:59:53 2009 +0100
+++ b/schemas/Card.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,4 +1,4 @@
-from cubicweb.schema import format_constraint
+# from cubicweb.schema import format_constraint
class Card(EntityType):
"""a card is a textual content used as documentation, reference, procedure reminder"""
@@ -12,7 +12,5 @@
title = String(required=True, fulltextindexed=True, maxsize=256)
synopsis = String(fulltextindexed=True, maxsize=512,
description=_("an abstract for this card"))
- content_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- content = String(fulltextindexed=True)
+ content = RichString(fulltextindexed=True, default_format='text/rest')
wikiid = String(maxsize=64, indexed=True)
--- a/schemas/base.py Fri Feb 27 09:59:53 2009 +0100
+++ b/schemas/base.py Mon Mar 02 21:03:54 2009 +0100
@@ -9,8 +9,9 @@
from cubicweb.schema import format_constraint
-class EUser(RestrictedEntityType):
+class EUser(WorkflowableEntityType):
"""define a CubicWeb user"""
+ meta = True # XXX backported from old times, shouldn't be there anymore
permissions = {
'read': ('managers', 'users', ERQLExpression('X identity U')),
'add': ('managers',),
@@ -33,11 +34,6 @@
in_group = SubjectRelation('EGroup', cardinality='+*',
constraints=[RQLConstraint('NOT O name "owners"')],
description=_('groups grant permissions to the user'))
- in_state = SubjectRelation('State', cardinality='1*',
- # XXX automatize this
- constraints=[RQLConstraint('S is ET, O state_of ET')],
- description=_('account state'))
- wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
class EmailAddress(MetaEntityType):
@@ -130,112 +126,7 @@
cardinality = '11'
subject = '**'
object = 'Datetime'
-
-
-class State(MetaEntityType):
- """used to associate simple states to an entity type and/or to define
- workflows
- """
- name = String(required=True, indexed=True, internationalizable=True,
- maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True,
- description=_('semantic description of this state'))
- state_of = SubjectRelation('EEType', cardinality='+*',
- description=_('entity types which may use this state'),
- constraints=[RQLConstraint('O final FALSE')])
- allowed_transition = SubjectRelation('Transition', cardinality='**',
- constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
- description=_('allowed transitions from this state'))
-
- initial_state = ObjectRelation('EEType', cardinality='?*',
- # S initial_state O, O state_of S
- constraints=[RQLConstraint('O state_of S')],
- description=_('initial state for entities of this type'))
-
-
-class Transition(MetaEntityType):
- """use to define a transition from one or multiple states to a destination
- states in workflow's definitions.
- """
- name = String(required=True, indexed=True, internationalizable=True,
- maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True,
- description=_('semantic description of this transition'))
- condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
- description=_('a RQL expression which should return some results, '
- 'else the transition won\'t be available. '
- 'This query may use X and U variables '
- 'that will respectivly represents '
- 'the current entity and the current user'))
-
- require_group = SubjectRelation('EGroup', cardinality='**',
- description=_('group in which a user should be to be '
- 'allowed to pass this transition'))
- transition_of = SubjectRelation('EEType', cardinality='+*',
- description=_('entity types which may use this transition'),
- constraints=[RQLConstraint('O final FALSE')])
- destination_state = SubjectRelation('State', cardinality='?*',
- constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
- description=_('destination state for this transition'))
-
-
-class TrInfo(MetaEntityType):
- from_state = SubjectRelation('State', cardinality='?*')
- to_state = SubjectRelation('State', cardinality='1*')
- comment_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- comment = String(fulltextindexed=True)
- # get actor and date time using owned_by and creation_date
-
-
-class from_state(MetaRelationType):
- inlined = True
-class to_state(MetaRelationType):
- inlined = True
-class wf_info_for(MetaRelationType):
- """link a transition information to its object"""
- permissions = {
- 'read': ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
- 'add': (), # handled automatically, no one should add one explicitly
- 'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
- }
- inlined = True
- composite = 'object'
- fulltext_container = composite
-
-class state_of(MetaRelationType):
- """link a state to one or more entity type"""
-class transition_of(MetaRelationType):
- """link a transition to one or more entity type"""
-
-class initial_state(MetaRelationType):
- """indicate which state should be used by default when an entity using
- states is created
- """
- inlined = True
-
-class destination_state(MetaRelationType):
- """destination state of a transition"""
- inlined = True
-
-class allowed_transition(MetaRelationType):
- """allowed transition from this state"""
-
-class in_state(UserRelationType):
- """indicate the current state of an entity"""
- meta = True
- # not inlined intentionnaly since when using ldap sources, user'state
- # has to be stored outside the EUser table
-
- # add/delete perms given to managers/users, after what most of the job
- # is done by workflow enforcment
-
-
class EProperty(EntityType):
"""used for cubicweb configuration. Once a property has been created you
can't change the key.
--- a/schemas/bootstrap.py Fri Feb 27 09:59:53 2009 +0100
+++ b/schemas/bootstrap.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""core CubicWeb schema necessary for bootstrapping the actual application's schema
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -14,10 +14,8 @@
"""define an entity type, used to build the application schema"""
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/plain', constraints=[format_constraint])
- description = String(internationalizable=True,
- description=_('semantic description of this entity type'))
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this entity type'))
meta = Boolean(description=_('is it an application entity type or not ?'))
# necessary to filter using RQL
final = Boolean(description=_('automatic'))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/workflow.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,108 @@
+"""workflow related schemas
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+class State(MetaEntityType):
+ """used to associate simple states to an entity type and/or to define
+ workflows
+ """
+ name = String(required=True, indexed=True, internationalizable=True,
+ maxsize=256)
+ description = RichString(fulltextindexed=True, default_format='text/rest',
+ description=_('semantic description of this state'))
+
+ state_of = SubjectRelation('EEType', cardinality='+*',
+ description=_('entity types which may use this state'),
+ constraints=[RQLConstraint('O final FALSE')])
+ allowed_transition = SubjectRelation('Transition', cardinality='**',
+ constraints=[RQLConstraint('S state_of ET, O transition_of ET')],
+ description=_('allowed transitions from this state'))
+
+ initial_state = ObjectRelation('EEType', cardinality='?*',
+ # S initial_state O, O state_of S
+ constraints=[RQLConstraint('O state_of S')],
+ description=_('initial state for entities of this type'))
+
+
+class Transition(MetaEntityType):
+ """use to define a transition from one or multiple states to a destination
+ states in workflow's definitions.
+ """
+ name = String(required=True, indexed=True, internationalizable=True,
+ maxsize=256)
+ description_format = String(meta=True, internationalizable=True, maxsize=50,
+ default='text/rest', constraints=[format_constraint])
+ description = String(fulltextindexed=True,
+ description=_('semantic description of this transition'))
+ condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
+ description=_('a RQL expression which should return some results, '
+ 'else the transition won\'t be available. '
+ 'This query may use X and U variables '
+ 'that will respectivly represents '
+ 'the current entity and the current user'))
+
+ require_group = SubjectRelation('EGroup', cardinality='**',
+ description=_('group in which a user should be to be '
+ 'allowed to pass this transition'))
+ transition_of = SubjectRelation('EEType', cardinality='+*',
+ description=_('entity types which may use this transition'),
+ constraints=[RQLConstraint('O final FALSE')])
+ destination_state = SubjectRelation('State', cardinality='?*',
+ constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
+ description=_('destination state for this transition'))
+
+
+class TrInfo(MetaEntityType):
+ from_state = SubjectRelation('State', cardinality='?*')
+ to_state = SubjectRelation('State', cardinality='1*')
+ comment_format = String(meta=True, internationalizable=True, maxsize=50,
+ default='text/rest', constraints=[format_constraint])
+ comment = String(fulltextindexed=True)
+ # get actor and date time using owned_by and creation_date
+
+
+class from_state(MetaRelationType):
+ inlined = True
+class to_state(MetaRelationType):
+ inlined = True
+class wf_info_for(MetaRelationType):
+ """link a transition information to its object"""
+ permissions = {
+ 'read': ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
+ 'add': (), # handled automatically, no one should add one explicitly
+ 'delete': ('managers',), # RRQLExpression('U has_delete_permission O')
+ }
+ inlined = True
+ composite = 'object'
+ fulltext_container = composite
+
+class state_of(MetaRelationType):
+ """link a state to one or more entity type"""
+class transition_of(MetaRelationType):
+ """link a transition to one or more entity type"""
+
+class initial_state(MetaRelationType):
+ """indicate which state should be used by default when an entity using
+ states is created
+ """
+ inlined = True
+
+class destination_state(MetaRelationType):
+ """destination state of a transition"""
+ inlined = True
+
+class allowed_transition(MetaRelationType):
+ """allowed transition from this state"""
+
+class in_state(UserRelationType):
+ """indicate the current state of an entity"""
+ meta = True
+ # not inlined intentionnaly since when using ldap sources, user'state
+ # has to be stored outside the EUser table
+
+ # add/delete perms given to managers/users, after what most of the job
+ # is done by workflow enforcment
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/selectors.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,1103 @@
+"""This file contains some basic selectors required by application objects.
+
+A selector is responsible to score how well an object may be used with a
+given context by returning a score.
+
+In CubicWeb Usually the context consists for a request object, a result set
+or None, a specific row/col in the result set, etc...
+
+
+If you have trouble with selectors, especially if the objet (typically
+a view or a component) you want to use is not selected and you want to
+know which one(s) of its selectors fail (e.g. returns 0), you can use
+`traced_selection` or even direclty `TRACED_OIDS`.
+
+`TRACED_OIDS` is a tuple of traced object ids. The special value
+'all' may be used to log selectors for all objects.
+
+For instance, say that the following code yields a `NoSelectableObject`
+exception::
+
+ self.view('calendar', myrset)
+
+You can log the selectors involved for *calendar* by replacing the line
+above by::
+
+ # in Python2.5
+ from cubicweb.selectors import traced_selection
+ with traced_selection():
+ self.view('calendar', myrset)
+
+ # in Python2.4
+ from cubicweb import selectors
+ selectors.TRACED_OIDS = ('calendar',)
+ self.view('calendar', myrset)
+ selectors.TRACED_OIDS = ()
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import logging
+from warnings import warn
+
+from logilab.common.compat import all
+from logilab.common.deprecation import deprecated_function
+from logilab.common.interface import implements as implements_iface
+
+from yams import BASE_TYPES
+
+from cubicweb import Unauthorized, NoSelectableObject, NotAnEntity, role
+from cubicweb.vregistry import (NoSelectableObject, Selector,
+ chainall, chainfirst, objectify_selector)
+from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.schema import split_expression
+
+# helpers for debugging selectors
+SELECTOR_LOGGER = logging.getLogger('cubicweb.selectors')
+TRACED_OIDS = ()
+
+def lltrace(selector):
+ # don't wrap selectors if not in development mode
+ if CubicWebConfiguration.mode == 'installed':
+ return selector
+ def traced(cls, *args, **kwargs):
+ # /!\ lltrace decorates pure function or __call__ method, this
+ # means argument order may be different
+ if isinstance(cls, Selector):
+ selname = str(cls)
+ vobj = args[0]
+ else:
+ selname = selector.__name__
+ vobj = cls
+ oid = vobj.id
+ 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 'selector %s returned %s for %s' % (selname, ret, vobj)
+ return ret
+ traced.__name__ = selector.__name__
+ return traced
+
+class traced_selection(object):
+ """selector debugging helper.
+
+ Typical usage is :
+
+ >>> with traced_selection():
+ ... # some code in which you want to debug selectors
+ ... # for all objects
+
+ or
+
+ >>> with traced_selection( ('oid1', 'oid2') ):
+ ... # some code in which you want to debug selectors
+ ... # for objects with id 'oid1' and 'oid2'
+
+ """
+ def __init__(self, traced='all'):
+ self.traced = traced
+
+ def __enter__(self):
+ global TRACED_OIDS
+ TRACED_OIDS = self.traced
+
+ def __exit__(self, exctype, exc, traceback):
+ global TRACED_OIDS
+ TRACED_OIDS = ()
+ return traceback is None
+
+
+# abstract selectors ##########################################################
+class PartialSelectorMixIn(object):
+ """convenience mix-in for selectors that will look into the containing
+ class to find missing information.
+
+ cf. `cubicweb.web.action.LinkToEntityAction` for instance
+ """
+ def __call__(self, cls, *args, **kwargs):
+ self.complete(cls)
+ return super(PartialSelectorMixIn, self).__call__(cls, *args, **kwargs)
+
+class EClassSelector(Selector):
+ """abstract class for selectors working on the entity classes of the result
+ set. Its __call__ method has the following behaviour:
+
+ * 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:
+ - `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
+ """
+ def __init__(self, once_is_enough=False):
+ self.once_is_enough = once_is_enough
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ if not rset:
+ return 0
+ score = 0
+ if row is None:
+ for etype in rset.column_types(col):
+ if etype is None: # outer join
+ continue
+ escore = self.score(cls, req, etype)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ else:
+ etype = rset.description[row][col]
+ if etype is not None:
+ score = self.score(cls, req, etype)
+ return score
+
+ def score(self, cls, req, etype):
+ if etype in BASE_TYPES:
+ return 0
+ return self.score_class(cls.vreg.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:
+
+ * 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:
+ - `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
+
+ note: None values (resulting from some outer join in the query) are not
+ considered.
+ """
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ if not rset:
+ return 0
+ score = 0
+ if row is None:
+ for row, rowvalue in enumerate(rset.rows):
+ if rowvalue[col] is None: # outer join
+ continue
+ escore = self.score(req, rset, row, col)
+ if not escore and not self.once_is_enough:
+ return 0
+ elif self.once_is_enough:
+ return escore
+ score += escore
+ else:
+ etype = rset.description[row][col]
+ if etype is not None: # outer join
+ score = self.score(req, rset, row, col)
+ return score
+
+ def score(self, req, rset, row, col):
+ try:
+ return self.score_entity(rset.get_entity(row, col))
+ except NotAnEntity:
+ return 0
+
+ def score_entity(self, entity):
+ raise NotImplementedError()
+
+
+# very basic selectors ########################################################
+@objectify_selector
+def yes(cls, *args, **kwargs):
+ """accept everything"""
+ return 1
+
+@objectify_selector
+@lltrace
+def none_rset(cls, req, rset, *args, **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, *args, **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, *args, **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, *args, **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, row=None, *args, **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, *args, **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, *args, **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, *args, **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, row=None, col=0, **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, row=None, col=0, *args, **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, row=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 anonymous"""
+ if req.cnx.anonymous_connection:
+ return 0
+ return 1
+
+def anonymous_user():
+ return ~ authenticated_user()
+
+@objectify_selector
+@lltrace
+def primary_view(cls, req, rset, 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, 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)
+ """
+ def __init__(self, *expected):
+ assert expected, self
+ self.expected = frozenset(expected)
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(sorted(str(s) for s in self.expected)))
+
+ @lltrace
+ def __call__(self, cls, req, rset, 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
+
+
+class match_form_params(match_search_state):
+ """accept if parameters specified as initializer arguments are specified
+ in request's form parameters
+
+ :param *expected: parameters (eg `basestring`) which are expected to be
+ found in request's form parameters
+ """
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ score = 0
+ for param in self.expected:
+ val = req.form.get(param)
+ if not val:
+ 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):
+ 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):
+ """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):
+ self.registry = registry
+ self.oid = oid
+
+ def __call__(self, cls, req, rset, *args, **kwargs):
+ try:
+ cls.vreg.select_object(self.registry, self.oid, req, rset, *args, **kwargs)
+ return 1
+ except NoSelectableObject:
+ return 0
+
+
+# not so basic selectors ######################################################
+
+class implements(EClassSelector):
+ """accept if entity class found in the result set implements at least one
+ of the interfaces given as argument. Returned score is the number of
+ implemented interfaces.
+
+ 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)
+
+ note: when interface is an entity class, the score will reflect class
+ proximity so the most specific object'll be selected
+ """
+ def __init__(self, *expected_ifaces):
+ super(implements, self).__init__()
+ self.expected_ifaces = expected_ifaces
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.expected_ifaces))
+
+ def score_class(self, eclass, req):
+ score = 0
+ for iface in self.expected_ifaces:
+ if isinstance(iface, basestring):
+ # entity type
+ iface = eclass.vreg.etype_class(iface)
+ if implements_iface(eclass, iface):
+ if getattr(iface, '__registry__', None) == 'etypes':
+ # adjust score if the interface is an entity class
+ if iface is eclass:
+ score += len(eclass.e_schema.ancestors()) + 4
+ else:
+ parents = [e.type for e in eclass.e_schema.ancestors()]
+ for index, etype in enumerate(reversed(parents)):
+ basecls = eclass.vreg.etype_class(etype)
+ if iface is basecls:
+ score += index + 3
+ break
+ else: # Any
+ score += 1
+ else:
+ # implenting an interface takes precedence other special Any
+ # interface
+ score += 2
+ return score
+
+
+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.
+
+ :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)
+
+ note: when interface is an entity class, the score will reflect class
+ proximity so the most specific object'll be selected
+ """
+
+ @lltrace
+ def __call__(self, cls, req, *args, **kwargs):
+ try:
+ etype = req.form['etype']
+ except KeyError:
+ try:
+ etype = kwargs['etype']
+ except KeyError:
+ return 0
+ return self.score_class(cls.vreg.etype_class(etype), req)
+
+
+class relation_possible(EClassSelector):
+ """accept if entity class found in the result set support the relation.
+
+ See `EClassSelector` 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'.
+ :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
+ """
+ def __init__(self, rtype, role='subject', target_etype=None,
+ action='read', once_is_enough=False):
+ super(relation_possible, self).__init__(once_is_enough)
+ self.rtype = rtype
+ self.role = role
+ self.target_etype = target_etype
+ self.action = action
+
+ @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
+ score = super(relation_possible, self).__call__(cls, req, *args, **kwargs)
+ return score
+
+ def score_class(self, eclass, req):
+ eschema = eclass.e_schema
+ try:
+ if self.role == 'object':
+ rschema = eschema.object_relation(self.rtype)
+ else:
+ rschema = eschema.subject_relation(self.rtype)
+ except KeyError:
+ return 0
+ 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))
+ except KeyError, ex:
+ 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
+
+ - `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
+ """
+ def __init__(self, action='read', once_is_enough=False):
+ super(partial_relation_possible, self).__init__(None, None, None,
+ action, once_is_enough)
+
+ def complete(self, cls):
+ self.rtype = cls.rtype
+ self.role = role(cls)
+ self.target_etype = getattr(cls, 'etype', None)
+
+
+class has_editable_relation(EntitySelector):
+ """accept if some relations for an entity found in the result set is
+ editable by the logged user.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+ """
+
+ def score_entity(self, entity):
+ # if user has no update right but it can modify some relation,
+ # display action anyway
+ for dummy in entity.srelations_by_category(('generic', 'metadata'),
+ 'add'):
+ return 1
+ for rschema, targetschemas, role in entity.relations_by_category(
+ ('primary', 'secondary'), 'add'):
+ if not rschema.is_final():
+ return 1
+ return 0
+
+
+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)
+
+
+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.
+
+ 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'.
+ :param target_type: if specified, check the relation's end may be of this
+ target type (`basestring`)
+ """
+ def __init__(self, rtype, role='subject', target_etype=None,
+ once_is_enough=False):
+ super(has_related_entities, self).__init__(once_is_enough)
+ 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):
+ return 0
+ rset = entity.related(self.rtype, self.role)
+ if self.target_etype:
+ return any(x for x, in rset.description if x == self.target_etype)
+ return rset and 1 or 0
+
+
+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
+
+ - `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
+ """
+ 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)
+
+
+class has_permission(EntitySelector):
+ """accept if user has the permission to do the requested action on a result
+ set entity.
+
+ * 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
+
+ note: None values (resulting from some outer join in the query) are not
+ considered.
+
+ :param action: an entity schema action (eg 'read'/'add'/'delete'/'update')
+ """
+ def __init__(self, action, once_is_enough=False):
+ super(has_permission, self).__init__(once_is_enough)
+ self.action = action
+
+ @lltrace
+ def __call__(self, cls, req, rset, row=None, col=0, **kwargs):
+ if rset is None:
+ return 0
+ user = req.user
+ action = self.action
+ if row is None:
+ score = 0
+ need_local_check = []
+ geteschema = cls.schema.eschema
+ for etype in rset.column_types(0):
+ if etype in BASE_TYPES:
+ return 0
+ eschema = geteschema(etype)
+ if not user.matching_groups(eschema.get_groups(action)):
+ if eschema.has_local_role(action):
+ # have to ckeck local roles
+ need_local_check.append(eschema)
+ continue
+ else:
+ # even a local role won't be enough
+ return 0
+ score += 1
+ if need_local_check:
+ # check local role for entities of necessary types
+ for i, row in enumerate(rset):
+ if not rset.description[i][0] in need_local_check:
+ continue
+ if not self.score(req, rset, i, col):
+ return 0
+ score += 1
+ return score
+ return self.score(req, rset, row, col)
+
+ def score_entity(self, entity):
+ if entity.has_perm(self.action):
+ return 1
+ return 0
+
+
+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.
+
+ See `EClassSelector` documentation for behaviour when row is not specified.
+ """
+ def score(self, cls, req, etype):
+ eschema = cls.schema.eschema(etype)
+ if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+ and eschema.has_perm(req, 'add'):
+ return 1
+ return 0
+
+
+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
+ condition.
+
+ See `EntitySelector` documentation for behaviour when row is not specified.
+
+ :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.
+ """
+ def __init__(self, expression, once_is_enough=False):
+ super(rql_condition, self).__init__(once_is_enough)
+ if 'U' in frozenset(split_expression(expression)):
+ rql = 'Any X WHERE X eid %%(x)s, U eid %%(u)s, %s' % expression
+ else:
+ rql = 'Any X WHERE X eid %%(x)s, %s' % expression
+ self.rql = rql
+
+ def score(self, req, rset, row, col):
+ try:
+ return len(req.execute(self.rql, {'x': rset[row][col],
+ 'u': req.user.eid}, 'x'))
+ except Unauthorized:
+ return 0
+
+
+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.
+
+ :param *etypes: entity types (`basestring`) which should be refused
+ """
+ def __init__(self, *etypes):
+ super(but_etype, self).__init__()
+ self.but_etypes = etypes
+
+ def score(self, req, rset, row, col):
+ if rset.description[row][col] in self.but_etypes:
+ 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(EntitySelector, self).__init__(once_is_enough)
+ self.score_entity = scorefunc
+
+
+# XXX DEPRECATED ##############################################################
+
+yes_selector = deprecated_function(yes)
+norset_selector = deprecated_function(none_rset)
+rset_selector = deprecated_function(any_rset)
+anyrset_selector = deprecated_function(nonempty_rset)
+emptyrset_selector = deprecated_function(empty_rset)
+onelinerset_selector = deprecated_function(one_line_rset)
+twolinerset_selector = deprecated_function(two_lines_rset)
+twocolrset_selector = deprecated_function(two_cols_rset)
+largerset_selector = deprecated_function(paginated_rset)
+sortedrset_selector = deprecated_function(sorted_rset)
+oneetyperset_selector = deprecated_function(one_etype_rset)
+multitype_selector = deprecated_function(two_etypes_rset)
+anonymous_selector = deprecated_function(anonymous_user)
+not_anonymous_selector = deprecated_function(authenticated_user)
+primaryview_selector = deprecated_function(primary_view)
+contextprop_selector = deprecated_function(match_context_prop)
+
+def nfentity_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return non_final_entity()(cls, req, rset, row, col)
+nfentity_selector = deprecated_function(nfentity_selector)
+
+def implement_interface(cls, req, rset, row=None, col=0, **kwargs):
+ return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
+_interface_selector = deprecated_function(implement_interface)
+interface_selector = deprecated_function(implement_interface)
+implement_interface = deprecated_function(implement_interface, 'use implements')
+
+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 = deprecated_function(accept_etype)
+accept_etype = deprecated_function(accept_etype, 'use specified_etype_implements')
+
+def searchstate_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return match_search_state(cls.search_states)(cls, req, rset, row, col)
+searchstate_selector = deprecated_function(searchstate_selector)
+
+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 = deprecated_function(match_user_group)
+match_user_group = deprecated_function(match_user_group)
+
+def has_relation(cls, req, rset, 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)
+has_relation = deprecated_function(has_relation)
+
+def one_has_relation(cls, req, rset, 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)
+one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
+
+def accept_rset(cls, req, rset, 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 = deprecated_function(accept_rset)
+accept_rset = deprecated_function(accept_rset, 'use implements selector')
+
+accept = chainall(non_final_entity(), accept_rset, name='accept')
+accept_selector = deprecated_function(accept)
+accept = deprecated_function(accept, 'use implements selector')
+
+accept_one = deprecated_function(chainall(one_line_rset, accept,
+ name='accept_one'))
+accept_one_selector = deprecated_function(accept_one)
+
+
+def _rql_condition(cls, req, rset, row=None, col=0, **kwargs):
+ if cls.condition:
+ return rql_condition(cls.condition)(cls, req, rset, row, col)
+ return 1
+_rqlcondition_selector = deprecated_function(_rql_condition)
+
+rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition,
+ name='rql_condition'))
+
+def but_etype_selector(cls, req, rset, row=None, col=0, **kwargs):
+ return but_etype(cls.etype)(cls, req, rset, row, col)
+but_etype_selector = deprecated_function(but_etype_selector)
+
+@lltrace
+def etype_rtype_selector(cls, req, rset, 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_function(etype_rtype_selector)
+
+#req_form_params_selector = deprecated_function(match_form_params) # form_params
+#kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs
+
+# compound selectors ##########################################################
+
+searchstate_accept = chainall(nonempty_rset(), accept,
+ name='searchstate_accept')
+searchstate_accept_selector = deprecated_function(searchstate_accept)
+
+searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
+ name='searchstate_accept_one')
+searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
+
+searchstate_accept = deprecated_function(searchstate_accept)
+searchstate_accept_one = deprecated_function(searchstate_accept_one)
+
+
+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',
+ 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
--- a/server/checkintegrity.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/checkintegrity.py Mon Mar 02 21:03:54 2009 +0100
@@ -84,7 +84,7 @@
', '.join(sorted(str(e) for e in etypes))
pb = ProgressBar(len(etypes) + 1)
# first monkey patch Entity.check to disable validation
- from cubicweb.common.entity import Entity
+ from cubicweb.entity import Entity
_check = Entity.check
Entity.check = lambda self, creation=False: True
# clear fti table first
--- a/server/hooksmanager.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/hooksmanager.py Mon Mar 02 21:03:54 2009 +0100
@@ -23,7 +23,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -180,13 +180,12 @@
# 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.vregistry import autoselectors
-from cubicweb.common.appobject import AppObject
-from cubicweb.common.registerers import accepts_registerer, yes_registerer
-from cubicweb.common.selectors import yes
+from cubicweb.selectors import yes
+from cubicweb.appobject import AppObject
-class autoid(autoselectors):
+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))
@@ -195,8 +194,7 @@
class Hook(AppObject):
__metaclass__ = autoid
__registry__ = 'hooks'
- __registerer__ = accepts_registerer
- __selectors__ = (yes,)
+ __select__ = yes()
# set this in derivated classes
events = None
accepts = None
@@ -245,7 +243,6 @@
raise NotImplementedError
class SystemHook(Hook):
- __registerer__ = yes_registerer
accepts = ('',)
from logging import getLogger
--- a/server/msplanner.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/msplanner.py Mon Mar 02 21:03:54 2009 +0100
@@ -64,7 +64,7 @@
from rql.nodes import VariableRef, Comparison, Relation, Constant, Exists, Variable
from cubicweb import server
-from cubicweb.common.utils import make_uid
+from cubicweb.utils import make_uid
from cubicweb.server.utils import cleanup_solutions
from cubicweb.server.ssplanner import SSPlanner, OneFetchStep, add_types_restriction
from cubicweb.server.mssteps import *
--- a/server/repository.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/repository.py Mon Mar 02 21:03:54 2009 +0100
@@ -491,6 +491,9 @@
try:
if session.execute('EUser X WHERE X login %(login)s', {'login': login}):
return False
+ if session.execute('EUser X WHERE X use_email C, C address %(login)s',
+ {'login': login}):
+ return False
# we have to create the user
user = self.vreg.etype_class('EUser')(session, None)
if isinstance(password, unicode):
@@ -502,6 +505,11 @@
self.glob_add_entity(session, user)
session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"',
{'x': user.eid})
+ # FIXME this does not work yet
+ if '@' in login:
+ session.execute('INSERT EmailAddress X: X address "%(login)s", '
+ 'U primary_email X, U use_email X WHERE U login "%(login)s"',
+ {'login':login})
session.commit()
finally:
session.close()
--- a/server/serverconfig.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/serverconfig.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""server.serverconfig definition
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
--- a/server/session.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/session.py Mon Mar 02 21:03:54 2009 +0100
@@ -18,7 +18,7 @@
from cubicweb import RequestSessionMixIn, Binary
from cubicweb.dbapi import ConnectionProperties
-from cubicweb.common.utils import make_uid
+from cubicweb.utils import make_uid
from cubicweb.server.rqlrewrite import RQLRewriter
_ETYPE_PYOBJ_MAP = { bool: 'Boolean',
--- a/server/sources/extlite.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/sources/extlite.py Mon Mar 02 21:03:54 2009 +0100
@@ -203,7 +203,7 @@
"""add a new entity to the source"""
raise NotImplementedError()
- def local_update_entity(self, session, entity):
+ def local_update_entity(self, session, entity, attrs=None):
"""update an entity in the source
This is not provided as update_entity implementation since usually
@@ -211,7 +211,8 @@
and the source implementor may use this method if necessary
"""
cu = session.pool[self.uri]
- attrs = self.sqladapter.preprocess_entity(entity)
+ if attrs is None:
+ attrs = self.sqladapter.preprocess_entity(entity)
sql = self.sqladapter.sqlgen.update(str(entity.e_schema), attrs, ['eid'])
cu.execute(sql, attrs)
--- a/server/test/data/schema/Affaire.py Fri Feb 27 09:59:53 2009 +0100
+++ b/server/test/data/schema/Affaire.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,6 +1,6 @@
from cubicweb.schema import format_constraint
-class Affaire(EntityType):
+class Affaire(WorkflowableEntityType):
permissions = {
'read': ('managers',
ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
@@ -13,9 +13,6 @@
constraints=[SizeConstraint(16)])
sujet = String(fulltextindexed=True,
constraints=[SizeConstraint(256)])
- in_state = SubjectRelation('State', cardinality='1*',
- constraints=[RQLConstraint('O state_of ET, ET name "Affaire"')],
- description=_('account state'))
descr_format = String(meta=True, internationalizable=True,
default='text/rest', constraints=[format_constraint])
descr = String(fulltextindexed=True,
@@ -23,8 +20,7 @@
duration = Int()
invoiced = Int()
-
- wf_info_for = ObjectRelation('TrInfo', cardinality='1*', composite='object')
+
depends_on = SubjectRelation('Affaire')
require_permission = SubjectRelation('EPermission')
--- a/server/test/runtests.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
- import sys, os
- main(os.path.dirname(sys.argv[0]) or '.')
--- a/sobjects/notification.py Fri Feb 27 09:59:53 2009 +0100
+++ b/sobjects/notification.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""some hooks and views to handle notification on entity's changes
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -16,12 +16,11 @@
return 'XXX'
from logilab.common.textutils import normalize_text
+from logilab.common.deprecation import class_renamed
from cubicweb import RegistryException
-from cubicweb.common.view import EntityView
-from cubicweb.common.appobject import Component
-from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.selectors import accept
+from cubicweb.selectors import implements, yes
+from cubicweb.view import EntityView, Component
from cubicweb.common.mail import format_mail
from cubicweb.server.pool import PreCommitOperation
@@ -37,9 +36,7 @@
email addresses specified in the configuration are used
"""
id = 'recipients_finder'
- __registerer__ = accepts_registerer
- __selectors__ = (accept,)
- accepts = ('Any',)
+ __select__ = yes()
user_rql = ('Any X,E,A WHERE X is EUser, X in_state S, S name "activated",'
'X primary_email E, E address A')
@@ -135,13 +132,10 @@
* set a content attribute to define the content of the email (unless you
override call)
"""
- accepts = ()
- id = None
msgid_timestamp = True
def recipients(self):
- finder = self.vreg.select_component('recipients_finder',
- req=self.req, rset=self.rset)
+ finder = self.vreg.select_component('recipients_finder', self.req, self.rset)
return finder.recipients()
def subject(self):
@@ -180,8 +174,7 @@
self._kwargs = kwargs
recipients = self.recipients()
if not recipients:
- self.info('skipping %s%s notification which has no recipients',
- self.id, self.accepts)
+ self.info('skipping %s notification, no recipients', self.id)
return
if not isinstance(recipients[0], tuple):
from warnings import warn
@@ -260,9 +253,16 @@
""")
-class ContentAddedMixIn(object):
- """define emailcontent view for entity types for which you want to be notified
- """
+###############################################################################
+# Actual notification views. #
+# #
+# disable them at the recipients_finder level if you don't want them #
+###############################################################################
+
+# XXX should be based on dc_title/dc_description, no?
+
+class ContentAddedView(NotificationView):
+ __abstract__ = True
id = 'notif_after_add_entity'
msgid_timestamp = False
message = _('new')
@@ -273,33 +273,25 @@
url: %(url)s
"""
-
-###############################################################################
-# Actual notification views. #
-# #
-# disable them at the recipients_finder level if you don't want them #
-###############################################################################
-
-# XXX should be based on dc_title/dc_description, no?
-
-class NormalizedTextView(ContentAddedMixIn, NotificationView):
+
def context(self, **kwargs):
entity = self.entity(0, 0)
content = entity.printable_value(self.content_attr, format='text/plain')
if content:
contentformat = getattr(entity, self.content_attr + '_format', 'text/rest')
content = normalize_text(content, 80, rest=contentformat=='text/rest')
- return super(NormalizedTextView, self).context(content=content, **kwargs)
+ return super(ContentAddedView, self).context(content=content, **kwargs)
def subject(self):
entity = self.entity(0, 0)
return u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema),
entity.eid, self.user_login())
+NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView)
-class CardAddedView(NormalizedTextView):
+class CardAddedView(ContentAddedView):
"""get notified from new cards"""
- accepts = ('Card',)
+ __select__ = implements('Card')
content_attr = 'synopsis'
--- a/sobjects/supervising.py Fri Feb 27 09:59:53 2009 +0100
+++ b/sobjects/supervising.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,13 +2,14 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from cubicweb import UnknownEid
-from cubicweb.common.view import ComponentMixIn, StartupView
+from cubicweb.selectors import none_rset
+from cubicweb.view import Component
from cubicweb.common.mail import format_mail
from cubicweb.server.hooksmanager import Hook
from cubicweb.server.hookhelper import SendMailOp
@@ -137,9 +138,10 @@
yield change
-class SupervisionEmailView(ComponentMixIn, StartupView):
+class SupervisionEmailView(Component):
"""view implementing the email API for data changes supervision notification
"""
+ __select__ = none_rset()
id = 'supervision_notif'
def recipients(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/bootstrap_cubes Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,1 @@
+file, tag
--- a/test/data/bootstrap_packages Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/entities.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,15 @@
+from cubicweb.entities import AnyEntity, fetch_config
+
+class Personne(AnyEntity):
+ """customized class forne Person entities"""
+ id = 'Personne'
+ fetch_attrs, fetch_order = fetch_config(['nom', 'prenom'])
+ rest_attr = 'nom'
+
+
+class Societe(AnyEntity):
+ id = 'Societe'
+ fetch_attrs = ('nom',)
+
+class Note(AnyEntity):
+ id = 'Note'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/data/schema.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,27 @@
+class Personne(EntityType):
+ nom = String(required=True)
+ prenom = String()
+ type = String()
+ travaille = SubjectRelation('Societe')
+ evaluee = SubjectRelation(('Note', 'Personne'))
+ connait = SubjectRelation('Personne', symetric=True)
+
+class Societe(EntityType):
+ nom = String()
+ evaluee = SubjectRelation('Note')
+
+class Note(EntityType):
+ type = String()
+ ecrit_par = SubjectRelation('Personne')
+
+class SubNote(Note):
+ __specializes_schema__ = True
+ description = String()
+
+class tags(RelationDefinition):
+ subject = 'Tag'
+ object = ('Personne', 'Note')
+
+class evaluee(RelationDefinition):
+ subject = 'EUser'
+ object = 'Note'
--- a/test/unittest_cwconfig.py Fri Feb 27 09:59:53 2009 +0100
+++ b/test/unittest_cwconfig.py Mon Mar 02 21:03:54 2009 +0100
@@ -68,7 +68,8 @@
self.assertEquals([unabsolutize(p) for p in self.config.vregistry_path()],
['entities', 'web/views', 'sobjects',
'file/entities.py', 'file/views', 'file/hooks.py',
- 'email/entities.py', 'email/views', 'email/hooks.py'])
+ 'email/entities.py', 'email/views', 'email/hooks.py',
+ 'test/data/entities.py'])
if __name__ == '__main__':
unittest_main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_entity.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,480 @@
+# -*- coding: utf-8 -*-
+"""unit tests for cubicweb.web.views.entities module"""
+
+from cubicweb.devtools.apptest import EnvBasedTC
+
+from mx.DateTime import DateTimeType, now
+
+from cubicweb import Binary
+from cubicweb.common.mttransforms import HAS_TAL
+
+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)
+
+ def test_boolean_value(self):
+ e = self.etype_instance('EUser')
+ self.failUnless(e)
+
+ def test_yams_inheritance(self):
+ from entities import Note
+ e = self.etype_instance('SubNote')
+ self.assertIsInstance(e, Note)
+ e2 = self.etype_instance('SubNote')
+ self.assertIs(e.__class__, e2.__class__)
+
+ def test_has_eid(self):
+ e = self.etype_instance('EUser')
+ self.assertEquals(e.eid, None)
+ self.assertEquals(e.has_eid(), False)
+ e.eid = 'X'
+ self.assertEquals(e.has_eid(), False)
+ e.eid = 0
+ self.assertEquals(e.has_eid(), True)
+ e.eid = 2
+ 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')
+ 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.copy_relations(oe.eid)
+ self.assertEquals(len(e.ecrit_par), 1)
+ self.assertEquals(e.ecrit_par[0].eid, p.eid)
+ self.assertEquals(len(e.reverse_tags), 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')
+ 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.copy_relations(oe.eid)
+ self.failIf(e.ecrit_par)
+ self.failUnless(oe.ecrit_par)
+
+ def test_copy_with_composite(self):
+ user = self.user()
+ adeleid = self.execute('INSERT EmailAddress X: X address "toto@logilab.org", U use_email X WHERE U login "admin"')[0][0]
+ e = self.entity('Any X WHERE X eid %(x)s', {'x':user.eid}, 'x')
+ self.assertEquals(e.use_email[0].address, "toto@logilab.org")
+ self.assertEquals(e.use_email[0].eid, adeleid)
+ usereid = self.execute('INSERT EUser X: X login "toto", X upassword "toto", X in_group G, X in_state S '
+ 'WHERE G name "users", S name "activated"')[0][0]
+ e = self.entity('Any X WHERE X eid %(x)s', {'x':usereid}, 'x')
+ e.copy_relations(user.eid)
+ self.failIf(e.use_email)
+ self.failIf(e.primary_email)
+
+ def test_copy_with_non_initial_state(self):
+ user = self.user()
+ eid = self.execute('INSERT EUser X: X login "toto", X upassword %(pwd)s, X in_group G WHERE G name "users"',
+ {'pwd': 'toto'})[0][0]
+ self.commit()
+ self.execute('SET X in_state S WHERE X eid %(x)s, S name "deactivated"', {'x': eid}, 'x')
+ self.commit()
+ eid2 = self.execute('INSERT EUser X: X login "tutu", X upassword %(pwd)s', {'pwd': 'toto'})[0][0]
+ e = self.entity('Any X WHERE X eid %(x)s', {'x': eid2}, 'x')
+ e.copy_relations(eid)
+ self.commit()
+ e.clear_related_cache('in_state', 'subject')
+ self.assertEquals(e.state, 'activated')
+
+ def test_related_cache_both(self):
+ 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(), [])
+ email = user.primary_email[0]
+ self.assertEquals(sorted(user._related_cache), ['primary_email_subject'])
+ self.assertEquals(email._related_cache.keys(), ['primary_email_object'])
+ groups = user.in_group
+ self.assertEquals(sorted(user._related_cache), ['in_group_subject', 'primary_email_subject'])
+ for group in groups:
+ 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')
+ for tag in u'abcd':
+ self.add_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)
+
+
+ def test_fetch_rql(self):
+ user = self.user()
+ Personne = self.vreg.etype_class('Personne')
+ Societe = self.vreg.etype_class('Societe')
+ Note = self.vreg.etype_class('Note')
+ peschema = Personne.e_schema
+ seschema = Societe.e_schema
+ peschema.subject_relation('travaille').set_rproperty(peschema, seschema, 'cardinality', '1*')
+ peschema.subject_relation('connait').set_rproperty(peschema, peschema, 'cardinality', '11')
+ peschema.subject_relation('evaluee').set_rproperty(peschema, Note.e_schema, 'cardinality', '1*')
+ seschema.subject_relation('evaluee').set_rproperty(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 WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC')
+ pfetch_attrs = Personne.fetch_attrs
+ sfetch_attrs = Societe.fetch_attrs
+ try:
+ # testing unknown attributes
+ Personne.fetch_attrs = ('bloug', 'beep')
+ self.assertEquals(Personne.fetch_rql(user), 'Any X WHERE X is Personne')
+ # testing one non final relation
+ Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
+ 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 two non final relations
+ Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee')
+ self.assertEquals(Personne.fetch_rql(user),
+ 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, '
+ 'X prenom AB, X travaille AC, AC nom AD, X evaluee AE, AE modification_date AF')
+ # testing one non final relation with recursion
+ Personne.fetch_attrs = ('nom', 'prenom', 'travaille')
+ Societe.fetch_attrs = ('nom', 'evaluee')
+ self.assertEquals(Personne.fetch_rql(user),
+ 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, X prenom AB, '
+ 'X travaille AC, AC nom AD, AC evaluee AE, AE modification_date AF'
+ )
+ # testing symetric 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.subject_relation('travaille').set_rproperty(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.subject_relation('travaille').set_rproperty(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
+ finally:
+ Personne.fetch_attrs = pfetch_attrs
+ Societe.fetch_attrs = sfetch_attrs
+
+ def test_related_rql(self):
+ from cubicweb.entities import fetch_config
+ Personne = self.vreg.etype_class('Personne')
+ Note = self.vreg.etype_class('Note')
+ Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type'))
+ Note.fetch_attrs, Note.fetch_order = fetch_config(('type',))
+ aff = self.add_entity('Personne', nom=u'pouet')
+ self.assertEquals(aff.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')
+ Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', ))
+ # XXX
+ self.assertEquals(aff.related_rql('evaluee'),
+ 'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA')
+
+ def test_entity_unrelated(self):
+ p = self.add_entity('Personne', nom=u'di mascio', prenom=u'adrien')
+ e = self.add_entity('Tag', name=u'x')
+ rschema = e.e_schema.subject_relation('tags')
+ related = [r.eid for r in e.tags]
+ self.failUnlessEqual(related, [])
+ unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+ self.failUnless(p.eid in unrelated)
+ self.execute('SET X tags Y WHERE X is Tag, Y is Personne')
+ e = self.entity('Any X WHERE X is Tag')
+ unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+ self.failIf(p.eid in unrelated)
+
+ def test_entity_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'di mascio', prenom=u'gwen')
+ rschema = e.e_schema.subject_relation('tags')
+ self.assertEquals(len(e.vocabulary(rschema, 'subject', limit=1)),
+ 1)
+
+ def test_new_entity_unrelated(self):
+ e = self.etype_instance('EUser')
+ rschema = e.e_schema.subject_relation('in_group')
+ unrelated = [reid for rview, reid in e.vocabulary(rschema, 'subject')]
+ # should be default groups but owners, i.e. managers, users, guests
+ self.assertEquals(len(unrelated), 3)
+
+
+ def test_rtags_expansion(self):
+ from cubicweb.entities import AnyEntity
+ class Personne(AnyEntity):
+ id = 'Personne'
+ __rtags__ = {
+ ('travaille', 'Societe', 'subject') : set(('primary',)),
+ ('evaluee', '*', 'subject') : set(('secondary',)),
+ 'ecrit_par' : set(('inlineview',)),
+ }
+ self.vreg.register_vobject_class(Personne)
+ rtags = Personne.rtags
+ self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('secondary', 'link')))
+ self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), False)
+ self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+ self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+ self.assertEquals(rtags.get_tags('ecrit_par', 'Note', 'object'), set(('inlineview', 'link')))
+ self.assertEquals(rtags.is_inlined('ecrit_par', 'Note', 'object'), True)
+ class Personne2(Personne):
+ id = 'Personne'
+ __rtags__ = {
+ ('evaluee', 'Note', 'subject') : set(('inlineview',)),
+ }
+ self.vreg.register_vobject_class(Personne2)
+ rtags = Personne2.rtags
+ self.assertEquals(rtags.get_tags('evaluee', 'Note', 'subject'), set(('inlineview', 'link')))
+ self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True)
+ self.assertEquals(rtags.get_tags('evaluee', 'Personne', 'subject'), set(('secondary', 'link')))
+ self.assertEquals(rtags.is_inlined('evaluee', 'Personne', 'subject'), False)
+
+ def test_relations_by_category(self):
+ e = self.etype_instance('EUser')
+ def rbc(iterable):
+ return [(rschema.type, x) for rschema, tschemas, x in iterable]
+ self.assertEquals(rbc(e.relations_by_category('primary')),
+ [('login', 'subject'), ('upassword', 'subject'),
+ ('in_group', 'subject'), ('in_state', 'subject'),
+ ('eid', 'subject'),])
+ # firstname and surname are put in secondary category in views.entities.EUserEntity
+ self.assertListEquals(rbc(e.relations_by_category('secondary')),
+ [('firstname', 'subject'), ('surname', 'subject')])
+ self.assertListEquals(rbc(e.relations_by_category('generic')),
+ [('primary_email', 'subject'),
+ ('evaluee', 'subject'),
+ ('for_user', 'object')])
+ # owned_by is defined both as subject and object relations on EUser
+ self.assertListEquals(rbc(e.relations_by_category('generated')),
+ [('last_login_time', 'subject'),
+ ('created_by', 'subject'),
+ ('creation_date', 'subject'),
+ ('is', 'subject'),
+ ('is_instance_of', 'subject'),
+ ('modification_date', 'subject'),
+ ('owned_by', 'subject'),
+ ('created_by', 'object'),
+ ('wf_info_for', 'object'),
+ ('owned_by', 'object'),
+ ('bookmarked_by', 'object')])
+ e = self.etype_instance('Personne')
+ self.assertListEquals(rbc(e.relations_by_category('primary')),
+ [('nom', 'subject'), ('eid', 'subject')])
+ self.assertListEquals(rbc(e.relations_by_category('secondary')),
+ [('prenom', 'subject'),
+ ('type', 'subject'),])
+ self.assertListEquals(rbc(e.relations_by_category('generic')),
+ [('travaille', 'subject'),
+ ('evaluee', 'subject'),
+ ('connait', 'subject'),
+ ('ecrit_par', 'object'),
+ ('evaluee', 'object'),
+ ('tags', 'object')])
+ self.assertListEquals(rbc(e.relations_by_category('generated')),
+ [('created_by', 'subject'),
+ ('creation_date', 'subject'),
+ ('is', 'subject'),
+ ('is_instance_of', 'subject'),
+ ('modification_date', 'subject'),
+ ('owned_by', 'subject')])
+
+
+ def test_printable_value_string(self):
+ e = self.add_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/egroup/managers">*ReST*</a></p>\n')
+ e['content'] = 'du <em>html</em> <ref rql="EUser X">users</ref>'
+ e['content_format'] = 'text/html'
+ self.assertEquals(e.printable_value('content'),
+ 'du <em>html</em> <a href="http://testing.fr/cubicweb/view?rql=EUser%20X">users</a>')
+ e['content'] = 'du *texte*'
+ e['content_format'] = 'text/plain'
+ self.assertEquals(e.printable_value('content'),
+ '<p>\ndu *texte*\n</p>')
+ e['title'] = 'zou'
+ #e = self.etype_instance('Task')
+ e['content'] = '''\
+a title
+=======
+du :eid:`1:*ReST*`'''
+ e['content_format'] = 'text/rest'
+ self.assertEquals(e.printable_value('content', format='text/plain'),
+ e['content'])
+
+ e['content'] = u'<b>yo (zou éà ;)</b>'
+ e['content_format'] = 'text/html'
+ self.assertEquals(e.printable_value('content', format='text/plain').strip(),
+ u'**yo (zou éà ;)**')
+ if HAS_TAL:
+ e['content'] = '<h1 tal:content="self/title">titre</h1>'
+ e['content_format'] = 'text/cubicweb-page-template'
+ self.assertEquals(e.printable_value('content'),
+ '<h1>zou</h1>')
+
+
+ def test_printable_value_bytes(self):
+ e = self.add_entity('File', data=Binary('lambda x: 1'), data_format=u'text/x-python',
+ data_encoding=u'ascii', name=u'toto.py')
+ from cubicweb.common 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="mf">1</span>
+</pre></div>
+''')
+ else:
+ self.assertEquals(e.printable_value('data'),
+ '''<pre class="python">
+<span style="color: #C00000;">lambda</span> <span style="color: #000000;">x</span><span style="color: #0000C0;">:</span> <span style="color: #0080C0;">1</span>
+</pre>
+''')
+
+ e = self.add_entity('File', data=Binary('*héhéhé*'), data_format=u'text/rest',
+ data_encoding=u'utf-8', 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>',
+ content_format=u'text/html')
+ tidy = lambda x: x.replace('\n', '')
+ self.assertEquals(tidy(e.printable_value('content')),
+ '<div>R&D<br/></div>')
+ e['content'] = u'yo !! R&D <div> pas fermé'
+ self.assertEquals(tidy(e.printable_value('content')),
+ u'yo !! R&D <div> pas fermé</div>')
+ e['content'] = u'R&D'
+ self.assertEquals(tidy(e.printable_value('content')), u'R&D')
+ e['content'] = u'R&D;'
+ self.assertEquals(tidy(e.printable_value('content')), u'R&D;')
+ e['content'] = u'yo !! R&D <div> pas fermé'
+ self.assertEquals(tidy(e.printable_value('content')),
+ u'yo !! R&D <div> pas fermé</div>')
+ e['content'] = u'été <div> été'
+ self.assertEquals(tidy(e.printable_value('content')),
+ u'été <div> été</div>')
+ e['content'] = u'C'est un exemple sérieux'
+ self.assertEquals(tidy(e.printable_value('content')),
+ u"C'est un exemple sérieux")
+ # make sure valid xhtml is left untouched
+ e['content'] = u'<div>R&D<br/></div>'
+ self.assertEquals(e.printable_value('content'), e['content'])
+ e['content'] = u'<div>été</div>'
+ self.assertEquals(e.printable_value('content'), e['content'])
+ e['content'] = u'été'
+ self.assertEquals(e.printable_value('content'), e['content'])
+
+
+ def test_entity_formatted_attrs(self):
+ e = self.etype_instance('EUser')
+ self.assertEquals(e.formatted_attrs(), [])
+ e = self.etype_instance('File')
+ self.assertEquals(e.formatted_attrs(), ['description'])
+
+
+ def test_fulltextindex(self):
+ e = self.etype_instance('File')
+ e['name'] = 'an html file'
+ e['description'] = 'du <em>html</em>'
+ e['description_format'] = 'text/html'
+ e['data'] = Binary('some <em>data</em>')
+ e['data_format'] = 'text/html'
+ e['data_encoding'] = 'ascii'
+ 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')
+ 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):
+ self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
+ self.commit()
+ try:
+ eid = self.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]
+ trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
+ trinfo.complete()
+ self.failUnless(trinfo.relation_cached('from_state', 'subject'))
+ self.failUnless(trinfo.relation_cached('to_state', 'subject'))
+ self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
+ # check with a missing relation
+ eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
+ 'X to_state S2 WHERE '
+ 'U login "admin", S2 name "activated"')[0][0]
+ trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
+ trinfo.complete()
+ self.failUnless(isinstance(trinfo.creation_date, DateTimeType))
+ self.failUnless(trinfo.relation_cached('from_state', 'subject'))
+ self.failUnless(trinfo.relation_cached('to_state', 'subject'))
+ self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
+ self.assertEquals(trinfo.from_state, [])
+ finally:
+ self.rollback()
+ self.execute('DELETE RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
+ self.commit()
+
+ def test_request_cache(self):
+ req = self.request()
+ user = self.entity('EUser X WHERE X login "admin"', req=req)
+ state = user.in_state[0]
+ samestate = self.entity('State X WHERE X name "activated"', req=req)
+ self.failUnless(state is samestate)
+
+ def test_rest_path(self):
+ note = self.add_entity('Note', type=u'z')
+ self.assertEquals(note.rest_path(), 'note/%s' % note.eid)
+ # unique attr
+ tag = self.add_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')
+ self.assertEquals(person.rest_path(), 'personne/doe')
+ # ambiguity test
+ person2 = self.add_entity('Personne', prenom=u'remi', nom=u'doe')
+ 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')
+ self.assertEquals(card1.rest_path(), 'card/eid/%s' % card1.eid)
+ card2 = self.add_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')
+ self.assertEquals(person.prenom, u'adrien')
+ self.assertEquals(person.nom, u'di mascio')
+ person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+ person = self.entity('Personne P') # XXX retreival needed ?
+ self.assertEquals(person.prenom, u'sylvain')
+ self.assertEquals(person.nom, u'thénault')
+
+ def test_metainformation(self):
+ note = self.add_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)
+ metainf['source'] = metainf['source'].copy()
+ metainf['source']['base-url'] = 'http://cubicweb2.com/'
+ self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/%s' % note.eid)
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
+
--- a/test/unittest_rset.py Fri Feb 27 09:59:53 2009 +0100
+++ b/test/unittest_rset.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,8 +1,11 @@
# coding: utf-8
"""unit tests for module cubicweb.common.utils"""
+from __future__ import with_statement
from logilab.common.testlib import TestCase, unittest_main
+
from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.selectors import traced_selection
from urlparse import urlsplit
from rql import parse
@@ -224,7 +227,8 @@
self.assertEquals(e.col, 0)
self.assertEquals(e['title'], 'zou')
self.assertRaises(KeyError, e.__getitem__, 'path')
- self.assertEquals(e.view('text'), 'zou')
+ with traced_selection():
+ self.assertEquals(e.view('text'), 'zou')
self.assertEquals(pprelcachedict(e._related_cache), [])
e = rset.get_entity(0, 1)
--- a/test/unittest_schema.py Fri Feb 27 09:59:53 2009 +0100
+++ b/test/unittest_schema.py Mon Mar 02 21:03:54 2009 +0100
@@ -134,7 +134,6 @@
self.assertListEquals([basename(f) for f in schema_files], ['Bookmark.py'])
def test_knownValues_load_schema(self):
- """read an url and return a Schema instance"""
schema = loader.load(config)
self.assert_(isinstance(schema, CubicWebSchema))
self.assertEquals(schema.name, 'data')
@@ -145,10 +144,10 @@
'ECache', 'EConstraint', 'EConstraintType', 'EEType',
'EFRDef', 'EGroup', 'EmailAddress', 'ENFRDef',
'EPermission', 'EProperty', 'ERType', 'EUser',
- 'Float', 'Int', 'Interval',
- 'Password',
+ 'File', 'Float', 'Image', 'Int', 'Interval', 'Note',
+ 'Password', 'Personne',
'RQLExpression',
- 'State', 'String', 'Time',
+ 'Societe', 'State', 'String', 'SubNote', 'Tag', 'Time',
'Transition', 'TrInfo']
self.assertListEquals(entities, sorted(expected_entities))
relations = [str(r) for r in schema.relations()]
@@ -157,13 +156,13 @@
'allowed_transition', 'bookmarked_by', 'canonical',
'cardinality', 'comment', 'comment_format',
- 'composite', 'condition', 'constrained_by', 'content',
+ 'composite', 'condition', 'connait', 'constrained_by', 'content',
'content_format', 'created_by', 'creation_date', 'cstrtype',
- 'defaultval', 'delete_permission', 'description',
- 'description_format', 'destination_state',
+ 'data', 'data_encoding', 'data_format', 'defaultval', 'delete_permission',
+ 'description', 'description_format', 'destination_state',
- 'eid', 'expression', 'exprtype',
+ 'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype',
'final', 'firstname', 'for_user',
'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed',
@@ -176,17 +175,17 @@
'mainvars', 'meta', 'modification_date',
- 'name',
+ 'name', 'nom',
'ordernum', 'owned_by',
- 'path', 'pkey', 'primary_email',
+ 'path', 'pkey', 'prenom', 'primary_email',
'read_permission', 'relation_type', 'require_group',
'specializes', 'state_of', 'surname', 'symetric', 'synopsis',
- 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of',
+ 'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type',
'upassword', 'update_permission', 'use_email',
@@ -199,7 +198,7 @@
eschema = schema.eschema('EUser')
rels = sorted(str(r) for r in eschema.subject_relations())
self.assertListEquals(rels, ['created_by', 'creation_date', 'eid',
- 'firstname', 'has_text', 'identity',
+ 'evaluee', 'firstname', 'has_text', 'identity',
'in_group', 'in_state', 'is',
'is_instance_of', 'last_login_time',
'login', 'modification_date', 'owned_by',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_selectors.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,102 @@
+"""unit tests for selectors mechanism
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.vregistry import Selector, AndSelector, OrSelector
+from cubicweb.selectors import implements
+
+from cubicweb.interfaces import IDownloadable
+
+class _1_(Selector):
+ def __call__(self, *args, **kwargs):
+ return 1
+
+class _0_(Selector):
+ def __call__(self, *args, **kwargs):
+ return 0
+
+def _2_(*args, **kwargs):
+ return 2
+
+
+class SelectorsTC(TestCase):
+ def test_basic_and(self):
+ selector = _1_() & _1_()
+ self.assertEquals(selector(None), 2)
+ selector = _1_() & _0_()
+ self.assertEquals(selector(None), 0)
+ selector = _0_() & _1_()
+ self.assertEquals(selector(None), 0)
+
+ def test_basic_or(self):
+ selector = _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _1_() | _0_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _0_()
+ self.assertEquals(selector(None), 0)
+
+ def test_selector_and_function(self):
+ selector = _1_() & _2_
+ self.assertEquals(selector(None), 3)
+ selector = _2_ & _1_()
+ self.assertEquals(selector(None), 3)
+
+ def test_three_and(self):
+ selector = _1_() & _1_() & _1_()
+ self.assertEquals(selector(None), 3)
+ selector = _1_() & _0_() & _1_()
+ self.assertEquals(selector(None), 0)
+ selector = _0_() & _1_() & _1_()
+ self.assertEquals(selector(None), 0)
+
+ def test_three_or(self):
+ selector = _1_() | _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _1_() | _0_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _1_() | _1_()
+ self.assertEquals(selector(None), 1)
+ selector = _0_() | _0_() | _0_()
+ self.assertEquals(selector(None), 0)
+
+ def test_composition(self):
+ selector = (_1_() & _1_()) & (_1_() & _1_())
+ self.failUnless(isinstance(selector, AndSelector))
+ self.assertEquals(len(selector.selectors), 4)
+ self.assertEquals(selector(None), 4)
+ selector = (_1_() & _0_()) | (_1_() & _1_())
+ self.failUnless(isinstance(selector, OrSelector))
+ self.assertEquals(len(selector.selectors), 2)
+ self.assertEquals(selector(None), 2)
+
+ def test_search_selectors(self):
+ sel = implements('something')
+ self.assertIs(sel.search_selector(implements), sel)
+ csel = AndSelector(sel, Selector())
+ self.assertIs(csel.search_selector(implements), sel)
+ csel = AndSelector(Selector(), sel)
+ self.assertIs(csel.search_selector(implements), sel)
+
+from cubicweb.devtools.testlib import EnvBasedTC
+
+class ImplementsSelectorTC(EnvBasedTC):
+ def test_etype_priority(self):
+ req = self.request()
+ cls = self.vreg.etype_class('File')
+ anyscore = implements('Any').score_class(cls, req)
+ idownscore = implements(IDownloadable).score_class(cls, req)
+ self.failUnless(idownscore > anyscore, (idownscore, anyscore))
+ filescore = implements('File').score_class(cls, req)
+ self.failUnless(filescore > idownscore, (filescore, idownscore))
+
+if __name__ == '__main__':
+ unittest_main()
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_utils.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,47 @@
+"""unit tests for module cubicweb.common.utils"""
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList
+
+
+class MakeUidTC(TestCase):
+ def test_1(self):
+ self.assertNotEquals(make_uid('xyz'), make_uid('abcd'))
+ self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
+
+ def test_2(self):
+ d = {}
+ while len(d)<10000:
+ uid = make_uid('xyz')
+ if d.has_key(uid):
+ self.fail(len(d))
+ d[uid] = 1
+
+
+class UStringIOTC(TestCase):
+ def test_boolean_value(self):
+ self.assert_(UStringIO())
+
+
+class SizeConstrainedListTC(TestCase):
+
+ def test_append(self):
+ l = SizeConstrainedList(10)
+ for i in xrange(12):
+ l.append(i)
+ self.assertEquals(l, range(2, 12))
+
+ def test_extend(self):
+ testdata = [(range(5), range(5)),
+ (range(10), range(10)),
+ (range(12), range(2, 12)),
+ ]
+ for extension, expected in testdata:
+ l = SizeConstrainedList(10)
+ l.extend(extension)
+ yield self.assertEquals, l, expected
+
+
+if __name__ == '__main__':
+ unittest_main()
--- a/test/unittest_vregistry.py Fri Feb 27 09:59:53 2009 +0100
+++ b/test/unittest_vregistry.py Mon Mar 02 21:03:54 2009 +0100
@@ -5,7 +5,9 @@
from cubicweb import CW_SOFTWARE_ROOT as BASE
from cubicweb.vregistry import VObject
from cubicweb.cwvreg import CubicWebRegistry, UnknownProperty
-from cubicweb.cwconfig import CubicWebConfiguration
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.entities.lib import Card
+from cubicweb.interfaces import IMileStone
class YesSchema:
def __contains__(self, something):
@@ -14,14 +16,16 @@
class VRegistryTC(TestCase):
def setUp(self):
- config = CubicWebConfiguration('data')
+ config = TestServerConfiguration('data')
self.vreg = CubicWebRegistry(config)
- self.vreg.schema = YesSchema()
+ config.bootstrap_cubes()
+ self.vreg.schema = config.load_schema()
def test_load(self):
self.vreg.load_file(join(BASE, 'web', 'views'), 'euser.py')
self.vreg.load_file(join(BASE, 'web', 'views'), 'baseviews.py')
- fpvc = [v for v in self.vreg.registry_objects('views', 'primary') if v.accepts[0] == 'EUser'][0]
+ fpvc = [v for v in self.vreg.registry_objects('views', 'primary')
+ if v.__module__ == 'cubicweb.web.views.euser'][0]
fpv = fpvc(None, None)
# don't want a TypeError due to super call
self.assertRaises(AttributeError, fpv.render_entity_attributes, None, None)
@@ -32,17 +36,31 @@
# check loading baseviews after idownloadable isn't kicking interface based views
self.assertEquals(len(self.vreg['views']['primary']), 2)
- def test_autoselectors(self):
+ def test___selectors__compat(self):
myselector1 = lambda *args: 1
myselector2 = lambda *args: 1
class AnAppObject(VObject):
__selectors__ = (myselector1, myselector2)
- self.assertEquals(AnAppObject.__select__(), 2)
+ AnAppObject.build___select__()
+ self.assertEquals(AnAppObject.__select__(AnAppObject), 2)
def test_properties(self):
self.failIf('system.version.cubicweb' in self.vreg['propertydefs'])
self.failUnless(self.vreg.property_info('system.version.cubicweb'))
self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key')
+
+ def test_load_subinterface_based_vobjects(self):
+ self.vreg.reset()
+ self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')])
+ # check progressbar was kicked
+ self.failIf(self.vreg['views'].get('progressbar'))
+ class MyCard(Card):
+ __implements__ = (IMileStone,)
+ self.vreg.reset()
+ self.vreg.register_vobject_class(MyCard)
+ self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')])
+ # check progressbar isn't kicked
+ self.assertEquals(len(self.vreg['views']['progressbar']), 1)
if __name__ == '__main__':
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/utils.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,277 @@
+"""Some utilities for CubicWeb server/clients.
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+import locale
+from md5 import md5
+from time import time
+from random import randint, seed
+
+# initialize random seed from current time
+seed()
+
+def make_uid(key):
+ """forge a unique identifier"""
+ msg = str(key) + "%.10f"%time() + str(randint(0, 1000000))
+ return md5(msg).hexdigest()
+
+def working_hours(mxdate):
+ """
+ Predicate returning True is the date's hour is in working hours (8h->20h)
+ """
+ if mxdate.hour > 7 and mxdate.hour < 21:
+ return True
+ return False
+
+def date_range(begin, end, incr=1, include=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.
+ """
+ date = begin
+ while date <= end:
+ if include is None or include(date):
+ yield date
+ date += 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(fmt), encoding)
+
+
+def dump_class(cls, clsname):
+ """create copy of a class by creating an empty class inheriting
+ from the given cls.
+
+ Those class will be used as place holder for attribute and relation
+ description
+ """
+ # type doesn't accept unicode name
+ # return type.__new__(type, str(clsname), (cls,), {})
+ # __autogenerated__ attribute is just a marker
+ return type(str(clsname), (cls,), {'__autogenerated__': True})
+
+
+def merge_dicts(dict1, dict2):
+ """update a copy of `dict1` with `dict2`"""
+ dict1 = dict(dict1)
+ dict1.update(dict2)
+ return dict1
+
+
+class SizeConstrainedList(list):
+ """simple list that makes sure the list does not get bigger
+ than a given size.
+
+ when the list is full and a new element is added, the first
+ element of the list is removed before appending the new one
+
+ >>> l = SizeConstrainedList(2)
+ >>> l.append(1)
+ >>> l.append(2)
+ >>> l
+ [1, 2]
+ >>> l.append(3)
+ [2, 3]
+ """
+ def __init__(self, maxsize):
+ self.maxsize = maxsize
+
+ def append(self, element):
+ if len(self) == self.maxsize:
+ del self[0]
+ super(SizeConstrainedList, self).append(element)
+
+ def extend(self, sequence):
+ super(SizeConstrainedList, self).extend(sequence)
+ keepafter = len(self) - self.maxsize
+ if keepafter > 0:
+ del self[:keepafter]
+
+ __iadd__ = extend
+
+
+class UStringIO(list):
+ """a file wrapper which automatically encode unicode string to an encoding
+ specifed in the constructor
+ """
+
+ def __nonzero__(self):
+ return True
+
+ def write(self, value):
+ assert isinstance(value, unicode), u"unicode required not %s : %s"\
+ % (type(value).__name__, repr(value))
+ self.append(value)
+
+ def getvalue(self):
+ return u''.join(self)
+
+ def __repr__(self):
+ return '<%s at %#x>' % (self.__class__.__name__, id(self))
+
+
+class HTMLHead(UStringIO):
+ """wraps HTML header's stream
+
+ Request objects use a HTMLHead instance to ease adding of
+ javascripts and stylesheets
+ """
+ js_unload_code = u'jQuery(window).unload(unloadPageData);'
+
+ def __init__(self):
+ super(HTMLHead, self).__init__()
+ self.jsvars = []
+ self.jsfiles = []
+ self.cssfiles = []
+ self.ie_cssfiles = []
+ self.post_inlined_scripts = []
+ self.pagedata_unload = False
+
+
+ def add_raw(self, rawheader):
+ self.write(rawheader)
+
+ def define_var(self, var, value):
+ self.jsvars.append( (var, value) )
+
+ def add_post_inline_script(self, content):
+ self.post_inlined_scripts.append(content)
+
+ def add_onload(self, jscode):
+ self.add_post_inline_script(u"""jQuery(document).ready(function () {
+ %s
+ });""" % jscode)
+
+
+ def add_js(self, jsfile):
+ """adds `jsfile` to the list of javascripts used in the webpage
+
+ This function checks if the file has already been added
+ :param jsfile: the script's URL
+ """
+ if jsfile not in self.jsfiles:
+ self.jsfiles.append(jsfile)
+
+ def add_css(self, cssfile, media):
+ """adds `cssfile` to the list of javascripts used in the webpage
+
+ This function checks if the file has already been added
+ :param cssfile: the stylesheet's URL
+ """
+ if (cssfile, media) not in self.cssfiles:
+ self.cssfiles.append( (cssfile, media) )
+
+ def add_ie_css(self, cssfile, media='all'):
+ """registers some IE specific CSS"""
+ if (cssfile, media) not in self.ie_cssfiles:
+ self.ie_cssfiles.append( (cssfile, media) )
+
+ def add_unload_pagedata(self):
+ """registers onunload callback to clean page data on server"""
+ if not self.pagedata_unload:
+ self.post_inlined_scripts.append(self.js_unload_code)
+ self.pagedata_unload = True
+
+ def getvalue(self, skiphead=False):
+ """reimplement getvalue to provide a consistent (and somewhat browser
+ optimzed cf. http://stevesouders.com/cuzillion) order in external
+ resources declaration
+ """
+ w = self.write
+ # 1/ variable declaration if any
+ if self.jsvars:
+ from simplejson import dumps
+ w(u'<script type="text/javascript">\n')
+ for var, value in self.jsvars:
+ w(u'%s = %s;\n' % (var, dumps(value)))
+ w(u'</script>\n')
+ # 2/ css files
+ for cssfile, media in self.cssfiles:
+ w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+ (media, cssfile))
+ # 3/ ie css if necessary
+ if self.ie_cssfiles:
+ w(u'<!--[if lt IE 8]>\n')
+ for cssfile, media in self.ie_cssfiles:
+ w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
+ (media, cssfile))
+ w(u'<![endif]--> \n')
+ # 4/ js files
+ for jsfile in self.jsfiles:
+ w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
+ # 5/ post inlined scripts (i.e. scripts depending on other JS files)
+ if self.post_inlined_scripts:
+ w(u'<script type="text/javascript">\n')
+ w(u'\n\n'.join(self.post_inlined_scripts))
+ w(u'\n</script>\n')
+ header = super(HTMLHead, self).getvalue()
+ if skiphead:
+ return header
+ return u'<head>\n%s</head>\n' % header
+
+
+class HTMLStream(object):
+ """represents a HTML page.
+
+ This is used my main templates so that HTML headers can be added
+ at any time during the page generation.
+
+ HTMLStream uses the (U)StringIO interface to be compliant with
+ existing code.
+ """
+
+ def __init__(self, req):
+ # stream for <head>
+ self.head = req.html_headers
+ # main stream
+ self.body = UStringIO()
+ self.doctype = u''
+ # xmldecl and html opening tag
+ self.xmldecl = u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding
+ self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
+ 'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
+ 'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
+
+
+ def write(self, data):
+ """StringIO interface: this method will be assigned to self.w
+ """
+ self.body.write(data)
+
+ def getvalue(self):
+ """writes HTML headers, closes </head> tag and writes HTML body"""
+ return u'%s\n%s\n%s\n%s\n%s\n</html>' % (self.xmldecl, self.doctype,
+ self.htmltag,
+ self.head.getvalue(),
+ self.body.getvalue())
+
+
+class AcceptMixIn(object):
+ """Mixin class for vobjects defining the 'accepts' attribute describing
+ a set of supported entity type (Any by default).
+ """
+ # XXX deprecated, no more necessary
+
+
+from logilab.common.deprecation import moved, class_moved
+rql_for_eid = moved('cubicweb.common.uilib', 'rql_for_eid')
+ajax_replace_url = moved('cubicweb.common.uilib', 'ajax_replace_url')
+
+import cubicweb
+Binary = class_moved(cubicweb.Binary)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/view.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,522 @@
+"""abstract views and templates classes for CubicWeb web client
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+
+from logilab.common.deprecation import obsolete
+from logilab.mtconverter import html_escape
+
+from cubicweb import NotAnEntity, NoSelectableObject
+from cubicweb.selectors import (yes, match_user_groups, non_final_entity,
+ nonempty_rset, none_rset)
+from cubicweb.selectors import require_group_compat, accepts_compat
+from cubicweb.appobject import AppRsetObject
+from cubicweb.utils import UStringIO, HTMLStream
+from cubicweb.vregistry import yes_registerer
+from cubicweb.common.registerers import accepts_registerer, priority_registerer, yes_registerer
+
+_ = unicode
+
+
+# robots control
+NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
+NOFOLLOW = u'<meta name="ROBOTS" content="NOFOLLOW" />'
+
+CW_XHTML_EXTENSIONS = '''[
+ <!ATTLIST html xmlns:cubicweb CDATA #FIXED \'http://www.logilab.org/2008/cubicweb\' >
+
+<!ENTITY % coreattrs
+ "id ID #IMPLIED
+ class CDATA #IMPLIED
+ style CDATA #IMPLIED
+ title CDATA #IMPLIED
+
+ cubicweb:sortvalue CDATA #IMPLIED
+ cubicweb:target CDATA #IMPLIED
+ cubicweb:limit CDATA #IMPLIED
+ cubicweb:type CDATA #IMPLIED
+ cubicweb:loadtype CDATA #IMPLIED
+ cubicweb:wdgtype CDATA #IMPLIED
+ cubicweb:initfunc CDATA #IMPLIED
+ cubicweb:inputid CDATA #IMPLIED
+ cubicweb:tindex CDATA #IMPLIED
+ cubicweb:inputname CDATA #IMPLIED
+ cubicweb:value CDATA #IMPLIED
+ cubicweb:required CDATA #IMPLIED
+ cubicweb:accesskey CDATA #IMPLIED
+ cubicweb:maxlength CDATA #IMPLIED
+ cubicweb:variables CDATA #IMPLIED
+ cubicweb:displayactions CDATA #IMPLIED
+ cubicweb:fallbackvid CDATA #IMPLIED
+ cubicweb:vid CDATA #IMPLIED
+ cubicweb:rql CDATA #IMPLIED
+ cubicweb:actualrql CDATA #IMPLIED
+ cubicweb:rooteid CDATA #IMPLIED
+ cubicweb:dataurl CDATA #IMPLIED
+ cubicweb:size CDATA #IMPLIED
+ cubicweb:tlunit CDATA #IMPLIED
+ cubicweb:loadurl CDATA #IMPLIED
+ cubicweb:uselabel CDATA #IMPLIED
+ cubicweb:facetargs CDATA #IMPLIED
+ cubicweb:facetName CDATA #IMPLIED
+ "> ] '''
+
+TRANSITIONAL_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" %s>\n'
+
+STRICT_DOCTYPE = u'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" %s>\n'
+
+# base view object ############################################################
+
+class View(AppRsetObject):
+ """abstract view class, used as base for every renderable object such
+ as views, templates, some components...web
+
+ A view is instantiated to render a [part of a] result set. View
+ subclasses may be parametred using the following class attributes:
+
+ * `templatable` indicates if the view may be embeded in a main
+ template or if it has to be rendered standalone (i.e. XML for
+ instance)
+ * if the view is not templatable, it should set the `content_type` class
+ attribute to the correct MIME type (text/xhtml by default)
+ * the `category` attribute may be used in the interface to regroup related
+ objects together
+
+ At instantiation time, the standard `req`, `rset`, and `cursor`
+ attributes are added and the `w` attribute will be set at rendering
+ time to a write function to use.
+ """
+ __registry__ = 'views'
+ __registerer__ = priority_registerer
+ registered = require_group_compat(AppRsetObject.registered)
+
+ templatable = True
+ need_navigation = True
+ # content_type = 'application/xhtml+xml' # text/xhtml'
+ binary = False
+ add_to_breadcrumbs = True
+ category = 'view'
+
+ def __init__(self, req=None, rset=None):
+ super(View, self).__init__(req, rset)
+ self.w = None
+
+ @property
+ def content_type(self):
+ if self.req.xhtml_browser():
+ return 'application/xhtml+xml'
+ return 'text/html'
+
+ def set_stream(self, w=None):
+ if self.w is not None:
+ return
+ if w is None:
+ if self.binary:
+ self._stream = stream = StringIO()
+ else:
+ self._stream = stream = UStringIO()
+ w = stream.write
+ else:
+ stream = None
+ self.w = w
+ return stream
+
+ # main view interface #####################################################
+
+ def dispatch(self, w=None, **context):
+ """called to render a view object for a result set.
+
+ This method is a dispatched to an actual method selected
+ according to optional row and col parameters, which are locating
+ a particular row or cell in the result set:
+
+ * if row [and col] are specified, `cell_call` is called
+ * if none of them is supplied, the view is considered to apply on
+ the whole result set (which may be None in this case), `call` is
+ called
+ """
+ row, col = context.get('row'), context.get('col')
+ if row is not None:
+ context.setdefault('col', 0)
+ view_func = self.cell_call
+ else:
+ view_func = self.call
+ stream = self.set_stream(w)
+ # stream = self.set_stream(context)
+ view_func(**context)
+ # return stream content if we have created it
+ if stream is not None:
+ return self._stream.getvalue()
+
+ # should default .call() method add a <div classs="section"> around each
+ # rset item
+ add_div_section = True
+
+ def call(self, **kwargs):
+ """the view is called for an entire result set, by default loop
+ other rows of the result set and call the same view on the
+ particular row
+
+ Views applicable on None result sets have to override this method
+ """
+ rset = self.rset
+ if rset is None:
+ raise NotImplementedError, self
+ wrap = self.templatable and len(rset) > 1 and self.add_div_section
+ for i in xrange(len(rset)):
+ if wrap:
+ self.w(u'<div class="section">')
+ self.wview(self.id, rset, row=i, **kwargs)
+ if wrap:
+ self.w(u"</div>")
+
+ def cell_call(self, row, col, **kwargs):
+ """the view is called for a particular result set cell"""
+ raise NotImplementedError, self
+
+ def linkable(self):
+ """return True if the view may be linked in a menu
+
+ by default views without title are not meant to be displayed
+ """
+ if not getattr(self, 'title', None):
+ return False
+ return True
+
+ def is_primary(self):
+ return self.id == '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.
+ """
+ try:
+ return self.build_url(vid=self.id, rql=self.req.form['rql'])
+ except KeyError:
+ return self.build_url(vid=self.id)
+
+ def set_request_content_type(self):
+ """set the content type returned by this view"""
+ self.req.set_content_type(self.content_type)
+
+ # view utilities ##########################################################
+
+ def view(self, __vid, rset=None, __fallback_vid=None, **kwargs):
+ """shortcut to self.vreg.render method avoiding to pass self.req"""
+ try:
+ view = self.vreg.select_view(__vid, self.req, rset, **kwargs)
+ except NoSelectableObject:
+ if __fallback_vid is None:
+ raise
+ view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs)
+ return view.dispatch(**kwargs)
+
+ def wview(self, __vid, rset, __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)
+
+ # XXX Template bw compat
+ template = obsolete('.template is deprecated, use .view')(wview)
+
+ def whead(self, data):
+ self.req.html_headers.write(data)
+
+ def wdata(self, data):
+ """simple helper that escapes `data` and writes into `self.w`"""
+ self.w(html_escape(data))
+
+ def action(self, actionid, row=0):
+ """shortcut to get action object with id `actionid`"""
+ return self.vreg.select_action(actionid, self.req, self.rset,
+ row=row)
+
+ def action_url(self, actionid, label=None, row=0):
+ """simple method to be able to display `actionid` as a link anywhere
+ """
+ action = self.vreg.select_action(actionid, self.req, self.rset,
+ row=row)
+ if action:
+ label = label or self.req._(action.title)
+ return u'<a href="%s">%s</a>' % (html_escape(action.url()), label)
+ return u''
+
+ 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 return a meta tag to disable robot indexation of the page
+ """
+ return [NOINDEX]
+
+ def page_title(self):
+ """returns a title according to the result set - used for the
+ title in the HTML header
+ """
+ vtitle = self.req.form.get('vtitle')
+ if vtitle:
+ return self.req._(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
+ if rset and rset.rowcount:
+ if rset.rowcount == 1:
+ try:
+ entity = self.complete_entity(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 = 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')
+ else :
+ clabel = u'#[*] (%s)' % vtitle
+ else:
+ clabel = vtitle
+ return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title'))
+
+ def output_url_builder( self, name, url, args ):
+ self.w(u'<script language="JavaScript"><!--\n' \
+ u'function %s( %s ) {\n' % (name, ','.join(args) ) )
+ url_parts = url.split("%s")
+ self.w(u' url="%s"' % url_parts[0] )
+ for arg, part in zip(args, url_parts[1:]):
+ self.w(u'+str(%s)' % arg )
+ if part:
+ self.w(u'+"%s"' % part)
+ self.w('\n document.window.href=url;\n')
+ self.w('}\n-->\n</script>\n')
+
+ 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)
+<<<<<<< /home/syt/src/fcubicweb/cubicweb/view.py
+
+=======
+
+
+# concrete views base classes #################################################
+
+class EntityView(View):
+ """base class for views applying on an entity (i.e. uniform result set)
+ """
+ __registerer__ = accepts_registerer
+ __selectors__ = (accept,)
+ accepts = ('Any',)
+ category = 'entityview'
+
+>>>>>>> /tmp/view.py~other.mliJlS
+ def field(self, label, value, row=True, show_label=True, w=None, tr=True):
+ """ read-only field """
+ if w is None:
+ w = self.w
+ if row:
+ w(u'<div class="row">')
+ if show_label:
+ if tr:
+ label = display_name(self.req, label)
+ w(u'<span class="label">%s</span>' % label)
+ w(u'<div class="field">%s</div>' % value)
+ if row:
+ w(u'</div>')
+
+
+# concrete views base classes #################################################
+
+class EntityView(View):
+ """base class for views applying on an entity (i.e. uniform result set)"""
+ __registerer__ = accepts_registerer
+ __select__ = non_final_entity()
+ registered = accepts_compat(View.registered)
+
+ category = 'entityview'
+
+
+class StartupView(View):
+ """base class for views which doesn't need a particular result set to be
+ displayed (so they can always be displayed !)
+ """
+ __registerer__ = priority_registerer
+ __select__ = none_rset()
+ registered = require_group_compat(View.registered)
+
+ category = 'startupview'
+
+ def url(self):
+ """return the url associated with this view. We can omit rql here"""
+ return self.build_url('view', vid=self.id)
+
+ 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 startup views are indexed
+ """
+ return []
+
+
+class EntityStartupView(EntityView):
+ """base class for entity views which may also be applied to None
+ result set (usually a default rql is provided by the view class)
+ """
+ __select__ = none_rset() | non_final_entity()
+
+ default_rql = None
+
+ def __init__(self, req, rset):
+ super(EntityStartupView, self).__init__(req, rset)
+ if rset is None:
+ # this instance is not in the "entityview" category
+ self.category = 'startupview'
+
+ def startup_rql(self):
+ """return some rql to be executed if the result set is None"""
+ return self.default_rql
+
+ def call(self, **kwargs):
+ """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
+ for i in xrange(len(rset)):
+ self.wview(self.id, rset, row=i, **kwargs)
+
+ def url(self):
+ """return the url associated with this view. We can omit rql if we are
+ on a result set on which we do not apply.
+ """
+ if self.rset is None:
+ return self.build_url(vid=self.id)
+ return super(EntityStartupView, self).url()
+
+
+class AnyRsetView(View):
+ """base class for views applying on any non empty result sets"""
+ __select__ = nonempty_rset()
+
+ category = 'anyrsetview'
+
+ def columns_labels(self, tr=True):
+ if tr:
+ translate = display_name
+ else:
+ translate = lambda req, val: val
+ rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
+ labels = []
+ for colindex, attr in enumerate(rqlstdescr):
+ # compute column header
+ if colindex == 0 or attr == 'Any': # find a better label
+ label = ','.join(translate(self.req, et)
+ for et in self.rset.column_types(colindex))
+ else:
+ label = translate(self.req, attr)
+ labels.append(label)
+ return labels
+
+
+# concrete template base classes ##############################################
+
+class MainTemplate(View):
+ """main template are primary access point to render a full HTML page.
+ There is usually at least a regular main template and a simple fallback
+ one to display error if the first one failed
+ """
+ base_doctype = STRICT_DOCTYPE
+ registered = require_group_compat(View.registered)
+
+ @property
+ def doctype(self):
+ if self.req.xhtml_browser():
+ return self.base_doctype % CW_XHTML_EXTENSIONS
+ return self.base_doctype % ''
+
+ def set_stream(self, w=None, templatable=True):
+ if templatable and self.w is not None:
+ return
+
+ if w is None:
+ if self.binary:
+ self._stream = stream = StringIO()
+ elif not templatable:
+ # not templatable means we're using a non-html view, we don't
+ # want the HTMLStream stuff to interfere during data generation
+ self._stream = stream = UStringIO()
+ else:
+ self._stream = stream = HTMLStream(self.req)
+ w = stream.write
+ else:
+ stream = None
+ self.w = w
+ return stream
+
+ def write_doctype(self, xmldecl=True):
+ assert isinstance(self._stream, HTMLStream)
+ self._stream.doctype = self.doctype
+ if not xmldecl:
+ self._stream.xmldecl = u''
+
+ def linkable(self):
+ return False
+
+# concrete component base classes #############################################
+
+class ReloadableMixIn(object):
+ """simple mixin for reloadable parts of UI"""
+
+ 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.req.add_js('cubicweb.ajax.js')
+ if nonify:
+ _cb = cb
+ def cb(*args):
+ _cb(*args)
+ cbname = self.req.register_onetime_callback(cb, *args)
+ return self.build_js(cbname, html_escape(msg or ''))
+
+ def build_update_js_call(self, cbname, msg):
+ rql = html_escape(self.rset.printable_rql())
+ return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
+ cbname, self.id, rql, msg, self.__registry__, self.div_id())
+
+ def build_reload_js_call(self, cbname, msg):
+ return "javascript:userCallbackThenReloadPage('%s', '%s')" % (cbname, msg)
+
+ build_js = build_update_js_call # expect updatable component by default
+
+ def div_id(self):
+ return ''
+
+
+class Component(ReloadableMixIn, View):
+ """base class for components"""
+ __registry__ = 'components'
+ __registerer__ = yes_registerer
+ __select__ = yes()
+ property_defs = {
+ _('visible'): dict(type='Boolean', default=True,
+ help=_('display the component or not')),
+ }
+
+ def div_class(self):
+ return '%s %s' % (self.propval('htmlclass'), self.id)
+
+ def div_id(self):
+ return '%sComponent' % self.id
--- a/vregistry.py Fri Feb 27 09:59:53 2009 +0100
+++ b/vregistry.py Mon Mar 02 21:03:54 2009 +0100
@@ -29,24 +29,14 @@
from os import listdir, stat
from os.path import dirname, join, realpath, split, isdir
from logging import getLogger
+import types
from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
-class vobject_helper(object):
- """object instantiated at registration time to help a wrapped
- VObject subclass
- """
- def __init__(self, registry, vobject):
- self.registry = registry
- self.vobject = vobject
- self.config = registry.config
- self.schema = registry.schema
-
-
-class registerer(vobject_helper):
+class registerer(object):
"""do whatever is needed at registration time for the wrapped
class, according to current application schema and already
registered objects of the same kind (i.e. same registry name and
@@ -59,7 +49,10 @@
"""
def __init__(self, registry, vobject):
- super(registerer, self).__init__(registry, vobject)
+ self.registry = registry
+ self.vobject = vobject
+ self.config = registry.config
+ self.schema = registry.schema
self.kicked = set()
def do_it_yourself(self, registered):
@@ -73,49 +66,11 @@
def skip(self):
self.debug('no schema compat, skipping %s', self.vobject)
-
-def selector(cls, *args, **kwargs):
- """selector is called to help choosing the correct object for a
- particular request and result set by returning a score.
-
- it must implement a .score_method taking a request, a result set and
- optionaly row and col arguments which return an int telling how well
- the wrapped class apply to the given request and result set. 0 score
- means that it doesn't apply.
-
- rset may be None. If not, row and col arguments may be optionally
- given if the registry is scoring a given row or a given cell of
- the result set (both row and col are int if provided).
- """
- raise NotImplementedError(cls)
-
-
-class autoselectors(type):
- """implements __selectors__ / __select__ compatibility layer so that:
-
- __select__ = chainall(classmethod(A, B, C))
+class yes_registerer(registerer):
+ """register without any other action"""
+ def do_it_yourself(self, registered):
+ return self.vobject
- can be replaced by something like:
-
- __selectors__ = (A, B, C)
- """
- def __new__(mcs, name, bases, classdict):
- if '__select__' in classdict and '__selectors__' in classdict:
- raise TypeError("__select__ and __selectors__ "
- "can't be used together")
- if '__select__' not in classdict and '__selectors__' in classdict:
- selectors = classdict['__selectors__']
- if len(selectors) > 1:
- classdict['__select__'] = classmethod(chainall(*selectors))
- else:
- classdict['__select__'] = classmethod(selectors[0])
- return super(autoselectors, mcs).__new__(mcs, name, bases, classdict)
-
- def __setattr__(self, attr, value):
- if attr == '__selectors__':
- self.__select__ = classmethod(chainall(*value))
- super(autoselectors, self).__setattr__(attr, value)
-
class VObject(object):
"""visual object, use to be handled somehow by the visual components
@@ -129,22 +84,16 @@
:id:
object's identifier in the registry (string like 'main',
'primary', 'folder_box')
- :__registerer__:
- registration helper class
:__select__:
- selection helper function
- :__selectors__:
- tuple of selectors to be chained
- (__select__ and __selectors__ are mutually exclusive)
+ class'selector
Moreover, the `__abstract__` attribute may be set to True to indicate
that a vobject is abstract and should not be registered
"""
- __metaclass__ = autoselectors
# necessary attributes to interact with the registry
id = None
__registry__ = None
- __registerer__ = None
+ __registerer__ = yes_registerer
__select__ = None
@classmethod
@@ -155,6 +104,7 @@
may be the right hook to create an instance for example). By
default the vobject is returned without any transformation.
"""
+ cls.build___select__()
return cls
@classmethod
@@ -173,6 +123,29 @@
"""returns a unique identifier for the vobject"""
return '%s.%s' % (cls.__module__, cls.__name__)
+ # XXX bw compat code
+ @classmethod
+ def build___select__(cls):
+ for klass in cls.mro():
+ if klass.__name__ == 'AppRsetObject':
+ 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
+
class VRegistry(object):
"""class responsible to register, propose and select the various
@@ -204,107 +177,6 @@
def __contains__(self, key):
return key in self._registries
-
- def register_vobject_class(self, cls, _kicked=set()):
- """handle vobject class registration
-
- vobject class with __abstract__ == True in their local dictionnary or
- with a name starting starting by an underscore are not registered.
- Also a vobject class needs to have __registry__ and id attributes set
- to a non empty string to be registered.
-
- Registration is actually handled by vobject's registerer.
- """
- if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
- or not cls.__registry__ or not cls.id):
- return
- # while reloading a module :
- # if cls was previously kicked, it means that there is a more specific
- # vobject defined elsewhere re-registering cls would kick it out
- if cls.classid() in _kicked:
- self.debug('not re-registering %s because it was previously kicked',
- cls.classid())
- else:
- regname = cls.__registry__
- if cls.id in self.config['disable-%s' % regname]:
- return
- registry = self._registries.setdefault(regname, {})
- vobjects = registry.setdefault(cls.id, [])
- registerer = cls.__registerer__(self, cls)
- cls = registerer.do_it_yourself(vobjects)
- #_kicked |= registerer.kicked
- if cls:
- vobject = cls.registered(self)
- try:
- vname = vobject.__name__
- except AttributeError:
- vname = vobject.__class__.__name__
- self.debug('registered vobject %s in registry %s with id %s',
- vname, cls.__registry__, cls.id)
- vobjects.append(vobject)
-
- def unregister_module_vobjects(self, modname):
- """removes registered objects coming from a given module
-
- returns a dictionnary classid/class of all classes that will need
- to be updated after reload (i.e. vobjects referencing classes defined
- in the <modname> module)
- """
- unregistered = {}
- # browse each registered object
- for registry, objdict in self.items():
- for oid, objects in objdict.items():
- for obj in objects[:]:
- objname = obj.classid()
- # if the vobject is defined in this module, remove it
- if objname.startswith(modname):
- unregistered[objname] = obj
- objects.remove(obj)
- self.debug('unregistering %s in %s registry',
- objname, registry)
- # if not, check if the vobject can be found in baseclasses
- # (because we also want subclasses to be updated)
- else:
- if not isinstance(obj, type):
- obj = obj.__class__
- for baseclass in obj.__bases__:
- if hasattr(baseclass, 'classid'):
- baseclassid = baseclass.classid()
- if baseclassid.startswith(modname):
- unregistered[baseclassid] = baseclass
- # update oid entry
- if objects:
- objdict[oid] = objects
- else:
- del objdict[oid]
- return unregistered
-
-
- def update_registered_subclasses(self, oldnew_mapping):
- """updates subclasses of re-registered vobjects
-
- if baseviews.PrimaryView is changed, baseviews.py will be reloaded
- automatically and the new version of PrimaryView will be registered.
- But all existing subclasses must also be notified of this change, and
- that's what this method does
-
- :param oldnew_mapping: a dict mapping old version of a class to
- the new version
- """
- # browse each registered object
- for objdict in self.values():
- for objects in objdict.values():
- for obj in objects:
- if not isinstance(obj, type):
- obj = obj.__class__
- # build new baseclasses tuple
- newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
- for baseclass in obj.__bases__)
- # update obj's baseclasses tuple (__bases__) if needed
- if newbases != obj.__bases__:
- self.debug('updating %s.%s base classes',
- obj.__module__, obj.__name__)
- obj.__bases__ = newbases
def registry(self, name):
"""return the registry (dictionary of class objects) associated to
@@ -330,7 +202,92 @@
for objs in registry.values():
result += objs
return result
-
+
+ def object_by_id(self, registry, cid, *args, **kwargs):
+ """return the most specific component according to the resultset"""
+ objects = self[registry][cid]
+ assert len(objects) == 1, objects
+ return objects[0].selected(*args, **kwargs)
+
+ # methods for explicit (un)registration ###################################
+
+# def clear(self, key):
+# regname, oid = key.split('.')
+# self[regname].pop(oid, None)
+ def register_all(self, objects, modname, butclasses=()):
+ for obj in objects:
+ try:
+ if obj.__module__ != modname or obj in butclasses:
+ continue
+ oid = obj.id
+ except AttributeError:
+ continue
+ if oid:
+ self.register(obj)
+
+ def register(self, obj, registryname=None, oid=None, clear=False):
+ """base method to add an object in the registry"""
+ assert not '__abstract__' in obj.__dict__
+ registryname = registryname or obj.__registry__
+ oid = oid or obj.id
+ assert oid
+ registry = self._registries.setdefault(registryname, {})
+ if clear:
+ vobjects = registry[oid] = []
+ else:
+ vobjects = registry.setdefault(oid, [])
+ # registered() is technically a classmethod but is not declared
+ # as such because we need to compose registered in some cases
+ vobject = obj.registered.im_func(obj, self)
+ assert not vobject in vobjects
+ assert callable(vobject.__select__), vobject
+ vobjects.append(vobject)
+ try:
+ vname = vobject.__name__
+ except AttributeError:
+ vname = vobject.__class__.__name__
+ self.debug('registered vobject %s in registry %s with id %s',
+ vname, registryname, oid)
+ # automatic reloading management
+ self._registered['%s.%s' % (obj.__module__, oid)] = obj
+
+ def unregister(self, obj, registryname=None):
+ registryname = registryname or obj.__registry__
+ registry = self.registry(registryname)
+ removed_id = obj.classid()
+ for registered in registry[obj.id]:
+ # use classid() to compare classes because vreg will probably
+ # have its own version of the class, loaded through execfile
+ if registered.classid() == removed_id:
+ # XXX automatic reloading management
+ try:
+ registry[obj.id].remove(registered)
+ except ValueError:
+ self.warning('can\'t remove %s, no id %s in the %s registry',
+ removed_id, obj.id, registryname)
+ except ValueError:
+ self.warning('can\'t remove %s, not in the %s registry with id %s',
+ removed_id, registryname, obj.id)
+# else:
+# # if objects is empty, remove oid from registry
+# if not registry[obj.id]:
+# del regcontent[oid]
+ break
+
+ def register_and_replace(self, obj, replaced, registryname=None):
+ if hasattr(replaced, 'classid'):
+ replaced = replaced.classid()
+ registryname = registryname or obj.__registry__
+ registry = self.registry(registryname)
+ registered_objs = registry[obj.id]
+ for index, registered in enumerate(registered_objs):
+ if registered.classid() == replaced:
+ del registry[obj.id][index]
+ break
+ self.register(obj, registryname=registryname)
+
+ # dynamic selection methods ###############################################
+
def select(self, vobjects, *args, **kwargs):
"""return an instance of the most specific object according
to parameters
@@ -339,7 +296,7 @@
"""
score, winners = 0, []
for vobject in vobjects:
- vobjectscore = vobject.__select__(*args, **kwargs)
+ vobjectscore = vobject.__select__(vobject, *args, **kwargs)
if vobjectscore > score:
score, winners = vobjectscore, [vobject]
elif vobjectscore > 0 and vobjectscore == score:
@@ -372,15 +329,8 @@
def select_object(self, registry, cid, *args, **kwargs):
"""return the most specific component according to the resultset"""
return self.select(self.registry_objects(registry, cid), *args, **kwargs)
-
- def object_by_id(self, registry, cid, *args, **kwargs):
- """return the most specific component according to the resultset"""
- objects = self[registry][cid]
- assert len(objects) == 1, objects
- return objects[0].selected(*args, **kwargs)
# intialization methods ###################################################
-
def register_objects(self, path, force_reload=None):
if force_reload is None:
@@ -471,15 +421,18 @@
return True
def load_module(self, module):
- registered = {}
- self.info('loading %s', module)
- for objname, obj in vars(module).items():
- if objname.startswith('_'):
- continue
- self.load_ancestors_then_object(module.__name__, registered, obj)
- return registered
+ self._registered = {}
+ if hasattr(module, 'registration_callback'):
+ module.registration_callback(self)
+ else:
+ self.info('loading %s', module)
+ for objname, obj in vars(module).items():
+ if objname.startswith('_'):
+ continue
+ self.load_ancestors_then_object(module.__name__, obj)
+ return self._registered
- def load_ancestors_then_object(self, modname, registered, obj):
+ def load_ancestors_then_object(self, modname, obj):
# skip imported classes
if getattr(obj, '__module__', None) != modname:
return
@@ -490,11 +443,11 @@
except TypeError:
return
objname = '%s.%s' % (modname, obj.__name__)
- if objname in registered:
+ if objname in self._registered:
return
- registered[objname] = obj
+ self._registered[objname] = obj
for parent in obj.__bases__:
- self.load_ancestors_then_object(modname, registered, parent)
+ self.load_ancestors_then_object(modname, parent)
self.load_object(obj)
def load_object(self, obj):
@@ -505,41 +458,277 @@
raise
self.exception('vobject %s registration failed: %s', obj, ex)
+ # old automatic registration XXX deprecated ###############################
+
+ def register_vobject_class(self, cls):
+ """handle vobject class registration
+
+ vobject class with __abstract__ == True in their local dictionnary or
+ with a name starting starting by an underscore are not registered.
+ Also a vobject class needs to have __registry__ and id attributes set
+ to a non empty string to be registered.
+
+ Registration is actually handled by vobject's registerer.
+ """
+ if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
+ or not cls.__registry__ or not cls.id):
+ return
+ regname = cls.__registry__
+ if cls.id in self.config['disable-%s' % regname]:
+ return
+ registry = self._registries.setdefault(regname, {})
+ vobjects = registry.setdefault(cls.id, [])
+ registerer = cls.__registerer__(self, cls)
+ cls = registerer.do_it_yourself(vobjects)
+ if cls:
+ self.register(cls)
+
+ def unregister_module_vobjects(self, modname):
+ """removes registered objects coming from a given module
+
+ returns a dictionnary classid/class of all classes that will need
+ to be updated after reload (i.e. vobjects referencing classes defined
+ in the <modname> module)
+ """
+ unregistered = {}
+ # browse each registered object
+ for registry, objdict in self.items():
+ for oid, objects in objdict.items():
+ for obj in objects[:]:
+ objname = obj.classid()
+ # if the vobject is defined in this module, remove it
+ if objname.startswith(modname):
+ unregistered[objname] = obj
+ objects.remove(obj)
+ self.debug('unregistering %s in %s registry',
+ objname, registry)
+ # if not, check if the vobject can be found in baseclasses
+ # (because we also want subclasses to be updated)
+ else:
+ if not isinstance(obj, type):
+ obj = obj.__class__
+ for baseclass in obj.__bases__:
+ if hasattr(baseclass, 'classid'):
+ baseclassid = baseclass.classid()
+ if baseclassid.startswith(modname):
+ unregistered[baseclassid] = baseclass
+ # update oid entry
+ if objects:
+ objdict[oid] = objects
+ else:
+ del objdict[oid]
+ return unregistered
+
+ def update_registered_subclasses(self, oldnew_mapping):
+ """updates subclasses of re-registered vobjects
+
+ if baseviews.PrimaryView is changed, baseviews.py will be reloaded
+ automatically and the new version of PrimaryView will be registered.
+ But all existing subclasses must also be notified of this change, and
+ that's what this method does
+
+ :param oldnew_mapping: a dict mapping old version of a class to
+ the new version
+ """
+ # browse each registered object
+ for objdict in self.values():
+ for objects in objdict.values():
+ for obj in objects:
+ if not isinstance(obj, type):
+ obj = obj.__class__
+ # build new baseclasses tuple
+ newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
+ for baseclass in obj.__bases__)
+ # update obj's baseclasses tuple (__bases__) if needed
+ if newbases != obj.__bases__:
+ self.debug('updating %s.%s base classes',
+ obj.__module__, obj.__name__)
+ obj.__bases__ = newbases
+
# init logging
set_log_methods(VObject, getLogger('cubicweb'))
set_log_methods(VRegistry, getLogger('cubicweb.registry'))
set_log_methods(registerer, getLogger('cubicweb.registration'))
-# advanced selector building functions ########################################
+# selector base classes and operations ########################################
+
+class Selector(object):
+ """base class for selector classes providing implementation
+ for operators ``&`` and ``|``
+
+ This class is only here to give access to binary operators, the
+ selector logic itself should be implemented in the __call__ method
+
+
+ a selector is called to help choosing the correct object for a
+ particular context by returning a score (`int`) telling how well
+ the class given as first argument apply to the given context.
+
+ 0 score means that the class doesn't apply.
+ """
+
+ @property
+ def func_name(self):
+ # backward compatibility
+ return self.__class__.__name__
+
+ def search_selector(self, selector):
+ """search for the given selector or selector instance in the selectors
+ tree. Return it of None if not found
+ """
+ if self is selector:
+ return self
+ if isinstance(selector, type) and isinstance(self, selector):
+ return self
+ return None
+
+ def __str__(self):
+ return self.__class__.__name__
+
+ def __and__(self, other):
+ return AndSelector(self, other)
+ def __rand__(self, other):
+ return AndSelector(other, self)
+
+ def __or__(self, other):
+ return OrSelector(self, other)
+ def __ror__(self, other):
+ return OrSelector(other, self)
+
+ def __invert__(self):
+ return NotSelector(self)
+
+ # XXX (function | function) or (function & function) not managed yet
+
+ def __call__(self, cls, *args, **kwargs):
+ return NotImplementedError("selector %s must implement its logic "
+ "in its __call__ method" % self.__class__)
+
+class MultiSelector(Selector):
+ """base class for compound selector classes"""
+
+ def __init__(self, *selectors):
+ self.selectors = self.merge_selectors(selectors)
+
+ def __str__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ ','.join(str(s) for s in self.selectors))
-def chainall(*selectors):
- """return a selector chaining given selectors. If one of
- the selectors fail, selection will fail, else the returned score
- will be the sum of each selector'score
+ @classmethod
+ def merge_selectors(cls, selectors):
+ """deal with selector instanciation when necessary and merge
+ multi-selectors if possible:
+
+ AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
+ ==> AndSelector(sel1, sel2, sel3, sel4)
+ """
+ merged_selectors = []
+ for selector in selectors:
+ try:
+ selector = _instantiate_selector(selector)
+ except:
+ pass
+ #assert isinstance(selector, Selector), selector
+ if isinstance(selector, cls):
+ merged_selectors += selector.selectors
+ else:
+ merged_selectors.append(selector)
+ return merged_selectors
+
+ def search_selector(self, selector):
+ """search for the given selector or selector instance in the selectors
+ tree. Return it of None if not found
+ """
+ for childselector in self.selectors:
+ if childselector is selector:
+ return childselector
+ found = childselector.search_selector(selector)
+ if found is not None:
+ return found
+ return None
+
+
+def objectify_selector(selector_func):
+ """convenience decorator for simple selectors where a class definition
+ would be overkill::
+
+ @objectify_selector
+ def yes(cls, *args, **kwargs):
+ return 1
+
"""
- assert selectors
- def selector(cls, *args, **kwargs):
+ return type(selector_func.__name__, (Selector,),
+ {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
+
+def _instantiate_selector(selector):
+ """ensures `selector` is a `Selector` instance
+
+ NOTE: This should only be used locally in build___select__()
+ XXX: then, why not do it ??
+ """
+ if isinstance(selector, types.FunctionType):
+ return objectify_selector(selector)()
+ if isinstance(selector, type) and issubclass(selector, Selector):
+ return selector()
+ return selector
+
+
+class AndSelector(MultiSelector):
+ """and-chained selectors (formerly known as chainall)"""
+ def __call__(self, cls, *args, **kwargs):
score = 0
- for selector in selectors:
+ for selector in self.selectors:
partscore = selector(cls, *args, **kwargs)
if not partscore:
return 0
score += partscore
return score
+
+
+class OrSelector(MultiSelector):
+ """or-chained selectors (formerly known as chainfirst)"""
+ def __call__(self, cls, *args, **kwargs):
+ for selector in self.selectors:
+ partscore = selector(cls, *args, **kwargs)
+ if partscore:
+ return partscore
+ return 0
+
+class NotSelector(Selector):
+ """negation selector"""
+ def __init__(self, selector):
+ self.selector = selector
+
+ def __call__(self, cls, *args, **kwargs):
+ score = self.selector(cls, *args, **kwargs)
+ return int(not score)
+
+ def __str__(self):
+ return 'NOT(%s)' % super(NotSelector, self).__str__()
+
+
+# XXX bw compat functions #####################################################
+
+def chainall(*selectors, **kwargs):
+ """return a selector chaining given selectors. If one of
+ the selectors fail, selection will fail, else the returned score
+ will be the sum of each selector'score
+ """
+ assert selectors
+ # XXX do we need to create the AndSelector here, a tuple might be enough
+ selector = AndSelector(*selectors)
+ if 'name' in kwargs:
+ selector.__name__ = kwargs['name']
return selector
-def chainfirst(*selectors):
+def chainfirst(*selectors, **kwargs):
"""return a selector chaining given selectors. If all
the selectors fail, selection will fail, else the returned score
will be the first non-zero selector score
"""
assert selectors
- def selector(cls, *args, **kwargs):
- for selector in selectors:
- partscore = selector(cls, *args, **kwargs)
- if partscore:
- return partscore
- return 0
+ selector = OrSelector(*selectors)
+ if 'name' in kwargs:
+ selector.__name__ = kwargs['name']
return selector
-
--- a/web/action.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/action.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,17 +1,18 @@
"""abstract action classes for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import add_etype_selector, \
- match_search_state, searchstate_accept_one, \
- searchstate_accept_one_but_etype
-
+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)
+from cubicweb.appobject import AppRsetObject
+from cubicweb.common.registerers import accepts_registerer
+
_ = unicode
@@ -20,10 +21,9 @@
request search state.
"""
__registry__ = 'actions'
- __registerer__ = action_registerer
- __selectors__ = (match_search_state,)
- # by default actions don't appear in link search mode
- search_states = ('normal',)
+ __registerer__ = accepts_registerer
+ __select__ = yes()
+
property_defs = {
'visible': dict(type='Boolean', default=True,
help=_('display the action or not')),
@@ -37,53 +37,6 @@
site_wide = True # don't want user to configuration actions eproperties
category = 'moreactions'
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- user = req.user
- action = cls.schema_action
- if row is None:
- score = 0
- need_local_check = []
- geteschema = cls.schema.eschema
- for etype in rset.column_types(0):
- accepted = cls.accept(user, etype)
- if not accepted:
- return 0
- if action:
- eschema = geteschema(etype)
- if not user.matching_groups(eschema.get_groups(action)):
- if eschema.has_local_role(action):
- # have to ckeck local roles
- need_local_check.append(eschema)
- continue
- else:
- # even a local role won't be enough
- return 0
- score += accepted
- if need_local_check:
- # check local role for entities of necessary types
- for i, row in enumerate(rset):
- if not rset.description[i][0] in need_local_check:
- continue
- if not cls.has_permission(rset.get_entity(i, 0), action):
- return 0
- score += 1
- return score
- col = col or 0
- etype = rset.description[row][col]
- score = cls.accept(user, etype)
- if score and action:
- if not cls.has_permission(rset.get_entity(row, col), action):
- return 0
- return score
-
- @classmethod
- def has_permission(cls, entity, action):
- """defined in a separated method to ease overriding (see ModifyAction
- for instance)
- """
- return entity.has_perm(action)
-
def url(self):
"""return the url associated with this action"""
raise NotImplementedError
@@ -94,6 +47,7 @@
if self.category:
return 'box' + self.category.capitalize()
+
class UnregisteredAction(Action):
"""non registered action used to build boxes. Unless you set them
explicitly, .vreg and .schema attributes at least are None.
@@ -111,111 +65,32 @@
return self._path
-class AddEntityAction(Action):
- """link to the entity creation form. Concrete class must set .etype and
- may override .vid
- """
- __selectors__ = (add_etype_selector, match_search_state)
- vid = 'creation'
- etype = None
-
- def url(self):
- return self.build_url(vid=self.vid, etype=self.etype)
-
-
-class EntityAction(Action):
- """an action for an entity. By default entity actions are only
- displayable on single entity result if accept match.
- """
- __selectors__ = (searchstate_accept_one,)
- schema_action = None
- condition = None
-
- @classmethod
- def accept(cls, user, etype):
- score = super(EntityAction, cls).accept(user, etype)
- if not score:
- return 0
- # check if this type of entity has the necessary relation
- if hasattr(cls, 'rtype') and not cls.relation_possible(etype):
- return 0
- return score
-
-
-class LinkToEntityAction(EntityAction):
+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
"""
- etype = None
- rtype = None
- target = None
+ __select__ = (match_search_state('normal') & one_line_rset()
+ & partial_relation_possible(action='add')
+ & partial_may_add_relation())
+ registered = accepts_compat(Action.registered)
+
category = 'addrelated'
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- entity = rset.get_entity(row or 0, col or 0)
- # check if this type of entity has the necessary relation
- if hasattr(cls, 'rtype') and not cls.relation_possible(entity.e_schema):
- return 0
- score = cls.accept(req.user, entity.e_schema)
- if not score:
- return 0
- if not cls.check_perms(req, entity):
- return 0
- return score
-
- @classmethod
- def check_perms(cls, req, entity):
- if not cls.check_rtype_perm(req, entity):
- return False
- # XXX document this:
- # if user can create the relation, suppose it can create the entity
- # this is because we usually can't check "add" permission before the
- # entity has actually been created, and schema security should be
- # defined considering this
- #if not cls.check_etype_perm(req, entity):
- # return False
- return True
-
- @classmethod
- def check_etype_perm(cls, req, entity):
- eschema = cls.schema.eschema(cls.etype)
- if not eschema.has_perm(req, 'add'):
- #print req.user.login, 'has no add perm on etype', cls.etype
- return False
- #print 'etype perm ok', cls
- return True
-
- @classmethod
- def check_rtype_perm(cls, req, entity):
- rschema = cls.schema.rschema(cls.rtype)
- # cls.target is telling us if we want to add the subject or object of
- # the relation
- if cls.target == 'subject':
- if not rschema.has_perm(req, 'add', toeid=entity.eid):
- #print req.user.login, 'has no add perm on subject rel', cls.rtype, 'with', entity
- return False
- elif not rschema.has_perm(req, 'add', fromeid=entity.eid):
- #print req.user.login, 'has no add perm on object rel', cls.rtype, 'with', entity
- return False
- #print 'rtype perm ok', cls
- return True
-
+
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, self.target)
+ linkto = '%s:%s:%s' % (self.rtype, current_entity.eid, target(self))
return self.build_url(vid='creation', etype=self.etype,
__linkto=linkto,
__redirectpath=current_entity.rest_path(), # should not be url quoted!
__redirectvid=self.req.form.get('__redirectvid', ''))
-
-class LinkToEntityAction2(LinkToEntityAction):
- """LinkToEntity action where the action is not usable on the same
- entity's type as the one refered by the .etype attribute
+class EntityAction(Action):
+ """DEPRECATED / BACKWARD COMPAT
"""
- __selectors__ = (searchstate_accept_one_but_etype,)
+ registered = deprecate(condition_compat(accepts_compat(Action.registered)),
+ msg='EntityAction is deprecated, use Action with '
+ 'appropriate selectors')
--- a/web/application.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/application.py Mon Mar 02 21:03:54 2009 +0100
@@ -18,13 +18,13 @@
from cubicweb.cwvreg import CubicWebRegistry
from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, NotFound,
RemoteCallFailed, ExplicitLogin, InvalidSession)
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
# make session manager available through a global variable so the debug view can
# print information about web session
SESSION_MANAGER = None
-class AbstractSessionManager(SingletonComponent):
+class AbstractSessionManager(Component):
"""manage session data associated to a session identifier"""
id = 'sessionmanager'
@@ -87,7 +87,7 @@
raise NotImplementedError()
-class AbstractAuthenticationManager(SingletonComponent):
+class AbstractAuthenticationManager(Component):
"""authenticate user associated to a request and check session validity"""
id = 'authmanager'
@@ -384,9 +384,11 @@
if tb:
req.data['excinfo'] = excinfo
req.form['vid'] = 'error'
- content = self.vreg.main_template(req, 'main')
+ errview = self.vreg.select_view('error', req, None)
+ template = self.main_template_id(req)
+ content = self.vreg.main_template(req, template, view=errview)
except:
- content = self.vreg.main_template(req, 'error')
+ content = self.vreg.main_template(req, 'error-template')
raise StatusResponse(500, content)
def need_login_content(self, req):
@@ -396,10 +398,17 @@
return self.vreg.main_template(req, 'loggedout')
def notfound_content(self, req):
- template = req.property_value('ui.main-template') or 'main'
req.form['vid'] = '404'
- return self.vreg.main_template(req, template)
+ view = self.vreg.select_view('404', req, None)
+ template = self.main_template_id(req)
+ return self.vreg.main_template(req, template, view=view)
+ def main_template_id(self, req):
+ template = req.property_value('ui.main-template')
+ if template not in self.vreg.registry('views') :
+ template = 'main-template'
+ return template
+
set_log_methods(CubicWebPublisher, LOGGER)
set_log_methods(CookieSessionHandler, LOGGER)
--- a/web/box.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/box.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""abstract box classes for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -10,15 +10,11 @@
from logilab.mtconverter import html_escape
from cubicweb import Unauthorized, role as get_role
-from cubicweb.common.registerers import (
- accepts_registerer, extresources_registerer,
- etype_rtype_priority_registerer)
-from cubicweb.common.selectors import (
- etype_rtype_selector, one_line_rset, accept, has_relation,
- primary_view, match_context_prop, has_related_entities,
- _rql_condition)
-from cubicweb.common.view import Template
-from cubicweb.common.appobject import ReloadableMixIn
+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)
+from cubicweb.view import View, ReloadableMixIn
from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget,
RawBoxItem, BoxSeparator)
@@ -27,7 +23,7 @@
_ = unicode
-class BoxTemplate(Template):
+class BoxTemplate(View):
"""base template for boxes, usually a (contextual) list of possible
actions. Various classes attributes may be used to control the box
@@ -42,7 +38,8 @@
box.render(self.w)
"""
__registry__ = 'boxes'
- __selectors__ = Template.__selectors__ + (match_context_prop,)
+ __select__ = match_context_prop()
+ registered = require_group_compat(View.registered)
categories_in_order = ()
property_defs = {
@@ -105,8 +102,7 @@
according to application schema and display according to connected
user's rights) and rql attributes
"""
- __registerer__ = etype_rtype_priority_registerer
- __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
+#XXX __selectors__ = BoxTemplate.__selectors__ + (etype_rtype_selector,)
rql = None
@@ -139,23 +135,11 @@
return (self.rql, {'x': self.req.user.eid}, 'x')
-class ExtResourcesBoxTemplate(BoxTemplate):
- """base class for boxes displaying external resources such as the RSS logo.
- It should list necessary resources with the .need_resources attribute.
- """
- __registerer__ = extresources_registerer
- need_resources = ()
-
-
class EntityBoxTemplate(BoxTemplate):
"""base class for boxes related to a single entity"""
- __registerer__ = accepts_registerer
- __selectors__ = (one_line_rset, primary_view,
- match_context_prop, etype_rtype_selector,
- has_relation, accept, _rql_condition)
- accepts = ('Any',)
+ __select__ = BoxTemplate.__select__ & one_line_rset() & primary_view()
+ registered = accepts_compat(has_relation_compat(condition_compat(BoxTemplate.registered)))
context = 'incontext'
- condition = None
def call(self, row=0, col=0, **kwargs):
"""classes inheriting from EntityBoxTemplate should define cell_call"""
@@ -163,8 +147,8 @@
class RelatedEntityBoxTemplate(EntityBoxTemplate):
- __selectors__ = EntityBoxTemplate.__selectors__ + (has_related_entities,)
-
+ __select__ = EntityBoxTemplate.__select__ & partial_has_related_entities()
+
def cell_call(self, row, col, **kwargs):
entity = self.entity(row, col)
limit = self.req.property_value('navigation.related-limit') + 1
--- a/web/component.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/component.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,25 +1,26 @@
"""abstract component class and base components definition for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.appobject import Component, SingletonComponent
-from cubicweb.common.utils import merge_dicts
-from cubicweb.common.view import VComponent, SingletonVComponent
-from cubicweb.common.registerers import action_registerer
-from cubicweb.common.selectors import (paginated_rset, one_line_rset,
- rql_condition, accept, primary_view,
- match_context_prop, has_relation,
- etype_rtype_selector)
-from cubicweb.common.uilib import html_escape
+from logilab.common.deprecation import class_renamed
+from logilab.mtconverter import html_escape
+
+from cubicweb import role
+from cubicweb.utils import merge_dicts
+from cubicweb.view import View, Component
+from cubicweb.selectors import (
+ paginated_rset, one_line_rset, primary_view, match_context_prop,
+ partial_has_related_entities, partial_relation_possible,
+ condition_compat, accepts_compat, has_relation_compat)
+from cubicweb.common.registerers import accepts_registerer
_ = unicode
-
-class EntityVComponent(VComponent):
+class EntityVComponent(Component):
"""abstract base class for additinal components displayed in content
headers and footer according to:
@@ -31,11 +32,9 @@
"""
__registry__ = 'contentnavigation'
- __registerer__ = action_registerer
- __selectors__ = (one_line_rset, primary_view,
- match_context_prop, etype_rtype_selector,
- has_relation, accept,
- rql_condition)
+ __registerer__ = accepts_registerer
+ __select__ = one_line_rset() & primary_view() & match_context_prop()
+ registered = accepts_compat(has_relation_compat(condition_compat(View.registered)))
property_defs = {
_('visible'): dict(type='Boolean', default=True,
@@ -51,21 +50,20 @@
help=_('html class of the component')),
}
- accepts = ('Any',)
context = 'navcontentbottom' # 'footer' | 'header' | 'incontext'
- condition = None
- def call(self, view):
+ def call(self, view=None):
return self.cell_call(0, 0, view)
- def cell_call(self, row, col, view):
+ def cell_call(self, row, col, view=None):
raise NotImplementedError()
-class NavigationComponent(VComponent):
+class NavigationComponent(Component):
"""abstract base class for navigation components"""
- __selectors__ = (paginated_rset,)
id = 'navigation'
+ __select__ = paginated_rset()
+
page_size_property = 'navigation.page-size'
start_param = '__start'
stop_param = '__stop'
@@ -73,19 +71,6 @@
selected_page_link_templ = u'<span class="selectedSlice"><a href="%s" title="%s">%s</a></span>'
previous_page_link_templ = next_page_link_templ = page_link_templ
no_previous_page_link = no_next_page_link = u''
-
- @classmethod
- def selected(cls, req, rset, row=None, col=None, page_size=None, **kwargs):
- """by default web app objects are usually instantiated on
- selection according to a request, a result set, and optional
- row and col
- """
- instance = super(NavigationComponent, cls).selected(req, rset, row, col, **kwargs)
- if page_size is not None:
- instance.page_size = page_size
- elif 'page_size' in req.form:
- instance.page_size = int(req.form['page_size'])
- return instance
def __init__(self, req, rset):
super(NavigationComponent, self).__init__(req, rset)
@@ -96,8 +81,14 @@
try:
return self._page_size
except AttributeError:
- self._page_size = self.req.property_value(self.page_size_property)
- return self._page_size
+ page_size = self.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'])
+ else:
+ page_size = self.req.property_value(self.page_size_property)
+ self._page_size = page_size
+ return page_size
def set_page_size(self, page_size):
self._page_size = page_size
@@ -151,25 +142,19 @@
class RelatedObjectsVComponent(EntityVComponent):
"""a section to display some related entities"""
- __selectors__ = (one_line_rset, primary_view,
- etype_rtype_selector, has_relation,
- match_context_prop, accept)
+ __select__ = EntityVComponent.__select__ & partial_has_related_entities()
+
vid = 'list'
-
+
def rql(self):
- """override this method if you want to use a custom rql query.
- """
+ """override this method if you want to use a custom rql query"""
return None
def cell_call(self, row, col, view=None):
rql = self.rql()
if rql is None:
entity = self.rset.get_entity(row, col)
- if self.target == 'object':
- role = 'subject'
- else:
- role = 'object'
- rset = entity.related(self.rtype, role)
+ rset = entity.related(self.rtype, role(self))
else:
eid = self.rset[row][col]
rset = self.req.execute(self.rql(), {'x': eid}, 'x')
@@ -178,3 +163,10 @@
self.w(u'<div class="%s">' % self.div_class())
self.wview(self.vid, rset, title=self.req._(self.title).capitalize())
self.w(u'</div>')
+
+
+VComponent = class_renamed('VComponent', Component,
+ 'VComponent is deprecated, use Component')
+SingletonVComponent = class_renamed('SingletonVComponent', Component,
+ 'SingletonVComponent is deprecated, use '
+ 'Component and explicit registration control')
--- a/web/controller.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/controller.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -11,8 +11,8 @@
from cubicweb import typed_eid
from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.selectors import match_user_group
-from cubicweb.common.appobject import AppObject
+from cubicweb.selectors import yes, require_group_compat
+from cubicweb.appobject import AppObject
from cubicweb.web import LOGGER, Redirect, RequestError
@@ -68,8 +68,8 @@
"""
__registry__ = 'controllers'
__registerer__ = priority_registerer
- __selectors__ = (match_user_group,)
- require_groups = ()
+ __select__ = yes()
+ registered = require_group_compat(AppObject.registered)
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
--- a/web/data/cubicweb.ajax.js Fri Feb 27 09:59:53 2009 +0100
+++ b/web/data/cubicweb.ajax.js Mon Mar 02 21:03:54 2009 +0100
@@ -9,7 +9,18 @@
var JSON_BASE_URL = baseuri() + 'json?';
-function postAjaxLoad(node) {
+/*
+ * inspect dom response, search for a <div class="ajaxHtmlHead"> node and
+ * put its content into the real document's head.
+ * This enables dynamic css and js loading and is used by replacePageChunk
+ */
+function loadAjaxHtmlHead(node) {
+ jQuery(node).find('div.ajaxHtmlHead').appendTo(jQuery('head'));
+}
+
+function postAjaxLoad(node, req) {
+ // addStylesheets(evalJSON(req.getResponseHeader('X-Cubicweb-Stylesheets') || '[]'));
+ loadAjaxHtmlHead(node);
// find sortable tables if there are some
if (typeof(Sortable) != 'undefined') {
Sortable.sortTables(node);
@@ -31,23 +42,18 @@
// cubicweb loadxhtml plugin to make jquery handle xhtml response
jQuery.fn.loadxhtml = function(url, data, reqtype, mode) {
- var ajax = null;
- if (reqtype == 'post') {
- ajax = jQuery.post;
- } else {
- ajax = jQuery.get;
- }
if (this.size() > 1) {
log('loadxhtml was called with more than one element');
}
+ var node = this.get(0); // only consider the first element
mode = mode || 'replace';
var callback = null;
if (data && data.callback) {
callback = data.callback;
delete data.callback;
}
- var node = this.get(0); // only consider the first element
- ajax(url, data, function(response) {
+ var deferred = loadJSON(url, data, reqtype);
+ deferred.addCallback(function(response, req) {
var domnode = getDomFromResponse(response);
if (mode == 'swap') {
var origId = node.id;
@@ -60,7 +66,7 @@
} else if (mode == 'append') {
jQuery(node).append(domnode);
}
- postAjaxLoad(node);
+ postAjaxLoad(node, req);
while (jQuery.isFunction(callback)) {
callback = callback.apply(this, [domnode]);
}
--- a/web/facet.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/facet.py Mon Mar 02 21:03:54 2009 +0100
@@ -19,10 +19,9 @@
from rql import parse, nodes
from cubicweb import Unauthorized, typed_eid
-from cubicweb.common.selectors import match_context_prop, one_has_relation
+from cubicweb.selectors import match_context_prop, partial_relation_possible
+from cubicweb.appobject import AppRsetObject
from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.appobject import AppRsetObject
-from cubicweb.common.utils import AcceptMixIn
from cubicweb.web.htmlwidgets import HTMLWidget
## rqlst manipulation functions used by facets ################################
@@ -234,7 +233,7 @@
## base facet classes #########################################################
-class AbstractFacet(AcceptMixIn, AppRsetObject):
+class AbstractFacet(AppRsetObject):
__registerer__ = priority_registerer
__abstract__ = True
__registry__ = 'facets'
@@ -334,10 +333,10 @@
class RelationFacet(VocabularyFacet):
- __selectors__ = (one_has_relation, match_context_prop)
- # class attributes to configure the relation facet
+ __select__ = partial_relation_possible() & match_context_prop()
+ # class attributes to configure the rel ation facet
rtype = None
- role = 'subject'
+ role = 'subject'
target_attr = 'eid'
# set this to a stored procedure name if you want to sort on the result of
# this function's result instead of direct value
--- a/web/form.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/form.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,32 +1,45 @@
"""abstract form classes for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
from simplejson import dumps
+from mx.DateTime import today, now
+from logilab.common.compat import any
from logilab.mtconverter import html_escape
+from yams.constraints import SizeConstraint, StaticVocabularyConstraint
+
from cubicweb import typed_eid
-from cubicweb.common.selectors import match_form_params
+from cubicweb.utils import ustrftime
+from cubicweb.selectors import match_form_params
+from cubicweb.view import NOINDEX, NOFOLLOW, View, EntityView, AnyRsetView
from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.view import NOINDEX, NOFOLLOW, View, EntityView, AnyRsetView
+from cubicweb.common.uilib import toggle_action
from cubicweb.web import stdmsgs
from cubicweb.web.httpcache import NoHTTPCacheManager
-from cubicweb.web.controller import redirect_params
+from cubicweb.web.controller import NAV_FORM_PARAMETERS, redirect_params
+from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
def relation_id(eid, rtype, target, reid):
if target == 'subject':
return u'%s:%s:%s' % (eid, rtype, reid)
return u'%s:%s:%s' % (reid, rtype, eid)
+
+def toggable_relation_link(eid, nodeid, label='x'):
+ js = u"javascript: togglePendingDelete('%s', %s);" % (nodeid, html_escape(dumps(eid)))
+ return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (js, nodeid, label)
class FormMixIn(object):
- """abstract form mix-in"""
+ """abstract form mix-in
+ XXX: you should inherit from this FIRST (obscure pb with super call)"""
category = 'form'
controller = 'edit'
domid = 'entityForm'
@@ -213,41 +226,930 @@
def button_apply(self, label=None, tabindex=None):
label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
- return self.ACTION_SUBMIT_STR % ('__action_apply', label, self.domid, label, tabindex or 3)
+ return self.ACTION_SUBMIT_STR % ('__action_apply', label, self.domid,
+ label, tabindex or 3)
def button_delete(self, label=None, tabindex=None):
label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
- return self.ACTION_SUBMIT_STR % ('__action_delete', label, self.domid, label, tabindex or 3)
+ return self.ACTION_SUBMIT_STR % ('__action_delete', label, self.domid,
+ label, tabindex or 3)
def button_cancel(self, label=None, tabindex=None):
label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
- return self.ACTION_SUBMIT_STR % ('__action_cancel', label, self.domid, label, tabindex or 4)
+ return self.ACTION_SUBMIT_STR % ('__action_cancel', label, self.domid,
+ label, tabindex or 4)
def button_reset(self, label=None, tabindex=None):
label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
return u'<input class="validateButton" type="reset" value="%s" tabindex="%s"/>' % (
label, tabindex or 4)
+
+
+###############################################################################
+
+from cubicweb.common import tags
+
+# widgets ############
+
+class FieldWidget(object):
+ needs_js = ()
+ needs_css = ()
+
+ def __init__(self, attrs=None):
+ self.attrs = attrs or {}
+
+ def add_media(self, form):
+ """adds media (CSS & JS) required by this widget"""
+ req = form.req
+ if self.needs_js:
+ req.add_js(self.needs_js)
+ if self.needs_css:
+ req.add_css(self.needs_css)
-def toggable_relation_link(eid, nodeid, label='x'):
- js = u"javascript: togglePendingDelete('%s', %s);" % (nodeid, html_escape(dumps(eid)))
- return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (js, nodeid, label)
+ def render(self, form, field):
+ raise NotImplementedError
+
+ def _render_attrs(self, form, field):
+ name = form.context[field]['name']
+ values = form.context[field]['value']
+ if not isinstance(values, (tuple, list)):
+ values = (values,)
+ attrs = dict(self.attrs)
+ attrs['id'] = form.context[field]['id']
+ return name, values, attrs
+
+class Input(FieldWidget):
+ type = None
+
+ def render(self, form, field):
+ self.add_media(form)
+ name, values, attrs = self._render_attrs(form, field)
+ inputs = [tags.input(name=name, value=value, type=self.type, **attrs)
+ for value in values]
+ return u'\n'.join(inputs)
+
+class TextInput(Input):
+ type = 'text'
+
+class PasswordInput(Input):
+ type = 'password'
+
+ def render(self, form, field):
+ self.add_media(form)
+ name, values, attrs = self._render_attrs(form, field)
+ assert len(values) == 1
+ inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs),
+ '<br/>',
+ tags.input(name=name+'-confirm', type=self.type, **attrs),
+ ' ', tags.span(form.req._('confirm password'),
+ **{'class': 'emphasis'})]
+ return u'\n'.join(inputs)
+
+class FileInput(Input):
+ type = 'file'
+
+ def _render_attrs(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
+
+class HiddenInput(Input):
+ type = 'hidden'
+
+class ButtonInput(Input):
+ type = 'button'
+
+class TextArea(FieldWidget):
+ def render(self, form, field):
+ name, values, attrs = self._render_attrs(form, field)
+ attrs.setdefault('onkeypress', 'autogrow(this)')
+ if not values:
+ value = u''
+ elif len(values) == 1:
+ value = values[0]
+ else:
+ raise ValueError('a textarea is not supposed to be multivalued')
+ return tags.textarea(value, name=name, **attrs)
+
+
+class FCKEditor(TextArea):
+ def __init__(self, attrs):
+ super(FCKEditor, self).__init__(attrs)
+ self.attrs['cubicweb:type'] = 'wysiwyg'
+
+ def render(self, form, field):
+ form.req.fckeditor_config()
+ return super(self, FCKEditor, self).render(form, field)
+
+
+#class EditableFile(Widget):
+# # XXX
+# pass
+
+class Select(FieldWidget):
+ def __init__(self, attrs=None, multiple=False):
+ super(Select, self).__init__(attrs)
+ self.multiple = multiple
+
+ def render(self, form, field):
+ name, curvalues, attrs = self._render_attrs(form, field)
+ vocab = field.vocabulary(form)
+ options = []
+ for label, value in vocab:
+ if value in curvalues:
+ options.append(tags.option(label, value=value, selected='selected'))
+ else:
+ options.append(tags.option(label, value=value))
+ if attrs is None:
+ return tags.select(name=name, options=options)
+ return tags.select(name=name, multiple=self.multiple,
+ options=options, **attrs)
+
+
+class CheckBox(Input):
+ type = 'checkbox'
+
+ def _render_attrs(self, form, field):
+ name, values, attrs = super(CheckBox, self)._render_attrs(form, field)
+ if values and values[0]:
+ attrs['checked'] = u'checked'
+ return name, values, attrs
+
+
+class Radio(FieldWidget):
+ pass
+
+
+class DateTimePicker(TextInput):
+ monthnames = ('january', 'february', 'march', 'april',
+ 'may', 'june', 'july', 'august',
+ 'september', 'october', 'november', 'december')
+ daynames = ('monday', 'tuesday', 'wednesday', 'thursday',
+ 'friday', 'saturday', 'sunday')
+
+ needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js')
+ needs_css = ('cubicweb.calendar_popup.css',)
+
+ @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 render(self, form, field):
+ txtwidget = super(DateTimePicker, self).render(form, field)
+ self.add_localized_infos(form.req)
+ cal_button = self._render_calendar_popup(form, field)
+ return txtwidget + cal_button
+
+ def _render_calendar_popup(self, form, field):
+ req = form.req
+ value = form.context[field]['rawvalue']
+ inputid = form.context[field]['id']
+ helperid = '%shelper' % inputid
+ if not value:
+ value = today()
+ year, month = value.year, value.month
+ onclick = "toggleCalendar('%s', '%s', %s, %s);" % (
+ helperid, inputid, year, 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) )
+
+
+# fields ############
+
+class Field(object):
+ """field class is introduced to control what's displayed in edition form
+ """
+ widget = TextInput
+ needs_multipart = False
+ creation_rank = 0
+
+ def __init__(self, name=None, id=None, label=None,
+ widget=None, required=False, initial=None,
+ choices=None, help=None, eidparam=False):
+ self.required = required
+ if widget is not None:
+ self.widget = widget
+ if isinstance(self.widget, type):
+ self.widget = self.widget()
+ self.name = name
+ self.label = label or name
+ self.id = id or name
+ self.initial = initial
+ self.choices = choices or ()
+ self.help = help
+ self.eidparam = eidparam
+ self.role = 'subject'
+ # global fields ordering in forms
+ Field.creation_rank += 1
+
+ def set_name(self, name):
+ assert name
+ self.name = name
+ if not self.id:
+ self.id = name
+ if not self.label:
+ self.label = name
+
+ def is_visible(self):
+ return not isinstance(self.widget, HiddenInput)
+
+ def actual_fields(self, form):
+ yield self
+
+ def __unicode__(self):
+ return u'<%s name=%r label=%r id=%r initial=%r>' % (
+ self.__class__.__name__, self.name, self.label,
+ self.id, self.initial)
+
+ def __repr__(self):
+ return self.__unicode__().encode('utf-8')
+
+ def format_value(self, req, value):
+ if isinstance(value, (list, tuple)):
+ return [self.format_single_value(req, val) for val in value]
+ return self.format_single_value(req, value)
+
+ def format_single_value(self, req, value):
+ if value is None:
+ return u''
+ return unicode(value)
+
+ def get_widget(self, form):
+ return self.widget
+
+ def example_format(self, req):
+ return u''
+
+ def render(self, form, renderer):
+ return self.get_widget(form).render(form, self)
+
+
+ def vocabulary(self, form):
+ return self.choices
+
+
+class StringField(Field):
+ def __init__(self, max_length=None, **kwargs):
+ super(StringField, self).__init__(**kwargs)
+ self.max_length = max_length
+
+
+class TextField(Field):
+ widget = TextArea
+ def __init__(self, rows=10, cols=80, **kwargs):
+ super(TextField, self).__init__(**kwargs)
+ self.rows = rows
+ self.cols = cols
+
+
+class RichTextField(TextField):
+ widget = None
+ def __init__(self, format_field=None, **kwargs):
+ super(RichTextField, self).__init__(**kwargs)
+ self.format_field = format_field
+
+ def get_widget(self, form):
+ if self.widget is None:
+ if self.use_fckeditor(form):
+ return FCKEditor()
+ return TextArea()
+ return self.widget
+
+ def get_format_field(self, form):
+ if self.format_field:
+ return self.format_field
+ # we have to cache generated field since it's use as key in the
+ # context dictionnary
+ try:
+ return form.req.data[self]
+ except KeyError:
+ if self.use_fckeditor(form):
+ # if fckeditor is used and format field isn't explicitly
+ # deactivated, we want an hidden field for the format
+ widget = HiddenInput()
+ else:
+ # else we want a format selector
+ # XXX compute vocabulary
+ widget = Select
+ field = StringField(name=self.name + '_format', widget=widget)
+ form.req.data[self] = field
+ return field
+
+ def actual_fields(self, form):
+ yield self
+ format_field = self.get_format_field(form)
+ if format_field:
+ yield format_field
+
+ def use_fckeditor(self, form):
+ """return True if fckeditor should be used to edit entity's attribute named
+ `attr`, according to user preferences
+ """
+ if form.config.fckeditor_installed() and form.req.property_value('ui.fckeditor'):
+ return form.form_format_field_value(self) == 'text/html'
+ return False
+
+ def render(self, form, renderer):
+ format_field = self.get_format_field(form)
+ if format_field:
+ result = format_field.render(form, renderer)
+ else:
+ result = u''
+ return result + self.get_widget(form).render(form, self)
+
+
+class FileField(StringField):
+ widget = FileInput
+ needs_multipart = True
+
+ def __init__(self, format_field=None, encoding_field=None, **kwargs):
+ super(FileField, self).__init__(**kwargs)
+ self.format_field = format_field
+ self.encoding_field = encoding_field
+
+ def actual_fields(self, form):
+ yield self
+ if self.format_field:
+ yield self.format_field
+ if self.encoding_field:
+ yield self.encoding_field
+
+ def render(self, form, renderer):
+ wdgs = [self.get_widget(form).render(form, self)]
+ if self.format_field or self.encoding_field:
+ divid = '%s-advanced' % form.context[self]['name']
+ wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
+ (html_escape(toggle_action(divid)),
+ form.req._('show advanced fields'),
+ html_escape(form.req.build_url('data/puce_down.png')),
+ form.req._('show advanced fields')))
+ wdgs.append(u'<div id="%s" class="hidden">' % divid)
+ if self.format_field:
+ wdgs.append(self.render_subfield(form, self.format_field, renderer))
+ 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']:
+ # 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'],
+ type=u'checkbox'))
+ wdgs.append(form.req._('detach attached file'))
+ return u'\n'.join(wdgs)
+
+ def render_subfield(self, form, field, renderer):
+ return (renderer.render_label(form, field)
+ + field.render(form, renderer)
+ + renderer.render_help(form, field)
+ + u'<br/>')
+
+
+class IntField(Field):
+ def __init__(self, min=None, max=None, **kwargs):
+ super(IntField, self).__init__(**kwargs)
+ self.min = min
+ self.max = max
+
+
+class BooleanField(Field):
+ widget = Radio
+
+
+class FloatField(IntField):
+ def format_single_value(self, req, value):
+ formatstr = entity.req.property_value('ui.float-format')
+ if value is None:
+ return u''
+ return formatstr % float(value)
+
+ def render_example(self, req):
+ return self.format_value(req, 1.234)
+
+
+class DateField(StringField):
+ format_prop = 'ui.date-format'
+ widget = DateTimePicker
+
+ def format_single_value(self, req, value):
+ return value and ustrftime(value, req.property_value(self.format_prop)) or u''
+
+ def render_example(self, req):
+ return self.format_value(req, now())
+
+
+class DateTimeField(DateField):
+ format_prop = 'ui.datetime-format'
+
+
+class TimeField(DateField):
+ format_prop = 'ui.datetime-format'
-class Form(FormMixIn, View):
- """base class for forms. Apply by default according to request form
- parameters specified using the `form_params` class attribute which
- should list necessary parameters in the form to be accepted.
- """
- __registerer__ = accepts_registerer
- __select__ = classmethod(match_form_params)
+class HiddenInitialValueField(Field):
+ def __init__(self, visible_field, name):
+ super(HiddenInitialValueField, self).__init__(name=name,
+ widget=HiddenInput,
+ eidparam=True)
+ self.visible_field = visible_field
+
+
+class RelationField(Field):
+ def __init__(self, **kwargs):
+ super(RelationField, self).__init__(**kwargs)
+
+ @staticmethod
+ def fromcardinality(card, role, **kwargs):
+ return RelationField(widget=Select(multiple=card in '*+'),
+ **kwargs)
+
+ def vocabulary(self, form):
+ entity = form.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.eid_rset(eid).get_entity(0, 0) 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
+ 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 = []
+ return res + form.form_field_vocabulary(self) + relatedvocab
+
+ def format_single_value(self, req, value):
+ return value
+
+# forms ############
+class metafieldsform(type):
+ def __new__(mcs, name, bases, classdict):
+ allfields = []
+ for base in bases:
+ if hasattr(base, '_fields_'):
+ allfields += base._fields_
+ clsfields = (item for item in classdict.items()
+ if isinstance(item[1], Field))
+ for fieldname, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
+ if not field.name:
+ field.set_name(fieldname)
+ allfields.append(field)
+ classdict['_fields_'] = allfields
+ return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
+
+
+class FieldsForm(FormMixIn):
+ __metaclass__ = metafieldsform
+
+ def __init__(self, req, domid=None, title=None, action='edit',
+ onsubmit="return freezeFormButtons('%s');",
+ cssclass=None, cssstyle=None, cwtarget=None, buttons=None,
+ redirect_path=None, set_error_url=True, copy_nav_params=False):
+ self.req = req
+ self.config = req.vreg.config
+ self.domid = domid or 'form'
+ self.title = title
+ self.action = action
+ self.onsubmit = onsubmit
+ self.cssclass = cssclass
+ self.cssstyle = cssstyle
+ self.cwtarget = cwtarget
+ self.redirect_path = redirect_path
+ self.fields = list(self.__class__._fields_)
+ if set_error_url:
+ self.form_add_hidden('__errorurl', req.url())
+ if copy_nav_params:
+ for param in NAV_FORM_PARAMETERS:
+ value = req.form.get(param)
+ if value:
+ self.form_add_hidden(param, initial=value)
+ self.buttons = buttons or []
+ self.context = {}
+
+ @property
+ def form_needs_multipart(self):
+ return any(field.needs_multipart for field in self.fields)
+
+ def form_add_hidden(self, name, value=None, **kwargs):
+ self.fields.append(StringField(name=name, widget=HiddenInput,
+ initial=value, **kwargs))
+
+ def form_render(self, **values):
+ renderer = values.pop('renderer', FormRenderer())
+ return renderer.render(self, values)
+
+ def form_build_context(self, values):
+ self.context = context = {}
+ # on validation error, we get a dictionnary of previously submitted values
+ previous_values = self.req.data.get('formvalues')
+ if previous_values:
+ values.update(previous_values)
+ for field in self.fields:
+ for field in field.actual_fields(self):
+ value = self.form_field_value(field, values)
+ context[field] = {'value': field.format_value(self.req, value),
+ 'rawvalue': value,
+ 'name': self.form_field_name(field),
+ 'id': self.form_field_id(field),
+ }
+
+ def form_field_value(self, field, values):
+ """looks for field's value in
+ 1. kw args given to render_form (including previously submitted form
+ values if any)
+ 2. req.form
+ 3. field's initial value
+ """
+ if field.name in values:
+ value = values[field.name]
+ elif field.name in self.req.form:
+ value = self.req.form[field.name]
+ else:
+ value = field.initial
+ return value
+
+ def form_format_field_value(self, field, values):
+ return self.req.property_value('ui.default-text-format')
+
+ def form_field_name(self, field):
+ return field.name
+
+ def form_field_id(self, field):
+ return field.id
+
+ def form_field_vocabulary(self, field):
+ raise NotImplementedError
+
+ def form_buttons(self):
+ return self.buttons
+
+
+class EntityFieldsForm(FieldsForm):
+
+ def __init__(self, *args, **kwargs):
+ kwargs.setdefault('domid', 'entityForm')
+ self.entity = kwargs.pop('entity', None)
+ super(EntityFieldsForm, self).__init__(*args, **kwargs)
+ self.form_add_hidden('__type', eidparam=True)
+ self.form_add_hidden('eid')
+
+ def form_render(self, **values):
+ self.form_add_entity_hiddens(self.entity.e_schema)
+ return super(EntityFieldsForm, self).form_render(**values)
+
+ def form_add_entity_hiddens(self, eschema):
+ for field in self.fields[:]:
+ for field in field.actual_fields(self):
+ fieldname = field.name
+ if fieldname != 'eid' and (
+ (eschema.has_subject_relation(fieldname) or
+ eschema.has_object_relation(fieldname))):
+ field.eidparam = True
+ self.fields.append(self.form_entity_hidden_field(field))
+
+ def form_entity_hidden_field(self, field):
+ """returns the hidden field which will indicate the value
+ before the modification
+ """
+ # Only RelationField has a `role` attribute, others are used
+ # to describe attribute fields => role is 'subject'
+ if getattr(field, 'role', 'subject') == 'subject':
+ name = 'edits-%s' % field.name
+ else:
+ name = 'edito-%s' % field.name
+ return HiddenInitialValueField(field, name=name)
+
+ def form_field_value(self, field, values):
+ """look for field's value with the following rules:
+ 1. handle special __type and eid fields
+ 2. looks in kw args given to render_form (including previously submitted
+ form values if any)
+ 3. looks in req.form
+ 4. if entity has an eid:
+ 1. looks for an associated attribute / method
+ 2. use field's initial value
+ else:
+ 1. looks for a default_<fieldname> attribute / method on the form
+ 2. use field's initial value
+
+ values found in step 4 may be a callable which'll then be called.
+ """
+ fieldname = field.name
+ if fieldname.startswith('edits-') or fieldname.startswith('edito-'):
+ # edit[s|o]- fieds must have the actual value stored on the entity
+ if self.entity.has_eid():
+ value = self.form_field_entity_value(field.visible_field, default_initial=False)
+ else:
+ value = INTERNAL_FIELD_VALUE
+ elif fieldname == '__type':
+ value = self.entity.id
+ elif fieldname == 'eid':
+ value = self.entity.eid
+ elif fieldname in values:
+ value = values[fieldname]
+ elif fieldname in self.req.form:
+ value = self.req.form[fieldname]
+ else:
+ if self.entity.has_eid() and field.eidparam:
+ # use value found on the entity or field's initial value if it's
+ # not an attribute of the entity (XXX may conflicts and get
+ # undesired value)
+ value = self.form_field_entity_value(field, default_initial=True)
+ else:
+ defaultattr = 'default_%s' % fieldname
+ if hasattr(self.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.entity.id), DeprecationWarning)
+ value = getattr(self.entity, defaultattr)
+ elif hasattr(self, defaultattr):
+ # search for default_<field name> on the form instance
+ value = getattr(self, defaultattr)
+ else:
+ # use field's initial value
+ value = field.initial
+ if callable(value):
+ values = value()
+ return value
+
+ def form_format_field_value(self, field, values):
+ entity = self.entity
+ if field.eidparam and entity.has_format(field.name) and (
+ entity.has_eid() or '%s_format' % field.name in entity):
+ return self.entity.format(field.name) == 'text/html'
+ return self.req.property_value('ui.default-text-format')
- form_params = ()
+ def form_field_entity_value(self, field, default_initial=True):
+ attr = field.name
+ if field.role == 'object':
+ attr += '_object'
+ else:
+ attrtype = self.entity.e_schema.destination(attr)
+ if attrtype == 'Password':
+ return self.entity.has_eid() and INTERNAL_FIELD_VALUE or ''
+ if attrtype == 'Bytes':
+ # XXX value should reflect if some file is already attached
+ return self.entity.has_eid()
+ if default_initial:
+ value = getattr(self.entity, attr, field.initial)
+ else:
+ value = getattr(self.entity, attr)
+ if isinstance(field, RelationField):
+ # in this case, value is the list of related entities
+ value = [ent.eid for ent in value]
+ return value
+
+ def form_field_name(self, field):
+ if field.eidparam:
+ return eid_param(field.name, self.entity.eid)
+ return field.name
+
+ def form_field_id(self, field):
+ if field.eidparam:
+ return eid_param(field.id, self.entity.eid)
+ return field.id
+
+ def form_field_vocabulary(self, field):
+ role, rtype = field.role, field.name
+ try:
+ vocabfunc = getattr(self.entity, '%s_%s_vocabulary' % (role, rtype))
+ except AttributeError:
+ vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+ else:
+ # XXX bw compat, default_<field name> on the entity
+ warn('found %s_%s_vocabulary on %s, should be set on a specific form'
+ % (role, rtype, self.entity.id), DeprecationWarning)
+ return vocabfunc(rtype)
+## XXX BACKPORT ME
+## 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)
+
+ 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.entity
+ if isinstance(rtype, basestring):
+ rtype = entity.schema.rschema(rtype)
+ done = None
+ assert not rtype.is_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.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 relation_vocabulary(self, rtype, targettype, role,
+ limit=None, done=None):
+ if done is None:
+ done = set()
+ req = self.req
+ rset = 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
+
+
+class MultipleFieldsForm(FieldsForm):
+ def __init__(self, *args, **kwargs):
+ super(MultipleFieldsForm, self).__init__(*args, **kwargs)
+ self.forms = []
-class EntityForm(FormMixIn, EntityView):
- """base class for forms applying on an entity (i.e. uniform result set)
+ def form_add_subform(self, subform):
+ self.forms.append(subform)
+
+
+# form renderers ############
+class FormRenderer(object):
+
+ # renderer interface ######################################################
+
+ def render(self, form, values, display_help=True):
+ data = []
+ w = data.append
+ w(self.open_form(form))
+ w(u'<div id="progress">%s</div>' % form.req._('validating...'))
+ w(u'<fieldset>')
+ w(tags.input(type='hidden', name='__form_id', value=form.domid))
+ if form.redirect_path:
+ w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path))
+ self.render_fields(w, form, values, display_help)
+ self.render_buttons(w, form)
+ w(u'</fieldset>')
+ w(u'</form>')
+ return '\n'.join(data)
+
+ def render_label(self, form, field):
+ label = form.req._(field.label)
+ attrs = {'for': form.context[field]['id']}
+ if field.required:
+ attrs['class'] = 'required'
+ return tags.label(label, **attrs)
+
+ def render_help(self, form, field):
+ help = [ u'<br/>' ]
+ descr = field.help
+ if descr:
+ help.append('<span class="helper">%s</span>' % req._(descr))
+ example = field.example_format(form.req)
+ if example:
+ help.append('<span class="helper">(%s: %s)</span>'
+ % (req._('sample format'), example))
+ return u' '.join(help)
+
+ # specific methods (mostly to ease overriding) #############################
+
+ def open_form(self, form):
+ if form.form_needs_multipart:
+ enctype = 'multipart/form-data'
+ else:
+ enctype = 'application/x-www-form-urlencoded'
+ tag = ('<form action="%s" methody="post" id="%s" enctype="%s"' % (
+ html_escape(form.action or '#'), form.domid, enctype))
+ if form.onsubmit:
+ tag += ' onsubmit="%s"' % html_escape(form.onsubmit)
+ if form.cssstyle:
+ tag += ' style="%s"' % html_escape(form.cssstyle)
+ if form.cssclass:
+ tag += ' class="%s"' % html_escape(form.cssclass)
+ if form.cwtarget:
+ tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+ return tag + '>'
+
+ def render_fields(self, w, form, values, display_help=True):
+ form.form_build_context(values)
+ fields = form.fields[:]
+ for field in form.fields:
+ if not field.is_visible():
+ w(field.render(form, self))
+ fields.remove(field)
+ if fields:
+ w(u'<table>')
+ for field in fields:
+ w(u'<tr>')
+ w('<th>%s</th>' % self.render_label(form, field))
+ w(u'<td style="width:100%;">')
+ w(field.render(form, self))
+ if display_help == True:
+ w(self.render_help(form, field))
+ w(u'</td></tr>')
+ w(u'</table>')
+ for childform in getattr(form, 'forms', []):
+ self.render_fields(w, childform, values)
+
+ def render_buttons(self, w, form):
+ w(u'<table class="formButtonBar">\n<tr>\n')
+ for button in form.form_buttons():
+ w(u'<td>%s</td>\n' % button)
+ w(u'</tr></table>')
+
+
+def stringfield_from_constraints(constraints, **kwargs):
+ field = None
+ for cstr in constraints:
+ if isinstance(cstr, StaticVocabularyConstraint):
+ return StringField(widget=Select(vocabulary=cstr.vocabulary),
+ **kwargs)
+ if isinstance(cstr, SizeConstraint) and cstr.max is not None:
+ if cstr.max > 257:
+ field = textfield_from_constraint(cstr, **kwargs)
+ else:
+ field = StringField(max_length=cstr.max, **kwargs)
+ return field or TextField(**kwargs)
+
+
+def textfield_from_constraint(constraint, **kwargs):
+ if 256 < constraint.max < 513:
+ rows, cols = 5, 60
+ else:
+ rows, cols = 10, 80
+ return TextField(rows, cols, **kwargs)
+
+
+def find_field(eclass, subjschema, rschema, role='subject'):
+ """return the most adapated widget to edit the relation
+ 'subjschema rschema objschema' according to information found in the schema
"""
+ fieldclass = None
+ if role == 'subject':
+ objschema = rschema.objects(subjschema)[0]
+ cardidx = 0
+ else:
+ objschema = rschema.subjects(subjschema)[0]
+ cardidx = 1
+ card = rschema.rproperty(subjschema, objschema, 'cardinality')[cardidx]
+ required = card in '1+'
+ if rschema in eclass.widgets:
+ fieldclass = eclass.widgets[rschema]
+ if isinstance(fieldclass, basestring):
+ return StringField(name=rschema.type)
+ elif not rschema.is_final():
+ return RelationField.fromcardinality(card, role,name=rschema.type,
+ required=required)
+ else:
+ fieldclass = FIELDS[objschema]
+ if fieldclass is StringField:
+ constraints = rschema.rproperty(subjschema, objschema, 'constraints')
+ return stringfield_from_constraints(constraints, name=rschema.type,
+ required=required)
+ return fieldclass(name=rschema.type, required=required)
-class AnyRsetForm(FormMixIn, AnyRsetView):
- """base class for forms applying on any empty result sets
- """
-
+FIELDS = {
+ 'Boolean': BooleanField,
+ 'Bytes': FileField,
+ 'Date': DateField,
+ 'Datetime': DateTimeField,
+ 'Int': IntField,
+ 'Float': FloatField,
+ 'Decimal': StringField,
+ 'Password': StringField,
+ 'String' : StringField,
+ 'Time': TimeField,
+ }
--- a/web/htmlwidgets.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/htmlwidgets.py Mon Mar 02 21:03:54 2009 +0100
@@ -4,13 +4,13 @@
serialization time
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
from logilab.mtconverter import html_escape
-from cubicweb.common.utils import UStringIO
+from cubicweb.utils import UStringIO
from cubicweb.common.uilib import toggle_action
# XXX HTMLWidgets should have access to req (for datadir / static urls,
--- a/web/httpcache.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/httpcache.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -92,7 +92,7 @@
# monkey patching, so view doesn't depends on this module and we have all
# http cache related logic here
-from cubicweb.common import view
+from cubicweb import view
def set_http_cache_headers(self):
self.http_cache_manager(self).set_headers()
--- a/web/request.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/request.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""abstract class for http request
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -18,15 +18,15 @@
from logilab.common.decorators import cached
-# XXX move _MARKER here once AppObject.external_resource has been removed
from cubicweb.dbapi import DBAPIRequest
-from cubicweb.common.appobject import _MARKER
from cubicweb.common.mail import header
from cubicweb.common.uilib import remove_html_tags
-from cubicweb.common.utils import SizeConstrainedList, HTMLHead
+from cubicweb.utils import SizeConstrainedList, HTMLHead
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, RequestError,
StatusResponse)
+_MARKER = object()
+
def list_form_param(form, param, pop=False):
"""get param from form parameters and return its value as a list,
@@ -265,9 +265,10 @@
@cached # so it's writed only once
def fckeditor_config(self):
+ self.add_js('fckeditor/fckeditor.js')
self.html_headers.define_var('fcklang', self.lang)
self.html_headers.define_var('fckconfigpath',
- self.build_url('data/fckcwconfig.js'))
+ self.build_url('data/cubicweb.fckcwconfig.js'))
def edited_eids(self, withtype=False):
"""return a list of edited eids"""
--- a/web/test/runtests.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# -*- coding: ISO-8859-1 -*-
-"""Script used to fire all tests"""
-
-__revision__ = '$Id: runtests.py,v 1.1 2005-06-17 14:09:18 adim Exp $'
-
-from logilab.common.testlib import main
-
-if __name__ == '__main__':
- import sys, os
- main(os.path.dirname(sys.argv[0]) or '.')
--- a/web/test/test_views.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/test/test_views.py Mon Mar 02 21:03:54 2009 +0100
@@ -37,11 +37,11 @@
def test_manual_tests(self):
rset = self.execute('Any P,F,S WHERE P is EUser, P firstname F, P surname S')
self.view('table', rset, template=None, displayfilter=True, displaycols=[0,2])
- rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
- rset.req.form['rtype'] = 'firstname'
- self.view('editrelation', rset, template=None)
- rset.req.form['rtype'] = 'use_email'
- self.view('editrelation', rset, template=None)
+# rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
+# rset.req.form['rtype'] = 'firstname'
+# self.view('editrelation', rset, template=None)
+# rset.req.form['rtype'] = 'use_email'
+# self.view('editrelation', rset, template=None)
def test_sortable_js_added(self):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_form.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,71 @@
+from logilab.common.testlib import unittest_main, mock_object
+from cubicweb import Binary
+from cubicweb.devtools.testlib import WebTest
+from cubicweb.web.form import *
+from cubicweb.web.views.baseforms import ChangeStateForm
+
+
+class CustomChangeStateForm(ChangeStateForm):
+ hello = IntField(name='youlou')
+ creation_date = DateTimeField(widget=DateTimePicker)
+
+
+class RTFForm(EntityFieldsForm):
+ content = RichTextField()
+
+class FFForm(EntityFieldsForm):
+ data = FileField(format_field=StringField(name='data_format'),
+ encoding_field=StringField(name='data_encoding'))
+
+class PFForm(EntityFieldsForm):
+ upassword = StringField(widget=PasswordInput)
+
+
+class EntityFieldsFormTC(WebTest):
+
+ def setUp(self):
+ super(EntityFieldsFormTC, self).setUp()
+ self.req = self.request()
+ self.entity = self.user(self.req)
+
+ def test_form_inheritance(self):
+ form = CustomChangeStateForm(self.req, redirect_path='perdu.com',
+ entity=self.entity)
+ self.assertEquals(form.form_render(state=123),
+ ''' ''')
+
+ def test_change_state_form(self):
+ form = ChangeStateForm(self.req, redirect_path='perdu.com',
+ entity=self.entity)
+ self.assertEquals(form.form_render(state=123),
+ ''' ''')
+
+ def test_delete_conf_form_multi(self):
+ rset = self.execute('EGroup X')
+ self.assertEquals(self.view('deleteconf', rset).source,
+ '')
+
+ def test_richtextfield(self):
+ card = self.add_entity('Card', title=u"tls sprint fev 2009",
+ content=u'new widgets system')
+ form = RTFForm(self.req, redirect_path='perdu.com',
+ entity=card)
+ self.assertEquals(form.form_render(),
+ '''''')
+
+ def test_filefield(self):
+ file = self.add_entity('File', name=u"pouet.txt",
+ data=Binary('new widgets system'))
+ form = FFForm(self.req, redirect_path='perdu.com',
+ entity=file)
+ self.assertEquals(form.form_render(),
+ '''''')
+
+ def test_passwordfield(self):
+ form = PFForm(self.req, redirect_path='perdu.com',
+ entity=self.entity)
+ self.assertEquals(form.form_render(),
+ '''''')
+
+if __name__ == '__main__':
+ unittest_main()
--- a/web/test/unittest_owl.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,4075 +0,0 @@
-"""unittests for schema2dot"""
-
-import os
-
-from logilab.common.testlib import TestCase, unittest_main
-from logilab.common.compat import set
-from cubicweb.devtools.testlib import WebTest
-
-from lxml import etree
-from StringIO import StringIO
-
-
-class OWLTC(WebTest):
-
- def test_schema2owl(self):
-
- parser = etree.XMLParser(dtd_validation=True)
-
- owl= StringIO('''<xsd:schema
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:owl="http://www.w3.org/2002/07/owl#"
- targetNamespace="http://www.w3.org/2002/07/owl#"
- elementFormDefault="qualified" attributeFormDefault="unqualified">
-
-<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
-
-<!-- The ontology -->
-
-<xsd:element name="Import">
- <xsd:complexType>
- <xsd:simpleContent>
- <xsd:extension base="xsd:anyURI">
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Ontology">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element ref="owl:Import" minOccurs="0" maxOccurs="unbounded"/>
- <xsd:group ref="owl:ontologyAnnotations"/>
- <xsd:group ref="owl:Axiom" minOccurs="0" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attribute name="ontologyIRI" type="xsd:anyURI" use="optional"/>
- <xsd:attribute name="versionIRI" type="xsd:anyURI" use="optional"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Entities, anonymous individuals, and literals -->
-
-<xsd:group name="Entity">
- <xsd:choice>
- <xsd:element ref="owl:Class"/>
- <xsd:element ref="owl:Datatype"/>
- <xsd:element ref="owl:ObjectProperty"/>
- <xsd:element ref="owl:DataProperty"/>
- <xsd:element ref="owl:AnnotationProperty"/>
- <xsd:element ref="owl:NamedIndividual"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="Class">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Datatype">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectProperty">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataProperty">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationProperty">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="Individual">
- <xsd:choice>
- <xsd:element ref="owl:NamedIndividual"/>
- <xsd:element ref="owl:AnonymousIndividual"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="NamedIndividual">
- <xsd:complexType>
- <xsd:attribute name="IRI" type="xsd:anyURI" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnonymousIndividual">
- <xsd:complexType>
- <xsd:attribute name="nodeID" type="xsd:NCName" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="Literal">
- <xsd:complexType>
- <xsd:simpleContent>
- <xsd:extension base="xsd:string">
- <xsd:attribute name="datatypeIRI" type="xsd:anyURI"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Declarations -->
-
-<xsd:element name="Declaration">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:Entity"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Object property expressions -->
-
-<xsd:group name="ObjectPropertyExpression">
- <xsd:choice>
- <xsd:element ref="owl:ObjectProperty"/>
- <xsd:element ref="owl:InverseObjectProperty"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="InverseObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element ref="owl:ObjectProperty"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Data property expressions -->
-
-<xsd:group name="DataPropertyExpression">
- <xsd:sequence>
- <xsd:element ref="owl:DataProperty"/>
- </xsd:sequence>
-</xsd:group>
-
-<!-- Data ranges -->
-
-<xsd:group name="DataRange">
- <xsd:choice>
- <xsd:element ref="owl:Datatype"/>
- <xsd:element ref="owl:DataIntersectionOf"/>
- <xsd:element ref="owl:DataUnionOf"/>
- <xsd:element ref="owl:DataComplementOf"/>
- <xsd:element ref="owl:DataOneOf"/>
- <xsd:element ref="owl:DatatypeRestriction"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="DataIntersectionOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataRange" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataUnionOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataRange" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataComplementOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataRange"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataOneOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element ref="owl:Literal" minOccurs="1" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DatatypeRestriction">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element ref="owl:Datatype"/>
- <xsd:element name="FacetRestriction" minOccurs="1" maxOccurs="unbounded">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element ref="owl:Literal"/>
- </xsd:sequence>
- <xsd:attribute name="facet" type="xsd:anyURI" use="required"/>
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Class expressions -->
-
-<xsd:group name="ClassExpression">
- <xsd:choice>
- <xsd:element ref="owl:Class"/>
- <xsd:element ref="owl:ObjectIntersectionOf"/>
- <xsd:element ref="owl:ObjectUnionOf"/>
- <xsd:element ref="owl:ObjectComplementOf"/>
- <xsd:element ref="owl:ObjectOneOf"/>
- <xsd:element ref="owl:ObjectSomeValuesFrom"/>
- <xsd:element ref="owl:ObjectAllValuesFrom"/>
- <xsd:element ref="owl:ObjectHasValue"/>
- <xsd:element ref="owl:ObjectHasSelf"/>
- <xsd:element ref="owl:ObjectMinCardinality"/>
- <xsd:element ref="owl:ObjectMaxCardinality"/>
- <xsd:element ref="owl:ObjectExactCardinality"/>
- <xsd:element ref="owl:DataSomeValuesFrom"/>
- <xsd:element ref="owl:DataAllValuesFrom"/>
- <xsd:element ref="owl:DataHasValue"/>
- <xsd:element ref="owl:DataMinCardinality"/>
- <xsd:element ref="owl:DataMaxCardinality"/>
- <xsd:element ref="owl:DataExactCardinality"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="ObjectIntersectionOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectUnionOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectComplementOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectOneOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:Individual" minOccurs="1" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectSomeValuesFrom">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectAllValuesFrom">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectHasValue">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:Individual"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectHasSelf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectMinCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectMaxCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectExactCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataSomeValuesFrom">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression" minOccurs="1" maxOccurs="unbounded"/>
- <xsd:group ref="owl:DataRange"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataAllValuesFrom">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression" minOccurs="1" maxOccurs="unbounded"/>
- <xsd:group ref="owl:DataRange"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataHasValue">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:element ref="owl:Literal"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataMinCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataMaxCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataExactCardinality">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:DataRange" minOccurs="0" maxOccurs="1"/>
- </xsd:sequence>
- <xsd:attribute name="cardinality" type="xsd:nonNegativeInteger" use="required"/>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Axioms -->
-
-<xsd:group name="Axiom">
- <xsd:choice>
- <xsd:element ref="owl:Declaration"/>
- <xsd:group ref="owl:ClassAxiom"/>
- <xsd:group ref="owl:ObjectPropertyAxiom"/>
- <xsd:group ref="owl:DataPropertyAxiom"/>
- <xsd:element ref="owl:HasKey"/>
- <xsd:group ref="owl:Assertion"/>
- <xsd:group ref="owl:AnnotationAxiom"/>
- </xsd:choice>
-</xsd:group>
-
-<!-- Class expression axioms -->
-
-<xsd:group name="ClassAxiom">
- <xsd:choice>
- <xsd:element ref="owl:SubClassOf"/>
- <xsd:element ref="owl:EquivalentClasses"/>
- <xsd:element ref="owl:DisjointClasses"/>
- <xsd:element ref="owl:DisjointUnion"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubClassOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ClassExpression"/> <!-- This is the subexpression -->
- <xsd:group ref="owl:ClassExpression"/> <!-- This is the superexpression -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentClasses">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointClasses">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointUnion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:element ref="owl:Class"/>
- <xsd:group ref="owl:ClassExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Object property axioms -->
-
-<xsd:group name="ObjectPropertyAxiom">
- <xsd:choice>
- <xsd:element ref="owl:SubObjectPropertyOf"/>
- <xsd:element ref="owl:EquivalentObjectProperties"/>
- <xsd:element ref="owl:DisjointObjectProperties"/>
- <xsd:element ref="owl:InverseObjectProperties"/>
- <xsd:element ref="owl:ObjectPropertyDomain"/>
- <xsd:element ref="owl:ObjectPropertyRange"/>
- <xsd:element ref="owl:FunctionalObjectProperty"/>
- <xsd:element ref="owl:InverseFunctionalObjectProperty"/>
- <xsd:element ref="owl:ReflexiveObjectProperty"/>
- <xsd:element ref="owl:IrreflexiveObjectProperty"/>
- <xsd:element ref="owl:SymmetricObjectProperty"/>
- <xsd:element ref="owl:AsymmetricObjectProperty"/>
- <xsd:element ref="owl:TransitiveObjectProperty"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubObjectPropertyOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:choice> <!-- This is the subproperty expression or the property chain -->
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:element name="PropertyChain">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- <xsd:group ref="owl:ObjectPropertyExpression"/> <!-- This is the superproperty expression -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentObjectProperties">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointObjectProperties">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyDomain">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyRange">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="InverseObjectProperties">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression" minOccurs="2" maxOccurs="2"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="FunctionalObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="InverseFunctionalObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ReflexiveObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="IrreflexiveObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="SymmetricObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AsymmetricObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="TransitiveObjectProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Data property axioms -->
-
-<xsd:group name="DataPropertyAxiom">
- <xsd:choice>
- <xsd:element ref="owl:SubDataPropertyOf"/>
- <xsd:element ref="owl:EquivalentDataProperties"/>
- <xsd:element ref="owl:DisjointDataProperties"/>
- <xsd:element ref="owl:DataPropertyDomain"/>
- <xsd:element ref="owl:DataPropertyRange"/>
- <xsd:element ref="owl:FunctionalDataProperty"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SubDataPropertyOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/> <!-- This is the subproperty expression -->
- <xsd:group ref="owl:DataPropertyExpression"/> <!-- This is the superproperty expression -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="EquivalentDataProperties">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DisjointDataProperties">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyDomain">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:ClassExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyRange">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:DataRange"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="FunctionalDataProperty">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Key axioms -->
-
-<xsd:element name="HasKey">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ClassExpression"/>
- <xsd:choice minOccurs="1" maxOccurs="unbounded">
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- </xsd:choice>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Assertions -->
-
-<xsd:group name="Assertion">
- <xsd:choice>
- <xsd:element ref="owl:SameIndividual"/>
- <xsd:element ref="owl:DifferentIndividuals"/>
- <xsd:element ref="owl:ClassAssertion"/>
- <xsd:element ref="owl:ObjectPropertyAssertion"/>
- <xsd:element ref="owl:NegativeObjectPropertyAssertion"/>
- <xsd:element ref="owl:DataPropertyAssertion"/>
- <xsd:element ref="owl:NegativeDataPropertyAssertion"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="SameIndividual">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:Individual" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DifferentIndividuals">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:Individual" minOccurs="2" maxOccurs="unbounded"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ClassAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ClassExpression"/>
- <xsd:group ref="owl:Individual"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="ObjectPropertyAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:Individual"/> <!-- This is the source invididual -->
- <xsd:group ref="owl:Individual"/> <!-- This is the target individual -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="NegativeObjectPropertyAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:ObjectPropertyExpression"/>
- <xsd:group ref="owl:Individual"/> <!-- This is the source invididual -->
- <xsd:group ref="owl:Individual"/> <!-- This is the target individual -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="DataPropertyAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:Individual"/> <!-- This is the source invididual -->
- <xsd:element ref="owl:Literal"/> <!-- This is the target value -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="NegativeDataPropertyAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:group ref="owl:DataPropertyExpression"/>
- <xsd:group ref="owl:Individual"/> <!-- This is the source invididual -->
- <xsd:element ref="owl:Literal"/> <!-- This is the target value -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<!-- Annotations -->
-
-<xsd:element name="IRI">
- <xsd:complexType>
- <xsd:simpleContent>
- <xsd:extension base="xsd:anyURI">
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:extension>
- </xsd:simpleContent>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="AnnotationSubject">
- <xsd:choice>
- <xsd:element ref="owl:IRI"/>
- <xsd:element ref="owl:AnonymousIndividual"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:group name="AnnotationValue">
- <xsd:choice>
- <xsd:element ref="owl:IRI"/>
- <xsd:element ref="owl:AnonymousIndividual"/>
- <xsd:element ref="owl:Literal"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="Annotation">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:annotationAnnotations"/>
- <xsd:element ref="owl:AnnotationProperty"/>
- <xsd:group ref="owl:AnnotationValue"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:group name="axiomAnnotations">
- <xsd:sequence>
- <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
- </xsd:sequence>
-</xsd:group>
-
-<xsd:group name="ontologyAnnotations">
- <xsd:sequence>
- <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
- </xsd:sequence>
-</xsd:group>
-
-<xsd:group name="annotationAnnotations">
- <xsd:sequence>
- <xsd:element ref="owl:Annotation" minOccurs="0" maxOccurs="unbounded"/>
- </xsd:sequence>
-</xsd:group>
-
-<!-- Annotation axioms -->
-
-<xsd:group name="AnnotationAxiom">
- <xsd:choice>
- <xsd:element ref="owl:AnnotationAssertion"/>
- <xsd:element ref="owl:SubAnnotationPropertyOf"/>
- <xsd:element ref="owl:AnnotationPropertyDomain"/>
- <xsd:element ref="owl:AnnotationPropertyRange"/>
- </xsd:choice>
-</xsd:group>
-
-<xsd:element name="AnnotationAssertion">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:element ref="owl:AnnotationProperty"/>
- <xsd:group ref="owl:AnnotationSubject"/>
- <xsd:group ref="owl:AnnotationValue"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="SubAnnotationPropertyOf">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:element ref="owl:AnnotationProperty"/> <!-- This is the subproperty -->
- <xsd:element ref="owl:AnnotationProperty"/> <!-- This is the superproperty -->
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationPropertyDomain">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:element ref="owl:AnnotationProperty"/>
- <xsd:element ref="owl:IRI"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-<xsd:element name="AnnotationPropertyRange">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:group ref="owl:axiomAnnotations"/>
- <xsd:element ref="owl:AnnotationProperty"/>
- <xsd:element ref="owl:IRI"/>
- </xsd:sequence>
- <xsd:attributeGroup ref="xml:specialAttrs"/>
- </xsd:complexType>
-</xsd:element>
-
-</xsd:schema>
-
-''')
-
- rdf = StringIO('''<xsd:schema
- xmlns:xsd="http://www.w3.org/1999/XMLSchema"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- targetNamespace="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-
- <xsd:element name="RDF">
- <xsd:complexType content="elementOnly" >
- <xsd:sequence maxOccurs="*" >
- <xsd:choice>
- <xsd:element ref="rdf:TypedNode" /><!-- abstract !-->
- <xsd:element ref="rdf:Bag" />
- <xsd:element ref="rdf:Seq" />
- <xsd:element ref="rdf:Alt" />
- </xsd:choice>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
-
- <!-- RDF Typed nodes -->
- <xsd:complexType name="TypedNodeType" content="elementOnly" >
- <xsd:sequence maxOccurs="*" >
- <xsd:element ref="rdf:PropertyElt" /><!--abstract !-->
- </xsd:sequence>
- <xsd:attribute name="id" minOccurs="0" type="ID" />
- <xsd:attribute name="type" minOccurs="0" type="string" />
- <xsd:attribute name="about" minOccurs="0" type="string" />
- <xsd:attribute name="aboutEach" minOccurs="0" type="string" />
- <xsd:attribute name="aboutEachPrefix" minOccurs="0" type="string" />
- <xsd:attribute name="badID" minOccurs="0" type="ID" />
- </xsd:complexType>
- <xsd:element name="TypedNode" abstract="true" type="rdf:TypedNodeType" />
-
- <xsd:element name="Description"
- type="rdf:TypedNodeType" equivClass="rdf:TypedNode" />
-
-
- <!-- RDF Property Elements -->
- <xsd:complexType name="PropertyEltType" >
- <xsd:any minOccurs="0" />
- <xsd:attribute name="id" minOccurs="0" type="ID" />
- <xsd:attribute name="resource" minOccurs="0" type="string" />
- <xsd:attribute name="value" minOccurs="0" type="string" />
- <xsd:attribute name="badID" minOccurs="0" type="ID" />
- <xsd:attribute name="parseType" minOccurs="0" >
- <xsd:simpleType base="NMTOKEN">
- <xsd:enumeration value="Resource"/>
- <xsd:enumeration value="Literal" />
- </xsd:simpleType>
- </xsd:attribute>
- <xsd:anyAttribute />
- </xsd:complexType>
-
- <xsd:element name="PropertyElt" abstract="true" type="rdf:PropertyEltType" />
-
- <xsd:element name="subject" equivClass="rdf:PropertyElt" />
- <xsd:element name="predicate" equivClass="rdf:PropertyElt" />
- <xsd:element name="object" equivClass="rdf:PropertyElt" />
- <xsd:element name="type" equivClass="rdf:PropertyElt" />
-
- <xsd:element name="value">
- <xsd:complexType>
- <xsd:any />
- <xsd:anyAttribute />
- </xsd:complexType>
- </xsd:element>
-
-
- <!-- RDF Containers -->
- <xsd:complexType name="Container" abstract="true" content="elementOnly" >
- <xsd:sequence maxOccurs="*">
- <xsd:element name="li">
- <xsd:complexType>
- <xsd:any/>
- <xsd:attribute name="id" minOccurs="0" type="ID" />
- <xsd:attribute name="parseType" minOccurs="0" >
- <xsd:simpleType base="NMTOKEN">
- <xsd:enumeration value="Resource"/>
- <xsd:enumeration value="Literal" />
- </xsd:simpleType>
- </xsd:attribute>
- <xsd:anyAttribute />
- </xsd:complexType>
- </xsd:element>
- </xsd:sequence>
- <xsd:attribute name="id" type="ID" />
- <xsd:anyAttribute />
- </xsd:complexType>
-
- <xsd:element name="Alt" type="rdf:Container" />
- <xsd:element name="Bag" type="rdf:Container" />
- <xsd:element name="Seq" type="rdf:Container" />
-
-</xsd:schema>
-
- ''')
-
-
- xmlschema_rdf = etree.parse(rdf)
- xmlschema_owl = etree.parse(owl)
-
- owlschema = etree.XMLSchema(xmlschema_owl)
- valid = StringIO('''<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE rdf:RDF [
- <!ENTITY owl "http://www.w3.org/2002/07/owl#" >
- <!ENTITY xsd "http://www.w3.org/2001/XMLSchema#" >
- <!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#" >
- <!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" >
- <!ENTITY inst_jplorg2 "http://logilab.org/owl/ontologies/inst_jplorg2#" >
-
- ]>
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:xsd="http://www.w3.org/2001/XMLSchema#" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns="http://logilab.org/owl/ontologies/inst_jplorg2#" xmlns:inst_jplorg2="http://logilab.org/owl/ontologies/inst_jplorg2#" xml:base="http://logilab.org/owl/ontologies/inst_jplorg2#">
-
- <owl:Ontology rdf:about="">
- <rdfs:comment>
- inst_jplorg2 Cubicweb OWL Ontology
-
- </rdfs:comment>
- <!-- classes definition --><owl:Class rdf:ID="Blog"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#interested_in"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#entry_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="BlogEntry"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#entry_of"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#interested_in"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#content_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#content"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Card"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#test_case_for"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#test_case_of"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#documented_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#instance_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#synopsis"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#content_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#content"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#wikiid"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Email"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#sent_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#sender"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#recipients"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#cc"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#parts"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#reply_to"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#cites"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_thread"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#generated_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#generated_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#reply_to"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#cites"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#subject"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#messageid"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#headers"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="EmailThread"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#forked_from"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_thread"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#forked_from"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="ExtProject"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#recommends"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#uses"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#url"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="File"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#documented_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data_encoding"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Image"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#screenshot"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#data_encoding"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="License"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#license_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#shortdesc"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#longdesc_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#longdesc"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#url"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Link"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#url"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#embed"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="MailingList"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#use_email"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#mailinglist_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#sent_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#mlid"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#archive"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#homepage"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Project"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#uses"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#uses"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#recommends"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#recommends"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#documented_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#documented_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#screenshot"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_state"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#filed_under"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#recommends"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#concerns"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#test_case_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#mailinglist_of"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#uses"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#interested_in"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#license_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#version_of"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#wf_info_for"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#summary"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#url"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#vcsurl"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#reporturl"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#downloadurl"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#debian_source_package"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="TestInstance"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#instance_of"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#for_version"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#generate_bug"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_state"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#wf_info_for"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#name"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Ticket"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#see_also"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#concerns"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#appeared_in"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#done_in"/>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_state"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#attachment"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#identical_to"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#depends_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#depends_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#comments"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#generate_bug"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#wf_info_for"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#test_case_for"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#title"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#type"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#priority"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#load"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#load_left"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#debian_bug_number"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><owl:Class rdf:ID="Version"><rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
- <!-- relations --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_basket"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#version_of"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#todo_by"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#in_state"/>
- <owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:minCardinality>
- <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:maxCardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#conflicts"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#depends_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#require_permission"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#done_in"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#tags"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#depends_on"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#for_version"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#wf_info_for"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#appeared_in"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">n</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf>
- <!-- attributes --><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#num"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description_format"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#description"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#starting_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#prevision_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#publication_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#creation_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf><rdfs:subClassOf>
- <owl:Restriction>
- <owl:onProperty rdf:resource="#modification_date"/>
- <owl:cardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#nonNegativeInteger">1</owl:cardinality>
- </owl:Restriction>
- </rdfs:subClassOf></owl:Class><!-- property definition --><!-- object property --><owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="interested_in">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="#EUser"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="entry_of">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="entry_of">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Blog"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="interested_in">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#EUser"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="test_case_for">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="test_case_of">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="documented_by">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="instance_of">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#TestInstance"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="sent_on">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#MailingList"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="sender">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#EmailAddress"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="recipients">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#EmailAddress"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="cc">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#EmailAddress"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="parts">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#EmailPart"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="reply_to">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="cites">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_thread">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#EmailThread"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="generated_by">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#TrInfo"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="generated_by">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="reply_to">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="cites">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="#EmailThread"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="forked_from">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="#EmailThread"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_thread">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="forked_from">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="#EmailThread"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="recommends">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="uses">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="documented_by">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="screenshot">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="license_of">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="use_email">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="#EmailAddress"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="mailinglist_of">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="sent_on">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="uses">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="uses">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="recommends">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="recommends">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="documented_by">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="documented_by">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="screenshot">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_state">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#State"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="filed_under">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Folder"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="recommends">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="concerns">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="test_case_of">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="mailinglist_of">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#MailingList"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="uses">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="interested_in">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#EUser"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="license_of">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#License"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="version_of">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="wf_info_for">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="#TrInfo"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="instance_of">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="for_version">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="generate_bug">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_state">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#State"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="wf_info_for">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="#TrInfo"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#ExtProject"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#BlogEntry"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Link"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Email"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="see_also">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="concerns">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="appeared_in">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="done_in">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_state">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#State"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Image"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="attachment">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#File"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="identical_to">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="depends_on">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="depends_on">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="comments">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Comment"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="generate_bug">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#TestInstance"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="wf_info_for">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#TrInfo"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="test_case_for">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="#Card"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_basket">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Basket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="version_of">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Project"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="todo_by">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#EUser"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="in_state">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#State"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="conflicts">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="depends_on">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="require_permission">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#EPermission"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="done_in">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="tags">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Tag"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="depends_on">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Version"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="for_version">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#TestInstance"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="wf_info_for">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#TrInfo"/>
- </owl:ObjectProperty>
-
- <owl:ObjectProperty rdf:ID="appeared_in">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="#Ticket"/>
- </owl:ObjectProperty>
-
- <!-- datatype property --><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Blog"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content_format">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#BlogEntry"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="synopsis">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content_format">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="content">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="wikiid">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Card"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="subject">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="date">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="messageid">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="headers">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Email"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#EmailThread"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#ExtProject"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:byte"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_format">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_encoding">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#File"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:byte"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_format">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="data_encoding">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Image"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="shortdesc">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="longdesc_format">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="longdesc">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#License"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="embed">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:boolean"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Link"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="mlid">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="archive">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="homepage">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#MailingList"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="summary">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="url">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="vcsurl">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="reporturl">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="downloadurl">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="debian_source_package">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Project"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="name">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#TestInstance"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="title">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="type">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="priority">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="load">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:float"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="load_left">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:float"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="debian_bug_number">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:int"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Ticket"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="num">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description_format">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="description">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:string"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="starting_date">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:date"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="prevision_date">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:date"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="publication_date">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:date"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="creation_date">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty><owl:DatatypeProperty rdf:ID="modification_date">
- <rdfs:domain rdf:resource="#Version"/>
- <rdfs:range rdf:resource="xsd:dateTime"/>
- </owl:DatatypeProperty> </owl:Ontology></rdf:RDF> ''')
- doc = etree.parse(valid)
- owlschema.validate(doc)
-
-if __name__ == '__main__':
- unittest_main()
-
--- a/web/test/unittest_views_baseforms.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/test/unittest_views_baseforms.py Mon Mar 02 21:03:54 2009 +0100
@@ -3,13 +3,14 @@
from StringIO import StringIO
import re
+from mx.DateTime import DateTime
+
from logilab.common.testlib import unittest_main
+from logilab.common.decorators import clear_cache
from cubicweb.devtools.apptest import EnvBasedTC
-
from cubicweb.entities import AnyEntity
+from cubicweb.web import widgets
-from mx.DateTime import DateTime
-from cubicweb.web import widgets
orig_today = widgets.today
orig_now = widgets.now
@@ -207,6 +208,7 @@
class BlogEntryPlus(BlogEntry):
__rtags__ = {'checked_by': 'primary'}
self.vreg.register_vobject_class(BlogEntryPlus)
+ clear_cache(self.vreg, 'etype_class')
# an admin should be able to edit the checked_by relation
html = self._build_creation_form('BlogEntry')
self.failUnless('name="edits-checked_by:A"' in html)
--- a/web/test/unittest_viewselector.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/test/unittest_viewselector.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,25 +1,21 @@
# -*- coding: iso-8859-1 -*-
"""XXX rename, split, reorganize this
"""
-
-import os.path as osp
+from __future__ import with_statement
-from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.devtools.apptest import EnvBasedTC
+from logilab.common.testlib import unittest_main
-
+from cubicweb.devtools.apptest import EnvBasedTC
from cubicweb import CW_SOFTWARE_ROOT as BASE, Binary
-from cubicweb.common.selectors import match_user_group
-
-from cubicweb.web._exceptions import NoSelectableObject
+from cubicweb.selectors import (match_user_groups, implements,
+ specified_etype_implements, rql_condition,
+ traced_selection)
+from cubicweb.web import NoSelectableObject
from cubicweb.web.action import Action
from cubicweb.web.views import (baseviews, tableview, baseforms, calendar,
management, embedding, actions, startup,
- euser, schemaentities, xbel, vcard,
- treeview, idownloadable, wdoc, debug)
-from cubicweb.entities.lib import Card
-from cubicweb.interfaces import IMileStone
-from cubicweb.web.views import owl
+ euser, schemaentities, xbel, vcard, owl,
+ treeview, idownloadable, wdoc, debug, eproperties)
USERACTIONS = [('myprefs', actions.UserPreferencesAction),
('myinfos', actions.UserInfoAction),
@@ -65,31 +61,25 @@
raise
- def test_possible_views(self):
- # no entity
+ def test_possible_views_none_rset(self):
req = self.request()
self.assertListEqual(self.pviews(req, None),
[('changelog', wdoc.ChangeLogView),
('debug', debug.DebugView),
- ('epropertiesform', management.EpropertiesForm),
+ ('epropertiesform', eproperties.EPropertiesForm),
('index', startup.IndexView),
('info', management.ProcessInformationView),
('manage', startup.ManageView),
('owl', owl.OWLView),
('schema', startup.SchemaView),
- ('systemepropertiesform', management.SystemEpropertiesForm)])
- # no entity but etype
+ ('systemepropertiesform', eproperties.SystemEPropertiesForm)])
+
+ def test_possible_views_noresult(self):
rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
self.assertListEqual(self.pviews(req, rset),
- [#('changelog', wdoc.ChangeLogView),
- #('epropertiesform', management.EpropertiesForm),
- #('index', startup.IndexView),
- #('info', management.ProcessInformationView),
- #('manage', startup.ManageView),
- #('schema', startup.SchemaView),
- #('systemepropertiesform', management.SystemEpropertiesForm)
- ])
- # one entity
+ [])
+
+ def test_possible_views_one_egroup(self):
rset, req = self.env.get_rset_and_req('EGroup X WHERE X name "managers"')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', baseviews.CSVRsetView),
@@ -110,7 +100,8 @@
('xbel', xbel.XbelView),
('xml', baseviews.XmlView),
])
- # list of entities of the same type
+
+ def test_possible_views_multiple_egroups(self):
rset, req = self.env.get_rset_and_req('EGroup X')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', baseviews.CSVRsetView),
@@ -131,7 +122,8 @@
('xbel', xbel.XbelView),
('xml', baseviews.XmlView),
])
- # list of entities of different types
+
+ def test_possible_views_multiple_different_types(self):
rset, req = self.env.get_rset_and_req('Any X')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', baseviews.CSVRsetView),
@@ -152,7 +144,8 @@
('xbel', xbel.XbelView),
('xml', baseviews.XmlView),
])
- # whatever
+
+ 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')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', baseviews.CSVRsetView),
@@ -160,7 +153,8 @@
('rsetxml', baseviews.XMLRsetView),
('table', tableview.TableView),
])
- # list of euser entities
+
+ def test_possible_views_multiple_eusers(self):
rset, req = self.env.get_rset_and_req('EUser X')
self.assertListEqual(self.pviews(req, rset),
[('csvexport', baseviews.CSVRsetView),
@@ -189,6 +183,7 @@
self.assertDictEqual(self.pactions(req, None),
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
+
})
def test_possible_actions_no_entity(self):
rset, req = self.env.get_rset_and_req('Any X WHERE X eid 999999')
@@ -196,6 +191,7 @@
{'useractions': USERACTIONS,
'siteactions': SITEACTIONS,
})
+
def test_possible_actions_same_type_entities(self):
rset, req = self.env.get_rset_and_req('EGroup X')
self.assertDictEqual(self.pactions(req, rset),
@@ -205,6 +201,7 @@
'moreactions': [('delete', actions.DeleteAction),
('addentity', 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),
@@ -212,6 +209,7 @@
'siteactions': SITEACTIONS,
'moreactions': [('delete', 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),
@@ -226,21 +224,9 @@
'mainactions': [('edit', actions.ModifyAction),
('workflow', schemaentities.ViewWorkflowAction),],
'moreactions': [('delete', actions.DeleteAction),
- ('copy', actions.CopyAction)],
+ ('copy', actions.CopyAction),
+ ('managepermission', actions.ManagePermissionsAction)],
})
-
- def test_load_subinterface_based_vojects(self):
- self.vreg._lastmodifs = {} # clear cache
- self.vreg.register_objects([osp.join(BASE, 'web', 'views', 'iprogress.py')])
- # check progressbar was kicked
- self.failIf('progressbar' in self.vreg['views'])
- class MyCard(Card):
- __implements__ = (IMileStone,)
- self.vreg.register_vobject_class(MyCard)
- self.vreg._lastmodifs = {} # clear cache
- self.vreg.register_objects([osp.join(BASE, 'web', 'views', 'iprogress.py')])
- # check progressbar isn't kicked
- self.assertEquals(len(self.vreg['views']['progressbar']), 1)
def test_select_creation_form(self):
@@ -253,7 +239,7 @@
del req.form['etype']
# custom creation form
class EUserCreationForm(baseforms.CreationForm):
- accepts = ('EUser',)
+ __select__ = specified_etype_implements('EUser')
self.vreg.register_vobject_class(EUserCreationForm)
req.form['etype'] = 'EUser'
self.assertIsInstance(self.vreg.select_view('creation', req, rset),
@@ -411,8 +397,7 @@
class SomeAction(Action):
id = 'yo'
category = 'foo'
- __selectors__ = (match_user_group,)
- require_groups = ('owners', )
+ __select__ = match_user_groups('owners')
self.vreg.register_vobject_class(SomeAction)
self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions'])
try:
@@ -435,15 +420,9 @@
del self.vreg[SomeAction.__registry__][SomeAction.id]
-
-
-
-from cubicweb.web.action import EntityAction
-
-class EETypeRQLAction(EntityAction):
+class EETypeRQLAction(Action):
id = 'testaction'
- accepts = ('EEType',)
- condition = 'X name "EEType"'
+ __select__ = implements('EEType') & rql_condition('X name "EEType"')
title = 'bla'
class RQLActionTC(ViewSelectorTC):
@@ -464,7 +443,8 @@
'mainactions': [('edit', actions.ModifyAction)],
'moreactions': [('delete', actions.DeleteAction),
('copy', actions.CopyAction),
- ('testaction', EETypeRQLAction)],
+ ('testaction', EETypeRQLAction),
+ ('managepermission', actions.ManagePermissionsAction)],
})
rset, req = self.env.get_rset_and_req('EEType X WHERE X name "ERType"')
self.assertDictEqual(self.pactions(req, rset),
@@ -472,7 +452,8 @@
'siteactions': SITEACTIONS,
'mainactions': [('edit', actions.ModifyAction)],
'moreactions': [('delete', actions.DeleteAction),
- ('copy', actions.CopyAction)],
+ ('copy', actions.CopyAction),
+ ('managepermission', actions.ManagePermissionsAction)],
})
--- a/web/test/unittest_widgets.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/test/unittest_widgets.py Mon Mar 02 21:03:54 2009 +0100
@@ -7,6 +7,7 @@
from logilab.common.testlib import unittest_main
from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.common.mttransforms import HAS_TAL
from cubicweb.web.widgets import widget, AutoCompletionWidget
@@ -41,6 +42,10 @@
entity
self.assertEquals(w.required(entity), False)
self.assertEquals(w.render(entity), '')
+ if HAS_TAL:
+ tal_format = u'\n<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>'
+ else:
+ tal_format = u''
self.assertTextEquals(w.edit_render(entity),
u'''<input type="hidden" name="edits-description:X" value="__cubicweb_internal_field__"/>
<input type="hidden" name="edits-description_format:X" value="__cubicweb_internal_field__"/>
@@ -48,9 +53,8 @@
<select name="description_format:X" id="description_format:X" tabindex="0">
<option value="text/rest" >text/rest</option>
<option value="text/html" selected="selected">text/html</option>
-<option value="text/plain" >text/plain</option>
-<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>
-</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1"></textarea>''')
+<option value="text/plain" >text/plain</option>%s
+</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1"></textarea>''' % tal_format)
def test_textarea_widget_previous_value(self):
self.add_entity('EProperty', pkey=u'ui.fckeditor', value=u'')
@@ -62,6 +66,10 @@
entity.eid = 'X'
self.assertEquals(w.required(entity), False)
self.assertEquals(w.render(entity), '')
+ if HAS_TAL:
+ tal_format = u'\n<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>'
+ else:
+ tal_format = u''
self.assertTextEquals(w.edit_render(entity),
u'''<input type="hidden" name="edits-description:X" value="__cubicweb_internal_field__"/>
<input type="hidden" name="edits-description_format:X" value="__cubicweb_internal_field__"/>
@@ -69,9 +77,8 @@
<select name="description_format:X" id="description_format:X" tabindex="0">
<option value="text/rest" >text/rest</option>
<option value="text/html" selected="selected">text/html</option>
-<option value="text/plain" >text/plain</option>
-<option value="text/cubicweb-page-template" >text/cubicweb-page-template</option>
-</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1">a description</textarea>''')
+<option value="text/plain" >text/plain</option>%s
+</select><br/><textarea onkeypress="autogrow(this)" name="description:X" accesskey="d" cols="80" id="description:X" rows="20" tabindex="1">a description</textarea>''' % tal_format)
def test_fckeditor_widget(self):
w = self.get_widget('State', 'description', 'String')
@@ -214,7 +221,7 @@
def test_float_widget(self):
w = self.get_widget('Personne', 'salary', 'Float')
self.assertEquals(w.name, 'salary')
- format = now().strftime(self.vreg.property_value('ui.float-format'))
+ format = self.vreg.property_value('ui.float-format')
self.assertEquals(w.render_example(self.request()), format % 1.23)
self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
entity = self.etype_instance('Personne')
@@ -229,7 +236,7 @@
def test_float_widget_previous_value(self):
w = self.get_widget('Personne', 'salary', 'Float')
self.assertEquals(w.name, 'salary')
- format = now().strftime(self.vreg.property_value('ui.float-format'))
+ format = self.vreg.property_value('ui.float-format')
self.assertEquals(w.render_example(self.request()), format % 1.23)
self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
req = self.request()
@@ -338,7 +345,7 @@
def test_nonregr_float_widget_with_none(self):
w = self.get_widget('Personne', 'salary', 'Float')
self.assertEquals(w.name, 'salary')
- format = now().strftime(self.vreg.property_value('ui.float-format'))
+ format = self.vreg.property_value('ui.float-format')
self.assertEquals(w.render_example(self.request()), format % 1.23)
self.assertDictEquals(w.attrs, {'accesskey': 's', 'maxlength': 15, 'size': 5})
req = self.request()
--- a/web/views/__init__.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/__init__.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""Views/forms and actions for the CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -67,30 +67,16 @@
return 'outofcontext-search'
return 'list'
return 'table'
-
-def linksearch_match(req, rset):
- """when searching an entity to create a relation, return True if entities in
- the given rset may be used as relation end
- """
- try:
- searchedtype = req.search_state[1][-1]
- except IndexError:
- return 0 # no searching for association
- for etype in rset.column_types(0):
- if etype != searchedtype:
- return 0
- return 1
def linksearch_select_url(req, rset):
"""when searching an entity to create a relation, return an url to select
entities in the given rset
"""
req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- target, link_eid, r_type, searchedtype = req.search_state[1]
+ target, eid, r_type, searchedtype = req.search_state[1]
if target == 'subject':
- id_fmt = '%s:%s:%%s' % (link_eid, r_type)
+ id_fmt = '%s:%s:%%s' % (eid, r_type)
else:
- id_fmt = '%%s:%s:%s' % (r_type, link_eid)
+ id_fmt = '%%s:%s:%s' % (r_type, eid)
triplets = '-'.join(id_fmt % row[0] for row in rset.rows)
- return "javascript: selectForAssociation('%s', '%s');" % (triplets,
- link_eid)
+ return "javascript: selectForAssociation('%s', '%s');" % (triplets, eid)
--- a/web/views/actions.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/actions.py Mon Mar 02 21:03:54 2009 +0100
@@ -6,70 +6,87 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.selectors import (searchstate_accept, match_user_group, yes,
- one_line_rset, two_lines_rset, one_etype_rset,
- authenticated_user,
- match_search_state, chainfirst, chainall)
-
-from cubicweb.web.action import Action, EntityAction, LinkToEntityAction
-from cubicweb.web.views import linksearch_select_url, linksearch_match
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import (
+ yes, one_line_rset, two_lines_rset, one_etype_rset, relation_possible,
+ non_final_entity,
+ authenticated_user, match_user_groups, match_search_state,
+ has_editable_relation, has_permission, has_add_permission,
+ )
+from cubicweb.web.action import Action
+from cubicweb.web.views import linksearch_select_url, vid_from_rset
_ = unicode
+@objectify_selector
+def match_searched_etype(cls, req, rset, **kwargs):
+ return req.match_search_state(rset)
+
+@objectify_selector
+def view_is_not_default_view(cls, req, rset, **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):
+ return 1
+ return 0
+
+@objectify_selector
+def addable_etype_empty_rset(cls, req, rset, **kwargs):
+ if rset is not None and not rset.rowcount:
+ rqlst = rset.syntax_tree()
+ if len(rqlst.children) > 1:
+ return 0
+ 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)
+ if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
+ and eschema.has_perm(req, 'add'):
+ return 1
+ return 0
+
# generic primary actions #####################################################
-class SelectAction(EntityAction):
+class SelectAction(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.
"""
- category = 'mainactions'
- __selectors__ = (searchstate_accept,)
- search_states = ('linksearch',)
- order = 0
+ id = 'select'
+ __select__ = match_search_state('linksearch') & match_searched_etype()
- id = 'select'
title = _('select')
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- return linksearch_match(req, rset)
+ category = 'mainactions'
+ order = 0
def url(self):
return linksearch_select_url(self.req, self.rset)
class CancelSelectAction(Action):
+ id = 'cancel'
+ __select__ = match_search_state('linksearch')
+
+ title = _('cancel select')
category = 'mainactions'
- search_states = ('linksearch',)
order = 10
- id = 'cancel'
- title = _('cancel select')
-
def url(self):
- target, link_eid, r_type, searched_type = self.req.search_state[1]
- return self.build_url(rql="Any X WHERE X eid %s" % link_eid,
+ target, eid, r_type, searched_type = self.req.search_state[1]
+ return self.build_url(str(eid),
vid='edition', __mode='normal')
class ViewAction(Action):
- category = 'mainactions'
- __selectors__ = (match_user_group, searchstate_accept)
- require_groups = ('users', 'managers')
- order = 0
-
id = 'view'
- title = _('view')
+ __select__ = (match_search_state('normal') &
+ match_user_groups('users', 'managers') &
+ view_is_not_default_view() &
+ non_final_entity())
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- # 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):
- return 1
- return 0
+ title = _('view')
+ category = 'mainactions'
+ order = 0
def url(self):
params = self.req.form.copy()
@@ -79,76 +96,67 @@
**params)
-class ModifyAction(EntityAction):
- category = 'mainactions'
- __selectors__ = (one_line_rset, searchstate_accept)
- #__selectors__ = searchstate_accept,
- schema_action = 'update'
- order = 10
-
+class ModifyAction(Action):
id = 'edit'
- title = _('modify')
+ __select__ = (match_search_state('normal') &
+ one_line_rset() &
+ (has_permission('update') | has_editable_relation('add')))
- @classmethod
- def has_permission(cls, entity, action):
- if entity.has_perm(action):
- return True
- # if user has no update right but it can modify some relation,
- # display action anyway
- for dummy in entity.srelations_by_category(('generic', 'metadata'),
- 'add'):
- return True
- for rschema, targetschemas, role in entity.relations_by_category(
- ('primary', 'secondary'), 'add'):
- if not rschema.is_final():
- return True
- return False
+ title = _('modify')
+ category = 'mainactions'
+ order = 10
def url(self):
entity = self.rset.get_entity(self.row or 0, self.col or 0)
return entity.absolute_url(vid='edition')
-class MultipleEditAction(EntityAction):
+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'))
+
+ title = _('modify')
category = 'mainactions'
- __selectors__ = (two_lines_rset, one_etype_rset,
- searchstate_accept)
- schema_action = 'update'
order = 10
- id = 'muledit' # XXX get strange conflicts if id='edit'
- title = _('modify')
-
def url(self):
return self.build_url('view', rql=self.rset.rql, vid='muledit')
# generic secondary actions ###################################################
-class ManagePermissions(LinkToEntityAction):
- accepts = ('Any',)
+class ManagePermissionsAction(Action):
+ id = 'managepermission'
+ __select__ = one_line_rset() & non_final_entity() & match_user_groups('managers')
+
+ title = _('manage permissions')
category = 'moreactions'
- id = 'addpermission'
- title = _('manage permissions')
order = 100
- etype = 'EPermission'
- rtype = 'require_permission'
- target = 'object'
+ @classmethod
+ def registered(cls, vreg):
+ super(ManagePermissionsAction, cls).registered(vreg)
+ if 'require_permission' in vreg.schema:
+ cls.__select__ = (one_line_rset() & non_final_entity() &
+ (match_user_groups('managers')
+ | relation_possible('require_permission', 'subject', 'EPermission',
+ action='add')))
+ return super(ManagePermissionsAction, cls).registered(vreg)
def url(self):
return self.rset.get_entity(0, 0).absolute_url(vid='security')
-class DeleteAction(EntityAction):
+class DeleteAction(Action):
+ id = 'delete'
+ __select__ = has_permission('delete')
+
+ title = _('delete')
category = 'moreactions'
- __selectors__ = (searchstate_accept,)
- schema_action = 'delete'
order = 20
- id = 'delete'
- title = _('delete')
-
def url(self):
if len(self.rset) == 1:
entity = self.rset.get_entity(0, 0)
@@ -156,14 +164,14 @@
return self.build_url(rql=self.rset.printable_rql(), vid='deleteconf')
-class CopyAction(EntityAction):
+class CopyAction(Action):
+ id = 'copy'
+ __select__ = one_line_rset() & has_permission('add')
+
+ title = _('copy')
category = 'moreactions'
- schema_action = 'add'
order = 30
- id = 'copy'
- title = _('copy')
-
def url(self):
entity = self.rset.get_entity(self.row or 0, self.col or 0)
return entity.absolute_url(vid='copy')
@@ -173,35 +181,15 @@
"""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') &
+ (addable_etype_empty_rset()
+ | (two_lines_rset() & one_etype_rset & has_add_permission()))
+ )
+
category = 'moreactions'
- id = 'addentity'
order = 40
- def etype_rset_selector(cls, req, rset, **kwargs):
- if rset is not None and not rset.rowcount:
- rqlst = rset.syntax_tree()
- if len(rqlst.children) > 1:
- return 0
- 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)
- if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
-
- def has_add_perm_selector(cls, req, rset, **kwargs):
- eschema = cls.schema.eschema(rset.description[0][0])
- if not (eschema.is_final() or eschema.is_subobject(strict=True)) \
- and eschema.has_perm(req, 'add'):
- return 1
- return 0
- __selectors__ = (match_search_state,
- chainfirst(etype_rset_selector,
- chainall(two_lines_rset, one_etype_rset,
- has_add_perm_selector)))
-
@property
def rsettype(self):
if self.rset:
@@ -219,36 +207,36 @@
# logged user actions #########################################################
class UserPreferencesAction(Action):
+ id = 'myprefs'
+ __select__ = authenticated_user()
+
+ title = _('user preferences')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 10
-
- id = 'myprefs'
- title = _('user preferences')
def url(self):
return self.build_url(self.id)
class UserInfoAction(Action):
+ id = 'myinfos'
+ __select__ = authenticated_user()
+
+ title = _('personnal informations')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 20
-
- id = 'myinfos'
- title = _('personnal informations')
def url(self):
return self.build_url('euser/%s'%self.req.user.login, vid='edition')
class LogoutAction(Action):
+ id = 'logout'
+ __select__ = authenticated_user()
+
+ title = _('logout')
category = 'useractions'
- __selectors__ = authenticated_user,
order = 30
-
- id = 'logout'
- title = _('logout')
def url(self):
return self.build_url(self.id)
@@ -257,60 +245,39 @@
# site actions ################################################################
class ManagersAction(Action):
+ __abstract__ = True
+ __select__ = match_user_groups('managers')
+
category = 'siteactions'
- __abstract__ = True
- __selectors__ = match_user_group,
- require_groups = ('managers',)
def url(self):
return self.build_url(self.id)
class SiteConfigurationAction(ManagersAction):
- order = 10
id = 'siteconfig'
title = _('site configuration')
+ order = 10
class ManageAction(ManagersAction):
- order = 20
id = 'manage'
title = _('manage')
+ order = 20
class ViewSchemaAction(Action):
+ id = 'schema'
+ __select__ = yes()
+
+ title = _("site schema")
category = 'siteactions'
- id = 'schema'
- title = _("site schema")
- __selectors__ = yes,
order = 30
def url(self):
return self.build_url(self.id)
-# content type specific actions ###############################################
-
-class FollowAction(EntityAction):
- category = 'mainactions'
- accepts = ('Bookmark',)
-
- id = 'follow'
- title = _('follow')
-
- def url(self):
- return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
-
-class UserPreferencesEntityAction(EntityAction):
- __selectors__ = EntityAction.__selectors__ + (one_line_rset, match_user_group,)
- require_groups = ('owners', 'managers')
- category = 'mainactions'
- accepts = ('EUser',)
-
- id = 'prefs'
- title = _('preferences')
-
- def url(self):
- login = self.rset.get_entity(self.row or 0, self.col or 0).login
- return self.build_url('euser/%s'%login, vid='epropertiesform')
-
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views.bookmark import FollowAction
+FollowAction = class_moved(FollowAction)
--- a/web/views/ajaxedit.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/ajaxedit.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,13 +1,12 @@
"""Set of views allowing edition of entities/relations using ajax
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.selectors import (chainfirst, match_form_params,
- match_kwargs)
+from cubicweb.selectors import chainfirst, match_form_params, match_kwargs
from cubicweb.web.box import EditRelationBoxTemplate
class AddRelationView(EditRelationBoxTemplate):
@@ -18,7 +17,8 @@
class attributes.
"""
__registry__ = 'views'
- __selectors__ = (chainfirst(match_form_params, match_kwargs),)
+ __select__ = (match_form_params('rtype', 'target')
+ | match_kwargs('rtype', 'target'))
property_defs = {} # don't want to inherit this from Box
id = 'xaddrelation'
expected_kwargs = form_params = ('rtype', 'target')
--- a/web/views/apacherewrite.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/apacherewrite.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
are much more limited for the moment)
:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -11,7 +11,7 @@
from re import compile
from cubicweb.web import Redirect
-from cubicweb.web.component import SingletonComponent
+from cubicweb.web.component import Component
class RewriteCond(object):
def __init__(self, condition, match='host', rules=(), action='rewrite'):
@@ -46,7 +46,7 @@
return path
-class ApacheURLRewrite(SingletonComponent):
+class ApacheURLRewrite(Component):
"""inherit from this class with actual rules to activate apache style rewriting
rules should have the form :
--- a/web/views/basecomponents.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/basecomponents.py Mon Mar 02 21:03:54 2009 +0100
@@ -5,7 +5,7 @@
* the workflow history section for workflowable objects
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -13,20 +13,20 @@
from rql import parse
from cubicweb import Unauthorized
+from cubicweb.selectors import (yes, non_final_entity, one_line_rset,
+ chainfirst, two_etypes_rset,
+ match_form_params, relation_possible)
from cubicweb.common.uilib import html_escape, toggle_action
-from cubicweb.common.selectors import yes, non_final_entity, one_line_rset
from cubicweb.schema import display_name
-from cubicweb.common.selectors import (chainfirst, two_etypes_rset,
- match_form_params)
from cubicweb.web.htmlwidgets import MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink
-from cubicweb.web.component import (VComponent, SingletonVComponent, EntityVComponent,
+from cubicweb.web.component import (Component, EntityVComponent,
RelatedObjectsVComponent)
_ = unicode
-class RQLInputForm(SingletonVComponent):
+class RQLInputForm(Component):
"""build the rql input form, usually displayed in the header"""
id = 'rqlinput'
visible = False
@@ -55,7 +55,7 @@
self.w(u'</form></div>')
-class ApplLogo(SingletonVComponent):
+class ApplLogo(Component):
"""build the application logo, usually displayed in the header"""
id = 'logo'
site_wide = True # don't want user to hide this component using an eproperty
@@ -64,7 +64,7 @@
% (self.req.base_url(), self.req.external_resource('LOGO')))
-class ApplHelp(SingletonVComponent):
+class ApplHelp(Component):
"""build the help button, usually displayed in the header"""
id = 'help'
def call(self):
@@ -73,7 +73,7 @@
self.req._(u'help'),))
-class UserLink(SingletonVComponent):
+class UserLink(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
"""
@@ -104,17 +104,22 @@
self.w(self.req._('anonymous'))
self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
% (self.req._('i18n_login_popup')))
+ # FIXME maybe have an other option to explicitely authorise registration
+ # also provide a working register view
+# if self.config['anonymous-user']:
+# self.w(u''' [<a class="logout" href="?vid=register">%s</a>]'''
+# % (self.req._('i18n_register_user')))
else:
self.w(self.req._('anonymous'))
self.w(u' [<a class="logout" href="%s">%s</a>]'
% (self.build_url('login'), self.req._('login')))
-class ApplicationMessage(SingletonVComponent):
+class ApplicationMessage(Component):
"""display application's messages given using the __message parameter
into a special div section
"""
- __selectors__ = yes,
+ __select__ = yes()
id = 'applmessages'
site_wide = True # don't want user to hide this component using an eproperty
@@ -132,10 +137,9 @@
class WFHistoryVComponent(EntityVComponent):
"""display the workflow history for entities supporting it"""
id = 'wfhistory'
- accepts = ('Any',)
+ __select__ = (EntityVComponent.__select__
+ & relation_possible('wf_info_for', role='object'))
context = 'navcontentbottom'
- rtype = 'wf_info_for'
- target = 'subject'
title = _('Workflow history')
def cell_call(self, row, col, view=None):
@@ -165,7 +169,7 @@
displaycols=displaycols, headers=headers)
-class ApplicationName(SingletonVComponent):
+class ApplicationName(Component):
"""display the application name"""
id = 'appliname'
@@ -186,13 +190,13 @@
help = _('contentnavigation_seealso_description')
-class EtypeRestrictionComponent(SingletonVComponent):
+class EtypeRestrictionComponent(Component):
"""displays the list of entity types contained in the resultset
to be able to filter accordingly.
"""
id = 'etypenavigation'
- __select__ = classmethod(chainfirst(two_etypes_rset, match_form_params))
- form_params = ('__restrtype', '__restrtypes', '__restrrql')
+ __select__ = two_etypes_rset() | match_form_params('__restrtype', '__restrtypes',
+ '__restrrql')
visible = False # disabled by default
def call(self):
@@ -236,17 +240,22 @@
-class RSSFeedURL(VComponent):
+class RSSFeedURL(Component):
id = 'rss_feed_url'
- __selectors__ = (non_final_entity,)
+ __select__ = non_final_entity()
def feed_url(self):
return self.build_url(rql=self.limited_rql(), vid='rss')
-class RSSEntityFeedURL(VComponent):
+class RSSEntityFeedURL(Component):
id = 'rss_feed_url'
- __selectors__ = (non_final_entity, one_line_rset)
+ __select__ = non_final_entity() & one_line_rset()
def feed_url(self):
return self.entity(0, 0).rss_feed_url()
+
+def registration_callback(vreg):
+ vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
+ if 'see_also' in vreg.schema:
+ vreg.register(SeeAlsoVComponent)
--- a/web/views/basecontrollers.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/basecontrollers.py Mon Mar 02 21:03:54 2009 +0100
@@ -17,11 +17,10 @@
from logilab.common.decorators import cached
-from cubicweb import NoSelectableObject, ValidationError, typed_eid
-from cubicweb.common.selectors import yes
+from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
+from cubicweb.selectors import yes, match_user_groups
+from cubicweb.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
from cubicweb.common.mail import format_mail
-from cubicweb.common.view import STRICT_DOCTYPE, CW_XHTML_EXTENSIONS
-
from cubicweb.web import ExplicitLogin, Redirect, RemoteCallFailed
from cubicweb.web.controller import Controller
from cubicweb.web.views import vid_from_rset
@@ -55,15 +54,73 @@
class ViewController(Controller):
+ """standard entry point :
+ - build result set
+ - select and call main template
+ """
id = 'view'
- template = 'main'
+ template = 'main-template'
def publish(self, rset=None):
"""publish a request, returning an encoded string"""
- template = self.req.property_value('ui.main-template')
- if template not in self.vreg.registry('templates') :
- template = self.template
- return self.vreg.main_template(self.req, template, rset=rset)
+ 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.main_template(self.req, template, rset=rset, view=view)
+
+ def _select_view_and_rset(self, rset):
+ req = self.req
+ if rset is None and not hasattr(req, '_rql_processed'):
+ req._rql_processed = True
+ rset = self.process_rql(req.form.get('rql'))
+ if rset and rset.rowcount == 1 and '__method' in req.form:
+ entity = rset.get_entity(0, 0)
+ try:
+ method = getattr(entity, req.form.pop('__method'))
+ method()
+ except 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)
+ try:
+ view = self.vreg.select_view(vid, req, 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.select_view(vid, req, rset)
+ except NoSelectableObject:
+ if rset:
+ req.set_message(req._("The view %s can not be applied to this query") % vid)
+ else:
+ req.set_message(req._("You have no access to this view or it's not applyable to current data"))
+ self.warning("the view %s can not be applied to this query", vid)
+ vid = vid_from_rset(req, rset, self.schema)
+ view = self.vreg.select_view(vid, req, rset)
+ return view, rset
+
+ def process_rql(self, rql):
+ """execute rql if specified"""
+ if rql:
+ self.ensure_ro_rql(rql)
+ if not isinstance(rql, unicode):
+ rql = unicode(rql, self.req.encoding)
+ pp = self.vreg.select_component('magicsearch', self.req)
+ self.rset = pp.process_query(rql, self.req)
+ return self.rset
+ return None
+
+ def add_to_breadcrumbs(self, view):
+ # update breadcrumps **before** validating cache, unless the view
+ # 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()
+
+ def validate_cache(self, view):
+ view.set_http_cache_headers()
+ self.req.validate_cache()
def execute_linkto(self, eid=None):
"""XXX __linkto parameter may cause security issue
@@ -211,14 +268,14 @@
self.req.set_content_type(content_type)
return xmlize(data)
return data
-
+
def html_exec(self, rset=None):
- """html mode: execute query and return the view as HTML"""
+ # XXX try to use the page-content template
req = self.req
rql = req.form.get('rql')
if rset is None and rql:
rset = self._exec(rql)
-
+
vid = req.form.get('vid') or vid_from_rset(req, rset, self.schema)
try:
view = self.vreg.select_view(vid, req, rset)
@@ -239,6 +296,11 @@
if divid == 'pageContent':
stream.write(u'<div id="contentmain">')
view.dispatch()
+ extresources = req.html_headers.getvalue(skiphead=True)
+ if extresources:
+ stream.write(u'<div class="ajaxHtmlHead">\n') # XXX use a widget ?
+ stream.write(extresources)
+ stream.write(u'</div>\n')
if req.form.get('paginate') and divid == 'pageContent':
stream.write(u'</div></div>')
source = stream.getvalue()
@@ -462,7 +524,7 @@
class SendMailController(Controller):
id = 'sendmail'
- require_groups = ('managers', 'users')
+ __select__ = match_user_groups('managers', 'users')
def recipients(self):
"""returns an iterator on email's recipients as entities"""
@@ -510,7 +572,7 @@
class MailBugReportController(SendMailController):
id = 'reportbug'
- __selectors__ = (yes,)
+ __select__ = yes()
def publish(self, rset=None):
body = self.req.form['description']
--- a/web/views/baseforms.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/baseforms.py Mon Mar 02 21:03:54 2009 +0100
@@ -15,20 +15,25 @@
from logilab.common.decorators import cached
from cubicweb.interfaces import IWorkflowable
-from cubicweb.common.utils import make_uid
+from cubicweb.selectors import (specified_etype_implements, implements,
+ match_kwargs, match_form_params, one_line_rset,
+ non_final_entity, accepts_etype_compat)
+from cubicweb.utils import make_uid
+from cubicweb.view import View, EntityView
+from cubicweb.common import tags
from cubicweb.common.uilib import cut
-from cubicweb.common.selectors import (accept_etype, match_kwargs,
- one_line_rset, implement_interface,
- match_form_params, accept)
-from cubicweb.common.view import EntityView
from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
from cubicweb.web.controller import NAV_FORM_PARAMETERS
from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
-from cubicweb.web.form import EntityForm, relation_id
+from cubicweb.web.form import FormMixIn, relation_id
_ = unicode
-class DeleteConfForm(EntityForm):
+from cubicweb.web.form import MultipleFieldsForm, EntityFieldsForm, StringField, \
+ RichTextField, HiddenInput
+
+
+class DeleteConfForm(EntityView):
id = 'deleteconf'
title = _('delete')
domid = 'deleteconf'
@@ -39,61 +44,53 @@
def call(self):
"""ask for confirmation before real deletion"""
- _ = self.req._
- self.req.add_css('cubicweb.form.css')
- self.req.add_js('cubicweb.edition.js')
- self.w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n' % _('this action is not reversible!'))
+ req, w = self.req, self.w
+ _ = req._
+ req.add_css('cubicweb.form.css')
+ req.add_js('cubicweb.edition.js')
+ w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
+ % _('this action is not reversible!'))
# XXX above message should have style of a warning
- self.w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
- if self.onsubmit:
- self.w(u'<form id="deleteconf" action="%s" onsubmit="%s" method="post">'
- % (self.build_url(), self.onsubmit))
- else:
- self.w(u'<form id="deleteconf" action="%s" method="post">'
- % (self.build_url()))
-
- self.w(u'<fieldset>\n')
- self.display_rset()
- #self.w(u'<input type="hidden" name="rql" value="%s"/>' % self.req.form['rql'])
- self.w(u'<input type="hidden" name="__form_id" value="%s"/>' % self.id)
- self.w(self.button_delete(label=stdmsgs.YES))
- self.w(self.button_cancel(label=stdmsgs.NO))
- for param in NAV_FORM_PARAMETERS:
- value = self.req.form.get(param)
- if value:
- self.w(u'<input type="hidden" name="%s" value="%s"/>' % (param, value))
- self.w(u'</fieldset></form>\n')
-
- def display_rset(self):
- self.w(u'<ul>\n')
+ w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
+ form = MultipleFieldsForm(req, domid='deleteconf', action=self.build_url('edit'),
+ onsubmit=self.onsubmit, copy_nav_params=True)
+ form.buttons.append(form.button_delete(label=stdmsgs.YES))
+ form.buttons.append(form.button_cancel(label=stdmsgs.NO))
done = set()
+ w(u'<ul>\n')
for i in xrange(self.rset.rowcount):
if self.rset[i][0] in done:
continue
done.add(self.rset[i][0])
- self.cell_call(i, 0)
- self.w(u'</ul>\n')
-
- def cell_call(self, row, col):
- entity = self.entity(row, col)
- self.w(u'<li>')
- self.w(u'<input type="hidden" name="eid" value="%s" />' % entity.eid)
- self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
- % (eid_param('__type', entity.eid), self.rset.description[row][0]))
- self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
- # don't use outofcontext view or any other that may contain inline edition form
- self.w(html_escape(entity.view('textoutofcontext')))
- self.w(u'</a>')
- self.w(u'</li>')
+ entity = self.rset.get_entity(i, 0)
+ subform = EntityFieldsForm(req, set_error_url=False,
+ entity=entity)
+ form.form_add_subform(subform)
+ # 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.form_render())
-class ChangeStateForm(EntityForm):
+class ChangeStateForm(EntityFieldsForm):
+ __method = StringField(name='__method', initial='set_state', widget=HiddenInput)
+ state = StringField(widget=HiddenInput, eidparam=True)
+ # XXX format field
+ trcomment = RichTextField(eidparam=True)
+
+ def form_buttons(self):
+ return [self.button_ok(label=stdmsgs.YES,
+ tabindex=self.req.next_tabindex()),
+ self.button_cancel(label=stdmsgs.NO,
+ tabindex=self.req.next_tabindex())]
+
+
+class ChangeStateFormView(EntityView):
id = 'statuschange'
title = _('status change')
- __selectors__ = (implement_interface, match_form_params)
- accepts_interfaces = (IWorkflowable,)
- form_params = ('treid',)
+ __select__ = implements(IWorkflowable) & match_form_params('treid')
def cell_call(self, row, col, vid='secondary'):
entity = self.entity(row, col)
@@ -104,59 +101,22 @@
self.req.add_js('cubicweb.edition.js')
self.req.add_css('cubicweb.form.css')
_ = self.req._
- self.w(self.error_message())
+ form = ChangeStateForm(self.req, entity=entity,
+ redirect_path=self.redirectpath(entity))
+ self.w(form.error_message())
self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
msg = _('status will change from %(st1)s to %(st2)s') % {
'st1': _(state.name),
'st2': _(dest.name)}
self.w(u'<p>%s</p>\n' % msg)
- self.w(u'<form action="%s" onsubmit="return freezeFormButtons(\'entityForm\');" method="post" id="entityForm">\n'
- % self.build_url('edit'))
- self.w(u'<div id="progress">%s</div>' % _('validating...'))
- self.w(u'<fieldset>\n')
- #self.w(u'<input id="errorurl" type="hidden" name="__errorurl" value="%s"/>\n'
- # % html_escape(self.req.url()))
- self.w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
- self.w(u'<input type="hidden" name="eid" value="%s" />' % eid)
- self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
- % (eid_param('__type', eid), entity.e_schema))
- self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
- % (eid_param('state', eid), dest.eid))
- self.w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n'
- % html_escape(self.redirectpath(entity)))
- self.fill_form(entity, state, dest)
- self.w(u'<input type="hidden" name="__method" value="set_state"/>\n')
- self.w(self.button_ok(label=stdmsgs.YES, tabindex=self.req.next_tabindex()))
- self.w(self.button_cancel(label=stdmsgs.NO, tabindex=self.req.next_tabindex()))
- self.w(u'</fieldset>\n')
- self.w(u'</form>')
-
- def fill_form(self, entity, state, dest):
- # hack to use the widget for comment_format
- trinfo = self.vreg.etype_class('TrInfo')(self.req, None)
- # widget are cached, copy it since we want to modify its name attribute
- wdg = trinfo.get_widget('comment_format')
- wdg.name = 'trcommentformat'
- # set a value in entity to avoid lookup for a non existant attribute...
- trinfo['trcommentformat'] = u''
- # comment format/content have to be grouped using the original entity eid
- wdg.rname = eid_param('trcommentformat', entity.eid)
- self.w(wdg.render_label(trinfo))
- self.w(wdg._edit_render(trinfo))
- self.w(u'<br/>\n')
- cformname = eid_param('trcomment', entity.eid)
- self.w(u'<label for="%s">%s</label>\n' % (cformname, self.req._('comment:')))
- self.w(u'<textarea rows="10" cols="80" name="%s" tabindex="%s"></textarea><br/>\n'
- % (cformname, self.req.next_tabindex()))
+ self.w(form.form_render(state=dest.eid))
def redirectpath(self, entity):
return entity.rest_path()
-
-class ClickAndEditForm(EntityForm):
+class ClickAndEditForm(FormMixIn, EntityView):
id = 'reledit'
- __selectors__ = (match_kwargs, )
- expected_kwargs = ('rtype',)
+ __select__ = match_kwargs('rtype')
#FIXME editableField class could be toggleable from userprefs
@@ -212,7 +172,7 @@
})
-class EditionForm(EntityForm):
+class EditionForm(FormMixIn, EntityView):
"""primary entity edition form
When generating a new attribute_input, the editor will look for a method
@@ -221,12 +181,12 @@
dynamic default values such as the 'tomorrow' date or the user's login
being connected
"""
- __selectors__ = (one_line_rset, accept)
+ id = 'edition'
+ __select__ = one_line_rset() & non_final_entity()
- id = 'edition'
title = _('edition')
controller = 'edit'
- skip_relations = EntityForm.skip_relations.copy()
+ skip_relations = FormMixIn.skip_relations.copy()
EDITION_BODY = u'''\
%(errormsg)s
@@ -527,7 +487,10 @@
class CreationForm(EditionForm):
- __selectors__ = (accept_etype, )
+ __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')
@@ -640,8 +603,8 @@
class InlineEntityCreationForm(InlineFormMixIn, CreationForm):
id = 'inline-creation'
- __selectors__ = (match_kwargs, accept_etype)
- expected_kwargs = ('ptype', 'peid', 'rtype')
+ __select__ = (match_kwargs('ptype', 'peid', 'rtype')
+ & specified_etype_implements('Any'))
EDITION_BODY = u'''\
<div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
@@ -673,14 +636,11 @@
self.w(self.req._('no such entity type %s') % etype)
return
self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
-
-
class InlineEntityEditionForm(InlineFormMixIn, EditionForm):
id = 'inline-edition'
- __selectors__ = (accept, match_kwargs)
- expected_kwargs = ('ptype', 'peid', 'rtype')
+ __select__ = non_final_entity() & match_kwargs('ptype', 'peid', 'rtype')
EDITION_BODY = u'''\
<div onclick="restoreInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s')" id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
@@ -732,7 +692,6 @@
ctx['count'] = entity.row + 1
return ctx
-
class CopyEditionForm(EditionForm):
id = 'copy'
@@ -777,8 +736,7 @@
return self.req._('element copied')
-
-class TableEditForm(EntityForm):
+class TableEditForm(FormMixIn, EntityView):
id = 'muledit'
title = _('multiple edit')
@@ -878,128 +836,10 @@
'widget': wobj.edit_render(entity)})
w(u'</tr>')
return '\n'.join(html)
-
-
-class UnrelatedDivs(EntityView):
- id = 'unrelateddivs'
- __selectors__ = (match_form_params,)
- form_params = ('relation',)
-
- @property
- def limit(self):
- if self.req.form.get('__force_display'):
- return None
- return self.req.property_value('navigation.related-limit') + 1
-
- 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, html_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
- for eview, reid in entity.vocabulary(rschema, target, self.limit):
- if reid is None:
- options.append('<option class="separator">-- %s --</option>' % html_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, html_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>' % (
- html_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'), html_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)
+# XXX bw compat
- THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
- """
- id = 'combobox'
- accepts = ('Any',)
- 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)
-
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views import editviews
+ComboboxView = class_moved(editviews.ComboboxView)
--- a/web/views/basetemplates.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/basetemplates.py Mon Mar 02 21:03:54 2009 +0100
@@ -11,17 +11,15 @@
from logilab.mtconverter import html_escape
from cubicweb import NoSelectableObject, ObjectNotFound
-from cubicweb.common.view import Template, MainTemplate, NOINDEX, NOFOLLOW
-from cubicweb.common.utils import make_uid
-from cubicweb.common.utils import UStringIO
-
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import match_kwargs
+from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW
+from cubicweb.utils import make_uid, UStringIO
# main templates ##############################################################
+class LogInOutTemplate(MainTemplate):
-class LogInOutTemplate(MainTemplate):
-
def call(self):
self.set_request_content_type()
w = self.w
@@ -41,17 +39,17 @@
w(NOINDEX)
w(NOFOLLOW)
w(u'\n'.join(additional_headers) + u'\n')
- self.template('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.rset)
w(u'<title>%s</title>\n' % html_escape(page_title))
-
+
class LogInTemplate(LogInOutTemplate):
id = 'login'
title = 'log in'
def content(self, w):
- self.template('logform', rset=self.rset, id='loginBox', klass='')
-
+ self.wview('logform', rset=self.rset, id='loginBox', klass='')
+
class LoggedOutTemplate(LogInOutTemplate):
id = 'loggedout'
@@ -67,110 +65,70 @@
html_escape(indexurl),
self.req._('go back to the index page')))
-
+@objectify_selector
+def templatable_view(cls, req, rset, *args, **kwargs):
+ view = kwargs.pop('view', None)
+ if view is None:
+ return 1
+ if view.binary:
+ return 0
+ if req.form.has_key('__notemplate'):
+ return 0
+ return view.templatable
+
+
+class NonTemplatableViewTemplate(MainTemplate):
+ """main template for any non templatable views (xml, binaries, etc.)"""
+ id = 'main-template'
+ __select__ = ~ templatable_view()
+
+ def call(self, view):
+ view.set_request_content_type()
+ self.set_stream(templatable=False)
+ # have to replace our unicode stream using view's binary stream
+ view.dispatch()
+ assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
+ self._stream = view._stream
+
+
class TheMainTemplate(MainTemplate):
"""default main template :
-
+
- call header / footer templates
- - build result set
- - guess and call an appropriate view through the view manager
"""
- id = 'main'
-
- def _select_view_and_rset(self):
- req = self.req
- if self.rset is None and not hasattr(req, '_rql_processed'):
- req._rql_processed = True
- rset = self.process_rql(req.form.get('rql'))
- else:
- rset = self.rset
- # handle special "method" param when necessary
- # XXX this should probably not be in the template (controller ?), however:
- # * we need to have the displayed rset
- # * we don't want to handle it in each view
- if rset and rset.rowcount == 1 and '__method' in req.form:
- entity = rset.get_entity(0, 0)
- try:
- method = getattr(entity, req.form.pop('__method'))
- method()
- except 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)
- try:
- view = self.vreg.select_view(vid, req, 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.select_view(vid, req, rset)
- except NoSelectableObject:
- if rset:
- req.set_message(req._("The view %s can not be applied to this query") % vid)
- else:
- req.set_message(req._("You have no access to this view or it's not applyable to current data"))
- self.warning("the view %s can not be applied to this query", vid)
- vid = vid_from_rset(req, rset, self.schema)
- view = self.vreg.select_view(vid, req, rset)
- return view, rset
+ id = 'main-template'
+ __select__ = templatable_view()
- def call(self):
- view, rset = self._select_view_and_rset()
- req = self.req
- # update breadcrumps **before** validating cache, unless the view
- # 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:
- req.update_breadcrumbs()
- view.set_http_cache_headers()
- req.validate_cache()
- with_templates = not view.binary and view.templatable and \
- not req.form.has_key('__notemplate')
- if not with_templates:
- view.set_request_content_type()
- self.set_stream(templatable=False)
- else:
- self.set_request_content_type()
- content_type = self.content_type
- self.template_header(content_type, view)
- if view.binary:
- # have to replace our unicode stream using view's binary stream
- view.dispatch()
- assert self._stream, 'duh, template used as a sub-view ?? (%s)' % self._stream
- self._stream = view._stream
- else:
- view.dispatch(w=self.w)
- if with_templates:
- self.template_footer(view)
-
-
- def process_rql(self, rql):
- """execute rql if specified"""
- if rql:
- self.ensure_ro_rql(rql)
- if not isinstance(rql, unicode):
- rql = unicode(rql, self.req.encoding)
- pp = self.vreg.select_component('magicsearch', self.req)
- self.rset = pp.process_query(rql, self.req)
- return self.rset
- return None
+ def call(self, view):
+ self.set_request_content_type()
+ self.template_header(self.content_type, view)
+ w = self.w
+ w(u'<div id="pageContent">\n')
+ vtitle = self.req.form.get('vtitle')
+ if vtitle:
+ w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
+ # display entity type restriction component
+ etypefilter = self.vreg.select_component('etypenavigation',
+ self.req, self.rset)
+ if etypefilter and etypefilter.propval('visible'):
+ etypefilter.dispatch(w=w)
+ self.nav_html = UStringIO()
+ if view and view.need_navigation:
+ view.paginate(w=self.nav_html.write)
+ w(_(self.nav_html.getvalue()))
+ w(u'<div id="contentmain">\n')
+ view.dispatch(w=w)
+ w(u'</div>\n') # close id=contentmain
+ w(_(self.nav_html.getvalue()))
+ w(u'</div>\n') # closes id=pageContent
+ self.template_footer(view)
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()
self.template_html_header(content_type, page_title, additional_headers)
self.template_body_header(view)
- # display entity type restriction component
- etypefilter = self.vreg.select_component('etypenavigation',
- self.req, self.rset)
- if etypefilter and etypefilter.propval('visible'):
- etypefilter.dispatch(w=self.w)
- self.nav_html = UStringIO()
- self.pagination(self.req, self.rset, self.nav_html.write,
- not (view and view.need_navigation))
- self.w(_(self.nav_html.getvalue()))
- self.w(u'<div id="contentmain">\n')
-
+
def template_html_header(self, content_type, page_title, additional_headers=()):
w = self.whead
lang = self.req.lang
@@ -179,14 +137,14 @@
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
% (content_type, self.req.encoding))
w(u'\n'.join(additional_headers) + u'\n')
- self.template('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.rset)
if page_title:
w(u'<title>%s</title>\n' % html_escape(page_title))
def template_body_header(self, view):
w = self.w
w(u'<body>\n')
- self.template('header', rset=self.rset, view=view)
+ self.wview('header', rset=self.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')
@@ -197,20 +155,13 @@
if msgcomp:
msgcomp.dispatch(w=self.w)
self.content_header(view)
- w(u'<div id="pageContent">\n')
- vtitle = self.req.form.get('vtitle')
- if vtitle:
- w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
-
+
def template_footer(self, view=None):
- self.w(u'</div>\n') # close id=contentmain
- self.w(_(self.nav_html.getvalue()))
- self.w(u'</div>\n') # closes id=pageContent
self.content_footer(view)
self.w(u'</td>\n')
self.nav_column(view, 'right')
self.w(u'</tr></table></div>\n')
- self.template('footer', rset=self.rset)
+ self.wview('footer', rset=self.rset)
self.w(u'</body>')
def nav_column(self, view, context):
@@ -224,10 +175,10 @@
def content_header(self, view=None):
"""by default, display informal messages in content header"""
- self.template('contentheader', rset=self.rset, view=view)
-
+ self.wview('contentheader', rset=self.rset, view=view)
+
def content_footer(self, view=None):
- self.template('contentfooter', rset=self.rset, view=view)
+ self.wview('contentfooter', rset=self.rset, view=view)
class ErrorTemplate(TheMainTemplate):
@@ -235,8 +186,8 @@
main template. This template may be called for authentication error,
which means that req.cnx and req.user may not be set.
"""
- id = 'error'
-
+ id = 'error-template'
+
def call(self):
"""display an unexpected error"""
self.set_request_content_type()
@@ -246,7 +197,7 @@
[NOINDEX, NOFOLLOW])
view.dispatch(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
@@ -254,7 +205,7 @@
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
% (content_type, self.req.encoding))
w(u'\n'.join(additional_headers))
- self.template('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.rset)
w(u'<title>%s</title>\n' % html_escape(page_title))
self.w(u'<body>\n')
@@ -265,7 +216,7 @@
class SimpleMainTemplate(TheMainTemplate):
id = '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()
@@ -275,7 +226,7 @@
whead(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
% (content_type, self.req.encoding))
whead(u'\n'.join(additional_headers) + u'\n')
- self.template('htmlheader', rset=self.rset)
+ self.wview('htmlheader', rset=self.rset)
w = self.w
w(u'<title>%s</title>\n' % html_escape(page_title))
w(u'<body>\n')
@@ -296,7 +247,7 @@
vtitle = self.req.form.get('vtitle')
if vtitle:
w(u'<h1 class="vtitle">%s</h1>' % html_escape(vtitle))
-
+
def topleft_header(self):
self.w(u'<table id="header"><tr>\n')
self.w(u'<td>')
@@ -306,10 +257,10 @@
# page parts templates ########################################################
-class HTMLHeader(Template):
+class HTMLHeader(View):
"""default html headers"""
id = 'htmlheader'
-
+
def call(self, **kwargs):
self.favicon()
self.stylesheets()
@@ -321,7 +272,7 @@
favicon = self.req.external_resource('FAVICON', None)
if favicon:
self.whead(u'<link rel="shortcut icon" href="%s"/>\n' % favicon)
-
+
def stylesheets(self):
req = self.req
add_css = req.add_css
@@ -331,11 +282,11 @@
add_css(css, u'print', localfile=False)
for css in req.external_resource('IE_STYLESHEETS'):
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)
-
+
def alternates(self):
urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
if urlgetter is not None:
@@ -350,10 +301,10 @@
req.html_headers.define_var('pageid', pid);
-class HTMLPageHeader(Template):
+class HTMLPageHeader(View):
"""default html page header"""
id = 'header'
-
+
def call(self, view, **kwargs):
self.main_header(view)
self.w(u'''
@@ -362,7 +313,7 @@
self.w(u'''
</div>
''')
-
+
def main_header(self, view):
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
@@ -391,9 +342,9 @@
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',
- title=False, message=False)
-
+ self.wview('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
+ title=False, message=False)
+
def state_header(self):
state = self.req.search_state
if state[0] == 'normal':
@@ -403,18 +354,18 @@
msg = ' '.join((_("searching for"),
display_name(self.req, state[1][3]),
_("to associate with"), value,
- _("by relation"), '"',
+ _("by relation"), '"',
display_name(self.req, state[1][2], state[1][0]),
'"'))
return self.w(u'<div class="stateMessage">%s</div>' % msg)
-class HTMLPageFooter(Template):
+class HTMLPageFooter(View):
"""default html page footer: include logo if any, and close the HTML body
"""
id = 'footer'
-
+
def call(self, **kwargs):
req = self.req
self.w(u'<div class="footer">')
@@ -429,13 +380,13 @@
self.w(u'</div>')
-class HTMLContentHeader(Template):
+class HTMLContentHeader(View):
"""default html page content header:
* include message component if selectable for this request
* include selectable content navigation components
"""
id = 'contentheader'
-
+
def call(self, view, **kwargs):
"""by default, display informal messages in content header"""
components = self.vreg.possible_vobjects('contentnavigation',
@@ -448,12 +399,12 @@
self.w(u'</div><div class="clear"></div>')
-class HTMLContentFooter(Template):
+class HTMLContentFooter(View):
"""default html page content footer: include selectable content navigation
components
"""
id = 'contentfooter'
-
+
def call(self, view, **kwargs):
components = self.vreg.possible_vobjects('contentnavigation',
self.req, self.rset,
@@ -465,17 +416,19 @@
self.w(u'</div>')
-class LogFormTemplate(Template):
+class LogFormTemplate(View):
id = '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.w(u'<div id="%s" class="%s">' % (id, klass))
if title:
self.w(u'<div id="loginTitle">%s</div>'
% self.req.property_value('ui.site-title'))
- self.w(u'<div id="loginContent">\n')
+ self.w(u'<div id="loginContent">\n')
if message:
self.display_message()
@@ -491,14 +444,14 @@
message = self.req.message
if message:
self.w(u'<div class="simpleMessage">%s</div>\n' % message)
-
+
def login_form(self, id):
_ = self.req._
self.w(u'<form method="post" action="%s" id="login_form">\n'
% html_escape(login_form_url(self.config, self.req)))
self.w(u'<table>\n')
self.w(u'<tr>\n')
- self.w(u'<td><label for="__login">%s</label></td>' % _('login'))
+ self.w(u'<td><label for="__login">%s</label></td>' % _('login or email'))
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'))
@@ -510,7 +463,7 @@
self.w(u'</form>\n')
self.req.html_headers.add_onload('jQuery("#__login:visible").focus()')
-
+
def login_form_url(config, req):
if req.https:
return req.url()
@@ -518,3 +471,7 @@
return req.url().replace(req.base_url(), config['https-url'])
return req.url()
+
+## vregistry registration callback ############################################
+def registration_callback(vreg):
+ vreg.register_all(globals().values(), modname=__name__)
--- a/web/views/baseviews.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/baseviews.py Mon Mar 02 21:03:54 2009 +0100
@@ -16,36 +16,30 @@
__docformat__ = "restructuredtext en"
from warnings import warn
-from time import timezone
from rql import nodes
-from logilab.common.decorators import cached
-from logilab.mtconverter import TransformError, html_escape, xml_escape
+from logilab.mtconverter import TransformError, html_escape
-from cubicweb import Unauthorized, NoSelectableObject, typed_eid
-from cubicweb.common.selectors import (yes, nonempty_rset, accept,
- one_line_rset, match_search_state,
- match_form_params, accept_rset)
-from cubicweb.common.uilib import (cut, printable_value, UnicodeCSVWriter,
- ajax_replace_url, rql_for_eid, simple_sgml_tag)
-from cubicweb.common.view import EntityView, AnyRsetView, EmptyRsetView
-from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
-from cubicweb.web.views import vid_from_rset, linksearch_select_url, linksearch_match
+from cubicweb import Unauthorized, NoSelectableObject
+from cubicweb.selectors import yes, empty_rset
+from cubicweb.view import EntityView, AnyRsetView, View
+from cubicweb.common.uilib import cut, printable_value
_ = unicode
class NullView(AnyRsetView):
"""default view when no result has been found"""
id = 'null'
- __select__ = classmethod(yes)
+ __select__ = yes()
def call(self, **kwargs):
pass
cell_call = call
-class NoResultView(EmptyRsetView):
+class NoResultView(View):
"""default view when no result has been found"""
+ __select__ = empty_rset()
id = 'noresult'
def call(self, **kwargs):
@@ -107,19 +101,6 @@
return
self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
-
-class EditableFinalView(FinalView):
- """same as FinalView but enables inplace-edition when possible"""
- id = 'editable-final'
-
- def cell_call(self, row, col, props=None, displaytime=False):
- etype = self.rset.description[row][col]
- value = self.rset.rows[row][col]
- entity, rtype = self.rset.related_entity(row, col)
- if entity is not None:
- self.w(entity.view('reledit', rtype=rtype))
- else:
- super(EditableFinalView, self).cell_call(row, col, props, displaytime)
PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity',
'owned_by', 'created_by',
@@ -191,9 +172,8 @@
def iter_attributes(self, entity):
for rschema, targetschema in entity.e_schema.attribute_definitions():
- attr = rschema.type
- if attr in self.skip_attrs:
- continue
+ if rschema.type in self.skip_attrs:
+ continue
yield rschema, targetschema
def iter_relations(self, entity):
@@ -334,36 +314,6 @@
label = display_name(self.req, rschema.type, role)
self.field(label, value, show_label=show_label, w=self.w, tr=False)
-
-class SideBoxView(EntityView):
- """side box usually displaying some related entities in a primary view"""
- id = 'sidebox'
-
- def call(self, boxclass='sideBox', title=u''):
- """display a list of entities by calling their <item_vid> view
- """
- if title:
- 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)
- else:
- self.wview('simplelist', self.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.w(u'</div>\n</div>\n')
-
-
class SecondaryView(EntityView):
id = 'secondary'
@@ -377,6 +327,7 @@
self.w(u' ')
self.wview('oneline', self.rset, row=row, col=col)
+
class OneLineView(EntityView):
id = 'oneline'
title = _('oneline')
@@ -389,12 +340,13 @@
self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
self.w(u'</a>')
+
class TextView(EntityView):
"""the simplest text view for an entity"""
id = 'text'
title = _('text')
content_type = 'text/plain'
- accepts = 'Any',
+
def call(self, **kwargs):
"""the view is called for an entire result set, by default loop
other rows of the result set and call the same view on the
@@ -419,7 +371,6 @@
class MetaDataView(EntityView):
"""paragraph view of some metadata"""
id = 'metadata'
- accepts = 'Any',
show_eid = True
def cell_call(self, row, col):
@@ -452,7 +403,8 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
self.w(entity.dc_title())
-
+
+
class OutOfContextTextView(InContextTextView):
id = 'textoutofcontext'
@@ -481,20 +433,8 @@
self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
self.w(u'</a>')
-class NotClickableInContextView(EntityView):
- id = 'incontext'
- accepts = ('State',)
- def cell_call(self, row, col):
- self.w(html_escape(self.view('textincontext', self.rset, row=row, col=col)))
-
-## class NotClickableOutOfContextView(EntityView):
-## id = 'outofcontext'
-## accepts = ('State',)
-## def cell_call(self, row, col):
-## self.w(html_escape(self.view('textoutofcontext', self.rset, row=row)))
-
-# list and table related views ################################################
+# list views ##################################################################
class ListView(EntityView):
id = 'list'
@@ -590,394 +530,12 @@
class TreeItemView(ListItemView):
- accepts = ('Any',)
id = 'treeitem'
def cell_call(self, row, col):
self.wview('incontext', self.rset, row=row, col=col)
-
-# xml and xml/rss views #######################################################
-
-class XmlView(EntityView):
- id = 'xml'
- title = _('xml')
- templatable = False
- content_type = 'text/xml'
- xml_root = 'rset'
- item_vid = 'xmlitem'
-
- def cell_call(self, row, col):
- self.wview(self.item_vid, self.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.cell_call(i, 0)
- self.w(u'</%s>\n' % self.xml_root)
-
-
-class XmlItemView(EntityView):
- id = 'xmlitem'
-
- def cell_call(self, row, col):
- """ element as an item for an xml feed """
- entity = self.complete_entity(row, col)
- self.w(u'<%s>\n' % (entity.e_schema))
- for rschema, attrschema in entity.e_schema.attribute_definitions():
- attr = rschema.type
- try:
- value = entity[attr]
- except KeyError:
- # Bytes
- continue
- if value is not None:
- if attrschema == 'Bytes':
- from base64 import b64encode
- value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
- elif isinstance(value, basestring):
- value = xml_escape(value)
- self.w(u' <%s>%s</%s>\n' % (attr, value, attr))
- self.w(u'</%s>\n' % (entity.e_schema))
-
-
-
-class XMLRsetView(AnyRsetView):
- """dumps xml in CSV"""
- id = 'rsetxml'
- title = _('xml export')
- templatable = False
- content_type = 'text/xml'
- xml_root = 'rset'
-
- def call(self):
- w = self.w
- rset, descr = self.rset, self.rset.description
- eschema = self.schema.eschema
- labels = self.columns_labels(False)
- w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
- w(u'<%s query="%s">\n' % (self.xml_root, html_escape(rset.printable_rql())))
- for rowindex, row in enumerate(self.rset):
- w(u' <row>\n')
- for colindex, val in enumerate(row):
- etype = descr[rowindex][colindex]
- tag = labels[colindex]
- attrs = {}
- if '(' in tag:
- attrs['expr'] = tag
- tag = 'funccall'
- if val is not None and not eschema(etype).is_final():
- attrs['eid'] = val
- # csvrow.append(val) # val is eid in that case
- val = self.view('textincontext', rset,
- row=rowindex, col=colindex)
- else:
- val = self.view('final', rset, displaytime=True,
- 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)
-
-
-class RssView(XmlView):
- id = 'rss'
- title = _('rss')
- templatable = False
- content_type = 'text/xml'
- http_cache_manager = MaxAgeHTTPCacheManager
- cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
- def cell_call(self, row, col):
- self.wview('rssitem', self.rset, row=row, col=col)
-
- def call(self):
- """display a list of entities by calling their <item_vid> view"""
- req = self.req
- self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
- self.w(u'''<rdf:RDF
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns="http://purl.org/rss/1.0/"
->''')
- self.w(u' <channel rdf:about="%s">\n' % html_escape(req.url()))
- self.w(u' <title>%s RSS Feed</title>\n' % html_escape(self.page_title()))
- self.w(u' <description>%s</description>\n' % html_escape(req.form.get('vtitle', '')))
- params = req.form.copy()
- params.pop('vid', None)
- self.w(u' <link>%s</link>\n' % html_escape(self.build_url(**params)))
- self.w(u' <items>\n')
- self.w(u' <rdf:Seq>\n')
- for entity in self.rset.entities():
- self.w(u' <rdf:li resource="%s" />\n' % html_escape(entity.absolute_url()))
- self.w(u' </rdf:Seq>\n')
- self.w(u' </items>\n')
- self.w(u' </channel>\n')
- for i in xrange(self.rset.rowcount):
- self.cell_call(i, 0)
- self.w(u'</rdf:RDF>')
-
-
-class RssItemView(EntityView):
- id = 'rssitem'
- date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
-
- def cell_call(self, row, col):
- entity = self.complete_entity(row, col)
- self.w(u'<item rdf:about="%s">\n' % html_escape(entity.absolute_url()))
- self._marker('title', entity.dc_long_title())
- self._marker('link', entity.absolute_url())
- self._marker('description', entity.dc_description())
- self._marker('dc:date', entity.dc_date(self.date_format))
- if entity.creator:
- self.w(u'<author>')
- self._marker('name', entity.creator.name())
- email = entity.creator.get_email()
- if email:
- self._marker('email', email)
- self.w(u'</author>')
- self.w(u'</item>\n')
-
- def _marker(self, marker, value):
- if value:
- self.w(u' <%s>%s</%s>\n' % (marker, html_escape(value), marker))
-
-
-class CSVMixIn(object):
- """mixin class for CSV views"""
- templatable = False
- content_type = "text/comma-separated-values"
- binary = True # avoid unicode assertion
- csv_params = {'dialect': 'excel',
- 'quotechar': '"',
- 'delimiter': ';',
- 'lineterminator': '\n'}
-
- def set_request_content_type(self):
- """overriden to set a .csv filename"""
- self.req.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)
-
-
-class CSVRsetView(CSVMixIn, AnyRsetView):
- """dumps rset in CSV"""
- id = '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
- 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).is_final():
- # csvrow.append(val) # val is eid in that case
- content = self.view('textincontext', rset,
- row=rowindex, col=colindex)
- else:
- content = self.view('final', rset,
- displaytime=True, format='text/plain',
- row=rowindex, col=colindex)
- csvrow.append(content)
- writer.writerow(csvrow)
-
-
-class CSVEntityView(CSVMixIn, EntityView):
- """dumps rset's entities (with full set of attributes) in CSV"""
- id = 'ecsvexport'
- title = _('csv entities export')
-
- def call(self):
- """
- the generated CSV file will have a table per entity type
- found in the resultset. ('table' here only means empty
- lines separation between table contents)
- """
- req = self.req
- rows_by_type = {}
- writer = self.csvwriter()
- rowdef_by_type = {}
- for index in xrange(len(self.rset)):
- entity = self.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']
- rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
- for rschema in rowdef_by_type[entity.e_schema]]]
- rows = rows_by_type[entity.e_schema]
- rows.append([entity.printable_value(rs.type, format='text/plain')
- for rs in rowdef_by_type[entity.e_schema]])
- for etype, rows in rows_by_type.items():
- writer.writerows(rows)
- # use two empty lines as separator
- writer.writerows([[], []])
-
-
-## Work in progress ###########################################################
-
-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'
- title = _('search for association')
- __selectors__ = (one_line_rset, match_search_state, accept)
- accepts = ('Any',)
- search_states = ('linksearch',)
-
- def cell_call(self, row, col):
- rset, vid, divid, paginate = self.filter_box_context_info()
- self.w(u'<div id="%s">' % divid)
- self.pagination(self.req, rset, w=self.w)
- self.wview(vid, rset, 'noresult')
- self.w(u'</div>')
-
- @cached
- def filter_box_context_info(self):
- entity = self.entity(0, 0)
- role, eid, rtype, etype = self.req.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 = entity.unrelated_rql(rtype, etype, role,
- ordermethod='fetch_order',
- vocabconstraints=False)
- rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
- #vid = vid_from_rset(self.req, rset, self.schema)
- return rset, 'list', "search-associate-content", True
-
-
-class OutOfContextSearch(EntityView):
- id = 'outofcontext-search'
- def cell_call(self, row, col):
- entity = self.entity(row, col)
- erset = entity.as_rset()
- if linksearch_match(self.req, erset):
- self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
- html_escape(linksearch_select_url(self.req, erset)),
- self.req._('select this entity'),
- html_escape(entity.view('textoutofcontext')),
- html_escape(entity.absolute_url(vid='primary')),
- self.req._('view detail for this entity')))
- else:
- entity.view('outofcontext', w=self.w)
-
-
-class EditRelationView(EntityView):
- """Note: This is work in progress
-
- This view is part of the edition view refactoring.
- It is still too big and cluttered with strange logic, but it's a start
-
- The main idea is to be able to call an edition view for a specific
- relation. For example :
- self.wview('editrelation', person_rset, rtype='firstname')
- self.wview('editrelation', person_rset, rtype='works_for')
- """
- id = 'editrelation'
-
- __selectors__ = (match_form_params,)
- form_params = ('rtype',)
-
- # TODO: inlineview, multiple edit, (widget view ?)
- def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
- showlabel=True):
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- entity = self.entity(row, col)
- rtype = self.req.form.get('rtype', rtype)
- showlabel = self.req.form.get('showlabel', showlabel)
- assert rtype is not None, "rtype is mandatory for 'edirelation' view"
- targettype = self.req.form.get('targettype', targettype)
- role = self.req.form.get('role', role)
- category = entity.rtags.get_category(rtype, targettype, role)
- if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
- if hasattr(entity, '%s_format' % rtype):
- formatwdg = entity.get_widget('%s_format' % rtype, role)
- self.w(formatwdg.edit_render(entity))
- self.w(u'<br/>')
- wdg = entity.get_widget(rtype, role)
- if showlabel:
- self.w(u'%s' % wdg.render_label(entity))
- self.w(u'%s %s %s' %
- (wdg.render_error(entity), wdg.edit_render(entity),
- wdg.render_help(entity),))
- else:
- self._render_generic_relation(entity, rtype, role)
-
- def _render_generic_relation(self, entity, relname, role):
- text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
- # pending operations
- operations = self.req.get_pending_operations(entity, relname, role)
- if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
- self.w(u'<h3>%s</h3>' % text)
- self._render_generic_relation_form(operations, entity, relname, role)
- else:
- divid = "%s%sreledit" % (relname, role)
- url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
- {'unfold' : 1, 'relname' : relname, 'role' : role})
- self.w(u'<a href="%s">%s</a>' % (url, text))
- self.w(u'<div id="%s"></div>' % divid)
-
-
- def _build_opvalue(self, entity, relname, target, role):
- if role == 'subject':
- return '%s:%s:%s' % (entity.eid, relname, target)
- else:
- return '%s:%s:%s' % (target, relname, entity.eid)
-
-
- def _render_generic_relation_form(self, operations, entity, relname, role):
- rqlexec = self.req.execute
- for optype, targets in operations.items():
- for target in targets:
- self._render_pending(optype, entity, relname, target, role)
- opvalue = self._build_opvalue(entity, relname, target, role)
- self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
- % (opvalue, entity.eid))
- rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
- self.wview('oneline', rset)
- # now, unrelated ones
- self._render_unrelated_selection(entity, relname, role)
-
- def _render_pending(self, optype, entity, relname, target, role):
- opvalue = self._build_opvalue(entity, relname, target, role)
- self.w(u'<input type="hidden" name="__%s" value="%s" />'
- % (optype, opvalue))
- if optype == 'insert':
- checktext = '-'
- else:
- checktext = '+'
- rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
- self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
- % (optype.capitalize(), relname, target, role,
- self.view('oneline', rset)))
-
- def _render_unrelated_selection(self, entity, relname, role):
- rschema = self.schema.rschema(relname)
- if role == 'subject':
- targettypes = rschema.objects(entity.e_schema)
- else:
- targettypes = rschema.subjects(entity.e_schema)
- self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
- for targettype in targettypes:
- unrelated = entity.unrelated(relname, targettype, role) # XXX limit
- for rowindex, row in enumerate(unrelated):
- teid = row[0]
- opvalue = self._build_opvalue(entity, relname, teid, role)
- self.w(u'<option name="__insert" value="%s>%s</option>'
- % (opvalue, self.view('text', unrelated, row=rowindex)))
- self.w(u'</select>')
-
+# context specific views ######################################################
class TextSearchResultView(EntityView):
"""this view is used to display full-text search
@@ -988,7 +546,6 @@
"""
id = 'tsearch'
-
def cell_call(self, row, col, **kwargs):
entity = self.complete_entity(row, col)
self.w(entity.view('incontext'))
@@ -1015,33 +572,28 @@
self.w(value.replace('\n', '<br/>'))
-class EntityRelationView(EntityView):
- accepts = ()
- vid = 'list'
-
- def cell_call(self, row, col):
- if self.target == 'object':
- role = 'subject'
- else:
- role = 'object'
- rset = self.rset.get_entity(row, col).related(self.rtype, role)
- if self.title:
- self.w(u'<h1>%s</h1>' % self.req._(self.title).capitalize())
- self.w(u'<div class="mainInfo">')
- self.wview(self.vid, rset, 'noresult')
- self.w(u'</div>')
-
-
-class TooltipView(OneLineView):
+class TooltipView(EntityView):
"""A entity view used in a tooltip"""
id = 'tooltip'
- title = None # don't display in possible views
def cell_call(self, row, col):
self.wview('oneline', self.rset, row=row, col=col)
+
+# XXX bw compat
+
+from logilab.common.deprecation import class_moved
+
try:
from cubicweb.web.views.tableview import TableView
- from logilab.common.deprecation import class_moved
TableView = class_moved(TableView)
except ImportError:
pass # gae has no tableview module (yet)
+
+from cubicweb.web.views import boxes, xmlrss
+SideBoxView = class_moved(boxes.SideBoxView)
+XmlView = class_moved(xmlrss.XmlView)
+XmlItemView = class_moved(xmlrss.XmlItemView)
+XmlRsetView = class_moved(xmlrss.XmlRsetView)
+RssView = class_moved(xmlrss.RssView)
+RssItemView = class_moved(xmlrss.RssItemView)
+
--- a/web/views/bookmark.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/bookmark.py Mon Mar 02 21:03:54 2009 +0100
@@ -9,13 +9,26 @@
from logilab.mtconverter import html_escape
from cubicweb import Unauthorized
+from cubicweb.selectors import implements
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, RawBoxItem
+from cubicweb.web.action import Action
from cubicweb.web.box import UserRQLBoxTemplate
from cubicweb.web.views.baseviews import PrimaryView
+class FollowAction(Action):
+ id = 'follow'
+ __select__ = implements('Bookmark')
+
+ title = _('follow')
+ category = 'mainactions'
+
+ def url(self):
+ return self.rset.get_entity(self.row or 0, self.col or 0).actual_url()
+
+
class BookmarkPrimaryView(PrimaryView):
- accepts = ('Bookmark',)
+ __select__ = implements('Bookmark')
def cell_call(self, row, col):
"""the primary view for bookmark entity"""
--- a/web/views/boxes.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/boxes.py Mon Mar 02 21:03:54 2009 +0100
@@ -17,9 +17,11 @@
from logilab.mtconverter import html_escape
-from cubicweb.common.selectors import any_rset, appobject_selectable
+from cubicweb.selectors import (any_rset, appobject_selectable,
+ match_user_groups, non_final_entity)
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.web.box import BoxTemplate, ExtResourcesBoxTemplate
+from cubicweb.view import EntityView
+from cubicweb.web.box import BoxTemplate
_ = unicode
@@ -29,7 +31,7 @@
box with all actions impacting the entity displayed: edit, copy, delete
change state, add related entities
"""
- __selectors__ = (any_rset,) + BoxTemplate.__selectors__
+ __select__ = BoxTemplate.__select__ & non_final_entity()
id = 'edit_box'
title = _('actions')
order = 2
@@ -133,10 +135,10 @@
class SearchBox(BoxTemplate):
"""display a box with a simple search form"""
id = 'search_box'
+
visible = True # enabled by default
title = _('search')
order = 0
- need_resources = 'SEARCH_GO'
formdef = u"""<form action="%s">
<table id="tsearch"><tr><td>
<input id="norql" type="text" accesskey="q" tabindex="%s" title="search text" value="%s" name="rql" />
@@ -147,7 +149,6 @@
</td></tr></table>
</form>"""
-
def call(self, view=None, **kwargs):
req = self.req
if req.form.pop('__fromsearchbox', None):
@@ -167,11 +168,11 @@
class PossibleViewsBox(BoxTemplate):
"""display a box containing links to all possible views"""
id = 'possible_views_box'
+ __select__ = BoxTemplate.__select__ & match_user_groups('users', 'managers')
+ visible = False
title = _('possible views')
order = 10
- require_groups = ('users', 'managers')
- visible = False
def call(self, **kwargs):
box = BoxWidget(self.req._(self.title), self.id)
@@ -186,25 +187,30 @@
box.render(self.w)
-class RSSIconBox(ExtResourcesBoxTemplate):
+class RSSIconBox(BoxTemplate):
"""just display the RSS icon on uniform result set"""
- __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (appobject_selectable('components', 'rss_feed_url'),)
+ id = 'rss'
+ __select__ = (BoxTemplate.__select__
+ & appobject_selectable('components', 'rss_feed_url'))
- id = 'rss'
+ visible = False
order = 999
- need_resources = 'RSS_LOGO',
- visible = False
def call(self, **kwargs):
+ try:
+ rss = self.req.external_resource('RSS_LOGO')
+ except KeyError:
+ self.error('missing RSS_LOGO external resource')
+ return
urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
url = urlgetter.feed_url()
- rss = self.req.external_resource('RSS_LOGO')
self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (html_escape(url), rss))
class StartupViewsBox(BoxTemplate):
"""display a box containing links to all startup views"""
id = 'startup_views_box'
+
visible = False # disabled by default
title = _('startup views')
order = 70
@@ -218,3 +224,31 @@
if not box.is_empty():
box.render(self.w)
+# helper classes ##############################################################
+
+class SideBoxView(EntityView):
+ """helper view class to display some entities in a sidebox"""
+ id = 'sidebox'
+
+ def call(self, boxclass='sideBox', title=u''):
+ """display a list of entities by calling their <item_vid> view"""
+ if title:
+ 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)
+ else:
+ self.wview('simplelist', self.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.w(u'</div>\n</div>\n')
--- a/web/views/calendar.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/calendar.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""html calendar views
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -13,11 +13,10 @@
from logilab.mtconverter import html_escape
from cubicweb.interfaces import ICalendarable
-from cubicweb.common.utils import date_range
+from cubicweb.selectors import implements
+from cubicweb.utils import date_range
+from cubicweb.view import EntityView
from cubicweb.common.uilib import ajax_replace_url
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.view import EntityView
# For backward compatibility
@@ -68,7 +67,8 @@
task.view('oneline', w=self.w)
if dates:
if task.start and task.stop:
- self.w('<br/>from %s'%self.format_date(task.start))
+ 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))
class CalendarLargeItemView(CalendarItemView):
@@ -82,9 +82,7 @@
Does apply to ICalendarable compatible entities
"""
- __registerer__ = priority_registerer
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ICalendarable,)
+ __select__ = implements(ICalendarable)
need_navigation = False
content_type = 'text/calendar'
title = _('iCalendar')
@@ -113,13 +111,11 @@
Does apply to ICalendarable compatible entities
"""
- __registerer__ = priority_registerer
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ICalendarable,)
+ id = 'hcal'
+ __select__ = implements(ICalendarable)
need_navigation = False
title = _('hCalendar')
#templatable = False
- id = 'hcal'
def call(self):
self.w(u'<div class="hcalendar">')
@@ -145,11 +141,9 @@
class OneMonthCal(EntityView):
"""At some point, this view will probably replace ampm calendars"""
- __registerer__ = priority_registerer
- __selectors__ = (implement_interface, )
- accepts_interfaces = (ICalendarable,)
+ id = 'onemonthcal'
+ __select__ = implements(ICalendarable)
need_navigation = False
- id = 'onemonthcal'
title = _('one month')
def call(self):
@@ -330,11 +324,9 @@
class OneWeekCal(EntityView):
"""At some point, this view will probably replace ampm calendars"""
- __registerer__ = priority_registerer
- __selectors__ = (implement_interface, )
- accepts_interfaces = (ICalendarable,)
+ id = 'oneweekcal'
+ __select__ = implements(ICalendarable)
need_navigation = False
- id = 'oneweekcal'
title = _('one week')
def call(self):
--- a/web/views/card.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/card.py Mon Mar 02 21:03:54 2009 +0100
@@ -6,13 +6,14 @@
"""
__docformat__ = "restructuredtext en"
+from cubicweb.selectors import implements
from cubicweb.web.views import baseviews
from logilab.mtconverter import html_escape
_ = unicode
class CardPrimaryView(baseviews.PrimaryView):
- accepts = ('Card',)
+ __select__ = implements('Card')
skip_attrs = baseviews.PrimaryView.skip_attrs + ('title', 'synopsis', 'wikiid')
show_attr_label = False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/csvexport.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,87 @@
+"""csv export views
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import UnicodeCSVWriter
+from cubicweb.view import EntityView, AnyRsetView
+
+class CSVMixIn(object):
+ """mixin class for CSV views"""
+ templatable = False
+ content_type = "text/comma-separated-values"
+ binary = True # avoid unicode assertion
+ csv_params = {'dialect': 'excel',
+ 'quotechar': '"',
+ 'delimiter': ';',
+ 'lineterminator': '\n'}
+
+ def set_request_content_type(self):
+ """overriden to set a .csv filename"""
+ self.req.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)
+
+
+class CSVRsetView(CSVMixIn, AnyRsetView):
+ """dumps raw result set in CSV"""
+ id = '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
+ 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).is_final():
+ # csvrow.append(val) # val is eid in that case
+ content = self.view('textincontext', rset,
+ row=rowindex, col=colindex)
+ else:
+ content = self.view('final', rset,
+ displaytime=True, format='text/plain',
+ row=rowindex, col=colindex)
+ csvrow.append(content)
+ writer.writerow(csvrow)
+
+
+class CSVEntityView(CSVMixIn, EntityView):
+ """dumps rset's entities (with full set of attributes) in CSV
+
+ the generated CSV file will have a table per entity type found in the
+ resultset. ('table' here only means empty lines separation between table
+ contents)
+ """
+ id = 'ecsvexport'
+ title = _('csv entities export')
+
+ def call(self):
+ req = self.req
+ rows_by_type = {}
+ writer = self.csvwriter()
+ rowdef_by_type = {}
+ for index in xrange(len(self.rset)):
+ entity = self.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']
+ rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
+ for rschema in rowdef_by_type[entity.e_schema]]]
+ rows = rows_by_type[entity.e_schema]
+ rows.append([entity.printable_value(rs.type, format='text/plain')
+ for rs in rowdef_by_type[entity.e_schema]])
+ for rows in rows_by_type.itervalues():
+ writer.writerows(rows)
+ # use two empty lines as separator
+ writer.writerows([[], []])
+
--- a/web/views/debug.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/debug.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -11,7 +11,8 @@
from logilab.mtconverter import html_escape
-from cubicweb.common.view import StartupView
+from cubicweb.selectors import none_rset, match_user_groups
+from cubicweb.view import StartupView
def dict_to_html(w, dict):
# XHTML doesn't allow emtpy <ul> nodes
@@ -21,11 +22,12 @@
w(u'<li><span class="label">%s</span>: <span>%s</span></li>' % (
html_escape(str(key)), html_escape(repr(dict[key]))))
w(u'</ul>')
+
class DebugView(StartupView):
id = 'debug'
+ __select__ = none_rset() & match_user_groups('managers')
title = _('server debug information')
- require_groups = ('managers',)
def call(self, **kwargs):
"""display server information"""
--- a/web/views/dynimages.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/dynimages.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""dynamically generated image views
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -13,7 +13,8 @@
from logilab.common.graph import escape, GraphGenerator, DotBackend
from yams import schema2dot as s2d
-from cubicweb.common.view import EntityView, StartupView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView, StartupView
class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
@@ -108,7 +109,7 @@
class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
id = 'eschemagraph'
content_type = 'image/png'
- accepts = ('EEType',)
+ __select__ = implements('EEType')
skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
def _generate(self, tmpfile):
@@ -120,7 +121,7 @@
prophdlr=RestrictedSchemaDotPropsHandler(self.req))
class ERTypeSchemaImageView(EETypeSchemaImageView):
- accepts = ('ERType',)
+ __select__ = implements('ERType')
def _generate(self, tmpfile):
"""display schema information for an entity"""
@@ -186,7 +187,7 @@
class EETypeWorkflowImageView(TmpFileViewMixin, EntityView):
id = 'ewfgraph'
content_type = 'image/png'
- accepts = ('EEType',)
+ __select__ = implements('EEType')
def _generate(self, tmpfile):
"""display schema information for an entity"""
--- a/web/views/edit_multiple.pt Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-<!-- rows are precomputed first to consume error messages if necessary -->
-<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);"
- action="%(action)s"
- tal:define="rows python:[self.edit_form(e) for e in rset.entities()]"
- >
- <div tal:replace="structure self/error_message"/>
- <div id="progress" tal:content="progress">validating changes...</div>
- <fieldset>
- <input type="hidden" name="__errorurl" value="#"
- tal:attributes="value req/url;" />
- <input type="hidden" name="__form_id" value="#"
- tal:attributes="value python:self.id"/>
- <input type="hidden" name="__redirectvid" value="primary"
- tal:attributes="value python:req.form.get('__redirectvid', 'list');"/>
- <input type="hidden" name="__redirectrql" value="#"
- tal:attributes="value python:req.form.get('__redirectrql', rset.printable_rql());"/>
- <table class="listing">
- <tr class="header">
- <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
- <tal:th tal:iter="rdef python:sampleentity.relations_by_category('primary', 'add')">
- <th tal:condition="python: rdef[0].type != 'eid'"
- tal:content="python: rdef[0].display_name(req, rdef[-1])"/>
- </tal:th>
- </tr>
- <tr tal:iter="row rows" tal:attributes="class python: repeat['row'].getOdd() and 'even' or 'odd'" tal:content="structure row"/>
- </table>
- <table width="100%%">
- <tr>
- <td align="left">
- <input class="validateButton" type="submit" value="#"
- tal:attributes="value okbuttonmsg; title okbuttontitle;"/>
- <input class="validateButton" type="reset" name="__action_cancel" value="#"
- tal:attributes="value cancelbuttonmsg; title cancelbuttontitle;"/>
- </td>
- </tr>
- </table>
- </fieldset>
-</form>
--- a/web/views/edit_relations.pt Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-<fieldset class="subentity">
-<legend class="iformTitle" tal:content="python: label">relations</legend>
-<table id="relatedEntities"
- tal:define="pendings python: list(self.restore_pending_inserts(entity))">
- <span tal:iter="row python: self.relations_table(entity)" tal:omit-tag="python: True">
- <tr tal:condition="python: row[2]">
- <th class="labelCol" tal:content="python: display_name(req, row[0].type, row[1])">relation name</th>
- <td>
- <ul>
- <li tal:iter="viewparams python: row[2]" class="invisible">
- <span tal:replace="structure python:viewparams[1]">[del it if you can]</span>
- <div tal:attributes="id python: 'span'+viewparams[0]; class python: viewparams[2]"
- tal:content="structure python: viewparams[3]">related entity view</div>
- </li>
- <li class="invisible"
- tal:condition="python: not self.force_display and self.maxrelitems < len(row[2])"
- tal:content="structure python:self.force_display_link()"/>
- </ul>
- </td>
- </tr>
- </span>
- <tr tal:iter="row pendings"
- tal:attributes="id python: 'tr' + row[1]">
- <!-- row: (relname, nodeid, js, url, eview) -->
- <th tal:content="python: row[3]">relation name</th>
- <td>
- <a class="handle" title="cancel this insert"
- tal:attributes="href python: row[2]">[x]</a>
- <a class="editionPending"
- tal:attributes="href python: row[4]; id python: 'a' + row[1]"
- tal:content="python: row[5]">entity\'s text_view</a>
- </td>
- </tr>
- <tr tal:condition="not:pendings"><th> </th><td> </td></tr>
- <tr class="separator" tal:attributes="id string: relationSelectorRow_$eid;">
- <th class="labelCol">
- <span i18n:content="add relation"></span>
- <select tal:attributes="id string: relationSelector_${eid};
- tabindex req/next_tabindex;
- onchange string: javascript:showMatchingSelect(this.options[this.selectedIndex].value,${eid});">
- <option value="" i18n:content="select a relation">select a relation</option>
- <option tal:iter="rel python: entity.srelations_by_category(('generic', 'metadata'), 'add')"
- tal:attributes="value python: '%s_%s' % (rel[1], rel[2])"
- tal:content="python: rel[0]">rel</option>
- </select>
- </th>
- <td tal:attributes="id string: unrelatedDivs_$eid">
- </td>
- </tr>
-</table>
-</fieldset>
--- a/web/views/editcontroller.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/editcontroller.py Mon Mar 02 21:03:54 2009 +0100
@@ -26,17 +26,14 @@
def publish(self, rset=None, fromjson=False):
"""edit / create / copy / delete entity / relations"""
self.fromjson = fromjson
- req = self.req
- form = req.form
- for key in form:
+ for key in self.req.form:
# There should be 0 or 1 action
if key.startswith('__action_'):
cbname = key[1:]
try:
callback = getattr(self, cbname)
except AttributeError:
- raise ValidationError(None,
- {None: req._('invalid action %r' % key)})
+ raise RequestError(self.req._('invalid action %r' % key))
else:
return callback()
self._default_publish()
@@ -199,7 +196,9 @@
value = Decimal(value)
elif attrtype == 'Bytes':
# if it is a file, transport it using a Binary (StringIO)
- if formparams.has_key('__%s_detach' % attr):
+ # 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,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editviews.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,310 @@
+"""Some views used to help to the edition process
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from simplejson import dumps
+
+from logilab.common.decorators import cached
+from logilab.mtconverter import html_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.form import relation_id
+from cubicweb.web.views.baseviews import FinalView
+
+_ = unicode
+
+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'
+ __select__ = (one_line_rset() & match_search_state('linksearch')
+ & non_final_entity())
+
+ title = _('search for association')
+
+ def cell_call(self, row, col):
+ rset, vid, divid, paginate = self.filter_box_context_info()
+ self.w(u'<div id="%s">' % divid)
+ self.pagination(self.req, rset, w=self.w)
+ self.wview(vid, rset, 'noresult')
+ self.w(u'</div>')
+
+ @cached
+ def filter_box_context_info(self):
+ entity = self.entity(0, 0)
+ role, eid, rtype, etype = self.req.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 = entity.unrelated_rql(rtype, etype, role,
+ ordermethod='fetch_order',
+ vocabconstraints=False)
+ rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
+ return rset, 'list', "search-associate-content", True
+
+
+class OutOfContextSearch(EntityView):
+ id = 'outofcontext-search'
+ def cell_call(self, row, col):
+ entity = self.entity(row, col)
+ erset = entity.as_rset()
+ if self.req.match_search_state(erset):
+ self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
+ html_escape(linksearch_select_url(self.req, erset)),
+ self.req._('select this entity'),
+ html_escape(entity.view('textoutofcontext')),
+ html_escape(entity.absolute_url(vid='primary')),
+ self.req._('view detail for this entity')))
+ else:
+ entity.view('outofcontext', w=self.w)
+
+
+class UnrelatedDivs(EntityView):
+ id = 'unrelateddivs'
+ __select__ = match_form_params('relation')
+
+ @property
+ def limit(self):
+ if self.req.form.get('__force_display'):
+ return None
+ return self.req.property_value('navigation.related-limit') + 1
+
+ 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, html_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
+ for eview, reid in entity.vocabulary(rschema, target, self.limit):
+ if reid is None:
+ options.append('<option class="separator">-- %s --</option>' % html_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, html_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>' % (
+ html_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'), html_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'
+ 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)
+
+
+# class EditRelationView(EntityView):
+# """Note: This is work in progress
+
+# This view is part of the edition view refactoring.
+# It is still too big and cluttered with strange logic, but it's a start
+
+# The main idea is to be able to call an edition view for a specific
+# relation. For example :
+# self.wview('editrelation', person_rset, rtype='firstname')
+# self.wview('editrelation', person_rset, rtype='works_for')
+# """
+# id = 'editrelation'
+
+# __select__ = match_form_params('rtype')
+
+# # TODO: inlineview, multiple edit, (widget view ?)
+# def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
+# showlabel=True):
+# self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
+# entity = self.entity(row, col)
+# rtype = self.req.form.get('rtype', rtype)
+# showlabel = self.req.form.get('showlabel', showlabel)
+# assert rtype is not None, "rtype is mandatory for 'edirelation' view"
+# targettype = self.req.form.get('targettype', targettype)
+# role = self.req.form.get('role', role)
+# category = entity.rtags.get_category(rtype, targettype, role)
+# if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
+# if hasattr(entity, '%s_format' % rtype):
+# formatwdg = entity.get_widget('%s_format' % rtype, role)
+# self.w(formatwdg.edit_render(entity))
+# self.w(u'<br/>')
+# wdg = entity.get_widget(rtype, role)
+# if showlabel:
+# self.w(u'%s' % wdg.render_label(entity))
+# self.w(u'%s %s %s' %
+# (wdg.render_error(entity), wdg.edit_render(entity),
+# wdg.render_help(entity),))
+# else:
+# self._render_generic_relation(entity, rtype, role)
+
+# def _render_generic_relation(self, entity, relname, role):
+# text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
+# # pending operations
+# operations = self.req.get_pending_operations(entity, relname, role)
+# if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
+# self.w(u'<h3>%s</h3>' % text)
+# self._render_generic_relation_form(operations, entity, relname, role)
+# else:
+# divid = "%s%sreledit" % (relname, role)
+# url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
+# {'unfold' : 1, 'relname' : relname, 'role' : role})
+# self.w(u'<a href="%s">%s</a>' % (url, text))
+# self.w(u'<div id="%s"></div>' % divid)
+
+
+# def _build_opvalue(self, entity, relname, target, role):
+# if role == 'subject':
+# return '%s:%s:%s' % (entity.eid, relname, target)
+# else:
+# return '%s:%s:%s' % (target, relname, entity.eid)
+
+
+# def _render_generic_relation_form(self, operations, entity, relname, role):
+# rqlexec = self.req.execute
+# for optype, targets in operations.items():
+# for target in targets:
+# self._render_pending(optype, entity, relname, target, role)
+# opvalue = self._build_opvalue(entity, relname, target, role)
+# self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
+# % (opvalue, entity.eid))
+# rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
+# self.wview('oneline', rset)
+# # now, unrelated ones
+# self._render_unrelated_selection(entity, relname, role)
+
+# def _render_pending(self, optype, entity, relname, target, role):
+# opvalue = self._build_opvalue(entity, relname, target, role)
+# self.w(u'<input type="hidden" name="__%s" value="%s" />'
+# % (optype, opvalue))
+# if optype == 'insert':
+# checktext = '-'
+# else:
+# checktext = '+'
+# rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
+# self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
+# % (optype.capitalize(), relname, target, role,
+# self.view('oneline', rset)))
+
+# def _render_unrelated_selection(self, entity, relname, role):
+# rschema = self.schema.rschema(relname)
+# if role == 'subject':
+# targettypes = rschema.objects(entity.e_schema)
+# else:
+# targettypes = rschema.subjects(entity.e_schema)
+# self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
+# for targettype in targettypes:
+# unrelated = entity.unrelated(relname, targettype, role) # XXX limit
+# for rowindex, row in enumerate(unrelated):
+# teid = row[0]
+# opvalue = self._build_opvalue(entity, relname, teid, role)
+# self.w(u'<option name="__insert" value="%s>%s</option>'
+# % (opvalue, self.view('text', unrelated, row=rowindex)))
+# self.w(u'</select>')
+
+
+class EditableFinalView(FinalView):
+ """same as FinalView but enables inplace-edition when possible"""
+ id = 'editable-final'
+
+ def cell_call(self, row, col, props=None, displaytime=False):
+ entity, rtype = self.rset.related_entity(row, col)
+ if entity is not None:
+ self.w(entity.view('reledit', rtype=rtype))
+ else:
+ super(EditableFinalView, self).cell_call(row, col, props, displaytime)
--- a/web/views/emailaddress.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/emailaddress.py Mon Mar 02 21:03:54 2009 +0100
@@ -8,11 +8,12 @@
from logilab.mtconverter import html_escape
+from cubicweb.selectors import implements
from cubicweb.common import Unauthorized
from cubicweb.web.views import baseviews
class EmailAddressPrimaryView(baseviews.PrimaryView):
- accepts = ('EmailAddress',)
+ __select__ = implements('EmailAddress')
def cell_call(self, row, col, skipeids=None):
self.skipeids = skipeids
@@ -59,7 +60,7 @@
class EmailAddressShortPrimaryView(EmailAddressPrimaryView):
- accepts = ('EmailAddress',)
+ __select__ = implements('EmailAddress')
id = 'shortprimary'
title = None # hidden view
def render_entity_attributes(self, entity, siderelations):
@@ -69,7 +70,7 @@
class EmailAddressOneLineView(baseviews.OneLineView):
- accepts = ('EmailAddress',)
+ __select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
entity = self.entity(row, col)
@@ -89,7 +90,7 @@
'mailto:'"""
id = 'mailto'
- accepts = ('EmailAddress',)
+ __select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
entity = self.entity(row, col)
@@ -113,7 +114,7 @@
class EmailAddressTextView(baseviews.TextView):
- accepts = ('EmailAddress',)
+ __select__ = implements('EmailAddress')
def cell_call(self, row, col, **kwargs):
self.w(self.entity(row, col).display_address())
--- a/web/views/embedding.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/embedding.py Mon Mar 02 21:03:54 2009 +0100
@@ -3,7 +3,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -15,11 +15,11 @@
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.common.selectors import (one_line_rset, score_entity_selector,
- match_search_state, implement_interface)
-from cubicweb.common.view import NOINDEX, NOFOLLOW
from cubicweb.web.controller import Controller
from cubicweb.web.action import Action
from cubicweb.web.views import basetemplates
@@ -75,30 +75,28 @@
return self.vreg.main_template(req, self.template, body=body)
+def entity_has_embedable_url(entity):
+ """return 1 if the entity provides an allowed embedable url"""
+ url = entity.embeded_url()
+ if not url or not url.strip():
+ return 0
+ allowed = entity.config['embed-allowed']
+ if allowed is None or not allowed.match(url):
+ return 0
+ return 1
+
+
class EmbedAction(Action):
"""display an 'embed' link on entity implementing `embeded_url` method
if the returned url match embeding configuration
"""
id = 'embed'
- controller = 'embed'
- __selectors__ = (one_line_rset, match_search_state,
- implement_interface, score_entity_selector)
- accepts_interfaces = (IEmbedable,)
+ __select__ = (one_line_rset() & match_search_state('normal')
+ & implements(IEmbedable)
+ & score_entity(entity_has_embedable_url))
title = _('embed')
-
- @classmethod
- def score_entity(cls, entity):
- """return a score telling how well I can display the given
- entity instance (required by the value_selector)
- """
- url = entity.embeded_url()
- if not url or not url.strip():
- return 0
- allowed = cls.config['embed-allowed']
- if allowed is None or not allowed.match(url):
- return 0
- return 1
+ controller = 'embed'
def url(self, row=0):
entity = self.rset.get_entity(row, 0)
@@ -132,6 +130,7 @@
url = '%s?custom_css=%s' % (url, self.custom_css)
return '<a href="%s"' % url
+
class absolutize_links:
def __init__(self, embedded_url, tag, custom_css=None):
self.embedded_url = embedded_url
@@ -152,7 +151,8 @@
for rgx, repl in filters:
body = rgx.sub(repl, body)
return body
-
+
+
def embed_external_page(url, prefix, headers=None, custom_css=None):
req = Request(url, headers=(headers or {}))
content = urlopen(req).read()
--- a/web/views/eproperties.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/eproperties.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,13 +2,253 @@
:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
+from logilab.mtconverter import html_escape
+
+from logilab.common.decorators import cached
+
+from cubicweb.selectors import (one_line_rset, none_rset, implements,
+ match_user_groups, chainfirst, chainall)
+from cubicweb.utils import UStringIO
+from cubicweb.view import StartupView
+from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
from cubicweb.web.views import baseviews
+from cubicweb.web.form import FormMixIn
+
+_ = unicode
+
+# some string we want to be internationalizable for nicer display of eproperty
+# groups
+_('navigation')
+_('ui')
+_('actions')
+_('boxes')
+_('components')
+_('contentnavigation')
class EPropertyPrimaryView(baseviews.PrimaryView):
- accepts = ('EProperty',)
+ __select__ = implements('EProperty')
skip_none = False
+
+
+def make_togglable_link(nodeid, label, cookiename):
+ """builds a HTML link that switches the visibility & remembers it"""
+ action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \
+ (nodeid, cookiename)
+ return u'<a href="%s">%s</a>' % (action, label)
+
+def css_class(someclass):
+ return someclass and 'class="%s"' % someclass or ''
+
+class SystemEPropertiesForm(FormMixIn, StartupView):
+ id = 'systemepropertiesform'
+ __select__ = none_rset & match_user_groups('managers')
+
+ title = _('site configuration')
+ controller = 'edit'
+ category = 'startupview'
+
+ def linkable(self):
+ return True
+
+ def url(self):
+ """return the url associated with this view. We can omit rql here"""
+ return self.build_url('view', vid=self.id)
+
+ def _cookie_name(self, somestr):
+ return str('%s_property_%s' % (self.config.appid, somestr))
+
+ def _group_status(self, group, default=u'hidden'):
+ cookies = self.req.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)
+ status = default
+ else:
+ status = cookie.value
+ return status
+
+ def call(self, **kwargs):
+ """The default view representing the application's index"""
+ self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js'))
+ self.req.add_css('cubicweb.preferences.css')
+ vreg = self.vreg
+ values = self.defined_keys
+ groupedopts = {}
+ mainopts = {}
+ # "self.id=='systemepropertiesform'" to skip site wide properties on
+ # user's preference but not site's configuration
+ for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
+ parts = key.split('.')
+ if parts[0] in vreg:
+ # appobject configuration
+ reg, oid, propid = parts
+ groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key)
+ else:
+ mainopts.setdefault(parts[0], []).append(key)
+ # precompute form to consume error message
+ for group, keys in mainopts.items():
+ mainopts[group] = self.form(keys, False)
+ for group, objects in groupedopts.items():
+ for oid, keys in objects.items():
+ groupedopts[group][oid] = self.form(keys, True)
+
+ w = self.w
+ req = self.req
+ _ = req._
+ w(u'<h1>%s</h1>\n' % _(self.title))
+ w(self.error_message())
+ for label, group, form in sorted((_(g), g, f)
+ for g, f in mainopts.iteritems()):
+ status = css_class(self._group_status(group)) #'hidden' (collapsed), or '' (open) ?
+ w(u'<h2 class="propertiesform">%s</h2>\n' %
+ (make_togglable_link('fieldset_' + group, label,
+ self._cookie_name(group))))
+ w(u'<div id="fieldset_%s" %s>' % (group, status))
+ w(u'<fieldset class="subentity">')
+ w(form)
+ w(u'</fieldset></div>')
+ for label, group, objects in sorted((_(g), g, o)
+ for g, o in groupedopts.iteritems()):
+ status = css_class(self._group_status(group))
+ w(u'<h2 class="propertiesform">%s</h2>\n' %
+ (make_togglable_link('fieldset_' + group, label,
+ self._cookie_name(group))))
+ w(u'<div id="fieldset_%s" %s>' % (group, status))
+ for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
+ for o, f in objects.iteritems()):
+ w(u'<fieldset class="subentity">')
+ w(u'<legend class="componentTitle">%s</legend>\n' % label)
+ docmsgid = '%s_%s_description' % (group, oid)
+ doc = _(docmsgid)
+ if doc != docmsgid:
+ w(u'<p class="description">%s</p>' % html_escape(doc))
+ w(form)
+ w(u'</fieldset>')
+ w(u'</div>')
+
+ @property
+ @cached
+ def eprops_rset(self):
+ return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U')
+
+ @property
+ def defined_keys(self):
+ values = {}
+ for i, entity in enumerate(self.eprops_rset.entities()):
+ values[entity.pkey] = i
+ return values
+
+ def entity_for_key(self, key):
+ values = self.defined_keys
+ if key in values:
+ entity = self.eprops_rset.get_entity(values[key], 0)
+ else:
+ entity = self.vreg.etype_class('EProperty')(self.req, None, None)
+ entity.eid = self.req.varmaker.next()
+ entity['value'] = self.vreg.property_value(key)
+ return entity
+
+ def form(self, keys, splitlabel=False):
+ stream = UStringIO()
+ w = stream.write
+ w(u'<form action="%s" method="post">\n' % self.build_url())
+ w(u'<fieldset>\n')
+ w(u'<input type="hidden" name="__errorurl" value="%s"/>\n'
+ % html_escape(self.req.url()))
+ w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
+ path = self.req.relative_path()
+ if '?' in path:
+ path, params = path.split('?', 1)
+ w(u'<input type="hidden" name="__redirectparams" value="%s"/>\n'
+ % html_escape(params))
+ w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % path)
+ #w(u'<input type="hidden" name="__redirectrql" value=""/>\n')
+ w(u'<input type="hidden" name="__message" value="%s"/>\n'
+ % self.req._('changes applied'))
+ w(u'<table><tr><td>\n')
+
+ w(u'<table>\n')
+ for key in keys:
+ w(u'<tr>\n')
+ self.form_row(w, key, splitlabel)
+ w(u'</tr>\n')
+ w(u'</table>\n')
+ w(u'</td></tr><tr><td>\n')
+ w(self.button_ok())
+ w(self.button_cancel())
+ w(u'</td></tr></table>\n')
+ w(u'</fieldset>\n')
+ w(u'</form>\n')
+ return stream.getvalue()
+
+ def form_row(self, w, key, splitlabel):
+ entity = self.entity_for_key(key)
+ eid = entity.eid
+ if splitlabel:
+ w(u'<td class="label">%s</td>' % self.req._(key.split('.')[-1]))
+ else:
+ w(u'<td class="label">%s</td>' % self.req._(key))
+ wdg = self.vreg.property_value_widget(key, req=self.req)
+ error = wdg.render_error(entity)
+ w(u'<td class="%s">' % (error and 'error' or ''))
+ w(error)
+ self.form_row_hiddens(w, entity, key)
+ w(wdg.edit_render(entity))
+ w(u'</td>\n')
+ w(u'<td>%s</td>' % wdg.render_help(entity))
+ return entity
+
+ def form_row_hiddens(self, w, entity, key):
+ eid = entity.eid
+ w(u'<input type="hidden" name="eid" value="%s"/>' % eid)
+ w(u'<input type="hidden" name="%s" value="EProperty"/>' % eid_param('__type', eid))
+ w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('pkey', eid), key))
+ w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
+
+
+
+def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
+ return req.user.eid == rset[row or 0][col]
+
+
+class EPropertiesForm(SystemEPropertiesForm):
+ id = 'epropertiesform'
+ __select__ = (
+ # we don't want guests to be able to come here
+ match_user_groups('users', 'managers') &
+ (none_rset | ((one_line_rset() & is_user_prefs) &
+ (one_line_rset() & match_user_groups('managers'))))
+ )
+
+ title = _('preferences')
+
+ @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)
+
+ @property
+ @cached
+ def eprops_rset(self):
+ return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,'
+ 'P for_user U, U eid %(x)s', {'x': self.user.eid})
+
+ def form_row_hiddens(self, w, entity, key):
+ super(EPropertiesForm, self).form_row_hiddens(w, entity, key)
+ # if user is in the managers group and the property is being created,
+ # we have to set for_user explicitly
+ if not entity.has_eid() and self.user.matching_groups('managers'):
+ eid = entity.eid
+ w(u'<input type="hidden" name="%s" value="%s"/>'
+ % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE))
+ w(u'<input type="hidden" name="%s" value="%s"/>'
+ % (eid_param('for_user', eid), self.user.eid))
+
--- a/web/views/error.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/error.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,12 +2,12 @@
as startup views and are used for standard error pages (404, 500, etc.)
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.view import StartupView
+from cubicweb.view import StartupView
class FourOhFour(StartupView):
id = '404'
--- a/web/views/euser.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/euser.py Mon Mar 02 21:03:54 2009 +0100
@@ -10,12 +10,30 @@
from logilab.mtconverter import html_escape
from cubicweb.schema import display_name
+from cubicweb.selectors import one_line_rset, implements, match_user_groups
+from cubicweb.view import EntityView
from cubicweb.web import INTERNAL_FIELD_VALUE
-from cubicweb.web.form import EntityForm
+from cubicweb.web.form import FormMixIn
+from cubicweb.web.action import Action
from cubicweb.web.views.baseviews import PrimaryView, EntityView
+
+class UserPreferencesEntityAction(Action):
+ id = 'prefs'
+ __select__ = (one_line_rset() & implements('EUser') &
+ match_user_groups('owners', 'managers'))
+
+ title = _('preferences')
+ category = 'mainactions'
+
+ def url(self):
+ login = self.rset.get_entity(self.row or 0, self.col or 0).login
+ return self.build_url('euser/%s'%login, vid='epropertiesform')
+
+
class EUserPrimaryView(PrimaryView):
- accepts = ('EUser',)
+ __select__ = implements('EUser')
+
skip_attrs = ('firstname', 'surname')
def iter_relations(self, entity):
@@ -34,7 +52,8 @@
]
class FoafView(EntityView):
id = 'foaf'
- accepts = ('EUser',)
+ __select__ = implements('EUser')
+
title = _('foaf')
templatable = False
content_type = 'text/xml'
@@ -54,7 +73,6 @@
<foaf:maker rdf:resource="%s"/>
<foaf:primaryTopic rdf:resource="%s"/>
</foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
-
self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
if entity.surname:
@@ -67,12 +85,13 @@
if emailaddr:
self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
self.w(u'</foaf:Person>\n')
-
-class EditGroups(EntityForm):
+
+
+class EditGroups(FormMixIn, EntityView):
"""displays a simple euser / egroups editable table"""
id = 'editgroups'
- accepts = ('EUser',)
+ __select__ = implements('EUser')
def call(self):
self.req.add_css('cubicweb.acl.css')
--- a/web/views/facets.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/facets.py Mon Mar 02 21:03:54 2009 +0100
@@ -10,13 +10,15 @@
from logilab.mtconverter import html_escape
-from cubicweb.common.selectors import (chainfirst, chainall, non_final_entity,
- two_lines_rset, match_context_prop,
- yes, one_has_relation)
+from cubicweb.vregistry import objectify_selector
+from cubicweb.selectors import (chainfirst, chainall, non_final_entity,
+ two_lines_rset, match_context_prop,
+ yes, relation_possible)
from cubicweb.web.box import BoxTemplate
from cubicweb.web.facet import (AbstractFacet, VocabularyFacet, FacetStringWidget,
RelationFacet, prepare_facets_rqlst, filter_hiddens)
+@objectify_selector
def contextview_selector(cls, req, rset, row=None, col=None, view=None,
**kwargs):
if view and getattr(view, 'filter_box_context_info', lambda: None)():
@@ -27,9 +29,9 @@
class FilterBox(BoxTemplate):
"""filter results of a query"""
id = 'filter_box'
- __selectors__ = (chainfirst(contextview_selector,
- chainall(non_final_entity, two_lines_rset)),
- match_context_prop)
+ __select__ = (((non_final_entity() & two_lines_rset())
+ | contextview_selector()
+ ) & match_context_prop())
context = 'left'
title = _('boxes_filter_box')
visible = True # functionality provided by the search box by default
@@ -129,7 +131,7 @@
# inherit from RelationFacet to benefit from its possible_values implementation
class ETypeFacet(RelationFacet):
id = 'etype-facet'
- __selectors__ = (yes,)
+ __select__ = yes()
order = 1
rtype = 'is'
target_attr = 'name'
@@ -153,7 +155,7 @@
class HasTextFacet(AbstractFacet):
- __selectors__ = (one_has_relation, match_context_prop)
+ __select__ = relation_possible('has_text', 'subject') & match_context_prop()
id = 'has_text-facet'
rtype = 'has_text'
role = 'subject'
--- a/web/views/ibreadcrumbs.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/ibreadcrumbs.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,20 +1,19 @@
"""navigation components definition for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from logilab.mtconverter import html_escape
+# don't use AnyEntity since this may cause bug with isinstance() due to reloading
from cubicweb.interfaces import IBreadCrumbs
-from cubicweb.common.selectors import (match_context_prop, one_line_rset,
- implement_interface)
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import match_context_prop, one_line_rset, implements
+from cubicweb.entity import Entity
+from cubicweb.view import EntityView
from cubicweb.common.uilib import cut
-# don't use AnyEntity since this may cause bug with isinstance() due to reloading
-from cubicweb.common.entity import Entity
from cubicweb.web.component import EntityVComponent
_ = unicode
@@ -29,8 +28,7 @@
# register msg not generated since no entity implements IPrevNext in cubicweb itself
title = _('contentnavigation_breadcrumbs')
help = _('contentnavigation_breadcrumbs_description')
- __selectors__ = (one_line_rset, match_context_prop, implement_interface)
- accepts_interfaces = (IBreadCrumbs,)
+ __select__ = (one_line_rset() & match_context_prop() & implements(IBreadCrumbs))
context = 'navtop'
order = 5
visible = False
@@ -73,7 +71,7 @@
class BreadCrumbComponent(BreadCrumbEntityVComponent):
__registry__ = 'components'
- __selectors__ = (one_line_rset, implement_interface)
+ __select__ = (one_line_rset() & implements(IBreadCrumbs))
visible = True
--- a/web/views/idownloadable.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/idownloadable.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,22 +1,28 @@
"""Specific views for entities implementing IDownloadable
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape
+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.common.selectors import (one_line_rset, score_entity_selector,
- implement_interface, match_context_prop)
from cubicweb.web.box import EntityBoxTemplate
from cubicweb.web.views import baseviews
_ = unicode
+def is_image(entity):
+ mt = entity.download_content_type()
+ if not (mt and mt.startswith('image/')):
+ return 0
+ return 1
+
def download_box(w, entity, title=None, label=None):
req = entity.req
w(u'<div class="sideRelated">')
@@ -32,21 +38,13 @@
w(u'</div>')
w(u'</div>\n</div>\n')
-
+
class DownloadBox(EntityBoxTemplate):
id = 'download_box'
+ # no download box for images
# XXX primary_view selector ?
- __selectors__ = (one_line_rset, implement_interface, match_context_prop, score_entity_selector)
- accepts_interfaces = (IDownloadable,)
+ __select__ = (one_line_rset() & implements(IDownloadable) & match_context_prop() & ~ score_entity(is_image)
order = 10
-
- @classmethod
- def score_entity(cls, entity):
- mt = entity.download_content_type()
- # no download box for images
- if mt and mt.startswith('image/'):
- return 0
- return 1
def cell_call(self, row, col, title=None, label=None, **kwargs):
entity = self.entity(row, col)
@@ -54,12 +52,11 @@
class DownloadView(baseviews.EntityView):
- """this view is replacing the deprecated 'download' controller and allow downloading
- of entities providing the necessary interface
+ """this view is replacing the deprecated 'download' controller and allow
+ downloading of entities providing the necessary interface
"""
id = 'download'
- __selectors__ = (one_line_rset, implement_interface)
- accepts_interfaces = (IDownloadable,)
+ __select__ = one_line_rset() & implements(IDownloadable)
templatable = False
content_type = 'application/octet-stream'
@@ -86,10 +83,9 @@
class DownloadLinkView(baseviews.EntityView):
"""view displaying a link to download the file"""
id = 'downloadlink'
+ __select__ = implements(IDownloadable)
title = None # should not be listed in possible views
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IDownloadable,)
def cell_call(self, row, col, title=None, **kwargs):
entity = self.entity(row, col)
@@ -99,8 +95,7 @@
class IDownloadablePrimaryView(baseviews.PrimaryView):
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IDownloadable,)
+ __select__ = implements(IDownloadable)
# XXX File/Image attributes but this is not specified in the IDownloadable interface
skip_attrs = baseviews.PrimaryView.skip_attrs + ('data', 'name')
@@ -133,10 +128,7 @@
class IDownloadableLineView(baseviews.OneLineView):
- __selectors__ = (implement_interface,)
- # don't kick default oneline view
- accepts_interfaces = (IDownloadable,)
-
+ __select__ = implements(IDownloadable)
def cell_call(self, row, col, title=None, **kwargs):
"""the secondary view is a link to download the file"""
@@ -149,10 +141,10 @@
class ImageView(baseviews.EntityView):
- __selectors__ = (implement_interface, score_entity_selector)
id = 'image'
+ __select__ = implements(IDownloadable) & score_entity(is_image)
+
title = _('image')
- accepts_interfaces = (IDownloadable,)
def call(self):
rset = self.rset
@@ -160,13 +152,6 @@
self.w(u'<div class="efile">')
self.wview(self.id, rset, row=i, col=0)
self.w(u'</div>')
-
- @classmethod
- def score_entity(cls, entity):
- mt = entity.download_content_type()
- if not (mt and mt.startswith('image/')):
- return 0
- return 1
def cell_call(self, row, col):
entity = self.entity(row, col)
--- a/web/views/igeocodable.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/igeocodable.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,10 +1,16 @@
-# -*- coding: utf-8 -*-
+"""Specific views for entities implementing IGeocodable
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
import simplejson
from cubicweb.interfaces import IGeocodable
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface
+from cubicweb.view import EntityView
+from cubicweb.selectors import implements
class GeocodingJsonView(EntityView):
id = 'geocoding-json'
@@ -12,8 +18,7 @@
templatable = False
content_type = 'application/json'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IGeocodable,)
+ __select__ = implements(IGeocodable)
def call(self):
zoomlevel = self.req.form.pop('zoomlevel', 8)
@@ -46,8 +51,7 @@
class GoogleMapBubbleView(EntityView):
id = 'gmap-bubble'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IGeocodable,)
+ __select__ = implements(IGeocodable)
def cell_call(self, row, col):
entity = self.entity(row, col)
@@ -58,8 +62,7 @@
class GoogleMapsView(EntityView):
id = 'gmap-view'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (IGeocodable,)
+ __select__ = implements(IGeocodable)
need_navigation = False
def call(self, gmap_key, width=400, height=400, uselabel=True, urlparams=None):
--- a/web/views/iprogress.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/iprogress.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""Specific views for entities implementing IProgress
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -9,10 +9,10 @@
from logilab.mtconverter import html_escape
+from cubicweb.selectors import implements, accept
from cubicweb.interfaces import IProgress, IMileStone
from cubicweb.schema import display_name
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface, accept
+from cubicweb.view import EntityView
from cubicweb.web.htmlwidgets import ProgressBarWidget
@@ -35,9 +35,7 @@
id = 'progress_table_view'
title = _('task progression')
- __selectors__ = (accept, implement_interface)
-
- accepts_interfaces = (IMileStone,)
+ __select__ = implements(IMileStone)
# default columns of the table
columns = (_('project'), _('milestone'), _('state'), _('eta_date'),
@@ -182,9 +180,7 @@
"""displays a progress bar"""
id = 'progressbar'
title = _('progress bar')
- __selectors__ = (accept, implement_interface)
-
- accepts_interfaces = (IProgress,)
+ __select__ = implements(IProgress)
def cell_call(self, row, col):
self.req.add_css('cubicweb.iprogress.css')
--- a/web/views/magicsearch.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/magicsearch.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,7 +2,7 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -15,7 +15,7 @@
from rql.nodes import Relation
from cubicweb import Unauthorized
-from cubicweb.common.appobject import Component, SingletonComponent
+from cubicweb.view import Component
LOGGER = getLogger('cubicweb.magicsearch')
@@ -349,7 +349,7 @@
-class MagicSearchComponent(SingletonComponent):
+class MagicSearchComponent(Component):
id = 'magicsearch'
def __init__(self, req, rset=None):
super(MagicSearchComponent, self).__init__(req, rset)
--- a/web/views/management.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/management.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,24 +2,18 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from logilab.mtconverter import html_escape
-from logilab.common.decorators import cached
-
-from cubicweb.common.utils import UStringIO
-from cubicweb.common.view import AnyRsetView, StartupView, EntityView
+from cubicweb.selectors import yes, none_rset, match_user_groups
+from cubicweb.view import AnyRsetView, StartupView, EntityView
from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.common.selectors import (yes, one_line_rset,
- accept_rset, none_rset,
- chainfirst, chainall)
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
from cubicweb.web.widgets import StaticComboBoxWidget
-from cubicweb.web.form import FormMixIn
_ = unicode
@@ -151,8 +145,8 @@
newperm.eid = self.req.varmaker.next()
w(u'<p>%s</p>' % _('add a new permission'))
begin_form(w, newperm, 'security', entity.rest_path())
- w(u'<input type="hidden" name="%s" value="__cubicweb_internal_field__"/>'
- % eid_param('edito-require_permission', newperm.eid))
+ w(u'<input type="hidden" name="%s" value="%s"/>'
+ % (eid_param('edito-require_permission', newperm.eid), INTERNAL_FIELD_VALUE))
w(u'<input type="hidden" name="%s" value="%s"/>'
% (eid_param('require_permission', newperm.eid), entity.eid))
w(u'<table border="0">\n')
@@ -180,7 +174,7 @@
class ErrorView(AnyRsetView):
"""default view when no result has been found"""
- __selectors__ = (yes,)
+ __select__ = yes()
id = 'error'
def page_title(self):
@@ -274,242 +268,11 @@
binfo += '\n'
return binfo
-# some string we want to be internationalizable for nicer display of eproperty
-# groups
-_('navigation')
-_('ui')
-_('actions')
-_('boxes')
-_('components')
-_('contentnavigation')
-
-
-def make_togglable_link(nodeid, label, cookiename):
- """builds a HTML link that switches the visibility & remembers it"""
- action = u"javascript: toggle_and_remember_visibility('%s', '%s')" % \
- (nodeid, cookiename)
- return u'<a href="%s">%s</a>' % (action, label)
-
-def css_class(someclass):
- return someclass and 'class="%s"' % someclass or ''
-
-class SystemEpropertiesForm(FormMixIn, StartupView):
- controller = 'edit'
- id = 'systemepropertiesform'
- title = _('site configuration')
- require_groups = ('managers',)
- category = 'startupview'
-
- def linkable(self):
- return True
-
- def url(self):
- """return the url associated with this view. We can omit rql here"""
- return self.build_url('view', vid=self.id)
-
- def _cookie_name(self, somestr):
- return str('%s_property_%s' % (self.config.appid, somestr))
-
- def _group_status(self, group, default=u'hidden'):
- cookies = self.req.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)
- status = default
- else:
- status = cookie.value
- return status
-
- def call(self, **kwargs):
- """The default view representing the application's index"""
- self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js'))
- self.req.add_css('cubicweb.preferences.css')
- vreg = self.vreg
- values = self.defined_keys
- groupedopts = {}
- mainopts = {}
- # "self.id=='systemepropertiesform'" to skip site wide properties on
- # user's preference but not site's configuration
- for key in vreg.user_property_keys(self.id=='systemepropertiesform'):
- parts = key.split('.')
- if parts[0] in vreg:
- # appobject configuration
- reg, oid, propid = parts
- groupedopts.setdefault(reg, {}).setdefault(oid, []).append(key)
- else:
- mainopts.setdefault(parts[0], []).append(key)
- # precompute form to consume error message
- for group, keys in mainopts.items():
- mainopts[group] = self.form(keys, False)
- for group, objects in groupedopts.items():
- for oid, keys in objects.items():
- groupedopts[group][oid] = self.form(keys, True)
-
- w = self.w
- req = self.req
- _ = req._
- w(u'<h1>%s</h1>\n' % _(self.title))
- w(self.error_message())
- for label, group, form in sorted((_(g), g, f)
- for g, f in mainopts.iteritems()):
- status = css_class(self._group_status(group)) #'hidden' (collapsed), or '' (open) ?
- w(u'<h2 class="propertiesform">%s</h2>\n' %
- (make_togglable_link('fieldset_' + group, label,
- self._cookie_name(group))))
- w(u'<div id="fieldset_%s" %s>' % (group, status))
- w(u'<fieldset class="subentity">')
- w(form)
- w(u'</fieldset></div>')
- for label, group, objects in sorted((_(g), g, o)
- for g, o in groupedopts.iteritems()):
- status = css_class(self._group_status(group))
- w(u'<h2 class="propertiesform">%s</h2>\n' %
- (make_togglable_link('fieldset_' + group, label,
- self._cookie_name(group))))
- w(u'<div id="fieldset_%s" %s>' % (group, status))
- for label, oid, form in sorted((self.req.__('%s_%s' % (group, o)), o, f)
- for o, f in objects.iteritems()):
- w(u'<fieldset class="subentity">')
- w(u'<legend class="componentTitle">%s</legend>\n' % label)
- docmsgid = '%s_%s_description' % (group, oid)
- doc = _(docmsgid)
- if doc != docmsgid:
- w(u'<p class="description">%s</p>' % html_escape(doc))
- w(form)
- w(u'</fieldset>')
- w(u'</div>')
-
- @property
- @cached
- def eprops_rset(self):
- return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V, NOT P for_user U')
-
- @property
- def defined_keys(self):
- values = {}
- for i, entity in enumerate(self.eprops_rset.entities()):
- values[entity.pkey] = i
- return values
-
- def entity_for_key(self, key):
- values = self.defined_keys
- if key in values:
- entity = self.eprops_rset.get_entity(values[key], 0)
- else:
- entity = self.vreg.etype_class('EProperty')(self.req, None, None)
- entity.eid = self.req.varmaker.next()
- entity['value'] = self.vreg.property_value(key)
- return entity
-
- def form(self, keys, splitlabel=False):
- stream = UStringIO()
- w = stream.write
- w(u'<form action="%s" method="post">\n' % self.build_url())
- w(u'<fieldset>\n')
- w(u'<input type="hidden" name="__errorurl" value="%s"/>\n'
- % html_escape(self.req.url()))
- w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
- path = self.req.relative_path()
- if '?' in path:
- path, params = path.split('?', 1)
- w(u'<input type="hidden" name="__redirectparams" value="%s"/>\n'
- % html_escape(params))
- w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n' % path)
- #w(u'<input type="hidden" name="__redirectrql" value=""/>\n')
- w(u'<input type="hidden" name="__message" value="%s"/>\n'
- % self.req._('changes applied'))
- w(u'<table><tr><td>\n')
-
- w(u'<table>\n')
- for key in keys:
- w(u'<tr>\n')
- self.form_row(w, key, splitlabel)
- w(u'</tr>\n')
- w(u'</table>\n')
- w(u'</td></tr><tr><td>\n')
- w(self.button_ok())
- w(self.button_cancel())
- w(u'</td></tr></table>\n')
- w(u'</fieldset>\n')
- w(u'</form>\n')
- return stream.getvalue()
-
- def form_row(self, w, key, splitlabel):
- entity = self.entity_for_key(key)
- eid = entity.eid
- if splitlabel:
- w(u'<td class="label">%s</td>' % self.req._(key.split('.')[-1]))
- else:
- w(u'<td class="label">%s</td>' % self.req._(key))
- wdg = self.vreg.property_value_widget(key, req=self.req)
- error = wdg.render_error(entity)
- w(u'<td class="%s">' % (error and 'error' or ''))
- w(error)
- self.form_row_hiddens(w, entity, key)
- w(wdg.edit_render(entity))
- w(u'</td>\n')
- w(u'<td>%s</td>' % wdg.render_help(entity))
- return entity
-
- def form_row_hiddens(self, w, entity, key):
- eid = entity.eid
- w(u'<input type="hidden" name="eid" value="%s"/>' % eid)
- w(u'<input type="hidden" name="%s" value="EProperty"/>' % eid_param('__type', eid))
- w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('pkey', eid), key))
- w(u'<input type="hidden" name="%s" value="%s"/>' % (eid_param('edits-pkey', eid), ''))
-
-
-class EpropertiesForm(SystemEpropertiesForm):
- id = 'epropertiesform'
- title = _('preferences')
- require_groups = ('users', 'managers') # we don't want guests to be able to come here
- __selectors__ = chainfirst(none_rset,
- chainall(one_line_rset, accept_rset)),
- accepts = ('EUser',)
-
- @classmethod
- def accept_rset(cls, req, rset, row, col):
- if row is None:
- row = 0
- score = super(EpropertiesForm, cls).accept_rset(req, rset, row, col)
- # check current user is the rset user or he is in the managers group
- if score and (req.user.eid == rset[row][col or 0]
- or req.user.matching_groups('managers')):
- return score
- return 0
-
- @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)
-
- @property
- @cached
- def eprops_rset(self):
- return self.req.execute('Any P,K,V WHERE P is EProperty, P pkey K, P value V,'
- 'P for_user U, U eid %(x)s', {'x': self.user.eid})
-
- def form_row_hiddens(self, w, entity, key):
- super(EpropertiesForm, self).form_row_hiddens(w, entity, key)
- # if user is in the managers group and the property is being created,
- # we have to set for_user explicitly
- if not entity.has_eid() and self.user.matching_groups('managers'):
- eid = entity.eid
- w(u'<input type="hidden" name="%s" value="%s"/>'
- % (eid_param('edits-for_user', eid), INTERNAL_FIELD_VALUE))
- w(u'<input type="hidden" name="%s" value="%s"/>'
- % (eid_param('for_user', eid), self.user.eid))
-
-
-
-
class ProcessInformationView(StartupView):
id = 'info'
+ __select__ = none_rset() & match_user_groups('managers')
+
title = _('server information')
- require_groups = ('managers',)
def call(self, **kwargs):
"""display server information"""
--- a/web/views/massmailing.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/massmailing.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""Mass mailing form views
:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -10,17 +10,16 @@
from logilab.mtconverter import html_escape
from cubicweb.interfaces import IEmailable
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import implement_interface, match_user_group
-from cubicweb.web.action import EntityAction
+from cubicweb.selectors import implements, match_user_groups
+from cubicweb.view import EntityView
+from cubicweb.web.action import Action
from cubicweb.web import stdmsgs
-class SendEmailAction(EntityAction):
+class SendEmailAction(Action):
category = 'mainactions'
- __selectors__ = (implement_interface, match_user_group)
- accepts_interfaces = (IEmailable,) # XXX should check email is set as well
- require_groups = ('managers', 'users')
+ # XXX should check email is set as well
+ __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
id = 'sendemail'
title = _('send email')
@@ -35,10 +34,7 @@
class MassMailingForm(EntityView):
id = 'massmailing'
- __selectors__ = (implement_interface, match_user_group)
- accepts_interfaces = (IEmailable,)
- require_groups = ('managers', 'users')
-
+ __select__ = implements(IEmailable) & match_user_groups('managers', 'users')
form_template = u"""
<div id="compose">
--- a/web/views/navigation.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/navigation.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""navigation components definition for CubicWeb web client
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -9,11 +9,12 @@
from rql.nodes import VariableRef, Constant
from logilab.mtconverter import html_escape
+from logilab.common.deprecation import obsolete
from cubicweb.interfaces import IPrevNext
-from cubicweb.common.selectors import (paginated_rset, sorted_rset,
- primary_view, match_context_prop,
- one_line_rset, implement_interface)
+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.web.component import EntityVComponent, NavigationComponent
@@ -51,7 +52,7 @@
"""sorted navigation apply if navigation is needed (according to page size)
and if the result set is sorted
"""
- __selectors__ = (paginated_rset, sorted_rset)
+ __select__ = paginated_rset() & sorted_rset()
# number of considered chars to build page links
nb_chars = 5
@@ -145,7 +146,7 @@
def limit_rset_using_paged_nav(self, req, rset, w, forcedisplay=False,
- show_all_option=True, page_size = None):
+ show_all_option=True, page_size=None):
showall = forcedisplay or req.form.get('__force_display') is not None
nav = not showall and self.vreg.select_component('navigation', req, rset,
page_size=page_size)
@@ -165,9 +166,9 @@
# monkey patch base View class to add a .pagination(req, rset, w, forcedisplay)
# method to be called on view's result set and printing pages index in the view
-from cubicweb.common.view import View
+from cubicweb.view import View
# XXX deprecated, use paginate
-View.pagination = limit_rset_using_paged_nav
+View.pagination = obsolete('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
def paginate(view, show_all_option=True, w=None):
limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w,
@@ -180,9 +181,8 @@
# itself
title = _('contentnavigation_prevnext')
help = _('contentnavigation_prevnext_description')
- __selectors__ = (one_line_rset, primary_view,
- match_context_prop, implement_interface)
- accepts_interfaces = (IPrevNext,)
+ __select__ = (one_line_rset() & primary_view()
+ & match_context_prop() & implements(IPrevNext))
context = 'navbottom'
order = 10
def call(self, view=None):
--- a/web/views/old_calendar.py Fri Feb 27 09:59:53 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,544 +0,0 @@
-"""html calendar views
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-from mx.DateTime import DateTime, RelativeDateTime, Date, Time, today, Sunday
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.interfaces import ICalendarViews
-from cubicweb.common.utils import date_range
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.registerers import priority_registerer
-from cubicweb.common.view import EntityView
-
-# Define some useful constants
-ONE_MONTH = RelativeDateTime(months=1)
-TODAY = today()
-THIS_MONTH = TODAY.month
-THIS_YEAR = TODAY.year
-# mx.DateTime and ustrftime could be used to build WEEKDAYS
-WEEKDAYS = [_("monday"), _("tuesday"), _("wednesday"), _("thursday"),
- _("friday"), _("saturday"), _("sunday")]
-
-# used by i18n tools
-MONTHNAMES = [ _('january'), _('february'), _('march'), _('april'), _('may'),
- _('june'), _('july'), _('august'), _('september'), _('october'),
- _('november'), _('december')
- ]
-
-class _CalendarView(EntityView):
- """base calendar view containing helpful methods to build calendar views"""
- __registerer__ = priority_registerer
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ICalendarViews,)
- need_navigation = False
-
- # Navigation building methods / views ####################################
-
- PREV = u'<a href="%s"><<</a> <a href="%s"><</a>'
- NEXT = u'<a href="%s">></a> <a href="%s">>></a>'
- NAV_HEADER = u"""<table class="calendarPageHeader">
-<tr><td class="prev">%s</td><td class="next">%s</td></tr>
-</table>
-""" % (PREV, NEXT)
-
- def nav_header(self, date, smallshift=3, bigshift=9):
- """prints shortcut links to go to previous/next steps (month|week)"""
- prev1 = date - RelativeDateTime(months=smallshift)
- prev2 = date - RelativeDateTime(months=bigshift)
- next1 = date + RelativeDateTime(months=smallshift)
- next2 = date + RelativeDateTime(months=bigshift)
- rql, vid = self.rset.printable_rql(), self.id
- return self.NAV_HEADER % (
- html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, month=prev2.month)),
- html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, month=prev1.month)),
- html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, month=next1.month)),
- html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, month=next2.month)))
-
-
- # Calendar building methods ##############################################
-
- def build_calendars(self, schedule, begin, end):
- """build several HTML calendars at once, one for each month
- between begin and end
- """
- return [self.build_calendar(schedule, date)
- for date in date_range(begin, end, incr=ONE_MONTH)]
-
- def build_calendar(self, schedule, first_day):
- """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
- rows = []
- current_row = [NO_CELL] * first_day.day_of_week
- for daynum in xrange(0, first_day.days_in_month):
- # build cell day
- day = first_day + daynum
- events = schedule.get(day)
- if events:
- events = [u'\n'.join(event) for event in events.values()]
- current_row.append(CELL % (daynum+1, '\n'.join(events)))
- else:
- current_row.append(EMPTY_CELL % (daynum+1))
- # store & reset current row on Sundays
- if day.day_of_week == Sunday:
- rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.iso_week[1], ''.join(current_row)))
- current_row = []
- current_row.extend([NO_CELL] * (Sunday-day.day_of_week))
- rql = self.rset.printable_rql()
- if day.day_of_week != Sunday:
- rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.iso_week[1], ''.join(current_row)))
- url = self.build_url(rql=rql, vid='calendarmonth',
- year=first_day.year, month=first_day.month)
- monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
-
- def _mk_schedule(self, begin, end, itemvid='calendaritem'):
- """private method that gathers information from resultset
- and builds calendars according to it
-
- :param begin: begin of date range
- :param end: end of date rangs
- :param itemvid: which view to call to render elements in cells
-
- returns { day1 : { hour : [views] },
- day2 : { hour : [views] } ... }
- """
- # put this here since all sub views are calling this method
- self.req.add_css('cubicweb.calendar.css')
- schedule = {}
- for row in xrange(len(self.rset.rows)):
- entity = self.entity(row)
- infos = u'<div class="event">'
- infos += self.view(itemvid, self.rset, row=row)
- infos += u'</div>'
- for date in entity.matching_dates(begin, end):
- day = Date(date.year, date.month, date.day)
- time = Time(date.hour, date.minute, date.second)
- schedule.setdefault(day, {})
- schedule[day].setdefault(time, []).append(infos)
- return schedule
-
-
- @staticmethod
- def get_date_range(day=TODAY, shift=4):
- """returns a couple (begin, end)
-
- <begin> is the first day of current_month - shift
- <end> is the last day of current_month + (shift+1)
- """
- first_day_in_month = DateTime(day.year, day.month, 1)
- begin = first_day_in_month - RelativeDateTime(months=shift)
- end = (first_day_in_month + RelativeDateTime(months=shift+1)) - 1
- return begin, end
-
-
- def _build_ampm_cells(self, daynum, events):
- """create a view without any hourly details.
-
- :param daynum: day of the built cell
- :param events: dictionnary with all events classified by hours"""
- # split events according am/pm
- am_events = [event for e_time, e_list in events.iteritems()
- if 0 <= e_time.hour < 12
- for event in e_list]
- pm_events = [event for e_time, e_list in events.iteritems()
- if 12 <= e_time.hour < 24
- for event in e_list]
- # format each am/pm cell
- if am_events:
- am_content = AMPM_CONTENT % ("amCell", "am", '\n'.join(am_events))
- else:
- am_content = AMPM_EMPTY % ("amCell", "am")
- if pm_events:
- pm_content = AMPM_CONTENT % ("pmCell", "pm", '\n'.join(pm_events))
- else:
- pm_content = AMPM_EMPTY % ("pmCell", "pm")
- return am_content, pm_content
-
-
-
-class YearCalendarView(_CalendarView):
- id = 'calendaryear'
- title = _('calendar (year)')
-
- def call(self, year=THIS_YEAR, month=THIS_MONTH):
- """this view renders a 3x3 calendars' table"""
- year = int(self.req.form.get('year', year))
- month = int(self.req.form.get('month', month))
- center_date = DateTime(year, month)
- begin, end = self.get_date_range(day=center_date)
- schedule = self._mk_schedule(begin, end)
- self.w(self.nav_header(center_date))
- calendars = tuple(self.build_calendars(schedule, begin, end))
- self.w(SMALL_CALENDARS_PAGE % calendars)
-
-
-class SemesterCalendarView(_CalendarView):
- """this view renders three semesters as three rows of six columns,
- one column per month
- """
- id = 'calendarsemester'
- title = _('calendar (semester)')
-
- def call(self, year=THIS_YEAR, month=THIS_MONTH):
- year = int(self.req.form.get('year', year))
- month = int(self.req.form.get('month', month))
- begin = DateTime(year, month) - RelativeDateTime(months=2)
- end = DateTime(year, month) + RelativeDateTime(months=3)
- schedule = self._mk_schedule(begin, end)
- self.w(self.nav_header(DateTime(year, month), 1, 6))
- self.w(u'<table class="semesterCalendar">')
- self.build_calendars(schedule, begin, end)
- self.w(u'</table>')
- self.w(self.nav_header(DateTime(year, month), 1, 6))
-
- def build_calendars(self, schedule, begin, end):
- self.w(u'<tr>')
- rql = self.rset.printable_rql()
- for cur_month in date_range(begin, end, incr=ONE_MONTH):
- 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)
- self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (html_escape(url),
- umonth))
- self.w(u'</tr>')
- _ = self.req._
- for day_num in xrange(31):
- self.w(u'<tr>')
- for cur_month in date_range(begin, end, incr=ONE_MONTH):
- if day_num >= cur_month.days_in_month:
- self.w(u'%s%s' % (NO_CELL, NO_CELL))
- else:
- day = DateTime(cur_month.year, cur_month.month, day_num+1)
- events = schedule.get(day)
- self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(), day_num+1))
- self.format_day_events(day, events)
- self.w(u'</tr>')
-
- def format_day_events(self, day, events):
- if events:
- events = ['\n'.join(event) for event in events.values()]
- self.w(WEEK_CELL % '\n'.join(events))
- else:
- self.w(WEEK_EMPTY_CELL)
-
-
-class MonthCalendarView(_CalendarView):
- """this view renders a 3x1 calendars' table"""
- id = 'calendarmonth'
- title = _('calendar (month)')
-
- def call(self, year=THIS_YEAR, month=THIS_MONTH):
- year = int(self.req.form.get('year', year))
- month = int(self.req.form.get('month', month))
- center_date = DateTime(year, month)
- begin, end = self.get_date_range(day=center_date, shift=1)
- schedule = self._mk_schedule(begin, end)
- calendars = self.build_calendars(schedule, begin, end)
- self.w(self.nav_header(center_date, 1, 3))
- self.w(BIG_CALENDARS_PAGE % tuple(calendars))
- self.w(self.nav_header(center_date, 1, 3))
-
-
-class WeekCalendarView(_CalendarView):
- """this view renders a calendar for week events"""
- id = 'calendarweek'
- title = _('calendar (week)')
-
- def call(self, year=THIS_YEAR, week=TODAY.iso_week[1]):
- year = int(self.req.form.get('year', year))
- week = int(self.req.form.get('week', week))
- day0 = DateTime(year)
- first_day_of_week = (day0-day0.day_of_week) + 7*week
- begin, end = first_day_of_week-7, first_day_of_week+14
- schedule = self._mk_schedule(begin, end, itemvid='calendarlargeitem')
- self.w(self.nav_header(first_day_of_week))
- self.w(u'<table class="weekCalendar">')
- _weeks = [(first_day_of_week-7, first_day_of_week-1),
- (first_day_of_week, first_day_of_week+6),
- (first_day_of_week+7, first_day_of_week+13)]
- self.build_calendar(schedule, _weeks)
- self.w(u'</table>')
- self.w(self.nav_header(first_day_of_week))
-
- def build_calendar(self, schedule, weeks):
- rql = self.rset.printable_rql()
- _ = self.req._
- 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)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
- self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
- % (_('week'), monday.iso_week[1], monthlink))
- for day in date_range(monday, sunday):
- self.w(u'<tr>')
- self.w(u'<td>%s</td>' % _(WEEKDAYS[day.day_of_week]))
- self.w(u'<td>%s</td>' % (day.strftime('%Y-%m-%d')))
- events = schedule.get(day)
- if events:
- events = ['\n'.join(event) for event in events.values()]
- self.w(WEEK_CELL % '\n'.join(events))
- else:
- self.w(WEEK_EMPTY_CELL)
- self.w(u'</tr>')
-
- def nav_header(self, date, smallshift=1, bigshift=3):
- """prints shortcut links to go to previous/next steps (month|week)"""
- prev1 = date - RelativeDateTime(weeks=smallshift)
- prev2 = date - RelativeDateTime(weeks=bigshift)
- next1 = date + RelativeDateTime(weeks=smallshift)
- next2 = date + RelativeDateTime(weeks=bigshift)
- rql, vid = self.rset.printable_rql(), self.id
- return self.NAV_HEADER % (
- html_escape(self.build_url(rql=rql, vid=vid, year=prev2.year, week=prev2.iso_week[1])),
- html_escape(self.build_url(rql=rql, vid=vid, year=prev1.year, week=prev1.iso_week[1])),
- html_escape(self.build_url(rql=rql, vid=vid, year=next1.year, week=next1.iso_week[1])),
- html_escape(self.build_url(rql=rql, vid=vid, year=next2.year, week=next2.iso_week[1])))
-
-
-
-class AMPMYearCalendarView(YearCalendarView):
- id = '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
- rows = [] # each row is: (am,pm), (am,pm) ... week_title
- current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
- rql = self.rset.printable_rql()
- for daynum in xrange(0, first_day.days_in_month):
- # build cells day
- day = first_day + daynum
- events = schedule.get(day)
- if events:
- current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
- else:
- current_row.append((AMPM_DAY % (daynum+1),
- AMPM_EMPTY % ("amCell", "am"),
- AMPM_EMPTY % ("pmCell", "pm")))
- # store & reset current row on Sundays
- if day.day_of_week == Sunday:
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.iso_week[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
- day.iso_week[1])
- current_row.append(WEEKNUM_CELL % weeklink)
- rows.append(current_row)
- current_row = []
- current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.iso_week[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.iso_week[1])
- current_row.append(WEEKNUM_CELL % weeklink)
- rows.append(current_row)
- # build two rows for each week: am & pm
- formatted_rows = []
- for row in rows:
- week_title = row.pop()
- day_row = [day for day, am, pm in row]
- am_row = [am for day, am, pm in row]
- pm_row = [pm for day, am, pm in row]
- formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
- 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)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
-
-
-
-class AMPMSemesterCalendarView(SemesterCalendarView):
- """this view renders a 3x1 calendars' table"""
- id = 'ampmcalendarsemester'
- title = _('am/pm calendar (semester)')
-
- def build_calendars(self, schedule, begin, end):
- self.w(u'<tr>')
- rql = self.rset.printable_rql()
- for cur_month in date_range(begin, end, incr=ONE_MONTH):
- 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)
- self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (html_escape(url),
- umonth))
- self.w(u'</tr>')
- _ = self.req._
- for day_num in xrange(31):
- self.w(u'<tr>')
- for cur_month in date_range(begin, end, incr=ONE_MONTH):
- if day_num >= cur_month.days_in_month:
- self.w(u'%s%s%s' % (NO_CELL, NO_CELL, NO_CELL))
- else:
- day = DateTime(cur_month.year, cur_month.month, day_num+1)
- events = schedule.get(day)
- self.w(u'<td>%s %s</td>\n' % (_(WEEKDAYS[day.day_of_week])[0].upper(),
- day_num+1))
- self.format_day_events(day, events)
- self.w(u'</tr>')
-
- def format_day_events(self, day, events):
- if events:
- self.w(u'\n'.join(self._build_ampm_cells(day, events)))
- else:
- self.w(u'%s %s'% (AMPM_EMPTY % ("amCell", "am"),
- AMPM_EMPTY % ("pmCell", "pm")))
-
-
-class AMPMMonthCalendarView(MonthCalendarView):
- """this view renders a 3x1 calendars' table"""
- id = '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
- rows = [] # each row is: (am,pm), (am,pm) ... week_title
- current_row = [(NO_CELL, NO_CELL, NO_CELL)] * first_day.day_of_week
- rql = self.rset.printable_rql()
- for daynum in xrange(0, first_day.days_in_month):
- # build cells day
- day = first_day + daynum
- events = schedule.get(day)
- if events:
- current_row.append((AMPM_DAY % (daynum+1),) + self._build_ampm_cells(daynum, events))
- else:
- current_row.append((AMPM_DAY % (daynum+1),
- AMPM_EMPTY % ("amCell", "am"),
- AMPM_EMPTY % ("pmCell", "pm")))
- # store & reset current row on Sundays
- if day.day_of_week == Sunday:
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.iso_week[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
- day.iso_week[1])
- current_row.append(WEEKNUM_CELL % weeklink)
- rows.append(current_row)
- current_row = []
- current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (Sunday-day.day_of_week))
- url = self.build_url(rql=rql, vid='ampmcalendarweek',
- year=day.year, week=day.iso_week[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
- day.iso_week[1])
- current_row.append(WEEKNUM_CELL % weeklink)
- rows.append(current_row)
- # build two rows for each week: am & pm
- formatted_rows = []
- for row in rows:
- week_title = row.pop()
- day_row = [day for day, am, pm in row]
- am_row = [am for day, am, pm in row]
- pm_row = [pm for day, am, pm in row]
- formatted_rows.append('<tr>%s%s</tr>'% (week_title, '\n'.join(day_row)))
- 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)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url),
- umonth)
- return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
-
-
-
-class AMPMWeekCalendarView(WeekCalendarView):
- """this view renders a 3x1 calendars' table"""
- id = 'ampmcalendarweek'
- title = _('am/pm calendar (week)')
-
- def build_calendar(self, schedule, weeks):
- rql = self.rset.printable_rql()
- w = self.w
- _ = self.req._
- 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)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
- w(u'<tr>%s</tr>' % (
- WEEK_TITLE % (_('week'), monday.iso_week[1], monthlink)))
- w(u'<tr><th>%s</th><th> </th></tr>'% _(u'Date'))
- for day in date_range(monday, sunday):
- events = schedule.get(day)
- style = day.day_of_week % 2 and "even" or "odd"
- w(u'<tr class="%s">' % style)
- if events:
- hours = events.keys()
- hours.sort()
- w(AMPM_DAYWEEK % (
- len(hours), _(WEEKDAYS[day.day_of_week]),
- self.format_date(day)))
- w(AMPM_WEEK_CELL % (
- hours[0].hour, hours[0].minute,
- '\n'.join(events[hours[0]])))
- w(u'</tr>')
- for hour in hours[1:]:
- w(u'<tr class="%s">%s</tr>'% (
- style, AMPM_WEEK_CELL % (hour.hour, hour.minute,
- '\n'.join(events[hour]))))
- else:
- w(AMPM_DAYWEEK_EMPTY % (
- _(WEEKDAYS[day.day_of_week]),
- self.format_date(day)))
- w(WEEK_EMPTY_CELL)
- w(u'</tr>')
-
-
-SMALL_CALENDARS_PAGE = u"""<table class="smallCalendars">
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td><td class="calendar">%s</td><td class="calendar">%s</td></tr>
-</table>
-"""
-
-BIG_CALENDARS_PAGE = u"""<table class="bigCalendars">
-<tr><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td></tr>
-<tr><td class="calendar">%s</td></tr>
-</table>
-"""
-
-WEEKNUM_CELL = u'<td class="weeknum">%s</td>'
-
-def CALENDAR(req):
- _ = req._
- WEEKNUM_HEADER = u'<th class="weeknum">%s</th>' % _('week')
- CAL_HEADER = WEEKNUM_HEADER + u' \n'.join([u'<th class="weekday">%s</th>' % _(day)[0].upper()
- for day in WEEKDAYS])
- return u"""<table>
-<tr><th class="month" colspan="8">%%s</th></tr>
-<tr>
- %s
-</tr>
-%%s
-</table>
-""" % (CAL_HEADER,)
-
-
-DAY_TEMPLATE = """<tr><td class="weekday">%(daylabel)s</td><td>%(dmydate)s</td><td>%(dayschedule)s</td>
-"""
-
-NO_CELL = u'<td class="noday"></td>'
-EMPTY_CELL = u'<td class="cellEmpty"><span class="cellTitle">%s</span></td>'
-CELL = u'<td class="cell"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
-
-AMPM_DAY = u'<td class="cellDay">%d</td>'
-AMPM_EMPTY = u'<td class="%sEmpty"><span class="cellTitle">%s</span></td>'
-AMPM_CONTENT = u'<td class="%s"><span class="cellTitle">%s</span><div class="cellContent">%s</div></td>'
-
-WEEK_TITLE = u'<th class="weekTitle" colspan="2">%s %s (%s)</th>'
-WEEK_EMPTY_CELL = u'<td class="weekEmptyCell"> </td>'
-WEEK_CELL = u'<td class="weekCell"><div class="cellContent">%s</div></td>'
-
-AMPM_DAYWEEK_EMPTY = u'<td>%s %s</td>'
-AMPM_DAYWEEK = u'<td rowspan="%d">%s %s</td>'
-AMPM_WEEK_CELL = u'<td class="ampmWeekCell"><div class="cellContent">%02d:%02d - %s</div></td>'
--- a/web/views/owl.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/owl.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,14 @@
+"""produces some Ontology Web Language schema and views
+
+:organization: Logilab
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
from logilab.mtconverter import TransformError, xml_escape
-from cubicweb.common.view import StartupView
-from cubicweb.common.view import EntityView
+from cubicweb.view import StartupView, EntityView
_ = unicode
@@ -155,7 +162,6 @@
id = 'owlabox'
title = _('owlabox')
templatable = False
- accepts = ('Any',)
content_type = 'application/xml' # 'text/xml'
def call(self):
@@ -169,11 +175,9 @@
class OWLABOXItemView(EntityView):
- '''This view represents a part of the ABOX for a given entity.'''
-
+ '''This view represents a part of the ABOX for a given entity.'''
id = 'owlaboxitem'
templatable = False
- accepts = ('Any',)
content_type = 'application/xml' # 'text/xml'
def cell_call(self, row, col, skiprels=DEFAULT_SKIP_RELS):
--- a/web/views/plots.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/plots.py Mon Mar 02 21:03:54 2009 +0100
@@ -2,8 +2,10 @@
from logilab.common import flatten
+from cubicweb.vregistry import objectify_selector
from cubicweb.web.views import baseviews
+@objectify_selector
def plot_selector(cls, req, rset, *args, **kwargs):
"""accept result set with at least one line and two columns of result
all columns after second must be of numerical types"""
@@ -34,7 +36,7 @@
binary = True
content_type = 'image/png'
_plot_count = 0
- __selectors__ = (plot_selector,)
+ __select__ = plot_selector()
def call(self, width=None, height=None):
# compute dimensions
--- a/web/views/schemaentities.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/schemaentities.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,21 +1,22 @@
"""Specific views for schema related entities
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
from logilab.mtconverter import html_escape
+from cubicweb.selectors import implements, rql_condition
from cubicweb.schemaviewer import SchemaViewer
+from cubicweb.view import EntityView
from cubicweb.common.uilib import ureport_as_html
-from cubicweb.common.view import EntityView
from cubicweb.web.views import baseviews
class ImageView(EntityView):
- accepts = ('EEType',)
+ __select__ = implements('EEType')
id = 'image'
title = _('image')
@@ -35,16 +36,16 @@
return html_escape(entity.dc_long_title())
class EETypePrimaryView(_SchemaEntityPrimaryView):
- accepts = ('EEType',)
+ __select__ = implements('EEType')
skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final')
class ERTypePrimaryView(_SchemaEntityPrimaryView):
- accepts = ('ERType',)
+ __select__ = implements('ERType')
skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final',
'symetric', 'inlined')
class ErdefPrimaryView(_SchemaEntityPrimaryView):
- accepts = ('EFRDef', 'ENFRDef')
+ __select__ = implements('EEType', 'ENFRDef')
show_attr_label = True
class EETypeSchemaView(EETypePrimaryView):
@@ -84,7 +85,7 @@
class EETypeWorkflowView(EntityView):
id = 'workflow'
- accepts = ('EEType',)
+ __select__ = implements('EEType')
cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
def cell_call(self, row, col, **kwargs):
@@ -97,7 +98,7 @@
class EETypeOneLineView(baseviews.OneLineView):
- accepts = ('EEType',)
+ __select__ = implements('EEType')
def cell_call(self, row, col, **kwargs):
entity = self.entity(row, col)
@@ -109,14 +110,14 @@
self.w(u'</em>')
-from cubicweb.web.action import EntityAction
+from cubicweb.web.action import Action
-class ViewWorkflowAction(EntityAction):
+class ViewWorkflowAction(Action):
id = 'workflow'
+ __select__ = implements('EEType') & rql_condition('S state_of X')
+
category = 'mainactions'
title = _('view workflow')
- accepts = ('EEType',)
- condition = 'S state_of X' # must have at least one state associated
def url(self):
entity = self.rset.get_entity(self.row or 0, self.col or 0)
return entity.absolute_url(vid='workflow')
--- a/web/views/startup.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/startup.py Mon Mar 02 21:03:54 2009 +0100
@@ -9,8 +9,8 @@
from logilab.mtconverter import html_escape
+from cubicweb.view import StartupView
from cubicweb.common.uilib import ureport_as_html, unormalize, ajax_replace_url
-from cubicweb.common.view import StartupView
from cubicweb.web.httpcache import EtagHTTPCacheManager
_ = unicode
--- a/web/views/tableview.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/tableview.py Mon Mar 02 21:03:54 2009 +0100
@@ -11,13 +11,12 @@
from logilab.mtconverter import html_escape
-from cubicweb.common.utils import make_uid
+from cubicweb.selectors import nonempty_rset, match_form_params, accept_rset
+from cubicweb.utils import make_uid
+from cubicweb.view import EntityView, AnyRsetView
from cubicweb.common.uilib import toggle_action, limitsize, jsonize, htmlescape
-from cubicweb.common.view import EntityView, AnyRsetView
-from cubicweb.common.selectors import (nonempty_rset, match_form_params,
- accept_rset)
from cubicweb.web.htmlwidgets import (TableWidget, TableColumn, MenuWidget,
- PopupBoxMenu, BoxLink)
+ PopupBoxMenu, BoxLink)
from cubicweb.web.facet import prepare_facets_rqlst, filter_hiddens
class TableView(AnyRsetView):
@@ -246,6 +245,7 @@
entity = self.rset.get_entity(row, col)
return entity.sortvalue()
+
class EditableTableView(TableView):
id = 'editable-table'
finalview = 'editable-final'
@@ -253,10 +253,9 @@
class CellView(EntityView):
- __selectors__ = (nonempty_rset, accept_rset)
+ __select__ = nonempty_rset()
id = 'cell'
- accepts = ('Any',)
def cell_call(self, row, col, cellvid=None):
"""
@@ -289,8 +288,7 @@
displayed with default restrictions set
"""
id = 'initialtable'
- __selectors__ = nonempty_rset, match_form_params
- form_params = ('actualrql',)
+ __select__ = nonempty_rset() & match_form_params('actualrql')
# should not be displayed in possible view since it expects some specific
# parameters
title = None
--- a/web/views/tabs.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/tabs.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""base classes to handle tabbed views
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -11,11 +11,10 @@
from logilab.mtconverter import html_escape
from cubicweb import NoSelectableObject, role
-from cubicweb.common.view import EntityView
-from cubicweb.common.selectors import has_related_entities
-from cubicweb.common.utils import HTMLHead
-from cubicweb.common.uilib import rql_for_eid
-
+from cubicweb.selectors import partial_has_related_entities
+from cubicweb.utils import HTMLHead
+from cubicweb.view import EntityView
+from cubicweb.common import uilib, tags
from cubicweb.web.views.basecontrollers import JSonController
@@ -41,7 +40,7 @@
self.req.add_js('cubicweb.lazy.js')
urlparams = {'vid' : vid, 'mode' : 'html'}
if eid:
- urlparams['rql'] = rql_for_eid(eid)
+ urlparams['rql'] = uilib.rql_for_eid(eid)
w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
vid, html_escape(self.build_url('json', **urlparams))))
if show_spinbox:
@@ -122,9 +121,8 @@
'cookiename' : self.cookie_name})
-class EntityRelatedTab(EntityView):
- """A view you should inherit from leftmost,
- to wrap another actual view displaying entity related stuff.
+class EntityRelationView(EntityView):
+ """view displaying entity related stuff.
Such a view _must_ provide the rtype, target and vid attributes :
Example :
@@ -132,24 +130,22 @@
class ProjectScreenshotsView(EntityRelationView):
'''display project's screenshots'''
id = title = _('projectscreenshots')
- accepts = ('Project',)
+ __select__ = EntityRelationView.__select__ & implements('Project')
rtype = 'screenshot'
- target = 'object'
+ role = 'subject'
vid = 'gallery'
- __selectors__ = EntityRelationView.__selectors__ + (one_line_rset,)
-
- This is the view we want to have in a tab, only if there is something to show.
- Then, just define as below, and declare this being the tab content :
-
- class ProjectScreenshotTab(DataDependantTab, ProjectScreenshotsView):
- id = 'screenshots_tab'
+ in this example, entities related to project entity by the'screenshot'
+ relation (where the project is subject of the relation) will be displayed
+ using the 'gallery' view.
"""
- __selectors__ = EntityView.__selectors__ + (has_related_entities,)
+ __select__ = EntityView.__select__ & partial_has_related_entities()
vid = 'list'
-
+
def cell_call(self, row, col):
rset = self.entity(row, col).related(self.rtype, role(self))
self.w(u'<div class="mainInfo">')
+ if self.title:
+ self.w(tags.h1(self.req._(title)))
self.wview(self.vid, rset, 'noresult')
self.w(u'</div>')
--- a/web/views/timeline.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/timeline.py Mon Mar 02 21:03:54 2009 +0100
@@ -3,7 +3,7 @@
cf. http://code.google.com/p/simile-widgets/
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -13,11 +13,10 @@
from logilab.mtconverter import html_escape
from cubicweb.interfaces import ICalendarable
-from cubicweb.common.view import EntityView, StartupView
-from cubicweb.common.selectors import implement_interface
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView, StartupView
-#
class TimelineJsonView(EntityView):
"""generates a json file to feed Timeline.loadJSON()
NOTE: work in progress (image_url, bubbleUrl and so on
@@ -28,8 +27,7 @@
templatable = False
content_type = 'application/json'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ICalendarable,)
+ __select__ = implements(ICalendarable)
date_fmt = '%Y/%m/%d'
def call(self):
@@ -103,8 +101,7 @@
class TimelineView(TimelineViewMixIn, EntityView):
"""builds a cubicweb timeline widget node"""
id = 'timeline'
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ICalendarable,)
+ __select__ = implements(ICalendarable)
need_navigation = False
def call(self, tlunit=None):
self.req.html_headers.define_var('Timeline_urlPrefix', self.req.datadir_url)
--- a/web/views/timetable.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/timetable.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,16 +1,16 @@
"""html calendar views
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
from logilab.mtconverter import html_escape
from cubicweb.interfaces import ITimetableViews
-from cubicweb.common.utils import date_range
-from cubicweb.common.selectors import implement_interface
-from cubicweb.common.view import AnyRsetView
+from cubicweb.selectors import implements
+from cubicweb.utils import date_range
+from cubicweb.view import AnyRsetView
class _TaskEntry(object):
@@ -25,8 +25,7 @@
class TimeTableView(AnyRsetView):
id = 'timetable'
title = _('timetable')
- __selectors__ = (implement_interface,)
- accepts_interfaces = (ITimetableViews,)
+ __select__ = implements(ITimetableViews)
need_navigation = False
def call(self, title=None):
--- a/web/views/treeview.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/treeview.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,18 +1,31 @@
+"""Set of tree-building widgets, based on jQuery treeview plugin
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.common.decorators import monkeypatch
from logilab.mtconverter import html_escape
from cubicweb.interfaces import ITree
-from cubicweb.common.selectors import implement_interface, yes
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
+from cubicweb.web.views.basecontrollers import JSonController
+
+def treecookiename(treeid):
+ return str('treestate-%s' % treeid)
+
from cubicweb.web.views.baseviews import OneLineView
class TreeView(EntityView):
id = 'treeview'
- accepts = ('Any',)
itemvid = 'treeitemview'
css_classes = 'treeview widget'
title = _('tree view')
-
+
def call(self, subvid=None):
if subvid is None and 'subvid' in self.req.form:
subvid = self.req.form.pop('subvid') # consume it
@@ -30,7 +43,7 @@
% self.css_classes)
for rowidx in xrange(len(self.rset)):
self.wview(self.itemvid, self.rset, row=rowidx, col=0,
- vid=subvid, parentvid=self.id)
+ vid=subvid, parentvid=self.id, treeid=treeid)
self.w(u'</ul>')
@@ -41,8 +54,8 @@
css_classes = 'treeview widget filetree'
title = _('file tree view')
- def call(self, subvid=None):
- super(FileTreeView, self).call(subvid='filetree-oneline')
+ def call(self, subvid=None, treeid=None, initial_load=True):
+ super(FileTreeView, self).call(treeid=treeid, subvid='filetree-oneline', initial_load=initial_load)
@@ -64,10 +77,8 @@
class DefaultTreeViewItemView(EntityView):
- """default treeitem view for entities which don't implement ITree
- """
+ """default treeitem view for entities which don't implement ITree"""
id = 'treeitemview'
- accepts = ('Any',)
def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
entity = self.entity(row, col)
@@ -80,14 +91,11 @@
class TreeViewItemView(EntityView):
"""specific treeitem view for entities which implement ITree
-
- (each item should be exandable if it's not a tree leaf)
+
+ (each item should be expandable if it's not a tree leaf)
"""
id = 'treeitemview'
- # XXX append yes to make sure we get an higher score than
- # the default treeitem view
- __selectors__ = (implement_interface, yes)
- accepts_interfaces = (ITree,)
+ __select_ = implements(ITree)
def cell_call(self, row, col, vid='oneline', parentvid='treeview'):
entity = self.entity(row, col)
@@ -111,7 +119,6 @@
divclasses.append('lastExpandable-hitarea')
self.w(u'<li cubicweb:loadurl="%s" class="%s">' % (url, u' '.join(cssclasses)))
self.w(u'<div class="%s"> </div>' % u' '.join(divclasses))
-
# add empty <ul> because jquery's treeview plugin checks for
# sublists presence
self.w(u'<ul class="placeholder"><li>place holder</li></ul>')
--- a/web/views/urlpublishing.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/urlpublishing.py Mon Mar 02 21:03:54 2009 +0100
@@ -18,7 +18,7 @@
because of redirecting instead of direct traversal
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
@@ -28,7 +28,7 @@
from cubicweb import RegistryException, typed_eid
from cubicweb.web import NotFound, Redirect
-from cubicweb.web.component import SingletonComponent, Component
+from cubicweb.web.component import Component, Component
class PathDontMatch(Exception):
@@ -36,7 +36,7 @@
a path
"""
-class URLPublisherComponent(SingletonComponent):
+class URLPublisherComponent(Component):
"""associate url's path to view identifier / rql queries,
by applying a chain of urlpathevaluator components.
--- a/web/views/urlrewrite.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/urlrewrite.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,22 +1,20 @@
"""Rules based url rewriter component, to get configurable RESTful urls
:organization: Logilab
-:copyright: 2007-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
import re
-from cubicweb.vregistry import autoselectors
-
+from cubicweb.appobject import AppObject
from cubicweb.common.registerers import accepts_registerer
-from cubicweb.common.appobject import AppObject
def rgx(pattern, flags=0):
"""this is just a convenient shortcout to add the $ sign"""
return re.compile(pattern+'$', flags)
-class metarewriter(autoselectors):
+class metarewriter(type):
"""auto-extend rules dictionnary"""
def __new__(mcs, name, bases, classdict):
# collect baseclass' rules
@@ -59,7 +57,6 @@
__abstract__ = True
id = 'urlrewriting'
- accepts = ('Any',)
priority = 1
def rewrite(self, req, uri):
--- a/web/views/vcard.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/vcard.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,12 +1,13 @@
"""vcard import / export
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.view import EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
_ = unicode
@@ -18,8 +19,7 @@
title = _('vcard')
templatable = False
content_type = 'text/x-vcard'
- accepts = ('EUser',)
-
+ __select__ = implements('EUser')
def set_request_content_type(self):
"""overriden to set a .vcf filename"""
--- a/web/views/wdoc.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/wdoc.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""inline help system, using ReST file in products `wdoc` directory
:organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -15,8 +15,8 @@
from logilab.common.changelog import ChangeLog
from logilab.mtconverter import CHARSET_DECL_RGX
-from cubicweb.common.selectors import match_form_params
-from cubicweb.common.view import StartupView
+from cubicweb.selectors import match_form_params
+from cubicweb.view import StartupView
from cubicweb.common.uilib import rest_publish
from cubicweb.web import NotFound
@@ -85,8 +85,7 @@
# help views ##################################################################
class InlineHelpView(StartupView):
- __selectors__ = (match_form_params,)
- form_params = ('fid',)
+ __select__ = match_form_params('fid')
id = 'wdoc'
title = _('site documentation')
@@ -163,9 +162,8 @@
class InlineHelpImageView(StartupView):
- __selectors__ = (match_form_params,)
- form_params = ('fid',)
id = 'wdocimages'
+ __select__ = match_form_params('fid')
binary = True
templatable = False
content_type = 'image/png'
--- a/web/views/wfentities.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/wfentities.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,16 +1,31 @@
"""html view for workflow related entities
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.view import EntityView
+from logilab.mtconverter import html_escape
+
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
class CellView(EntityView):
id = 'cell'
- accepts = ('TrInfo',)
+ __select__ = implements('TrInfo')
+
def cell_call(self, row, col, cellvid=None):
entity = self.entity(row, col)
self.w(entity.printable_value('comment'))
+
+
+class StateInContextView(EntityView):
+ """convenience trick, State's incontext view should not be clickable"""
+ id = 'incontext'
+ __select__ = implements('State')
+
+ def cell_call(self, row, col):
+ self.w(html_escape(self.view('textincontext', self.rset,
+ row=row, col=col)))
+
--- a/web/views/xbel.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/views/xbel.py Mon Mar 02 21:03:54 2009 +0100
@@ -1,7 +1,7 @@
"""xbel views
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -9,7 +9,9 @@
from logilab.mtconverter import html_escape
-from cubicweb.web.views.baseviews import XmlView, EntityView
+from cubicweb.selectors import implements
+from cubicweb.view import EntityView
+from cubicweb.web.views.xmlrss import XmlView
class XbelView(XmlView):
@@ -45,9 +47,10 @@
def url(self, entity):
return entity.absolute_url()
+
class XbelItemBookmarkView(XbelItemView):
- accepts = ('Bookmark',)
+ __select__ = implements('Bookmark')
def url(self, entity):
return entity.actual_url()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/xmlrss.py Mon Mar 02 21:03:54 2009 +0100
@@ -0,0 +1,162 @@
+"""base xml and rss views
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from time import timezone
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.view import EntityView, AnyRsetView
+from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
+from cubicweb.common.uilib import simple_sgml_tag
+
+
+class XmlView(EntityView):
+ """xml view for entities"""
+ id = 'xml'
+ title = _('xml')
+ templatable = False
+ content_type = 'text/xml'
+ xml_root = 'rset'
+ item_vid = 'xmlitem'
+
+ def cell_call(self, row, col):
+ self.wview(self.item_vid, self.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.cell_call(i, 0)
+ self.w(u'</%s>\n' % self.xml_root)
+
+
+class XmlItemView(EntityView):
+ id = 'xmlitem'
+
+ def cell_call(self, row, col):
+ """ element as an item for an xml feed """
+ entity = self.complete_entity(row, col)
+ self.w(u'<%s>\n' % (entity.e_schema))
+ for rschema, attrschema in entity.e_schema.attribute_definitions():
+ attr = rschema.type
+ try:
+ value = entity[attr]
+ except KeyError:
+ # Bytes
+ continue
+ if value is not None:
+ if attrschema == 'Bytes':
+ from base64 import b64encode
+ value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
+ elif isinstance(value, basestring):
+ value = xml_escape(value)
+ self.w(u' <%s>%s</%s>\n' % (attr, value, attr))
+ self.w(u'</%s>\n' % (entity.e_schema))
+
+
+
+class XmlRsetView(AnyRsetView):
+ """dumps raw rset as xml"""
+ id = 'rsetxml'
+ title = _('xml export')
+ templatable = False
+ content_type = 'text/xml'
+ xml_root = 'rset'
+
+ def call(self):
+ w = self.w
+ rset, descr = self.rset, self.rset.description
+ eschema = self.schema.eschema
+ labels = self.columns_labels(False)
+ w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+ w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
+ for rowindex, row in enumerate(self.rset):
+ w(u' <row>\n')
+ for colindex, val in enumerate(row):
+ etype = descr[rowindex][colindex]
+ tag = labels[colindex]
+ attrs = {}
+ if '(' in tag:
+ attrs['expr'] = tag
+ tag = 'funccall'
+ if val is not None and not eschema(etype).is_final():
+ attrs['eid'] = val
+ # csvrow.append(val) # val is eid in that case
+ val = self.view('textincontext', rset,
+ row=rowindex, col=colindex)
+ else:
+ val = self.view('final', rset, displaytime=True,
+ 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)
+
+
+class RssView(XmlView):
+ id = 'rss'
+ title = _('rss')
+ templatable = False
+ content_type = 'text/xml'
+ http_cache_manager = MaxAgeHTTPCacheManager
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+ def cell_call(self, row, col):
+ self.wview('rssitem', self.rset, row=row, col=col)
+
+ def call(self):
+ """display a list of entities by calling their <item_vid> view"""
+ req = self.req
+ self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
+ self.w(u'''<rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns="http://purl.org/rss/1.0/"
+>''')
+ self.w(u' <channel rdf:about="%s">\n' % xml_escape(req.url()))
+ self.w(u' <title>%s RSS Feed</title>\n' % xml_escape(self.page_title()))
+ self.w(u' <description>%s</description>\n' % 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' <items>\n')
+ self.w(u' <rdf:Seq>\n')
+ for entity in self.rset.entities():
+ self.w(u' <rdf:li resource="%s" />\n' % xml_escape(entity.absolute_url()))
+ self.w(u' </rdf:Seq>\n')
+ self.w(u' </items>\n')
+ self.w(u' </channel>\n')
+ for i in xrange(self.rset.rowcount):
+ self.cell_call(i, 0)
+ self.w(u'</rdf:RDF>')
+
+
+class RssItemView(EntityView):
+ id = 'rssitem'
+ date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
+
+ def cell_call(self, row, col):
+ entity = self.complete_entity(row, col)
+ self.w(u'<item rdf:about="%s">\n' % xml_escape(entity.absolute_url()))
+ self._marker('title', entity.dc_long_title())
+ self._marker('link', entity.absolute_url())
+ self._marker('description', entity.dc_description())
+ self._marker('dc:date', entity.dc_date(self.date_format))
+ if entity.creator:
+ self.w(u'<author>')
+ self._marker('name', entity.creator.name())
+ email = entity.creator.get_email()
+ if email:
+ self._marker('email', email)
+ self.w(u'</author>')
+ self.w(u'</item>\n')
+
+ def _marker(self, marker, value):
+ if value:
+ self.w(u' <%s>%s</%s>\n' % (marker, xml_escape(value), marker))
--- a/web/webconfig.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/webconfig.py Mon Mar 02 21:03:54 2009 +0100
@@ -26,7 +26,7 @@
'sitewide': True, 'group': 'ui',
}),
('main-template',
- {'type' : 'string', 'default': 'main',
+ {'type' : 'string', 'default': 'main-template',
'help': _('id of main template used to render pages'),
'sitewide': True, 'group': 'ui',
}),
@@ -199,6 +199,15 @@
return 'http://intranet.logilab.fr/jpl/view?__linkto=concerns:%s:subject&etype=Ticket&type=bug&vid=creation' % cubeeid
return None
+ def fckeditor_installed(self):
+ return exists(self.ext_resources['FCKEDITOR_PATH'])
+
+ def eproperty_definitions(self):
+ for key, pdef in super(WebConfiguration, self).eproperty_definitions():
+ if key == 'ui.fckeditor' and not self.fckeditor_installed():
+ continue
+ yield key, pdef
+
# method used to connect to the repository: 'inmemory' / 'pyro'
# Pyro repository by default
repo_method = 'pyro'
--- a/web/widgets.py Fri Feb 27 09:59:53 2009 +0100
+++ b/web/widgets.py Mon Mar 02 21:03:54 2009 +0100
@@ -4,7 +4,7 @@
serialization time
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
@@ -391,11 +391,7 @@
if not entity.has_eid():
return u''
return entity.printable_value(self.name)
-
- def add_fckeditor_info(self, req):
- req.add_js('fckeditor.js')
- req.fckeditor_config()
-
+
def _edit_render(self, entity, with_format=True):
req = entity.req
editor = self._edit_render_textarea(entity, with_format)
@@ -411,7 +407,7 @@
if isinstance(dvalue, basestring):
dvalue = html_escape(dvalue)
if entity.use_fckeditor(self.name):
- self.add_fckeditor_info(entity.req)
+ entity.req.fckeditor_config()
if with_format:
if entity.has_eid():
format = entity.format(self.name)
@@ -663,6 +659,7 @@
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
@@ -672,7 +669,6 @@
def render_example(self, req):
return '23'
-
class FloatWidget(StringWidget):
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
@@ -696,6 +692,7 @@
return [formatstr % value]
return ()
+
class DecimalWidget(StringWidget):
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
kwattrs['size'] = 5
@@ -704,17 +701,25 @@
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")
+ 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)
@@ -732,16 +737,6 @@
formatstr = req.property_value(self.format_key)
return now().strftime(formatstr)
- @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 _edit_render(self, entity):
wdg = super(DateWidget, self)._edit_render(entity)
@@ -780,6 +775,11 @@
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')
@@ -790,14 +790,6 @@
}
-
-
- def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):
- kwattrs['size'] = 16
- kwattrs['maxlength'] = 16
- DateWidget.__init__(self, vreg, subjschema, rschema, objschema, **kwattrs)
-
-
class TimeWidget(StringWidget):
format_key = 'ui.time-format'
def __init__(self, vreg, subjschema, rschema, objschema, **kwattrs):