"""
- def depart_document(self, node):
- """syt: i don't want the enclosing
"""
-
- 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 = '; backlink'
- else:
- i = 1
- backlinks = []
- for backref in backrefs:
- backlinks.append(str(i))
- i += 1
- backref_text = ('; backlinks: %s'
- % ', '.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\n' % (
- a_start, node['type'], node['level'], a_end,
- self.encode(node['source']), line, backref_text)
- self.body.append(u'
ReST / HTML errors:%s
' % html_escape(error))
-
- def depart_system_message(self, node):
- pass
diff -r 292b7989b166 -r ddf4f2d8d51c common/i18n.py
--- a/common/i18n.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/i18n.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,14 +1,15 @@
"""Some i18n/gettext utilities.
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import re
import os
-from os.path import join, abspath, basename, splitext, exists
+from os.path import join, basename, splitext, exists
from glob import glob
from cubicweb.toolsutils import create_dir
@@ -90,4 +91,3 @@
except Exception:
continue
return errors
-
diff -r 292b7989b166 -r ddf4f2d8d51c common/mail.py
--- a/common/mail.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/mail.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""Common utilies to format / semd emails.
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
diff -r 292b7989b166 -r ddf4f2d8d51c common/migration.py
--- a/common/migration.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/migration.py Wed Jun 03 19:50:34 2009 +0200
@@ -2,8 +2,9 @@
version
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -125,10 +126,10 @@
def repo_connect(self):
return self.config.repository()
-
+
def migrate(self, vcconf, toupgrade, options):
"""upgrade the given set of cubes
-
+
`cubes` is an ordered list of 3-uple:
(cube, fromversion, toversion)
"""
@@ -149,23 +150,23 @@
'versions_map': vmap})
self.scripts_session(scripts)
else:
- print 'no migration script to execute'
+ print 'no migration script to execute'
def shutdown(self):
pass
-
+
def __getattribute__(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError:
cmd = 'cmd_%s' % name
if hasattr(self, cmd):
- meth = getattr(self, cmd)
+ meth = getattr(self, cmd)
return lambda *args, **kwargs: self.interact(args, kwargs,
meth=meth)
raise
raise AttributeError(name)
-
+
def interact(self, args, kwargs, meth):
"""execute the given method according to user's confirmation"""
msg = 'execute command: %s(%s) ?' % (
@@ -224,7 +225,7 @@
except ImportError:
# readline not available
pass
- else:
+ else:
readline.set_completer(Completer(local_ctx).complete)
readline.parse_and_bind('tab: complete')
histfile = os.path.join(os.environ["HOME"], ".eshellhist")
@@ -256,7 +257,7 @@
else:
context[attr[4:]] = getattr(self, attr)
return context
-
+
def process_script(self, migrscript, funcname=None, *args, **kwargs):
"""execute a migration script
in interactive mode, display the migration script path, ask for
@@ -280,7 +281,7 @@
self.critical('no %s in script %s', funcname, migrscript)
return None
return func(*args, **kwargs)
-
+
def scripts_session(self, migrscripts):
"""execute some scripts in a transaction"""
try:
@@ -311,7 +312,7 @@
def cmd_option_type_changed(self, optname, oldtype, newvalue):
"""a configuration option's type has changed"""
self._option_changes.append(('typechanged', optname, oldtype, newvalue))
-
+
def cmd_add_cubes(self, cubes):
"""modify the list of used cubes in the in-memory config
returns newly inserted cubes, including dependencies
@@ -319,7 +320,7 @@
if isinstance(cubes, basestring):
cubes = (cubes,)
origcubes = self.config.cubes()
- newcubes = [p for p in self.config.expand_cubes(cubes)
+ newcubes = [p for p in self.config.expand_cubes(cubes)
if not p in origcubes]
if newcubes:
for cube in cubes:
@@ -340,7 +341,7 @@
assert cube in removed, \
"can't remove cube %s, used as a dependancy" % cube
return removed
-
+
def rewrite_configuration(self):
# import locally, show_diffs unavailable in gae environment
from cubicweb.toolsutils import show_diffs
diff -r 292b7989b166 -r ddf4f2d8d51c common/mixins.py
--- a/common/mixins.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/mixins.py Wed Jun 03 19:50:34 2009 +0200
@@ -2,14 +2,17 @@
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+from logilab.common.deprecation import obsolete
from logilab.common.decorators import cached
-from cubicweb.common.selectors import implement_interface
+from cubicweb import typed_eid
+from cubicweb.selectors import implements
from cubicweb.interfaces import IWorkflowable, IEmailable, ITree
@@ -24,10 +27,10 @@
# XXX misnamed
parent_target = 'subject'
children_target = 'object'
-
+
def different_type_children(self, entities=True):
"""return children entities of different type as this entity.
-
+
according to the `entities` parameter, return entity objects or the
equivalent result set
"""
@@ -39,7 +42,7 @@
def same_type_children(self, entities=True):
"""return children entities of the same type as this entity.
-
+
according to the `entities` parameter, return entity objects or the
equivalent result set
"""
@@ -48,7 +51,7 @@
if entities:
return [e for e in res if e.e_schema == self.e_schema]
return res.filtered_rset(lambda x: x.e_schema == self.e_schema, self.col)
-
+
def iterchildren(self, _done=None):
if _done is None:
_done = set()
@@ -72,7 +75,7 @@
yield entity
except AttributeError:
pass
-
+
@cached
def path(self):
"""returns the list of eids from the root object to this object"""
@@ -94,7 +97,7 @@
path.reverse()
return path
-
+
def iterparents(self):
def _uptoroot(self):
curr = self
@@ -108,7 +111,7 @@
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
"""
@@ -140,7 +143,7 @@
def children_rql(self):
return self.related_rql(self.tree_attribute, self.children_target)
-
+
def __iter__(self):
return self.iterchildren()
@@ -161,7 +164,7 @@
relation (which implies supporting 'wf_info_for' as well)
"""
__implements__ = (IWorkflowable,)
-
+
@property
def state(self):
try:
@@ -169,36 +172,37 @@
except IndexError:
self.warning('entity %s has no state', self)
return None
-
+
@property
def displayable_state(self):
return self.req._(self.state)
def wf_state(self, statename):
- rset = self.req.execute('Any S, SN WHERE S name %(n)s, S state_of E, E name %(e)s',
+ rset = self.req.execute('Any S, SN WHERE S name SN, S name %(n)s, S state_of E, E name %(e)s',
{'n': statename, 'e': str(self.e_schema)})
if rset:
return rset.get_entity(0, 0)
return None
-
+
def wf_transition(self, trname):
- rset = self.req.execute('Any T, TN WHERE T name %(n)s, T transition_of E, E name %(e)s',
+ rset = self.req.execute('Any T, TN WHERE T name TN, T name %(n)s, T transition_of E, E name %(e)s',
{'n': trname, 'e': str(self.e_schema)})
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
"""
+ assert not isinstance(stateeid, basestring), 'change_state wants a state eid'
if trcomment:
self.req.set_shared_data('trcomment', trcomment)
if trcommentformat:
self.req.set_shared_data('trcommentformat', trcommentformat)
self.req.execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
{'x': self.eid, 's': stateeid}, 'x')
-
+
def can_pass_transition(self, trname):
"""return the Transition instance if the current user can pass the
transition with the given name, else None
@@ -212,42 +216,30 @@
for tr in rset.entities():
if tr.may_be_passed(self.eid, stateeid):
return tr
-
+
def latest_trinfo(self):
"""return the latest transition information for this entity"""
return self.reverse_wf_info_for[-1]
-
- # specific vocabulary methods #############################################
- def subject_in_state_vocabulary(self, rschema, limit=None):
- """vocabulary method for the in_state relation, looking for
- relation's object entities (i.e. self is the subject) according
- to initial_state, state_of and next_state relation
- """
- if not self.has_eid() or not self.in_state:
- # get the initial state
- rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
- rset = self.req.execute(rql, {'etype': str(self.e_schema)})
- if rset:
- return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])]
- return []
- results = []
- for tr in self.in_state[0].transitions(self):
- state = tr.destination_state[0]
- results.append((state.view('combobox'), state.eid))
- return sorted(results)
-
# __method methods ########################################################
-
+
def set_state(self, params=None):
"""change the entity's state according to a state defined in given
parameters, used to be called using __method controler facility
"""
params = params or self.req.form
- self.change_state(int(params.pop('state')), params.get('trcomment'),
+ self.change_state(typed_eid(params.pop('state')),
+ params.get('trcomment'),
params.get('trcommentformat'))
self.req.set_message(self.req._('__msg state changed'))
+ # specific vocabulary methods #############################################
+
+ @obsolete('use EntityFieldsForm.subject_in_state_vocabulary')
+ def subject_in_state_vocabulary(self, rschema, limit=None):
+ form = self.vreg.select_object('forms', 'edition', self.req, entity=self)
+ return form.subject_in_state_vocabulary(rschema, limit)
+
class EmailableMixIn(object):
@@ -258,7 +250,7 @@
primary_email / use_email scheme
"""
__implements__ = (IEmailable,)
-
+
def get_email(self):
if getattr(self, 'primary_email', None):
return self.primary_email[0].address
@@ -280,14 +272,14 @@
def as_email_context(self):
"""returns the dictionary as used by the sendmail controller to
build email bodies.
-
+
NOTE: the dictionary keys should match the list returned by the
`allowed_massmail_keys` method.
"""
return dict( (attr, getattr(self, attr)) for attr in self.allowed_massmail_keys() )
-
+
MI_REL_TRIGGERS = {
('in_state', 'subject'): WorkflowableMixIn,
('primary_email', 'subject'): EmailableMixIn,
@@ -315,14 +307,13 @@
"""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:
done = set()
super(TreeViewMixIn, self).call(done=done, **kwargs)
-
+
def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
done, entity = _done_init(done, self, row, col)
if done is None:
@@ -351,7 +342,7 @@
self.w(u'
')
-
+
def cell_call(self, row, col=0, vid=None, done=None, **kwargs):
done, entity = _done_init(done, self, row, col)
if done is None:
@@ -369,22 +360,18 @@
"""provide default implementations for IProgress interface methods"""
@property
- @cached
def cost(self):
return self.progress_info()['estimated']
@property
- @cached
def revised_cost(self):
return self.progress_info().get('estimatedcorrected', self.cost)
@property
- @cached
def done(self):
return self.progress_info()['done']
@property
- @cached
def todo(self):
return self.progress_info()['todo']
@@ -397,12 +384,12 @@
def in_progress(self):
raise NotImplementedError()
-
+
def progress(self):
try:
return 100. * self.done / self.revised_cost
except ZeroDivisionError:
# total cost is 0 : if everything was estimated, task is completed
- if self.progress_info().get('notestmiated'):
+ if self.progress_info().get('notestimated'):
return 0.
return 100
diff -r 292b7989b166 -r ddf4f2d8d51c common/mttransforms.py
--- a/common/mttransforms.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/mttransforms.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""mime type transformation engine for cubicweb, based on mtconverter
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -11,10 +12,10 @@
from logilab.mtconverter.engine import TransformEngine
from logilab.mtconverter.transform import Transform
from logilab.mtconverter import (register_base_transforms,
- register_pil_transforms,
+ register_pil_transforms,
register_pygments_transforms)
-from cubicweb.common.uilib import rest_publish, html_publish, remove_html_tags
+from cubicweb.common.uilib import rest_publish, html_publish
HTML_MIMETYPES = ('text/html', 'text/xhtml', 'application/xhtml+xml')
@@ -32,15 +33,6 @@
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,13 +41,32 @@
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
else:
HAS_PIL_TRANSFORMS = False
-
+
try:
from logilab.mtconverter.transforms import pygmentstransforms
for mt in ('text/plain',) + HTML_MIMETYPES:
@@ -74,9 +85,9 @@
return origconvert(self, trdata)
cls._convert = _convert
patch_convert(pygmentstransforms.PygmentsHTMLTransform)
-
+
HAS_PYGMENTS_TRANSFORMS = True
except ImportError:
HAS_PYGMENTS_TRANSFORMS = False
-
+
register_base_transforms(ENGINE, verb=False)
diff -r 292b7989b166 -r ddf4f2d8d51c common/registerers.py
--- a/common/registerers.py Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-"""This file contains some basic registerers required by application objects
-registry to handle registration at startup time.
-
-A registerer is responsible to tell if an object should be registered according
-to the application's schema or to already registered object
-
-:organization: Logilab
-:copyright: 2006-2008 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
-
-class priority_registerer(registerer):
- """systematically kick previous registered class and register the
- wrapped class (based on the fact that directory containing vobjects
- are loaded from the most generic to the most specific).
-
- This is usually for templates or startup views where we want to
- keep only the latest in the load path
- """
- def do_it_yourself(self, registered):
- if registered:
- if len(registered) > 1:
- self.warning('priority_registerer found more than one registered objects '
- '(registerer monkey patch ?)')
- for regobj in registered[:]:
- self.kick(registered, regobj)
- return self.vobject
-
- def remove_equivalents(self, registered):
- for _obj in registered[:]:
- if self.equivalent(_obj):
- self.kick(registered, _obj)
- break
-
- def remove_all_equivalents(self, registered):
- for _obj in registered[:]:
- if _obj is self.vobject:
- continue
- if self.equivalent(_obj):
- self.kick(registered, _obj)
-
- 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):
- """register according to the .accepts attribute of the wrapped
- class, which should be a tuple refering some entity's types
-
- * if no type is defined the application'schema, skip the wrapped
- class
- * if the class defines a requires attribute, each entity type defined
- in the requires list must be in the schema
- * if an object previously registered has equivalent .accepts
- attribute, kick it out
- * register
- """
- 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):
- 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):
- return False
- try:
- newaccepts = list(other.accepts)
- for etype in self.vobject.accepts:
- try:
- newaccepts.remove(etype)
- except ValueError:
- continue
- if newaccepts:
- other.accepts = tuple(newaccepts)
- return False
- return True
- except AttributeError:
- 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]
diff -r 292b7989b166 -r ddf4f2d8d51c common/rest.py
--- a/common/rest.py Wed Jun 03 19:49:44 2009 +0200
+++ /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)
diff -r 292b7989b166 -r ddf4f2d8d51c common/schema.py
--- a/common/schema.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/schema.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,3 +1,11 @@
+"""pre 3.0 bw compat
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+# pylint: disable-msg=W0614,W0401
from warnings import warn
warn('moved to cubicweb.schema', DeprecationWarning, stacklevel=2)
from cubicweb.schema import *
diff -r 292b7989b166 -r ddf4f2d8d51c common/selectors.py
--- a/common/selectors.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/selectors.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,571 +1,12 @@
-"""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 = ()
-
-
+"""pre 3.2 bw compat
:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-
-__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)
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.selectors', DeprecationWarning, stacklevel=2)
+from cubicweb.selectors import *
+from cubicweb.selectors import _rql_condition
diff -r 292b7989b166 -r ddf4f2d8d51c common/tags.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/common/tags.py Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,46 @@
+"""helper classes to generate simple (X)HTML tags
+
+:organization: Logilab
+:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import simple_sgml_tag
+
+class tag(object):
+ def __init__(self, name, escapecontent=True):
+ self.name = name
+ self.escapecontent = escapecontent
+
+ def __call__(self, __content=None, **attrs):
+ attrs.setdefault('escapecontent', self.escapecontent)
+ return simple_sgml_tag(self.name, __content, **attrs)
+
+input = tag('input')
+textarea = tag('textarea')
+a = tag('a')
+span = tag('span')
+div = tag('div', False)
+img = tag('img')
+label = tag('label')
+option = tag('option')
+h1 = tag('h1')
+h2 = tag('h2')
+h3 = tag('h3')
+h4 = tag('h4')
+h5 = tag('h5')
+
+def select(name, id=None, multiple=False, options=[], **attrs):
+ if multiple:
+ attrs['multiple'] = 'multiple'
+ if id:
+ attrs['id'] = id
+ attrs['name'] = name
+ html = [u'')
+ return u'\n'.join(html)
+
diff -r 292b7989b166 -r ddf4f2d8d51c common/tal.py
--- a/common/tal.py Wed Jun 03 19:49:44 2009 +0200
+++ /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'(? 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'),
- '
')
- 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'
...')
@@ -75,18 +81,6 @@
got = uilib.text_cut(text, 30)
self.assertEquals(got, expected)
- def test_ajax_replace_url(self):
- # NOTE: for the simplest use cases, we could use doctest
- arurl = uilib.ajax_replace_url
- self.assertEquals(arurl('foo', 'Person P'),
- "javascript: replacePageChunk('foo', 'Person%20P');")
- self.assertEquals(arurl('foo', 'Person P', 'oneline'),
- "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');")
- self.assertEquals(arurl('foo', 'Person P', 'oneline', name='bar', age=12),
- 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'oneline\', {"age": 12, "name": "bar"});')
- self.assertEquals(arurl('foo', 'Person P', name='bar', age=12),
- 'javascript: replacePageChunk(\'foo\', \'Person%20P\', \'null\', {"age": 12, "name": "bar"});')
-
tree = ('root', (
('child_1_1', (
('child_2_1', ()), ('child_2_2', (
@@ -116,18 +110,18 @@
for child in tuple[1]:
n.append(make_tree(child))
return n
-
+
class UIlibHTMLGenerationTC(TestCase):
""" a basic tree node, caracterised by an id"""
def setUp(self):
- """ called before each test from this class """
+ """ called before each test from this class """
self.o = make_tree(tree)
def test_generated_html(self):
s = uilib.render_HTML_tree(self.o, selected_node="child_2_2")
self.assertTextEqual(s, generated_html)
-
-
+
+
if __name__ == '__main__':
unittest_main()
diff -r 292b7989b166 -r ddf4f2d8d51c common/test/unittest_utils.py
--- a/common/test/unittest_utils.py Wed Jun 03 19:49:44 2009 +0200
+++ /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()
diff -r 292b7989b166 -r ddf4f2d8d51c common/uilib.py
--- a/common/uilib.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/uilib.py Wed Jun 03 19:50:34 2009 +0200
@@ -4,36 +4,20 @@
contains some functions designed to help implementation of cubicweb user interface
:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import csv
-import decimal
-import locale
import re
from urllib import quote as urlquote
-from cStringIO import StringIO
-from copy import deepcopy
+from StringIO import StringIO
-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
@@ -56,7 +40,7 @@
# don't translate empty value if you don't want strange results
if props is not None and value and props.get('internationalizable'):
return req._(value)
-
+
return value
if attrtype == 'Date':
return ustrftime(value, req.property_value('ui.date-format'))
@@ -78,12 +62,12 @@
# text publishing #############################################################
try:
- from cubicweb.common.rest import rest_publish # pylint: disable-msg=W0611
+ from cubicweb.ext.rest import rest_publish # pylint: disable-msg=W0611
except ImportError:
def rest_publish(entity, data):
"""default behaviour if docutils was not found"""
- return data
-
+ return html_escape(data)
+
TAG_PROG = re.compile(r'?.*?>', re.U)
def remove_html_tags(text):
"""Removes HTML tags from text
@@ -180,21 +164,27 @@
if add_ellipsis:
return text + u'...'
return text
-
-def text_cut(text, nbwords=30):
+
+def text_cut(text, nbwords=30, gotoperiod=True):
"""from the given plain text, return a text with at least words,
trying to go to the end of the current sentence.
+ :param nbwords: the minimum number of words required
+ :param gotoperiod: specifies if the function should try to go to
+ the first period after the cut (i.e. finish
+ the sentence if possible)
+
Note that spaces are normalized.
"""
if text is None:
return u''
words = text.split()
- text = ' '.join(words) # normalize spaces
- minlength = len(' '.join(words[:nbwords]))
- textlength = text.find('.', minlength) + 1
- if textlength == 0: # no point found
- textlength = minlength
+ text = u' '.join(words) # normalize spaces
+ textlength = minlength = len(' '.join(words[:nbwords]))
+ if gotoperiod:
+ textlength = text.find('.', minlength) + 1
+ if textlength == 0: # no period found
+ textlength = minlength
return text[:textlength]
def cut(text, length):
@@ -210,20 +200,27 @@
return text[:length] + u'...'
-
+
# HTML generation helper functions ############################################
-def simple_sgml_tag(tag, content=None, **attrs):
+def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
"""generation of a simple sgml tag (eg without children tags) easier
content and attributes will be escaped
"""
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 sorted(attrs.items())
+ if value is not None)
if content:
- value += u'>%s%s>' % (html_escape(unicode(content)), tag)
+ if escapecontent:
+ content = html_escape(unicode(content))
+ value += u'>%s%s>' % (content, tag)
else:
value += u'/>'
return value
@@ -241,30 +238,6 @@
"""builds a HTML link that uses the js toggleVisibility function"""
return u'%s' % (toggle_action(nodeid), label)
-def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
- """builds a replacePageChunk-like url
- >>> ajax_replace_url('foo', 'Person P')
- "javascript: replacePageChunk('foo', 'Person%20P');"
- >>> ajax_replace_url('foo', 'Person P', 'oneline')
- "javascript: replacePageChunk('foo', 'Person%20P', 'oneline');"
- >>> ajax_replace_url('foo', 'Person P', 'oneline', name='bar', age=12)
- "javascript: replacePageChunk('foo', 'Person%20P', 'oneline', {'age':12, 'name':'bar'});"
- >>> ajax_replace_url('foo', 'Person P', name='bar', age=12)
- "javascript: replacePageChunk('foo', 'Person%20P', 'null', {'age':12, 'name':'bar'});"
- """
- params = [repr(nodeid), repr(urlquote(rql))]
- if extraparams and not vid:
- params.append("'null'")
- elif vid:
- params.append(repr(vid))
- if extraparams:
- params.append(simplejson.dumps(extraparams))
- if swap:
- params.append('true')
- return "javascript: replacePageChunk(%s);" % ', '.join(params)
-
-
-from StringIO import StringIO
def ureport_as_html(layout):
from logilab.common.ureports import HTMLWriter
@@ -318,7 +291,7 @@
else:
for child in path[-1].children:
build_matrix(path[:] + [child], matrix)
-
+
matrix = []
build_matrix([tree], matrix)
@@ -347,12 +320,12 @@
cell_12 = line[j+1] is not None
cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None
link_type = link_types.get((cell_11, cell_12, cell_21), 0)
- if link_type == 0 and i > 0 and links[i-1][j] in (1,2,3):
+ if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3):
link_type = 2
links[-1].append(link_type)
-
+
- # We can now generate the HTML code for the
+ # We can now generate the HTML code for the
s = u'
\n'
if caption:
s += '
%s
\n' % caption
@@ -372,7 +345,7 @@
s += '
'
s += '
' % link_cell
s += '
' % link_cell
-
+
cell = line[-1]
if cell:
if cell.id == selected_node:
@@ -462,7 +435,7 @@
(boxid, ''.join(html_info)))
tcbk = tcbk.tb_next
except Exception:
- pass # doesn't really matter if we have no context info
+ pass # doesn't really matter if we have no context info
strings.append(u'')
return '\n'.join(strings)
@@ -470,7 +443,7 @@
class UnicodeCSVWriter:
"""proxies calls to csv.writer.writerow to be able to deal with unicode"""
-
+
def __init__(self, wfunc, encoding, **kwargs):
self.writer = csv.writer(self, **kwargs)
self.wfunc = wfunc
@@ -508,23 +481,6 @@
return newfunc
-def jsonize(function):
- import simplejson
- def newfunc(*args, **kwargs):
- ret = function(*args, **kwargs)
- if isinstance(ret, decimal.Decimal):
- ret = float(ret)
- elif isinstance(ret, DateTimeType):
- ret = ret.strftime('%Y-%m-%d %H:%M')
- elif isinstance(ret, DateTimeDeltaType):
- ret = ret.seconds
- try:
- return simplejson.dumps(ret)
- except TypeError:
- return simplejson.dumps(repr(ret))
- return newfunc
-
-
def htmlescape(function):
def newfunc(*args, **kwargs):
ret = function(*args, **kwargs)
diff -r 292b7989b166 -r ddf4f2d8d51c common/utils.py
--- a/common/utils.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/utils.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,263 +1,11 @@
-"""Some utilities for CubicWeb server/clients.
+"""pre 3.2 bw compat
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-__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'\n')
- # 2/ css files
- for cssfile, media in self.cssfiles:
- w(u'\n' %
- (media, cssfile))
- # 3/ ie css if necessary
- if self.ie_cssfiles:
- w(u' \n')
- # 4/ js files
- for jsfile in self.jsfiles:
- w(u'\n' % jsfile)
- # 5/ post inlined scripts (i.e. scripts depending on other JS files)
- if self.post_inlined_scripts:
- w(u'\n')
- return u'\n%s\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
- self.head = req.html_headers
- # main stream
- self.body = UStringIO()
- self.doctype = u''
- # xmldecl and html opening tag
- self.xmldecl = u'\n' % req.encoding
- self.htmltag = u'' % (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 tag and writes HTML body"""
- return u'%s\n%s\n%s\n%s\n%s\n' % (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)
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.utils', DeprecationWarning, stacklevel=2)
+from cubicweb.utils import *
diff -r 292b7989b166 -r ddf4f2d8d51c common/view.py
--- a/common/view.py Wed Jun 03 19:49:44 2009 +0200
+++ b/common/view.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,480 +1,11 @@
-"""abstract views and templates classes for CubicWeb web client
-
+"""pre 3.2 bw compat
:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-__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''
-NOFOLLOW = u''
-
-CW_XHTML_EXTENSIONS = '''[
-
-
- ] '''
-
-TRANSITIONAL_DOCTYPE = u'\n'
-
-STRICT_DOCTYPE = u'\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
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'
")
-
- 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'%s' % (html_escape(action.url()), label)
- return u''
-
- def html_headers(self):
- """return a list of html headers (eg something to be inserted between
- and 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'\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,)
- 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'
')
- if show_label:
- if tr:
- label = display_name(self.req, label)
- w(u'%s' % label)
- w(u'
%s
' % value)
- if row:
- w(u'
')
-
-
-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
- and 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
+# pylint: disable-msg=W0614,W0401
+from warnings import warn
+warn('moved to cubicweb.view', DeprecationWarning, stacklevel=2)
+from cubicweb.view import *
diff -r 292b7989b166 -r ddf4f2d8d51c cwconfig.py
--- a/cwconfig.py Wed Jun 03 19:49:44 2009 +0200
+++ b/cwconfig.py Wed Jun 03 19:50:34 2009 +0200
@@ -2,15 +2,22 @@
"""common configuration utilities for cubicweb
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+
+.. envvar:: CW_CUBES_PATH
+
+ Augments the default search path for cubes
+
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
import sys
import os
import logging
-from os.path import exists, join, expanduser, abspath, basename
+from os.path import exists, join, expanduser, abspath, normpath, basename, isdir
from logilab.common.decorators import cached
from logilab.common.logging_ext import set_log_methods, init_log
@@ -18,11 +25,10 @@
ConfigurationMixIn, merge_options)
from cubicweb import CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, ConfigurationError
-from cubicweb.toolsutils import env_path, read_config, create_dir
+from cubicweb.toolsutils import env_path, create_dir
CONFIGURATIONS = []
-_ = unicode
class metaconfiguration(type):
"""metaclass to automaticaly register configuration"""
@@ -56,17 +62,6 @@
% (directory, modes))
return modes[0]
-# 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',
- 'facets'):
- VREGOPTIONS.append(('disable-%s'%registry,
- {'type' : 'csv', 'default': (),
- 'help': 'list of identifier of application objects from the %s registry to disable'%registry,
- 'group': 'appobjects', 'inputlevel': 2,
- }))
-VREGOPTIONS = tuple(VREGOPTIONS)
# persistent options definition
PERSISTENT_OPTIONS = (
@@ -75,44 +70,44 @@
'default': 'UTF-8',
'help': _('user interface encoding'),
'group': 'ui', 'sitewide': True,
- }),
+ }),
('language',
{'type' : 'string',
'default': 'en',
'vocabulary': Method('available_languages'),
'help': _('language of the user interface'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('date-format',
{'type' : 'string',
'default': '%Y/%m/%d',
'help': _('how to format date in the ui ("man strftime" for format description)'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('datetime-format',
{'type' : 'string',
'default': '%Y/%m/%d %H:%M',
'help': _('how to format date and time in the ui ("man strftime" for format description)'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('time-format',
{'type' : 'string',
'default': '%H:%M',
'help': _('how to format time in the ui ("man strftime" for format description)'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('float-format',
{'type' : 'string',
'default': '%.3f',
'help': _('how to format float numbers in the ui'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('default-text-format',
{'type' : 'choice',
'choices': ('text/plain', 'text/rest', 'text/html'),
'default': 'text/html', # use fckeditor in the web ui
'help': _('default text format for rich text fields.'),
- 'group': 'ui',
+ 'group': 'ui',
}),
('short-line-size',
{'type' : 'int',
@@ -125,7 +120,7 @@
def register_persistent_options(options):
global PERSISTENT_OPTIONS
PERSISTENT_OPTIONS = merge_options(PERSISTENT_OPTIONS + options)
-
+
CFGTYPE2ETYPE_MAP = {
'string': 'String',
'choice': 'String',
@@ -133,7 +128,7 @@
'int': 'Int',
'float' : 'Float',
}
-
+
class CubicWebNoAppConfiguration(ConfigurationMixIn):
"""base class for cubicweb configuration without a specific instance directory
"""
@@ -152,12 +147,12 @@
file(join(CUBES_DIR, '__init__.py'), 'w').close()
elif exists(join(CW_SOFTWARE_ROOT, '.hg')):
mode = 'dev'
- CUBES_DIR = join(CW_SOFTWARE_ROOT, '../cubes')
+ CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes')))
else:
mode = 'installed'
CUBES_DIR = '/usr/share/cubicweb/cubes/'
- options = VREGOPTIONS + (
+ options = (
('log-threshold',
{'type' : 'string', # XXX use a dedicated type?
'default': 'ERROR',
@@ -195,6 +190,14 @@
'help': 'web server root url',
'group': 'main', 'inputlevel': 1,
}),
+ ('use-request-subdomain',
+ {'type' : 'yn',
+ 'default': None,
+ 'help': ('if set, base-url subdomain is replaced by the request\'s '
+ 'host, to help managing sites with several subdomains in a '
+ 'single cubicweb instance'),
+ 'group': 'main', 'inputlevel': 1,
+ }),
('mangle-emails',
{'type' : 'yn',
'default': False,
@@ -202,9 +205,14 @@
this option is set to yes",
'group': 'email', 'inputlevel': 2,
}),
+ ('disable-appobjects',
+ {'type' : 'csv', 'default': (),
+ 'help': 'comma separated list of identifiers of application objects (.) to disable',
+ 'group': 'appobjects', 'inputlevel': 2,
+ }),
)
# static and class methods used to get application independant resources ##
-
+
@staticmethod
def cubicweb_version():
"""return installed cubicweb version"""
@@ -213,7 +221,7 @@
version = __pkginfo__.numversion
assert len(version) == 3, version
return Version(version)
-
+
@staticmethod
def persistent_options_configuration():
return Configuration(options=PERSISTENT_OPTIONS)
@@ -225,8 +233,8 @@
"""
if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'):
return join(CW_SOFTWARE_ROOT, 'web')
- return join(cls.cubes_dir(), 'shared')
-
+ return cls.cube_dir('shared')
+
@classmethod
def i18n_lib_dir(cls):
"""return application's i18n directory"""
@@ -236,32 +244,44 @@
@classmethod
def available_cubes(cls):
- cubes_dir = cls.cubes_dir()
- return sorted(cube for cube in os.listdir(cubes_dir)
- if os.path.isdir(os.path.join(cubes_dir, cube))
- and not cube in ('CVS', '.svn', 'shared', '.hg'))
-
+ cubes = set()
+ for directory in cls.cubes_search_path():
+ for cube in os.listdir(directory):
+ if isdir(join(directory, cube)) and not cube in ('CVS', '.svn', 'shared', '.hg'):
+ cubes.add(cube)
+ return sorted(cubes)
+
@classmethod
- def cubes_dir(cls):
- """return the application cubes directory"""
- return env_path('CW_CUBES', cls.CUBES_DIR, 'cubes')
-
+ def cubes_search_path(cls):
+ """return the path of directories where cubes should be searched"""
+ path = []
+ try:
+ for directory in os.environ['CW_CUBES_PATH'].split(os.pathsep):
+ directory = abspath(normpath(directory))
+ if exists(directory) and not directory in path:
+ path.append(directory)
+ except KeyError:
+ pass
+ if not cls.CUBES_DIR in path:
+ path.append(cls.CUBES_DIR)
+ return path
+
@classmethod
def cube_dir(cls, cube):
"""return the cube directory for the given cube id,
raise ConfigurationError if it doesn't exists
"""
- cube_dir = join(cls.cubes_dir(), cube)
- if not exists(cube_dir):
- raise ConfigurationError('no cube %s in %s' % (
- cube, cls.cubes_dir()))
- return cube_dir
+ for directory in cls.cubes_search_path():
+ cubedir = join(directory, cube)
+ if exists(cubedir):
+ return cubedir
+ raise ConfigurationError('no cube %s in %s' % (cube, cls.cubes_search_path()))
@classmethod
def cube_migration_scripts_dir(cls, cube):
"""cube migration scripts directory"""
return join(cls.cube_dir(cube), 'migration')
-
+
@classmethod
def cube_pkginfo(cls, cube):
"""return the information module for the given cube"""
@@ -274,7 +294,7 @@
@classmethod
def cube_version(cls, cube):
- """return the version of the cube located in the given directory
+ """return the version of the cube located in the given directory
"""
from logilab.common.changelog import Version
version = cls.cube_pkginfo(cube).numversion
@@ -337,16 +357,18 @@
except KeyError:
continue
return tuple(reversed(cubes))
-
+
@classmethod
def cls_adjust_sys_path(cls):
"""update python path if necessary"""
+ cubes_parent_dir = normpath(join(cls.CUBES_DIR, '..'))
+ if not cubes_parent_dir in sys.path:
+ sys.path.insert(0, cubes_parent_dir)
try:
- templdir = abspath(join(cls.cubes_dir(), '..'))
- if not templdir in sys.path:
- sys.path.insert(0, templdir)
- except ConfigurationError:
- return # cube dir doesn't exists
+ import cubes
+ cubes.__path__ = cls.cubes_search_path()
+ except ImportError:
+ return # cubes dir doesn't exists
@classmethod
def load_cwctl_plugins(cls):
@@ -358,10 +380,9 @@
if exists(join(CW_SOFTWARE_ROOT, ctlfile)):
load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile))
cls.info('loaded cubicweb-ctl plugin %s', ctlfile)
- templdir = cls.cubes_dir()
for cube in cls.available_cubes():
- pluginfile = join(templdir, cube, 'ecplugin.py')
- initfile = join(templdir, cube, '__init__.py')
+ pluginfile = join(cls.cube_dir(cube), 'ecplugin.py')
+ initfile = join(cls.cube_dir(cube), '__init__.py')
if exists(pluginfile):
try:
__import__('cubes.%s.ecplugin' % cube)
@@ -374,7 +395,7 @@
except:
cls.exception('while loading cube %s', cube)
else:
- cls.warning('no __init__ file in cube %s', cube)
+ cls.warning('no __init__ file in cube %s', cube)
@classmethod
def init_available_cubes(cls):
@@ -386,7 +407,7 @@
__import__('cubes.%s' % cube)
except Exception, ex:
cls.warning("can't init cube %s: %s", cube, ex)
-
+
cubicweb_vobject_path = set(['entities'])
cube_vobject_path = set(['entities'])
@@ -434,17 +455,17 @@
elif exists(path + '.py'):
vregpath.append(path + '.py')
return vregpath
-
+
def __init__(self):
ConfigurationMixIn.__init__(self)
self.adjust_sys_path()
self.load_defaults()
- self.translations = {}
+ self.translations = {}
def adjust_sys_path(self):
self.cls_adjust_sys_path()
-
- def init_log(self, logthreshold=None, debug=False,
+
+ def init_log(self, logthreshold=None, debug=False,
logfile=None, syslog=False):
"""init the log service"""
if logthreshold is None:
@@ -461,7 +482,7 @@
for application objects. By default return nothing in NoApp config.
"""
return []
-
+
def eproperty_definitions(self):
cfg = self.persistent_options_configuration()
for section, options in cfg.options_by_section():
@@ -474,7 +495,7 @@
'help': optdict['help'],
'sitewide': optdict.get('sitewide', False)}
yield key, pdef
-
+
def map_option(self, optdict):
try:
vocab = optdict['choices']
@@ -484,21 +505,20 @@
vocab = getattr(self, vocab.method, ())
return CFGTYPE2ETYPE_MAP[optdict['type']], vocab
-
+
class CubicWebConfiguration(CubicWebNoAppConfiguration):
"""base class for cubicweb server and web configurations"""
-
+
+ INSTANCE_DATA_DIR = None
if CubicWebNoAppConfiguration.mode == 'test':
root = os.environ['APYCOT_ROOT']
REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
- INSTANCE_DATA_DIR = REGISTRY_DIR
RUNTIME_DIR = '/tmp/'
MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
if not exists(REGISTRY_DIR):
os.makedirs(REGISTRY_DIR)
elif CubicWebNoAppConfiguration.mode == 'dev':
REGISTRY_DIR = expanduser('~/etc/cubicweb.d/')
- INSTANCE_DATA_DIR = REGISTRY_DIR
RUNTIME_DIR = '/tmp/'
MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration')
else: #mode = 'installed'
@@ -511,7 +531,7 @@
set_language = True
# set this to true to avoid false error message while creating an application
creating = False
-
+
options = CubicWebNoAppConfiguration.options + (
('log-file',
{'type' : 'string',
@@ -534,7 +554,7 @@
}),
('sender-name',
{'type' : 'string',
- 'default': Method('default_application_id'),
+ 'default': Method('default_application_id'),
'help': 'name used as HELO name for outgoing emails from the \
repository.',
'group': 'email', 'inputlevel': 2,
@@ -552,7 +572,7 @@
def runtime_dir(cls):
"""run time directory for pid file..."""
return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time')
-
+
@classmethod
def registry_dir(cls):
"""return the control directory"""
@@ -561,9 +581,10 @@
@classmethod
def instance_data_dir(cls):
"""return the instance data directory"""
- return env_path('CW_INSTANCE_DATA', cls.INSTANCE_DATA_DIR,
+ return env_path('CW_INSTANCE_DATA',
+ cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR,
'additional data')
-
+
@classmethod
def migration_scripts_dir(cls):
"""cubicweb migration scripts directory"""
@@ -576,7 +597,7 @@
config = config or guess_configuration(cls.application_home(appid))
configcls = configuration_cls(config)
return configcls(appid)
-
+
@classmethod
def possible_configurations(cls, appid):
"""return the name of possible configurations for the given
@@ -584,7 +605,7 @@
"""
home = cls.application_home(appid)
return possible_configurations(home)
-
+
@classmethod
def application_home(cls, appid):
"""return the home directory of the application with the given
@@ -603,9 +624,9 @@
def accept_mode(cls, mode):
#assert mode in cls.MODES, mode
return mode in cls.MCOMPAT[cls.name]
-
+
# default configuration methods ###########################################
-
+
def default_application_id(self):
"""return the application identifier, useful for option which need this
as default value
@@ -627,13 +648,13 @@
i += 1
return path
return '/var/log/cubicweb/%s-%s.log' % (self.appid, self.name)
-
+
def default_pid_file(self):
"""return default path to the pid file of the application'server"""
return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name))
-
+
# instance methods used to get application specific resources #############
-
+
def __init__(self, appid):
self.appid = appid
CubicWebNoAppConfiguration.__init__(self)
@@ -651,13 +672,13 @@
@property
def apphome(self):
return join(self.registry_dir(), self.appid)
-
+
@property
def appdatahome(self):
return join(self.instance_data_dir(), self.appid)
-
+
def init_cubes(self, cubes):
- assert self._cubes is None
+ assert self._cubes is None, self._cubes
self._cubes = self.reorder_cubes(cubes)
# load cubes'__init__.py file first
for cube in cubes:
@@ -668,7 +689,7 @@
self.load_file_configuration(self.main_config_file())
# configuration initialization hook
self.load_configuration()
-
+
def cubes(self):
"""return the list of cubes used by this instance
@@ -677,7 +698,7 @@
"""
assert self._cubes is not None
return self._cubes
-
+
def cubes_path(self):
"""return the list of path to cubes used by this instance, from outer
most to inner most cubes
@@ -689,11 +710,11 @@
if not isinstance(cubes, list):
cubes = list(cubes)
self._cubes = self.reorder_cubes(list(self._cubes) + cubes)
-
+
def main_config_file(self):
"""return application's control configuration file"""
return join(self.apphome, '%s.conf' % self.name)
-
+
def save(self):
"""write down current configuration"""
self.generate_config(open(self.main_config_file(), 'w'))
@@ -706,7 +727,7 @@
version = self.cube_version(pkg)
infos.append('%s-%s' % (pkg, version))
return md5.new(';'.join(infos)).hexdigest()
-
+
def load_site_cubicweb(self):
"""load (web?) application's specific site_cubicweb file"""
for path in reversed([self.apphome] + self.cubes_path()):
@@ -720,7 +741,7 @@
self._load_site_cubicweb(sitefile)
self._site_loaded.add(sitefile)
self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
-
+
def _load_site_cubicweb(self, sitefile):
context = {}
execfile(sitefile, context, context)
@@ -729,14 +750,14 @@
if context.get('options'):
self.register_options(context['options'])
self.load_defaults()
-
+
def load_configuration(self):
"""load application's configuration files"""
super(CubicWebConfiguration, self).load_configuration()
if self.apphome and self.set_language:
# init gettext
self._set_language()
-
+
def init_log(self, logthreshold=None, debug=False, force=False):
"""init the log service"""
if not force and hasattr(self, '_logging_initialized'):
@@ -762,7 +783,7 @@
lang = path.split(os.sep)[-3]
if lang != 'en':
yield lang
-
+
def _set_language(self):
"""set language for gettext"""
from gettext import translation
@@ -774,8 +795,8 @@
self.translations[language] = tr.ugettext
except (ImportError, AttributeError, IOError):
self.exception('localisation support error for language %s',
- language)
-
+ language)
+
def vregistry_path(self):
"""return a list of files or directories where the registry will look
for application objects
@@ -789,7 +810,7 @@
if not 'all' in sources:
print 'warning: ignoring specified sources, requires a repository '\
'configuration'
-
+
def migration_handler(self):
"""return a migration handler instance"""
from cubicweb.common.migration import MigrationHelper
@@ -807,7 +828,7 @@
return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs)
set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
-
+
# alias to get a configuration instance from an application id
-application_configuration = CubicWebConfiguration.config_for
+application_configuration = CubicWebConfiguration.config_for
diff -r 292b7989b166 -r ddf4f2d8d51c cwctl.py
--- a/cwctl.py Wed Jun 03 19:49:44 2009 +0200
+++ b/cwctl.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,17 +1,19 @@
"""%%prog %s [options] %s
-CubicWeb main applications controller.
+CubicWeb main applications controller.
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
%s"""
import sys
-from os import remove, listdir, system, kill, getpgid
+from os import remove, listdir, system, kill, getpgid, pathsep
from os.path import exists, join, isfile, isdir
+from logilab.common.clcommands import register_commands, pop_arg
+
from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
-from cubicweb.cwconfig import CubicWebConfiguration, CONFIGURATIONS
-from cubicweb.toolsutils import (Command, register_commands, main_run,
- rm, create_dir, pop_arg, confirm)
-
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
+from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm
+
def wait_process_end(pid, maxtry=10, waittime=1):
"""wait for a process to actually die"""
import signal
@@ -41,13 +43,13 @@
modes.append('web ui')
break
return modes
-
-
+
+
class ApplicationCommand(Command):
"""base class for command taking 0 to n application id as arguments
(0 meaning all registered applications)
"""
- arguments = '[...]'
+ arguments = '[...]'
options = (
("force",
{'short': 'f', 'action' : 'store_true',
@@ -57,13 +59,13 @@
),
)
actionverb = None
-
+
def ordered_instances(self):
"""return instances in the order in which they should be started,
considering $REGISTRY_DIR/startorder file if it exists (useful when
some instances depends on another as external source
"""
- regdir = CubicWebConfiguration.registry_dir()
+ regdir = cwcfg.registry_dir()
_allinstances = list_instances(regdir)
if isfile(join(regdir, 'startorder')):
allinstances = []
@@ -74,12 +76,13 @@
_allinstances.remove(line)
allinstances.append(line)
except ValueError:
- print 'ERROR: startorder file contains unexistant instance %s' % line
+ print ('ERROR: startorder file contains unexistant '
+ 'instance %s' % line)
allinstances += _allinstances
else:
allinstances = _allinstances
return allinstances
-
+
def run(self, args):
"""run the _method on each argument (a list of application
identifiers)
@@ -94,7 +97,7 @@
else:
askconfirm = False
self.run_args(args, askconfirm)
-
+
def run_args(self, args, askconfirm):
for appid in args:
if askconfirm:
@@ -102,7 +105,7 @@
if not confirm('%s application %r ?' % (self.name, appid)):
continue
self.run_arg(appid)
-
+
def run_arg(self, appid):
cmdmeth = getattr(self, '%s_application' % self.name)
try:
@@ -141,7 +144,7 @@
sys.exit(status)
else:
self.run_arg(appid)
-
+
# base commands ###############################################################
class ListCommand(Command):
@@ -153,16 +156,16 @@
name = 'list'
options = (
('verbose',
- {'short': 'v', 'action' : 'store_true',
- 'help': "display more information."}),
+ {'short': 'v', 'action' : 'store_true',
+ 'help': "display more information."}),
)
-
+
def run(self, args):
"""run the command with its specific arguments"""
if args:
raise BadCommandUsage('Too much arguments')
- print 'CubicWeb version:', CubicWebConfiguration.cubicweb_version()
- print 'Detected mode:', CubicWebConfiguration.mode
+ print 'CubicWeb version:', cwcfg.cubicweb_version()
+ print 'Detected mode:', cwcfg.mode
print
print 'Available configurations:'
for config in CONFIGURATIONS:
@@ -172,22 +175,21 @@
if not line:
continue
print ' ', line
- print
+ print
try:
- cubesdir = CubicWebConfiguration.cubes_dir()
- namesize = max(len(x) for x in CubicWebConfiguration.available_cubes())
+ cubesdir = pathsep.join(cwcfg.cubes_search_path())
+ namesize = max(len(x) for x in cwcfg.available_cubes())
except ConfigurationError, ex:
print 'No cubes available:', ex
except ValueError:
print 'No cubes available in %s' % cubesdir
else:
print 'Available cubes (%s):' % cubesdir
- for cube in CubicWebConfiguration.available_cubes():
+ for cube in cwcfg.available_cubes():
if cube in ('CVS', '.svn', 'shared', '.hg'):
continue
- templdir = join(cubesdir, cube)
try:
- tinfo = CubicWebConfiguration.cube_pkginfo(cube)
+ tinfo = cwcfg.cube_pkginfo(cube)
tversion = tinfo.version
except ConfigurationError:
tinfo = None
@@ -198,11 +200,11 @@
or tinfo.__doc__)
if shortdesc:
print ' '+ ' \n'.join(shortdesc.splitlines())
- modes = detect_available_modes(templdir)
+ modes = detect_available_modes(cwcfg.cube_dir(cube))
print ' available modes: %s' % ', '.join(modes)
print
try:
- regdir = CubicWebConfiguration.registry_dir()
+ regdir = cwcfg.registry_dir()
except ConfigurationError, ex:
print 'No application available:', ex
print
@@ -211,14 +213,14 @@
if instances:
print 'Available applications (%s):' % regdir
for appid in instances:
- modes = CubicWebConfiguration.possible_configurations(appid)
+ modes = cwcfg.possible_configurations(appid)
if not modes:
print '* %s (BROKEN application, no configuration found)' % appid
continue
print '* %s (%s)' % (appid, ', '.join(modes))
try:
- config = CubicWebConfiguration.config_for(appid, modes[0])
- except Exception, exc:
+ config = cwcfg.config_for(appid, modes[0])
+ except Exception, exc:
print ' (BROKEN application, %s)' % exc
continue
else:
@@ -260,7 +262,7 @@
}
),
)
-
+
def run(self, args):
"""run the command with its specific arguments"""
from logilab.common.textutils import get_csv
@@ -268,19 +270,19 @@
cubes = get_csv(pop_arg(args, 1))
appid = pop_arg(args)
# get the configuration and helper
- CubicWebConfiguration.creating = True
- config = CubicWebConfiguration.config_for(appid, configname)
+ cwcfg.creating = True
+ config = cwcfg.config_for(appid, configname)
config.set_language = False
config.init_cubes(config.expand_cubes(cubes))
helper = self.config_helper(config)
# check the cube exists
try:
- templdirs = [CubicWebConfiguration.cube_dir(cube)
+ templdirs = [cwcfg.cube_dir(cube)
for cube in cubes]
except ConfigurationError, ex:
print ex
print '\navailable cubes:',
- print ', '.join(CubicWebConfiguration.available_cubes())
+ print ', '.join(cwcfg.available_cubes())
return
# create the registry directory for this application
create_dir(config.apphome)
@@ -296,7 +298,6 @@
# write down configuration
config.save()
# handle i18n files structure
- # XXX currently available languages are guessed from translations found
# in the first cube given
from cubicweb.common import i18n
langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
@@ -323,21 +324,21 @@
print
helper.postcreate()
-
+
class DeleteApplicationCommand(Command):
"""Delete an application. Will remove application's files and
unregister it.
"""
name = 'delete'
arguments = ''
-
+
options = ()
def run(self, args):
"""run the command with its specific arguments"""
appid = pop_arg(args, msg="No application specified !")
- configs = [CubicWebConfiguration.config_for(appid, configname)
- for configname in CubicWebConfiguration.possible_configurations(appid)]
+ configs = [cwcfg.config_for(appid, configname)
+ for configname in cwcfg.possible_configurations(appid)]
if not configs:
raise ExecutionError('unable to guess configuration for %s' % appid)
for config in configs:
@@ -361,7 +362,7 @@
class StartApplicationCommand(ApplicationCommand):
"""Start the given applications. If no application is given, start them all.
-
+
...
identifiers of the applications to start. If no application is
given, start them all.
@@ -390,7 +391,7 @@
# without all options defined
debug = self.get('debug')
force = self.get('force')
- config = CubicWebConfiguration.config_for(appid)
+ config = cwcfg.config_for(appid)
if self.get('profile'):
config.global_set_option('profile', self.config.profile)
helper = self.config_helper(config, cmdname='start')
@@ -414,22 +415,22 @@
class StopApplicationCommand(ApplicationCommand):
"""Stop the given applications.
-
+
...
identifiers of the applications to stop. If no application is
given, stop them all.
"""
name = 'stop'
actionverb = 'stopped'
-
+
def ordered_instances(self):
instances = super(StopApplicationCommand, self).ordered_instances()
instances.reverse()
return instances
-
+
def stop_application(self, appid):
"""stop the application's server"""
- config = CubicWebConfiguration.config_for(appid)
+ config = cwcfg.config_for(appid)
helper = self.config_helper(config, cmdname='stop')
helper.poststop() # do this anyway
pidf = config['pid-file']
@@ -460,12 +461,12 @@
# already removed by twistd
pass
print 'application %s stopped' % appid
-
+
class RestartApplicationCommand(StartApplicationCommand,
StopApplicationCommand):
"""Restart the given applications.
-
+
...
identifiers of the applications to restart. If no application is
given, restart them all.
@@ -474,7 +475,7 @@
actionverb = 'restarted'
def run_args(self, args, askconfirm):
- regdir = CubicWebConfiguration.registry_dir()
+ regdir = cwcfg.registry_dir()
if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
# no specific startorder
super(RestartApplicationCommand, self).run_args(args, askconfirm)
@@ -497,30 +498,30 @@
status = system('%s %s' % (forkcmd, appid))
if status:
sys.exit(status)
-
+
def restart_application(self, appid):
self.stop_application(appid)
if self.start_application(appid):
print 'application %s %s' % (appid, self.actionverb)
-
+
class ReloadConfigurationCommand(RestartApplicationCommand):
"""Reload the given applications. This command is equivalent to a
restart for now.
-
+
...
identifiers of the applications to reload. If no application is
given, reload them all.
"""
name = 'reload'
-
+
def reload_application(self, appid):
self.restart_application(appid)
-
+
class StatusCommand(ApplicationCommand):
"""Display status information about the given applications.
-
+
...
identifiers of the applications to status. If no application is
given, get status information about all registered applications.
@@ -528,10 +529,11 @@
name = 'status'
options = ()
- def status_application(self, appid):
+ @staticmethod
+ def status_application(appid):
"""print running status information for an application"""
- for mode in CubicWebConfiguration.possible_configurations(appid):
- config = CubicWebConfiguration.config_for(appid, mode)
+ for mode in cwcfg.possible_configurations(appid):
+ config = cwcfg.config_for(appid, mode)
print '[%s-%s]' % (appid, mode),
try:
pidf = config['pid-file']
@@ -575,7 +577,7 @@
{'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
'default': None,
'help': 'force migration from the indicated cubicweb version.'}),
-
+
('fs-only',
{'short': 's', 'action' : 'store_true',
'default': False,
@@ -585,13 +587,13 @@
{'short': 'n', 'action' : 'store_true',
'default': False,
'help': 'don\'t try to stop application before migration and to restart it after.'}),
-
+
('verbosity',
{'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
'default': 1,
'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
for everything."}),
-
+
('backup-db',
{'short': 'b', 'type' : 'yn', 'metavar': '',
'default': None,
@@ -612,15 +614,17 @@
def ordered_instances(self):
# need this since mro return StopApplicationCommand implementation
return ApplicationCommand.ordered_instances(self)
-
+
def upgrade_application(self, appid):
from logilab.common.changelog import Version
- if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
- self.stop_application(appid)
- config = CubicWebConfiguration.config_for(appid)
+ config = cwcfg.config_for(appid)
config.creating = True # notice we're not starting the server
config.verbosity = self.config.verbosity
- config.set_sources_mode(self.config.ext_sources or ('migration',))
+ try:
+ config.set_sources_mode(self.config.ext_sources or ('migration',))
+ except AttributeError:
+ # not a server config
+ pass
# get application and installed versions for the server and the componants
print 'getting versions configuration from the repository...'
mih = config.migration_handler()
@@ -643,7 +647,7 @@
continue
if installedversion > applversion:
toupgrade.append( (cube, applversion, installedversion) )
- cubicwebversion = config.cubicweb_version()
+ cubicwebversion = config.cubicweb_version()
if self.config.force_cubicweb_version:
applcubicwebversion = Version(self.config.force_cubicweb_version)
vcconf['cubicweb'] = applcubicwebversion
@@ -656,6 +660,9 @@
return
for cube, fromversion, toversion in toupgrade:
print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
+ # only stop once we're sure we have something to do
+ if not (cwcfg.mode == 'dev' or self.config.nostartstop):
+ self.stop_application(appid)
# run cubicweb/componants migration scripts
mih.migrate(vcconf, reversed(toupgrade), self.config)
# rewrite main configuration file
@@ -663,10 +670,9 @@
# handle i18n upgrade:
# * install new languages
# * recompile catalogs
- # XXX currently available languages are guessed from translations found
# in the first componant given
from cubicweb.common import i18n
- templdir = CubicWebConfiguration.cube_dir(config.cubes()[0])
+ templdir = cwcfg.cube_dir(config.cubes()[0])
langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
errors = config.i18ncompile(langs)
if errors:
@@ -679,7 +685,7 @@
mih.shutdown()
print
print 'application migrated'
- if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
+ if not (cwcfg.mode == 'dev' or self.config.nostartstop):
self.start_application(appid)
print
@@ -702,7 +708,7 @@
'help': 'only connect to the system source when the instance is '
'using multiple sources. You can\'t use this option and the '
'--ext-sources option at the same time.'}),
-
+
('ext-sources',
{'short': 'E', 'type' : 'csv', 'metavar': '',
'default': None,
@@ -711,11 +717,11 @@
will connect to all defined sources. If 'migration' is given, appropriate \
sources for migration will be automatically selected.",
}),
-
+
)
def run(self, args):
appid = pop_arg(args, 99, msg="No application specified !")
- config = CubicWebConfiguration.config_for(appid)
+ config = cwcfg.config_for(appid)
if self.config.ext_sources:
assert not self.config.system_only
sources = self.config.ext_sources
@@ -729,21 +735,22 @@
mih.scripts_session(args)
else:
mih.interactive_shell()
- mih.shutdown()
+ mih.shutdown()
class RecompileApplicationCatalogsCommand(ApplicationCommand):
"""Recompile i18n catalogs for applications.
-
+
...
identifiers of the applications to consider. If no application is
given, recompile for all registered applications.
"""
- name = 'i18ncompile'
-
- def i18ncompile_application(self, appid):
+ name = 'i18ninstance'
+
+ @staticmethod
+ def i18ninstance_application(appid):
"""recompile application's messages catalogs"""
- config = CubicWebConfiguration.config_for(appid)
+ config = cwcfg.config_for(appid)
try:
config.bootstrap_cubes()
except IOError, ex:
@@ -767,10 +774,10 @@
"""list available instances, useful for bash completion."""
name = 'listinstances'
hidden = True
-
+
def run(self, args):
"""run the command with its specific arguments"""
- regdir = CubicWebConfiguration.registry_dir()
+ regdir = cwcfg.registry_dir()
for appid in sorted(listdir(regdir)):
print appid
@@ -779,10 +786,10 @@
"""list available componants, useful for bash completion."""
name = 'listcubes'
hidden = True
-
+
def run(self, args):
"""run the command with its specific arguments"""
- for cube in CubicWebConfiguration.available_cubes():
+ for cube in cwcfg.available_cubes():
print cube
register_commands((ListCommand,
@@ -799,10 +806,10 @@
ListInstancesCommand, ListCubesCommand,
))
-
+
def run(args):
"""command line tool"""
- CubicWebConfiguration.load_cwctl_plugins()
+ cwcfg.load_cwctl_plugins()
main_run(args, __doc__)
if __name__ == '__main__':
diff -r 292b7989b166 -r ddf4f2d8d51c cwvreg.py
--- a/cwvreg.py Wed Jun 03 19:49:44 2009 +0200
+++ b/cwvreg.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,32 +1,46 @@
"""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), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
-
-from warnings import warn
+_ = unicode
from logilab.common.decorators import cached, clear_cache
from rql import RQLHelper
-from cubicweb import Binary, UnknownProperty
+from cubicweb import ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid
from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject
+from cubicweb.rtags import RTAGS
-_ = unicode
-class DummyCursorError(Exception): pass
-class RaiseCursor:
- @classmethod
- def execute(cls, rql, args=None, eid_key=None):
- raise DummyCursorError()
+def use_interfaces(obj):
+ """return interfaces used by the given object by searchinf for implements
+ selectors, with a bw compat fallback to accepts_interfaces attribute
+ """
+ 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
+ except:
+ print 'bad selector %s on %s' % (obj.__select__, obj)
+ raise
+ return ()
class CubicWebRegistry(VRegistry):
"""extend the generic VRegistry with some cubicweb specific stuff"""
-
+
def __init__(self, config, debug=None, initlog=True):
if initlog:
# first init log service
@@ -35,32 +49,34 @@
self.schema = None
self.reset()
self.initialized = False
-
+
def items(self):
return [item for item in self._registries.items()
if not item[0] in ('propertydefs', 'propertyvalues')]
def values(self):
- return [value for key,value in self._registries.items()
+ return [value for key, value in self._registries.items()
if not key in ('propertydefs', 'propertyvalues')]
-
+
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():
self.register_property(key, **propdef)
-
+
def set_schema(self, schema):
"""set application'schema and load application objects"""
self.schema = schema
clear_cache(self, 'rqlhelper')
# now we can load application's web objects
self.register_objects(self.config.vregistry_path())
-
+
def update_schema(self, schema):
"""update .schema attribute on registered objects, necessary for some
tests
@@ -72,56 +88,80 @@
for objects in regcontent.values():
for obj in objects:
obj.schema = schema
-
- def register_objects(self, path, force_reload=None):
- """overriden to handle type class cache issue"""
- 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')
- # 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:
+
+ 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':
+ if obj.id != 'Any' and not obj.id in self.schema:
+ self.error('don\'t register %s, %s type not defined in the '
+ 'schema', obj, obj.id)
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]
+ 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 remove objects requiring a missing interface"""
+ extrapath = {}
+ for cubesdir in self.config.cubes_search_path():
+ if cubesdir != self.config.CUBES_DIR:
+ extrapath[cubesdir] = 'cubes'
+ if super(CubicWebRegistry, self).register_objects(path, force_reload,
+ extrapath):
+ self.initialization_completed()
+ # call vreg_initialization_completed on appobjects and print
+ # registry content
+ for registry, objects in self.items():
+ self.debug('available in registry %s: %s', registry,
+ sorted(objects))
+ for appobjects in objects.itervalues():
+ for appobject in appobjects:
+ appobject.vreg_initialization_completed()
+ # don't check rtags if we don't want to cleanup_interface_sobjects
+ for rtag in RTAGS:
+ rtag.init(self.schema,
+ check=self.config.cleanup_interface_sobjects)
- def 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
- entity)
- """
- msg = '.eid_rset is deprecated, use req.eid_rset'
- warn(msg, DeprecationWarning, stacklevel=2)
- try:
- return cursor.req.eid_rset(eid, etype)
- except AttributeError:
- # cursor is a session
- return cursor.eid_rset(eid, etype)
-
+ def initialization_completed(self):
+ # 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 self.config.cleanup_interface_sobjects:
+ # remove vobjects that don't support any available interface
+ implemented_interfaces = set()
+ if 'Any' in self.get('etypes', ()):
+ for etype in self.schema.entities():
+ cls = self.etype_class(etype)
+ for iface in cls.__implements__:
+ implemented_interfaces.update(iface.__mro__)
+ implemented_interfaces.update(cls.__mro__)
+ for obj, ifaces in self._needs_iface.items():
+ ifaces = frozenset(isinstance(iface, basestring)
+ and iface in self.schema
+ and self.etype_class(iface)
+ or iface
+ for iface in ifaces)
+ if not ('Any' in ifaces or ifaces & implemented_interfaces):
+ self.debug('kicking vobject %s (no implemented '
+ 'interface among %s)', obj, ifaces)
+ self.unregister(obj)
+ # clear needs_iface so we don't try to remove some not-anymore-in
+ # objects on automatic reloading
+ self._needs_iface.clear()
+
@cached
def etype_class(self, etype):
"""return an entity class for the given entity type.
@@ -129,25 +169,34 @@
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
# try to find an associated custom entity class
for baseschema in baseschemas:
- btype = str(baseschema)
try:
- return self.select(self.registry_objects('etypes', btype), etype)
+ btype = ETYPE_NAME_MAP[baseschema]
+ except KeyError:
+ btype = str(baseschema)
+ try:
+ 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)
+ return cls
def render(self, registry, oid, req, **context):
"""select an object in a given registry and render it
- registry: the registry's name
- oid : the view to call
- - req : the HTTP request
+ - req : the HTTP request
"""
objclss = self.registry_objects(registry, oid)
try:
@@ -155,14 +204,14 @@
except KeyError:
rset = None
selected = self.select(objclss, req, rset, **context)
- return selected.dispatch(**context)
-
- def main_template(self, req, oid='main', **context):
+ return selected.render(**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)
@@ -176,17 +225,17 @@
return [x for x in sorted(self.possible_objects(registry, *args, **kwargs),
key=lambda x: x.propval('order'))
if x.propval('visible')]
-
+
def possible_actions(self, req, rset, **kwargs):
if rset is None:
- actions = self.possible_vobjects('actions', req, rset)
+ actions = self.possible_vobjects('actions', req, rset, **kwargs)
else:
- actions = rset.possible_actions() # cached implementation
+ actions = rset.possible_actions(**kwargs) # cached implementation
result = {}
for action in actions:
result.setdefault(action.category, []).append(action)
return result
-
+
def possible_views(self, req, rset, **kwargs):
"""return an iterator on possible views for this result set
@@ -204,7 +253,17 @@
except Exception:
self.exception('error while trying to list possible %s views for %s',
vid, rset)
-
+
+ def view(self, __vid, req, rset=None, __fallback_vid=None, **kwargs):
+ """shortcut to self.vreg.render method avoiding to pass self.req"""
+ try:
+ view = self.select_view(__vid, req, rset, **kwargs)
+ except NoSelectableObject:
+ if __fallback_vid is None:
+ raise
+ view = self.select_view(__fallback_vid, req, rset, **kwargs)
+ return view.render(**kwargs)
+
def select_box(self, oid, *args, **kwargs):
"""return the most specific view according to the result set"""
try:
@@ -218,7 +277,7 @@
return self.select_object('actions', oid, *args, **kwargs)
except NoSelectableObject:
return
-
+
def select_component(self, cid, *args, **kwargs):
"""return the most specific component according to the result set"""
try:
@@ -226,12 +285,11 @@
except (NoSelectableObject, ObjectNotFound):
return
- def select_view(self, __vid, req, rset, **kwargs):
+ def select_view(self, __vid, req, rset=None, **kwargs):
"""return the most specific view according to the result set"""
views = self.registry_objects('views', __vid)
return self.select(views, req, rset, **kwargs)
-
# properties handling #####################################################
def user_property_keys(self, withsitewide=False):
@@ -245,7 +303,7 @@
"""register a given property"""
properties = self._registries['propertydefs']
assert type in YAMS_TO_PY
- properties[key] = {'type': type, 'vocabulary': vocabulary,
+ properties[key] = {'type': type, 'vocabulary': vocabulary,
'default': default, 'help': help,
'sitewide': sitewide}
@@ -263,7 +321,7 @@
'default': None, 'vocabulary': None,
'help': _('%s software version of the database') % soft}
raise UnknownProperty('unregistered property %r' % key)
-
+
def property_value(self, key):
try:
return self._registries['propertyvalues'][key]
@@ -286,7 +344,7 @@
if not value in vocab:
raise ValueError(_('unauthorized value'))
return value
-
+
def init_properties(self, propvalues):
"""init the property values registry using the given set of couple (key, value)
"""
@@ -302,42 +360,15 @@
self.warning('%s (you should probably delete that property '
'from the database)', ex)
-
- def property_value_widget(self, propkey, req=None, **attrs):
- """return widget according to key's type / vocab"""
- from cubicweb.web.widgets import StaticComboBoxWidget, widget_factory
- if req is None:
- tr = unicode
- else:
- tr = req._
- try:
- pdef = self.property_info(propkey)
- except UnknownProperty, ex:
- self.warning('%s (you should probably delete that property '
- 'from the database)', ex)
- return widget_factory(self, 'EProperty', self.schema['value'], 'String',
- description=u'', **attrs)
- req.form['value'] = pdef['default'] # XXX hack to pass the default value
- vocab = pdef['vocabulary']
- if vocab is not None:
- if callable(vocab):
- # list() just in case its a generator function
- vocabfunc = lambda e: list(vocab(propkey, req))
- else:
- vocabfunc = lambda e: vocab
- w = StaticComboBoxWidget(self, 'EProperty', self.schema['value'], 'String',
- vocabfunc=vocabfunc, description=tr(pdef['help']),
- **attrs)
- else:
- w = widget_factory(self, 'EProperty', self.schema['value'], pdef['type'],
- description=tr(pdef['help']), **attrs)
- return w
-
def parse(self, session, rql, args=None):
rqlst = self.rqlhelper.parse(rql)
def type_from_eid(eid, session=session):
return session.describe(eid)[0]
- self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
+ try:
+ self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
+ except UnknownEid:
+ for select in rqlst.children:
+ select.solutions = []
return rqlst
@property
@@ -359,6 +390,8 @@
default to a dump of the class registered for 'Any'
"""
usercls = super(MulCnxCubicWebRegistry, self).etype_class(etype)
+ if etype == 'Any':
+ return usercls
usercls.e_schema = self.schema.eschema(etype)
return usercls
@@ -372,9 +405,16 @@
vobject.vreg = self
vobject.schema = self.schema
vobject.config = self.config
- return super(MulCnxCubicWebRegistry, self).select(vobjects, *args, **kwargs)
-
-from mx.DateTime import DateTime, Time, DateTimeDelta
+ selected = super(MulCnxCubicWebRegistry, self).select(vobjects, *args,
+ **kwargs)
+ # redo the same thing on the instance so it won't use equivalent class
+ # attributes (which may change)
+ selected.vreg = self
+ selected.schema = self.schema
+ selected.config = self.config
+ return selected
+
+from datetime import datetime, date, time, timedelta
YAMS_TO_PY = {
'Boolean': bool,
@@ -383,9 +423,9 @@
'Bytes': Binary,
'Int': int,
'Float': float,
- 'Date': DateTime,
- 'Datetime': DateTime,
- 'Time': Time,
- 'Interval': DateTimeDelta,
+ 'Date': date,
+ 'Datetime': datetime,
+ 'Time': time,
+ 'Interval': timedelta,
}
diff -r 292b7989b166 -r ddf4f2d8d51c dbapi.py
--- a/dbapi.py Wed Jun 03 19:49:44 2009 +0200
+++ b/dbapi.py Wed Jun 03 19:50:34 2009 +0200
@@ -5,18 +5,20 @@
(most parts of this document are reported here in docstrings)
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
-from logging import getLogger, StreamHandler
+from logging import getLogger
from time import time, clock
-from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods
+from logilab.common.logging_ext import set_log_methods
+from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry
from cubicweb.cwconfig import CubicWebNoAppConfiguration
-
+
_MARKER = object()
class ConnectionProperties(object):
@@ -29,7 +31,7 @@
def get_repository(method, database=None, config=None, vreg=None):
"""get a proxy object to the CubicWeb repository, using a specific RPC method.
-
+
Only 'in-memory' and 'pyro' are supported for now. Either vreg or config
argument should be given
"""
@@ -42,7 +44,7 @@
from cubicweb.server.repository import Repository
return Repository(config, vreg=vreg)
else: # method == 'pyro'
- from Pyro import core, naming, config as pyroconfig
+ from Pyro import core, naming
from Pyro.errors import NamingError, ProtocolError
core.initClient(banner=0)
nsid = ':%s.%s' % (config['pyro-ns-group'], database)
@@ -50,20 +52,20 @@
# resolve the Pyro object
try:
nshost, nsport = config['pyro-ns-host'], config['pyro-ns-port']
- uri = locator.getNS(nshost, nsport) .resolve(nsid)
+ uri = locator.getNS(nshost, nsport).resolve(nsid)
except ProtocolError:
raise ConnectionError('Could not connect to the Pyro name server '
'(host: %s:%i)' % (nshost, nsport))
- except NamingError, ex:
+ except NamingError:
raise ConnectionError('Could not get repository for %s '
'(not registered in Pyro), '
'you may have to restart your server-side '
'application' % nsid)
return core.getProxyForURI(uri)
-
+
def repo_connect(repo, user, password, cnxprops=None):
"""Constructor to create a new connection to the CubicWeb repository.
-
+
Returns a Connection instance.
"""
cnxprops = cnxprops or ConnectionProperties('inmemory')
@@ -72,7 +74,7 @@
if cnxprops.cnxtype == 'inmemory':
cnx.vreg = repo.vreg
return cnx
-
+
def connect(database=None, user=None, password=None, host=None,
group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
initlog=True):
@@ -101,7 +103,12 @@
vreg = MulCnxCubicWebRegistry(config, initlog=initlog)
else:
vreg = CubicWebRegistry(config, initlog=initlog)
- vreg.set_schema(repo.get_schema())
+ schema = repo.get_schema()
+ for oldetype, newetype in ETYPE_NAME_MAP.items():
+ if oldetype in schema:
+ print 'aliasing', newetype, 'to', oldetype
+ schema._entities[newetype] = schema._entities[oldetype]
+ vreg.set_schema(schema)
else:
vreg = None
cnx = repo_connect(repo, user, password, cnxprops)
@@ -110,7 +117,7 @@
def in_memory_cnx(config, user, password):
"""usefull method for testing and scripting to get a dbapi.Connection
- object connected to an in-memory repository instance
+ object connected to an in-memory repository instance
"""
if isinstance(config, CubicWebRegistry):
vreg = config
@@ -126,7 +133,7 @@
class DBAPIRequest(RequestSessionMixIn):
-
+
def __init__(self, vreg, cnx=None):
super(DBAPIRequest, self).__init__(vreg)
try:
@@ -146,10 +153,10 @@
def base_url(self):
return self.vreg.config['base-url']
-
+
def from_controller(self):
return 'view'
-
+
def set_connection(self, cnx, user=None):
"""method called by the session handler when the user is authenticated
or an anonymous connection is open
@@ -157,7 +164,7 @@
self.cnx = cnx
self.cursor = cnx.cursor(self)
self.set_user(user)
-
+
def set_default_language(self, vreg):
try:
self.lang = vreg.property_value('ui.language')
@@ -175,26 +182,26 @@
rset.vreg = self.vreg
rset.req = self
return rset
-
+
def describe(self, eid):
"""return a tuple (type, sourceuri, extid) for the entity with id """
return self.cnx.describe(eid)
-
+
def source_defs(self):
"""return the definition of sources used by the repository."""
return self.cnx.source_defs()
-
+
# entities cache management ###############################################
-
+
def entity_cache(self, eid):
return self._eid_cache[eid]
-
+
def set_entity_cache(self, entity):
self._eid_cache[entity.eid] = entity
def cached_entities(self):
return self._eid_cache.values()
-
+
def drop_entity_cache(self, eid=None):
if eid is None:
self._eid_cache = {}
@@ -210,11 +217,11 @@
def get_session_data(self, key, default=None, pop=False):
"""return value associated to `key` in session data"""
return self.cnx.get_session_data(key, default, pop)
-
+
def set_session_data(self, key, value):
"""set value associated to `key` in session data"""
return self.cnx.set_session_data(key, value)
-
+
def del_session_data(self, key):
"""remove value associated to `key` in session data"""
return self.cnx.del_session_data(key)
@@ -222,7 +229,7 @@
def get_shared_data(self, key, default=None, pop=False):
"""return value associated to `key` in shared data"""
return self.cnx.get_shared_data(key, default, pop)
-
+
def set_shared_data(self, key, value, querydata=False):
"""set value associated to `key` in shared data
@@ -245,14 +252,14 @@
self._user = user
if user:
self.set_entity_cache(user)
-
+
def execute(self, *args, **kwargs):
"""Session interface compatibility"""
return self.cursor.execute(*args, **kwargs)
set_log_methods(DBAPIRequest, getLogger('cubicweb.dbapi'))
-
-
+
+
# exceptions ##################################################################
class ProgrammingError(Exception): #DatabaseError):
@@ -288,15 +295,15 @@
"""String constant stating the type of parameter marker formatting expected by
the interface. Possible values are :
- 'qmark' Question mark style,
+ 'qmark' Question mark style,
e.g. '...WHERE name=?'
- 'numeric' Numeric, positional style,
+ 'numeric' Numeric, positional style,
e.g. '...WHERE name=:1'
- 'named' Named style,
+ 'named' Named style,
e.g. '...WHERE name=:name'
- 'format' ANSI C printf format codes,
+ 'format' ANSI C printf format codes,
e.g. '...WHERE name=%s'
- 'pyformat' Python extended format codes,
+ 'pyformat' Python extended format codes,
e.g. '...WHERE name=%(name)s'
"""
paramstyle = 'pyformat'
@@ -333,41 +340,37 @@
def request(self):
return DBAPIRequest(self.vreg, self)
-
+
def session_data(self):
"""return a dictionnary containing session data"""
return self.data
-
+
def get_session_data(self, key, default=None, pop=False):
"""return value associated to `key` in session data"""
if pop:
return self.data.pop(key, default)
else:
return self.data.get(key, default)
-
+
def set_session_data(self, key, value):
"""set value associated to `key` in session data"""
self.data[key] = value
-
+
def del_session_data(self, key):
"""remove value associated to `key` in session data"""
try:
del self.data[key]
except KeyError:
- pass
+ pass
def check(self):
"""raise `BadSessionId` if the connection is no more valid"""
- try:
- self._repo.check_session(self.sessionid)
- except AttributeError:
- # XXX backward compat for repository running cubicweb < 2.48.3
- self._repo.session_data(self.sessionid)
+ self._repo.check_session(self.sessionid)
def get_shared_data(self, key, default=None, pop=False):
"""return value associated to `key` in shared data"""
return self._repo.get_shared_data(self.sessionid, key, default, pop)
-
+
def set_shared_data(self, key, value, querydata=False):
"""set value associated to `key` in shared data
@@ -377,10 +380,10 @@
repository side.
"""
return self._repo.set_shared_data(self.sessionid, key, value, querydata)
-
+
def get_schema(self):
"""Return the schema currently used by the repository.
-
+
This is NOT part of the DB-API.
"""
if self._closed is not None:
@@ -418,10 +421,10 @@
# application specific hooks
if self._repo.config.application_hooks:
hm.register_hooks(config.load_hooks(self.vreg))
-
+
def source_defs(self):
"""Return the definition of sources used by the repository.
-
+
This is NOT part of the DB-API.
"""
if self._closed is not None:
@@ -434,9 +437,9 @@
eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
if req is None:
req = self.request()
- rset = req.eid_rset(eid, 'EUser')
- user = self.vreg.etype_class('EUser')(req, rset, row=0, groups=groups,
- properties=properties)
+ rset = req.eid_rset(eid, 'CWUser')
+ user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups,
+ properties=properties)
user['login'] = login # cache login
return user
@@ -447,13 +450,13 @@
self.close()
except:
pass
-
+
def describe(self, eid):
return self._repo.describe(self.sessionid, eid)
-
+
def close(self):
"""Close the connection now (rather than whenever __del__ is called).
-
+
The connection will be unusable from this point forward; an Error (or
subclass) exception will be raised if any operation is attempted with
the connection. The same applies to all cursor objects trying to use the
@@ -469,7 +472,7 @@
"""Commit any pending transaction to the database. Note that if the
database supports an auto-commit feature, this must be initially off. An
interface method may be provided to turn it back on.
-
+
Database modules that do not support transactions should implement this
method with void functionality.
"""
@@ -480,7 +483,7 @@
def rollback(self):
"""This method is optional since not all databases provide transaction
support.
-
+
In case a database does provide transactions this method causes the the
database to roll back to the start of any pending transaction. Closing
a connection without committing the changes first will cause an implicit
@@ -514,7 +517,7 @@
support is implemented (see also the connection's rollback() and commit()
methods.)
"""
-
+
def __init__(self, connection, repo, req=None):
"""This read-only attribute return a reference to the Connection
object on which the cursor was created.
@@ -526,7 +529,7 @@
"""This read/write attribute specifies the number of rows to fetch at a
time with fetchmany(). It defaults to 1 meaning to fetch a single row
at a time.
-
+
Implementations must observe this value with respect to the fetchmany()
method, but are free to interact with the database a single row at a
time. It may also be used in the implementation of executemany().
@@ -539,7 +542,7 @@
self._closed = None
self._index = 0
-
+
def close(self):
"""Close the cursor now (rather than whenever __del__ is called). The
cursor will be unusable from this point forward; an Error (or subclass)
@@ -547,30 +550,30 @@
"""
self._closed = True
-
+
def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
"""Prepare and execute a database operation (query or command).
Parameters may be provided as sequence or mapping and will be bound to
variables in the operation. Variables are specified in a
database-specific notation (see the module's paramstyle attribute for
details).
-
+
A reference to the operation will be retained by the cursor. If the
same operation object is passed in again, then the cursor can optimize
its behavior. This is most effective for algorithms where the same
operation is used, but different parameters are bound to it (many
times).
-
+
For maximum efficiency when reusing an operation, it is best to use the
setinputsizes() method to specify the parameter types and sizes ahead
of time. It is legal for a parameter to not match the predefined
information; the implementation should compensate, possibly with a loss
of efficiency.
-
+
The parameters may also be specified as list of tuples to e.g. insert
multiple rows in a single operation, but this kind of usage is
depreciated: executemany() should be used instead.
-
+
Return values are not defined by the DB-API, but this here it returns a
ResultSet object.
"""
@@ -579,25 +582,25 @@
self.req.decorate_rset(res)
self._index = 0
return res
-
+
def executemany(self, operation, seq_of_parameters):
"""Prepare a database operation (query or command) and then execute it
against all parameter sequences or mappings found in the sequence
seq_of_parameters.
-
+
Modules are free to implement this method using multiple calls to the
execute() method or by using array operations to have the database
process the sequence as a whole in one call.
-
+
Use of this method for an operation which produces one or more result
sets constitutes undefined behavior, and the implementation is
permitted (but not required) to raise an exception when it detects that
a result set has been created by an invocation of the operation.
-
+
The same comments as for execute() also apply accordingly to this
method.
-
+
Return values are not defined.
"""
for parameters in seq_of_parameters:
@@ -610,7 +613,7 @@
def fetchone(self):
"""Fetch the next row of a query result set, returning a single
sequence, or None when no more data is available.
-
+
An Error (or subclass) exception is raised if the previous call to
execute*() did not produce any result set or no call was issued yet.
"""
@@ -620,21 +623,21 @@
self._index += 1
return row
-
+
def fetchmany(self, size=None):
"""Fetch the next set of rows of a query result, returning a sequence
of sequences (e.g. a list of tuples). An empty sequence is returned
when no more rows are available.
-
+
The number of rows to fetch per call is specified by the parameter. If
it is not given, the cursor's arraysize determines the number of rows
to be fetched. The method should try to fetch as many rows as indicated
by the size parameter. If this is not possible due to the specified
number of rows not being available, fewer rows may be returned.
-
+
An Error (or subclass) exception is raised if the previous call to
execute*() did not produce any result set or no call was issued yet.
-
+
Note there are performance considerations involved with the size
parameter. For optimal performance, it is usually best to use the
arraysize attribute. If the size parameter is used, then it is best
@@ -648,12 +651,12 @@
self._index += size
return rows
-
+
def fetchall(self):
"""Fetch all (remaining) rows of a query result, returning them as a
sequence of sequences (e.g. a list of tuples). Note that the cursor's
arraysize attribute can affect the performance of this operation.
-
+
An Error (or subclass) exception is raised if the previous call to
execute*() did not produce any result set or no call was issued yet.
"""
@@ -669,39 +672,39 @@
def setinputsizes(self, sizes):
"""This can be used before a call to execute*() to predefine memory
areas for the operation's parameters.
-
+
sizes is specified as a sequence -- one item for each input parameter.
The item should be a Type Object that corresponds to the input that
will be used, or it should be an integer specifying the maximum length
of a string parameter. If the item is None, then no predefined memory
area will be reserved for that column (this is useful to avoid
predefined areas for large inputs).
-
+
This method would be used before the execute*() method is invoked.
-
+
Implementations are free to have this method do nothing and users are
free to not use it.
"""
pass
-
+
def setoutputsize(self, size, column=None):
"""Set a column buffer size for fetches of large columns (e.g. LONGs,
BLOBs, etc.). The column is specified as an index into the result
sequence. Not specifying the column will set the default size for all
large columns in the cursor.
-
+
This method would be used before the execute*() method is invoked.
-
+
Implementations are free to have this method do nothing and users are
free to not use it.
- """
+ """
pass
-
+
class LogCursor(Cursor):
"""override the standard cursor to log executed queries"""
-
+
def execute(self, operation, parameters=None, eid_key=None, build_descr=True):
"""override the standard cursor to log executed queries"""
tstart, cstart = time(), clock()
diff -r 292b7989b166 -r ddf4f2d8d51c debian.hardy/compat
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/compat Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,1 @@
+5
diff -r 292b7989b166 -r ddf4f2d8d51c debian.hardy/control
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/control Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,131 @@
+Source: cubicweb
+Section: web
+Priority: optional
+Maintainer: Logilab S.A.
+Uploaders: Sylvain Thenault ,
+ Julien Jehannet ,
+ Aurélien Campéas
+Build-Depends: debhelper (>= 5), python-dev (>=2.4), python-central (>= 0.5)
+Standards-Version: 3.8.0
+Homepage: http://www.cubicweb.org
+XS-Python-Version: >= 2.4, << 2.6
+
+
+Package: cubicweb
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-twisted (= ${source:Version}), cubicweb-client (= ${source:Version})
+XB-Recommends: (postgresql, postgresql-plpython, postgresql-contrib) | mysql | sqlite3
+Recommends: postgresql | mysql | sqlite3
+Description: the complete CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package will install all the components you need to run cubicweb on
+ a single machine. You can also deploy cubicweb by running the different
+ process on different computers, in which case you need to install the
+ corresponding packages on the different hosts.
+
+
+Package: cubicweb-server
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Conflicts: cubicweb-multisources
+Replaces: cubicweb-multisources
+Provides: cubicweb-multisources
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-indexer (>= 0.6.1), python-psycopg2 | python-mysqldb | python-pysqlite2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: server part of the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the repository server part of the system.
+ .
+ This package provides the repository server part of the library and
+ necessary shared data files such as the schema library.
+
+
+Package: cubicweb-twisted
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Provides: cubicweb-web-frontend
+Depends: ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web2
+Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Description: twisted-based web interface for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a twisted based HTTP server to serve
+ the adaptative web interface (see cubicweb-web package).
+ .
+ This package provides only the twisted server part of the library.
+
+
+Package: cubicweb-web
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-docutils, python-vobject, python-elementtree
+Recommends: fckeditor
+Description: web interface library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides an adaptative web interface to the CubicWeb server.
+ Install the cubicweb-twisted package to serve this interface via HTTP.
+ .
+ This package provides the web interface part of the library and
+ necessary shared data files such as defaut views, images...
+
+
+Package: cubicweb-common
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, python-logilab-mtconverter (>= 0.6.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml, python-logilab-common (>= 0.38.1), python-yams (>= 0.20.2), python-rql (>= 0.20.2), python-simplejson (>= 1.3)
+Recommends: python-psyco
+Conflicts: cubicweb-core
+Replaces: cubicweb-core
+Description: common library for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the common parts of the library used by both server
+ code and web application code.
+
+
+Package: cubicweb-ctl
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version})
+Description: tool to manage the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a control script to manage (create, upgrade, start,
+ stop, etc) CubicWeb applications. It also include the init.d script
+ to automatically start and stop CubicWeb applications on boot or shutdown.
+
+
+Package: cubicweb-client
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-ctl (= ${source:Version}), pyro
+Description: RQL command line client for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides a RQL (Relation Query Language) command line client using
+ pyro to connect to a repository server.
+
+
+Package: cubicweb-dev
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, cubicweb-server (= ${source:Version}), cubicweb-web (= ${source:Version}), python-pysqlite2
+Suggests: w3c-dtd-xhtml
+Description: tests suite and development tools for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the CubicWeb tests suite and some development tools
+ helping in the creation of application.
+
+
+Package: cubicweb-documentation
+Architecture: all
+Recommends: doc-base
+Description: documentation for the CubicWeb framework
+ CubicWeb is a semantic web application framework.
+ .
+ This package provides the system's documentation.
diff -r 292b7989b166 -r ddf4f2d8d51c debian.hardy/rules
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/debian.hardy/rules Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,78 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+PY_VERSION:=$(shell pyversions -d)
+
+build: build-stamp
+build-stamp:
+ dh_testdir
+ # XXX doesn't work if logilab-doctools, logilab-xml are not in build depends
+ # and I can't get pbuilder find them in its chroot :(
+ # cd doc && make
+ # FIXME cleanup and use sphinx-build as build-depends ?
+ python setup.py build
+ touch build-stamp
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+ rm -rf build
+ #rm -rf debian/cubicweb-*/
+ find . -name "*.pyc" -delete
+ rm -f $(basename $(wildcard debian/*.in))
+ dh_clean
+
+install: build $(basename $(wildcard debian/*.in))
+ dh_testdir
+ dh_testroot
+ dh_clean
+ dh_installdirs
+
+ #python setup.py install_lib --no-compile --install-dir=debian/cubicweb-common/usr/lib/python2.4/site-packages/
+ python setup.py -q install --no-compile --prefix=debian/tmp/usr
+
+ # Put all the python library and data in cubicweb-common
+ # and scripts in cubicweb-server
+ dh_install -vi
+ #dh_lintian XXX not before debhelper 7
+
+ # Remove unittests directory (should be available in cubicweb-dev only)
+ rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
+ rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
+ rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
+ rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
+
+ # cubes directory must be managed as a valid python module
+ touch debian/cubicweb-common/usr/share/cubicweb/cubes/__init__.py
+
+%: %.in
+ sed "s/PY_VERSION/${PY_VERSION}/g" < $< > $@
+
+# Build architecture-independent files here.
+binary-indep: build install
+ dh_testdir
+ dh_testroot -i
+ dh_pycentral -i
+ dh_installinit -i -n --name cubicweb -u"defaults 99"
+ dh_installlogrotate -i
+ dh_installdocs -i -A README
+ dh_installman -i
+ dh_installchangelogs -i
+ dh_link -i
+ dh_compress -i -X.py -X.ini -X.xml
+ dh_fixperms -i
+ dh_installdeb -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+binary-arch:
+
+binary: binary-indep
+.PHONY: build clean binary binary-indep binary-arch
+
diff -r 292b7989b166 -r ddf4f2d8d51c debian/changelog
--- a/debian/changelog Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/changelog Wed Jun 03 19:50:34 2009 +0200
@@ -1,3 +1,51 @@
+cubicweb (3.2.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Nicolas Chauvat Wed, 27 May 2009 12:31:49 +0200
+
+cubicweb (3.2.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Aurélien Campéas Mon, 25 May 2009 16:45:00 +0200
+
+cubicweb (3.2.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault Thu, 14 May 2009 12:31:06 +0200
+
+cubicweb (3.1.4-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Aurélien Campéas Mon, 06 Apr 2009 14:30:00 +0200
+
+cubicweb (3.1.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault Mon, 06 Apr 2009 08:52:27 +0200
+
+cubicweb (3.1.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Aurélien Campéas Wed, 10 Mar 2009 12:30:00 +0100
+
+cubicweb (3.1.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Aurélien Campéas Wed, 9 Mar 2009 18:32:00 +0100
+
+cubicweb (3.1.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault Wed, 25 Feb 2009 18:41:47 +0100
+
cubicweb (3.0.10-1) unstable; urgency=low
* merge cubicweb-core package into cubicweb-common
diff -r 292b7989b166 -r ddf4f2d8d51c debian/control
--- a/debian/control Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/control Wed Jun 03 19:50:34 2009 +0200
@@ -1,15 +1,15 @@
Source: cubicweb
Section: web
Priority: optional
-Maintainer: Logilab Packaging Team
+Maintainer: Logilab S.A.
Uploaders: Sylvain Thenault ,
- Julien Jehannet
+ Julien Jehannet ,
+ Aurélien Campéas
Build-Depends: debhelper (>= 7), python-dev (>=2.4), python-central (>= 0.5)
Standards-Version: 3.8.0
Homepage: http://www.cubicweb.org
XS-Python-Version: >= 2.4, << 2.6
-
Package: cubicweb
Architecture: all
XB-Python-Version: ${python:Versions}
@@ -54,14 +54,14 @@
This package provides a twisted based HTTP server to serve
the adaptative web interface (see cubicweb-web package).
.
- This package provides only the twisted server part of the library.
+ This package provides only the twisted server part of the library.
Package: cubicweb-web
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-docutils, python-vobject, python-elementtree
-Recommends: fckeditor
+Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree
+Recommends: python-docutils, python-vobject, fckeditor
Description: web interface library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -75,8 +75,8 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, python-logilab-mtconverter (>= 0.4.0), python-simpletal (>= 4.0), graphviz, gettext, python-lxml, python-logilab-common (>= 0.37.2), python-yams (>= 0.20.2), python-rql (>= 0.20.2)
-Recommends: python-psyco
+Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.0)
+Recommends: python-simpletal (>= 4.0), python-lxml
Conflicts: cubicweb-core
Replaces: cubicweb-core
Description: common library for the CubicWeb framework
@@ -105,7 +105,7 @@
Description: RQL command line client for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
- This package provides a RQL (Relation Query Language) command line client using
+ This package provides a RQL (Relation Query Language) command line client using
pyro to connect to a repository server.
diff -r 292b7989b166 -r ddf4f2d8d51c debian/cubicweb-common.install.in
--- a/debian/cubicweb-common.install.in Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/cubicweb-common.install.in Wed Jun 03 19:50:34 2009 +0200
@@ -1,17 +1,5 @@
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/common/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/entities/ usr/lib/PY_VERSION/site-packages/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/ usr/lib/PY_VERSION/site-packages/cubicweb
debian/tmp/usr/share/cubicweb/cubes/shared/i18n usr/share/cubicweb/cubes/shared/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/rset.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/gettext.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/toolsutils.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/cwvreg.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/_exceptions.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/schemaviewer.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/dbapi.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/cwconfig.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/__init__.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/md5crypt.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/schema.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/interfaces.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/vregistry.py usr/share/pyshared/cubicweb
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/__pkginfo__.py usr/share/pyshared/cubicweb
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/*.py usr/share/pyshared/cubicweb
diff -r 292b7989b166 -r ddf4f2d8d51c debian/cubicweb-ctl.bash_completion
--- a/debian/cubicweb-ctl.bash_completion Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/cubicweb-ctl.bash_completion Wed Jun 03 19:50:34 2009 +0200
@@ -75,7 +75,7 @@
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$options $instances' -- "$cur"))
;;
# commands with template as argument
- i18nupdate)
+ i18ncube)
cubes="$("$ec" listcubes 2>/dev/null)" || cubes=""
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$options $cubes' -- "$cur"))
;;
@@ -86,12 +86,12 @@
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$options $instances' -- "$cur"))
;;
# generic commands without argument
- list|newtemplate|i18nlibupdate|live-server)
+ list|newtemplate|i18ncubicweb|live-server)
options="$("$ec" listcommands "$cmd" 2>/dev/null)" || options=""
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$options $instances' -- "$cur"))
;;
# generic commands without option
- shell|i18ncompile|delete|status|schema-sync)
+ shell|i18ninstance|delete|status|schema-sync)
instances="$("$ec" listinstances 2>/dev/null)" || instances=""
COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$options $instances' -- "$cur"))
;;
diff -r 292b7989b166 -r ddf4f2d8d51c debian/cubicweb-ctl.postinst
--- a/debian/cubicweb-ctl.postinst Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/cubicweb-ctl.postinst Wed Jun 03 19:50:34 2009 +0200
@@ -13,25 +13,29 @@
if [ "$1" = configure ]; then
# XXX bw compat: erudi -> cubicweb migration
if [ -e "/etc/erudi.d/" ]; then
- mv /etc/erudi.d/* /etc/cubicweb.d/
- echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/'
- sed -i s/ginco/cubicweb/g /etc/*/*.py
- sed -i s/erudi/cubicweb/ */*.conf
+ mv /etc/erudi.d/* /etc/cubicweb.d/ && (
+ echo 'moved /etc/erudi.d/* to /etc/cubicweb.d/'
+ sed -i s/ginco/cubicweb/g /etc/*/*.py
+ sed -i s/erudi/cubicweb/ */*.conf
+ ) || true # empty dir
fi
if [ -e "/var/log/erudi/" ]; then
- mv /var/log/erudi/* /var/log/cubicweb/
- echo 'moved /var/log/erudi/* to /var/log/cubicweb/'
+ mv /var/log/erudi/* /var/log/cubicweb/ && (
+ echo 'moved /var/log/erudi/* to /var/log/cubicweb/'
+ ) || true # empty dir
fi
if [ -e "/var/lib/erudi/backup" ]; then
- mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/
- echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/'
+ mv /var/lib/erudi/backup/* /var/lib/cubicweb/backup/ && (
+ echo 'moved /var/lib/erudi/backup/* to /var/lib/cubicweb/backup/'
+ ) || true # empty dir
fi
if [ -e "/var/lib/erudi/instances" ]; then
- mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/
- echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/'
+ mv /var/lib/erudi/instances/* /var/lib/cubicweb/instances/ && (
+ echo 'moved /var/lib/erudi/instances/* to /var/lib/cubicweb/instances/'
+ ) || true # empty dir
fi
fi
-
+
#DEBHELPER#
-
+
exit 0
diff -r 292b7989b166 -r ddf4f2d8d51c debian/cubicweb-dev.install.in
--- a/debian/cubicweb-dev.install.in Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/cubicweb-dev.install.in Wed Jun 03 19:50:34 2009 +0200
@@ -1,7 +1,2 @@
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/devtools/ usr/lib/PY_VERSION/site-packages/cubicweb/
debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/skeleton/ usr/lib/PY_VERSION/site-packages/cubicweb/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/test usr/lib/PY_VERSION/site-packages/cubicweb/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/common/test usr/lib/PY_VERSION/site-packages/cubicweb/common/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/test usr/lib/PY_VERSION/site-packages/cubicweb/server/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
-debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/web/test usr/lib/PY_VERSION/site-packages/cubicweb/web/
diff -r 292b7989b166 -r ddf4f2d8d51c debian/cubicweb-web.postinst
--- a/debian/cubicweb-web.postinst Wed Jun 03 19:49:44 2009 +0200
+++ /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
diff -r 292b7989b166 -r ddf4f2d8d51c debian/rules
--- a/debian/rules Wed Jun 03 19:49:44 2009 +0200
+++ b/debian/rules Wed Jun 03 19:50:34 2009 +0200
@@ -39,16 +39,22 @@
# Put all the python library and data in cubicweb-common
# and scripts in cubicweb-server
dh_install -vi
+ # cwctl in the cubicweb-ctl package
+ rm -f debian/cubicweb-common/usr/share/pyshared/cubicweb/cwctl.py
+ # hercule in the cubicweb-client package
+ rm -f debian/cubicweb-common/usr/share/pyshared/cubicweb/hercule.py
+
dh_lintian
# Remove unittests directory (should be available in cubicweb-dev only)
- rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/server/test
- rm -rf debian/cubicweb-server/usr/lib/${PY_VERSION}/site-packages/cubicweb/sobjects/test
- rm -rf debian/cubicweb-web/usr/lib/${PY_VERSION}/site-packages/cubicweb/web/test
- rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
+ rm -rf debian/cubicweb-server/usr/share/pyshared/cubicweb/server/test
+ rm -rf debian/cubicweb-server/usr/share/pyshared/cubicweb/sobjects/test
+ rm -rf debian/cubicweb-dev/usr/share/pyshared/cubicweb/devtools/test
+ rm -rf debian/cubicweb-web/usr/share/pyshared/cubicweb/web/test
+ rm -rf debian/cubicweb-common/usr/share/pyshared/cubicweb/common/test
+ rm -rf debian/cubicweb-common/usr/share/pyshared/cubicweb/entities/test
# cubes directory must be managed as a valid python module
- ls -l debian/cubicweb-common/usr/share/cubicweb/cubes
touch debian/cubicweb-common/usr/share/cubicweb/cubes/__init__.py
%: %.in
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/__init__.py
--- a/devtools/__init__.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/__init__.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,19 +1,20 @@
"""Test tools for cubicweb
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import os
import logging
+from datetime import timedelta
from os.path import (abspath, join, exists, basename, dirname, normpath, split,
isfile, isabs)
-from mx.DateTime import strptime, DateTimeDelta
-
from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError
+from cubicweb.utils import strptime
from cubicweb.toolsutils import read_config
from cubicweb.cwconfig import CubicWebConfiguration, merge_options
from cubicweb.server.serverconfig import ServerConfiguration
@@ -59,10 +60,10 @@
'group': 'main', 'inputlevel': 1,
}),
))
-
+
if not os.environ.get('APYCOT_ROOT'):
REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes'))
-
+
def __init__(self, appid, log_threshold=logging.CRITICAL+10):
ServerConfiguration.__init__(self, appid)
self.global_set_option('log-file', None)
@@ -71,7 +72,7 @@
self.load_cwctl_plugins()
anonymous_user = TwistedConfiguration.anonymous_user.im_func
-
+
@property
def apphome(self):
if exists(self.appid):
@@ -79,7 +80,7 @@
# application cube test
return abspath('..')
appdatahome = apphome
-
+
def main_config_file(self):
"""return application's control configuration file"""
return join(self.apphome, '%s.conf' % self.name)
@@ -116,7 +117,7 @@
if not sources:
sources = DEFAULT_SOURCES
return sources
-
+
def load_defaults(self):
super(TestServerConfiguration, self).load_defaults()
# note: don't call global set option here, OptionManager may not yet be initialized
@@ -146,25 +147,25 @@
def available_languages(self, *args):
return ('en', 'fr', 'de')
-
+
def ext_resources_file(self):
"""return application's external resources file"""
return join(self.apphome, 'data', 'external_resources')
-
+
def pyro_enabled(self):
# but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads
return True
class ApptestConfiguration(BaseApptestConfiguration):
-
+
def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
self.init_repository = sourcefile is None
self.sourcefile = sourcefile
import re
self.global_set_option('embed-allowed', re.compile('.*'))
-
+
class RealDatabaseConfiguration(ApptestConfiguration):
init_repository = False
@@ -180,7 +181,7 @@
'password': u'gingkow',
},
}
-
+
def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
ApptestConfiguration.__init__(self, appid)
self.init_repository = False
@@ -191,7 +192,7 @@
By default, we run tests with the sqlite DB backend.
One may use its own configuration by just creating a
'sources' file in the test directory from wich tests are
- launched.
+ launched.
"""
self._sources = self.sourcesdef
return self._sources
@@ -220,11 +221,11 @@
"""
return type('MyRealDBConfig', (RealDatabaseConfiguration,),
{'sourcesdef': read_config(filename)})
-
+
class LivetestConfiguration(BaseApptestConfiguration):
init_repository = False
-
+
def __init__(self, cube=None, sourcefile=None, pyro_name=None,
log_threshold=logging.CRITICAL):
TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold)
@@ -254,12 +255,14 @@
return False
CubicWebConfiguration.cls_adjust_sys_path()
-
+
def install_sqlite_path(querier):
"""This patch hotfixes the following sqlite bug :
- http://www.sqlite.org/cvstrac/tktview?tn=1327,33
(some dates are returned as strings rather thant date objects)
"""
+ if hasattr(querier.__class__, '_devtools_sqlite_patched'):
+ return # already monkey patched
def wrap_execute(base_execute):
def new_execute(*args, **kwargs):
rset = base_execute(*args, **kwargs)
@@ -269,6 +272,7 @@
for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)):
if vtype in ('Date', 'Datetime') and type(value) is unicode:
found_date = True
+ value = value.rsplit('.', 1)[0]
try:
row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
except:
@@ -282,13 +286,13 @@
row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
if vtype == 'Interval' and type(value) is int:
found_date = True
- row[cellindex] = DateTimeDelta(0, 0, 0, value)
+ row[cellindex] = timedelta(0, value, 0) # XXX value is in number of seconds?
if not found_date:
break
return rset
return new_execute
querier.__class__.execute = wrap_execute(querier.__class__.execute)
-
+ querier.__class__._devtools_sqlite_patched = True
def init_test_database(driver='sqlite', configdir='data', config=None,
vreg=None):
@@ -325,21 +329,21 @@
pass
if removecube:
try:
- os.remove('%s-cube' % dbfile)
+ os.remove('%s-template' % dbfile)
except OSError:
pass
-
+
def init_test_database_sqlite(config, source, vreg=None):
"""initialize a fresh sqlite databse used for testing purpose"""
import shutil
# remove database file if it exists (actually I know driver == 'sqlite' :)
dbfile = source['system']['db-name']
cleanup_sqlite(dbfile)
- cube = '%s-cube' % dbfile
- if exists(cube):
- shutil.copy(cube, dbfile)
+ template = '%s-template' % dbfile
+ if exists(template):
+ shutil.copy(template, dbfile)
else:
# initialize the database
from cubicweb.server import init_repository
init_repository(config, interactive=False, vreg=vreg)
- shutil.copy(dbfile, cube)
+ shutil.copy(dbfile, template)
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/_apptest.py
--- a/devtools/_apptest.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/_apptest.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""Hidden internals for the devtools.apptest module
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -20,11 +21,11 @@
from cubicweb.devtools import ApptestConfiguration, init_test_database
from cubicweb.devtools.fake import FakeRequest
-
-SYSTEM_ENTITIES = ('EGroup', 'EUser',
- 'EFRDef', 'ENFRDef',
- 'EConstraint', 'EConstraintType', 'EProperty',
- 'EEType', 'ERType',
+
+SYSTEM_ENTITIES = ('CWGroup', 'CWUser',
+ 'CWAttribute', 'CWRelation',
+ 'CWConstraint', 'CWConstraintType', 'CWProperty',
+ 'CWEType', 'CWRType',
'State', 'Transition', 'TrInfo',
'RQLExpression',
)
@@ -35,7 +36,7 @@
'is', 'is_instance_of', 'owned_by', 'created_by', 'specializes',
# workflow related
'state_of', 'transition_of', 'initial_state', 'allowed_transition',
- 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
+ 'destination_state', 'in_state', 'wf_info_for', 'from_state', 'to_state',
'condition',
# permission
'in_group', 'require_group', 'require_permission',
@@ -46,11 +47,11 @@
'relation_type', 'from_entity', 'to_entity',
'constrained_by', 'cstrtype', 'widget',
# deducted from other relations
- 'primary_email',
+ 'primary_email',
)
def unprotected_entities(app_schema, strict=False):
- """returned a Set of each non final entity type, excluding EGroup, and EUser...
+ """returned a Set of each non final entity type, excluding CWGroup, and CWUser...
"""
if strict:
protected_entities = yams.schema.BASE_TYPES
@@ -58,16 +59,17 @@
protected_entities = yams.schema.BASE_TYPES.union(set(SYSTEM_ENTITIES))
entities = set(app_schema.entities())
return entities - protected_entities
-
+
def ignore_relations(*relations):
+ global SYSTEM_RELATIONS
SYSTEM_RELATIONS += relations
class TestEnvironment(object):
"""TestEnvironment defines a context (e.g. a config + a given connection) in
which the tests are executed
"""
-
+
def __init__(self, appid, reporter=None, verbose=False,
configcls=ApptestConfiguration, requestcls=FakeRequest):
config = configcls(appid)
@@ -83,14 +85,13 @@
self.restore_database()
if verbose:
print "init done"
- login = source['db-user']
config.repository = lambda x=None: self.repo
self.app = CubicWebPublisher(config, vreg=vreg)
self.verbose = verbose
schema = self.vreg.schema
# else we may run into problems since email address are ususally share in app tests
# XXX should not be necessary anymore
- schema.rschema('primary_email').set_rproperty('EUser', 'EmailAddress', 'composite', False)
+ schema.rschema('primary_email').set_rproperty('CWUser', 'EmailAddress', 'composite', False)
self.deletable_entities = unprotected_entities(schema)
def restore_database(self):
@@ -114,12 +115,12 @@
self.cnx.vreg = self.vreg
self.cnx.login = source['db-user']
self.cnx.password = source['db-password']
-
+
def create_user(self, login, groups=('users',), req=None):
req = req or self.create_request()
cursor = self._orig_cnx.cursor(req)
- rset = cursor.execute('INSERT EUser X: X login %(login)s, X upassword %(passwd)s,'
+ rset = cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s,'
'X in_state S WHERE S name "activated"',
{'login': unicode(login), 'passwd': login.encode('utf8')})
user = rset.get_entity(0, 0)
@@ -140,7 +141,7 @@
if login == self.vreg.config.anonymous_user()[0]:
self.cnx.anonymous_connection = True
return self.cnx
-
+
def restore_connection(self):
if not self.cnx is self._orig_cnx:
try:
@@ -157,7 +158,7 @@
"""
req = req or self.create_request(rql=rql)
return self.cnx.cursor(req).execute(unicode(rql), args, eidkey)
-
+
def create_request(self, rql=None, **kwargs):
"""executes , builds a resultset, and returns a
couple (rset, req) where req is a FakeRequest
@@ -167,14 +168,14 @@
req = self.requestcls(self.vreg, form=kwargs)
req.set_connection(self.cnx)
return req
-
+
def get_rset_and_req(self, rql, optional_args=None, args=None, eidkey=None):
"""executes , builds a resultset, and returns a
couple (rset, req) where req is a FakeRequest
"""
return (self.execute(rql, args, eidkey),
self.create_request(rql=rql, **optional_args or {}))
-
+
def check_view(self, rql, vid, optional_args, template='main'):
"""checks if vreg.view() raises an exception in this environment
@@ -183,7 +184,7 @@
"""
return self.call_view(vid, rql,
template=template, optional_args=optional_args)
-
+
def call_view(self, vid, rql, template='main', optional_args=None):
"""shortcut for self.vreg.view()"""
assert template
@@ -227,23 +228,22 @@
yield action
class ExistingTestEnvironment(TestEnvironment):
-
+
def __init__(self, appid, sourcefile, verbose=False):
config = ApptestConfiguration(appid, sourcefile=sourcefile)
if verbose:
print "init test database ..."
source = config.sources()['system']
self.vreg = CubicWebRegistry(config)
- repo, self.cnx = init_test_database(driver=source['db-driver'],
- vreg=self.vreg)
+ self.cnx = init_test_database(driver=source['db-driver'],
+ vreg=self.vreg)[1]
if verbose:
- print "init done"
+ print "init done"
self.app = CubicWebPublisher(config, vreg=self.vreg)
self.verbose = verbose
# this is done when the publisher is opening a connection
self.cnx.vreg = self.vreg
- login = source['db-user']
-
+
def setup(self, config=None):
"""config is passed by TestSuite but is ignored in this environment"""
cursor = self.cnx.cursor()
@@ -255,4 +255,3 @@
cursor.execute('DELETE Any X WHERE X eid > %(x)s', {'x' : self.last_eid}, eid_key='x')
print "cleaning done"
self.cnx.commit()
-
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/apptest.py
--- a/devtools/apptest.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/apptest.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""This module provides misc utilities to test applications
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -14,6 +15,8 @@
from logilab.common.pytest import nocoverage
from logilab.common.umessage import message_from_string
+from logilab.common.deprecation import deprecated_function
+
from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
from cubicweb.devtools._apptest import TestEnvironment
from cubicweb.devtools.fake import FakeRequest
@@ -30,11 +33,11 @@
@property
def message(self):
return message_from_string(self.msg)
-
+
def __repr__(self):
return '' % (','.join(self.recipients),
self.message.get('Subject'))
-
+
class MockSMTP:
def __init__(self, server, port):
pass
@@ -98,7 +101,7 @@
env = None
configcls = ApptestConfiguration
requestcls = FakeRequest
-
+
# user / session management ###############################################
def user(self, req=None):
@@ -116,13 +119,13 @@
def restore_connection(self):
self.env.restore_connection()
-
+
# db api ##################################################################
@nocoverage
def cursor(self, req=None):
return self.env.cnx.cursor(req or self.request())
-
+
@nocoverage
def execute(self, *args, **kwargs):
return self.env.execute(*args, **kwargs)
@@ -130,19 +133,19 @@
@nocoverage
def commit(self):
self.env.cnx.commit()
-
+
@nocoverage
def rollback(self):
try:
self.env.cnx.rollback()
except ProgrammingError:
pass
-
+
# other utilities #########################################################
def set_debug(self, debugmode):
from cubicweb.server import set_debug
set_debug(debugmode)
-
+
@property
def config(self):
return self.vreg.config
@@ -150,7 +153,7 @@
def session(self):
"""return current server side session (using default manager account)"""
return self.env.repo._sessions[self.env.cnx.sessionid]
-
+
def request(self, *args, **kwargs):
"""return a web interface request"""
return self.env.create_request(*args, **kwargs)
@@ -158,16 +161,16 @@
@nocoverage
def rset_and_req(self, *args, **kwargs):
return self.env.get_rset_and_req(*args, **kwargs)
-
+
def entity(self, rql, args=None, eidkey=None, req=None):
return self.execute(rql, args, eidkey, req=req).get_entity(0, 0)
-
+
def etype_instance(self, etype, req=None):
req = req or self.request()
e = self.env.vreg.etype_class(etype)(req, None, None)
e.eid = None
return e
-
+
def add_entity(self, etype, **kwargs):
rql = ['INSERT %s X' % etype]
@@ -183,15 +186,15 @@
sub_rql = []
for key, value in kwargs.iteritems():
# entities
- if hasattr(value, 'eid'):
+ if hasattr(value, 'eid'):
new_value = "%s__" % key.upper()
-
+
entities[new_value] = value.eid
rql_args[new_value] = value.eid
-
+
sub_rql.append("X %s %s" % (key, new_value))
# final attributes
- else:
+ else:
sub_rql.append('X %s %%(%s)s' % (key, key))
rql_args[key] = value
rql.append(', '.join(sub_rql))
@@ -213,11 +216,18 @@
self.vreg.config.global_set_option(optname, value)
def pviews(self, req, rset):
- return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset))
-
+ return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset))
+
def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
if a.category not in skipcategories]
+
+ def pactions_by_cats(self, req, rset, categories=('addrelated',)):
+ return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
+ if a.category in categories]
+
+ paddrelactions = deprecated_function(pactions_by_cats)
+
def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
res = {}
for a in self.vreg.possible_vobjects('actions', req, rset):
@@ -225,20 +235,17 @@
res.setdefault(a.category, []).append(a.__class__)
return res
- def paddrelactions(self, req, rset):
- return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset)
- if a.category == 'addrelated']
-
+
def remote_call(self, fname, *args):
"""remote call simulation"""
dump = simplejson.dumps
args = [dump(arg) for arg in args]
- req = self.request(mode='remote', fname=fname, pageid='123', arg=args)
+ req = self.request(fname=fname, pageid='123', arg=args)
ctrl = self.env.app.select_controller('json', req)
return ctrl.publish(), req
# default test setup and teardown #########################################
-
+
def setup_database(self):
pass
@@ -258,7 +265,7 @@
self.setup_database()
self.commit()
MAILBOX[:] = [] # reset mailbox
-
+
@nocoverage
def tearDown(self):
self.rollback()
@@ -349,13 +356,13 @@
"""
__metaclass__ = autorepo
repo_config = None # set a particular config instance if necessary
-
+
# user / session management ###############################################
def create_user(self, user, groups=('users',), password=None, commit=True):
if password is None:
password = user
- eid = self.execute('INSERT EUser X: X login %(x)s, X upassword %(p)s,'
+ eid = self.execute('INSERT CWUser X: X login %(x)s, X upassword %(p)s,'
'X in_state S WHERE S name "activated"',
{'x': unicode(user), 'p': password})[0][0]
groups = ','.join(repr(group) for group in groups)
@@ -363,9 +370,9 @@
{'x': eid})
if commit:
self.commit()
- self.session.reset_pool()
+ self.session.reset_pool()
return eid
-
+
def login(self, login, password=None):
cnx = repo_connect(self.repo, unicode(login), password or login,
ConnectionProperties('inmemory'))
@@ -374,7 +381,7 @@
def current_session(self):
return self.repo._sessions[self.cnxs[-1].sessionid]
-
+
def restore_connection(self):
assert len(self.cnxs) == 1, self.cnxs
cnx = self.cnxs.pop()
@@ -382,7 +389,7 @@
cnx.close()
except Exception, ex:
print "exception occured while closing connection", ex
-
+
# db api ##################################################################
def execute(self, rql, args=None, eid_key=None):
@@ -394,26 +401,27 @@
# application entities for convenience
self.session.set_pool()
return rset
-
+
def commit(self):
self.__commit(self.cnxid)
- self.session.set_pool()
-
+ self.session.set_pool()
+
def rollback(self):
self.__rollback(self.cnxid)
- self.session.set_pool()
-
+ self.session.set_pool()
+
def close(self):
self.__close(self.cnxid)
# other utilities #########################################################
+
def set_debug(self, debugmode):
from cubicweb.server import set_debug
set_debug(debugmode)
-
+
def set_option(self, optname, value):
self.vreg.config.global_set_option(optname, value)
-
+
def add_entity(self, etype, **kwargs):
restrictions = ', '.join('X %s %%(%s)s' % (key, key) for key in kwargs)
rql = 'INSERT %s X' % etype
@@ -427,7 +435,7 @@
user = unicode(config.sources()['system']['db-user'])
passwd = config.sources()['system']['db-password']
return user, passwd
-
+
def close_connections(self):
for cnx in self.cnxs:
try:
@@ -439,10 +447,10 @@
pactions = EnvBasedTC.pactions.im_func
pactionsdict = EnvBasedTC.pactionsdict.im_func
-
+
# default test setup and teardown #########################################
copy_schema = False
-
+
def _prepare(self):
MAILBOX[:] = [] # reset mailbox
if hasattr(self, 'cnxid'):
@@ -452,7 +460,7 @@
self.__commit = repo.commit
self.__rollback = repo.rollback
self.__close = repo.close
- self.cnxid = repo.connect(*self.default_user_password())
+ self.cnxid = self.cnx.sessionid
self.session = repo._sessions[self.cnxid]
# XXX copy schema since hooks may alter it and it may be not fully
# cleaned (missing some schema synchronization support)
@@ -487,18 +495,15 @@
@property
def schema(self):
return self.repo.schema
-
+
def setUp(self):
self._prepare()
self.session.set_pool()
self.maxeid = self.session.system_sql('SELECT MAX(eid) FROM entities').fetchone()[0]
- #self.maxeid = self.execute('Any MAX(X)')
-
- def tearDown(self, close=True):
+
+ def tearDown(self):
self.close_connections()
self.rollback()
self.session.unsafe_execute('DELETE Any X WHERE X eid > %(x)s', {'x': self.maxeid})
self.commit()
- if close:
- self.close()
-
+
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/cwtwill.py
--- a/devtools/cwtwill.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/cwtwill.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,4 +1,10 @@
-"""cubicweb extensions for twill"""
+"""cubicweb extensions for twill
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
import re
from urllib import quote
@@ -24,9 +30,9 @@
# if url is specified linkurl must match
if url and linkurl != url:
continue
- return
+ return
raise AssertionError('link %s (%s) not found' % (text, url))
-
+
def view(rql, vid=''):
"""
@@ -56,7 +62,7 @@
twc.go('view?rql=%s&vid=edition' % quote(rql))
-
+
def setvalue(formname, fieldname, value):
"""
@@ -104,5 +110,5 @@
browser._browser.form = form
browser.submit(submit_button)
-
+
# missing actions: delete, copy, changeview
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/devctl.py
--- a/devtools/devctl.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/devctl.py Wed Jun 03 19:50:34 2009 +0200
@@ -2,24 +2,26 @@
cubes development
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
import sys
-from os import walk, mkdir, chdir, listdir, getcwd
+from datetime import datetime
+from os import mkdir, chdir
from os.path import join, exists, abspath, basename, normpath, split, isdir
from logilab.common import STD_BLACKLIST
from logilab.common.modutils import get_module_files
from logilab.common.textutils import get_csv
+from logilab.common.clcommands import register_commands
-from cubicweb import CW_SOFTWARE_ROOT as BASEDIR
+from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
from cubicweb.__pkginfo__ import version as cubicwebversion
-from cubicweb import BadCommandUsage
-from cubicweb.toolsutils import Command, register_commands, confirm, copy_skeleton
+from cubicweb.toolsutils import Command, confirm, copy_skeleton
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration
@@ -35,11 +37,11 @@
if cube is None:
self._cubes = ()
else:
- self._cubes = self.expand_cubes(self.my_cubes(cube))
+ self._cubes = self.reorder_cubes(self.expand_cubes(self.my_cubes(cube)))
def my_cubes(self, cube):
return (cube,) + self.cube_dependencies(cube) + self.cube_recommends(cube)
-
+
@property
def apphome(self):
return None
@@ -76,7 +78,12 @@
if mod.__file__.startswith(path):
del sys.modules[name]
break
-
+ # fresh rtags
+ from cubicweb import rtags
+ from cubicweb.web import uicfg
+ rtags.RTAGS[:] = []
+ reload(uicfg)
+
def generate_schema_pot(w, cubedir=None):
"""generate a pot file with schema specific i18n messages
@@ -85,30 +92,31 @@
"""
from cubicweb.cwvreg import CubicWebRegistry
cube = cubedir and split(cubedir)[-1]
- config = DevDepConfiguration(cube)
- cleanup_sys_modules(config)
+ libconfig = DevDepConfiguration(cube)
+ libconfig.cleanup_interface_sobjects = False
+ cleanup_sys_modules(libconfig)
if cubedir:
- libschema = config.load_schema()
config = DevCubeConfiguration(cube)
- schema = config.load_schema()
+ config.cleanup_interface_sobjects = False
else:
- schema = config.load_schema()
- libschema = None
- config.cleanup_interface_sobjects = False
+ config = libconfig
+ libconfig = None
+ schema = config.load_schema(remove_unused_rtypes=False)
vreg = CubicWebRegistry(config)
# set_schema triggers objects registrations
vreg.set_schema(schema)
w(DEFAULT_POT_HEAD)
- _generate_schema_pot(w, vreg, schema, libschema=libschema, cube=cube)
-
-def _generate_schema_pot(w, vreg, schema, libschema=None, cube=None):
- from mx.DateTime import now
+ _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube)
+
+
+def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
from cubicweb.common.i18n import add_msg
- w('# schema pot file, generated on %s\n' % now().strftime('%Y-%m-%d %H:%M:%S'))
+ w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
w('# \n')
w('# singular and plural forms for each entity type\n')
w('\n')
- if libschema is not None:
+ if libconfig is not None:
+ libschema = libconfig.load_schema(remove_unused_rtypes=False)
entities = [e for e in schema.entities() if not e in libschema]
else:
entities = schema.entities()
@@ -128,7 +136,7 @@
w('# subject and object forms for each relation type\n')
w('# (no object form for final relation types)\n')
w('\n')
- if libschema is not None:
+ if libconfig is not None:
relations = [r for r in schema.relations() if not r in libschema]
else:
relations = schema.relations()
@@ -143,46 +151,70 @@
add_msg(w, rschema.description)
w('# add related box generated message\n')
w('\n')
+ actionbox = vreg['boxes']['edit_box'][0]
for eschema in schema.entities():
if eschema.is_final():
continue
- entity = vreg.etype_class(eschema)(None, None)
- for x, rschemas in (('subject', eschema.subject_relations()),
+ for role, rschemas in (('subject', eschema.subject_relations()),
('object', eschema.object_relations())):
for rschema in rschemas:
if rschema.is_final():
continue
- for teschema in rschema.targets(eschema, x):
- if defined_in_library(libschema, eschema, rschema, teschema, x):
- continue
- if entity.relation_mode(rschema.type, teschema.type, x) == 'create':
- if x == 'subject':
- label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
- label2 = "creating %s (%s %%(linkto)s %s %s)" % (teschema, eschema, rschema, teschema)
+ if libconfig is not None:
+ librschema = libschema.get(rschema)
+ for teschema in rschema.targets(eschema, role):
+ if libconfig is not None and librschema is not None:
+ if role == 'subject':
+ subjtype, objtype = eschema, teschema
else:
- label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
- label2 = "creating %s (%s %s %s %%(linkto)s)" % (teschema, teschema, rschema, eschema)
+ subjtype, objtype = teschema, eschema
+ if librschema.has_rdef(subjtype, objtype):
+ continue
+ if actionbox.appearsin_addmenu.etype_get(eschema, rschema,
+ role, teschema):
+ if role == 'subject':
+ label = 'add %s %s %s %s' % (eschema, rschema,
+ teschema, role)
+ label2 = "creating %s (%s %%(linkto)s %s %s)" % (
+ teschema, eschema, rschema, teschema)
+ else:
+ label = 'add %s %s %s %s' % (teschema, rschema,
+ eschema, role)
+ label2 = "creating %s (%s %s %s %%(linkto)s)" % (
+ teschema, teschema, rschema, eschema)
add_msg(w, label)
add_msg(w, label2)
- cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
+ #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
done = set()
+ if libconfig is not None:
+ from cubicweb.cwvreg import CubicWebRegistry
+ libvreg = CubicWebRegistry(libconfig)
+ libvreg.set_schema(libschema) # trigger objects registration
+ # prefill done set
+ list(_iter_vreg_objids(libvreg, done))
+ for objid in _iter_vreg_objids(vreg, done):
+ add_msg(w, '%s_description' % objid)
+ add_msg(w, objid)
+
+def _iter_vreg_objids(vreg, done, prefix=None):
for reg, objdict in vreg.items():
for objects in objdict.values():
for obj in objects:
objid = '%s_%s' % (reg, obj.id)
if objid in done:
- continue
- if obj.__module__.startswith(cube) and obj.property_defs:
- add_msg(w, '%s_description' % objid)
- add_msg(w, objid)
+ break
+ if obj.property_defs:
+ yield objid
done.add(objid)
+ break
-
-def defined_in_library(libschema, etype, rtype, tetype, x):
- """return true if the given relation definition exists in cubicweb's library"""
+
+def defined_in_library(etype, rtype, tetype, role):
+ """return true if the given relation definition exists in cubicweb's library
+ """
if libschema is None:
return False
- if x == 'subject':
+ if role == 'subject':
subjtype, objtype = etype, tetype
else:
subjtype, objtype = tetype, etype
@@ -211,11 +243,11 @@
class UpdateCubicWebCatalogCommand(Command):
"""Update i18n catalogs for cubicweb library.
-
+
It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those
files to add translations of newly added messages.
"""
- name = 'i18nlibupdate'
+ name = 'i18ncubicweb'
def run(self, args):
"""run the command with its specific arguments"""
@@ -251,8 +283,12 @@
cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
if lang is not None:
cmd += ' -L %s' % lang
- potfiles.append(join(tempdir, '%s.pot' % id))
- execute(cmd % (potfiles[-1], ' '.join(files)))
+ potfile = join(tempdir, '%s.pot' % id)
+ execute(cmd % (potfile, ' '.join(files)))
+ if exists(potfile):
+ potfiles.append(potfile)
+ else:
+ print 'WARNING: %s file not generated' % potfile
print '******** merging .pot files'
cubicwebpot = join(tempdir, 'cubicweb.pot')
execute('msgcat %s > %s' % (' '.join(potfiles), cubicwebpot))
@@ -272,94 +308,105 @@
print 'you can now edit the following files:'
print '* ' + '\n* '.join(toedit)
print
- print "then you'll have to update cubes catalogs using the i18nupdate command"
+ print "then you'll have to update cubes catalogs using the i18ncube command"
class UpdateTemplateCatalogCommand(Command):
"""Update i18n catalogs for cubes. If no cube is specified, update
catalogs of all registered cubes.
"""
- name = 'i18nupdate'
+ name = 'i18ncube'
arguments = '[...]'
-
+
def run(self, args):
"""run the command with its specific arguments"""
- CUBEDIR = DevCubeConfiguration.cubes_dir()
if args:
- cubes = [join(CUBEDIR, app) for app in args]
+ cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
else:
- cubes = [join(CUBEDIR, app) for app in listdir(CUBEDIR)
- if exists(join(CUBEDIR, app, 'i18n'))]
+ cubes = [DevCubeConfiguration.cube_dir(cube) for cube in DevCubeConfiguration.available_cubes()]
+ cubes = [cubepath for cubepath in cubes if exists(join(cubepath, 'i18n'))]
update_cubes_catalogs(cubes)
+
def update_cubes_catalogs(cubes):
+ toedit = []
+ for cubedir in cubes:
+ if not isdir(cubedir):
+ print 'not a directory', cubedir
+ continue
+ try:
+ toedit += update_cube_catalogs(cubedir)
+ except Exception:
+ import traceback
+ traceback.print_exc()
+ print 'error while updating catalogs for', cubedir
+ # instructions pour la suite
+ print '*' * 72
+ print 'you can now edit the following files:'
+ print '* ' + '\n* '.join(toedit)
+
+
+def update_cube_catalogs(cubedir):
import shutil
from tempfile import mktemp
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
from cubicweb.common.i18n import extract_from_tal, execute
toedit = []
- for cubedir in cubes:
- cube = basename(normpath(cubedir))
- if not isdir(cubedir):
- print 'unknown cube', cube
- continue
- tempdir = mktemp()
- mkdir(tempdir)
- print '*' * 72
- print 'updating %s cube...' % cube
- chdir(cubedir)
- potfiles = [join('i18n', scfile) for scfile in ('entities.pot',)
- if exists(join('i18n', scfile))]
- print '******** extract schema messages'
- schemapot = join(tempdir, 'schema.pot')
- potfiles.append(schemapot)
- # explicit close necessary else the file may not be yet flushed when
- # we'll using it below
- schemapotstream = file(schemapot, 'w')
- generate_schema_pot(schemapotstream.write, cubedir)
- schemapotstream.close()
- print '******** extract TAL messages'
- tali18nfile = join(tempdir, 'tali18n.py')
- extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
- print '******** extract Javascript messages'
- jsfiles = [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
- if jsfiles:
- tmppotfile = join(tempdir, 'js.pot')
- execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s'
- % (tmppotfile, ' '.join(jsfiles)))
- # no pot file created if there are no string to translate
- if exists(tmppotfile):
- potfiles.append(tmppotfile)
- print '******** create cube specific catalog'
- tmppotfile = join(tempdir, 'generated.pot')
- cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
- cubefiles.append(tali18nfile)
- execute('xgettext --no-location --omit-header -k_ -o %s %s'
- % (tmppotfile, ' '.join(cubefiles)))
- if exists(tmppotfile): # doesn't exists of no translation string found
+ cube = basename(normpath(cubedir))
+ tempdir = mktemp()
+ mkdir(tempdir)
+ print '*' * 72
+ print 'updating %s cube...' % cube
+ chdir(cubedir)
+ potfiles = [join('i18n', scfile) for scfile in ('entities.pot',)
+ if exists(join('i18n', scfile))]
+ print '******** extract schema messages'
+ schemapot = join(tempdir, 'schema.pot')
+ potfiles.append(schemapot)
+ # explicit close necessary else the file may not be yet flushed when
+ # we'll using it below
+ schemapotstream = file(schemapot, 'w')
+ generate_schema_pot(schemapotstream.write, cubedir)
+ schemapotstream.close()
+ print '******** extract TAL messages'
+ tali18nfile = join(tempdir, 'tali18n.py')
+ extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
+ print '******** extract Javascript messages'
+ jsfiles = [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
+ if jsfiles:
+ tmppotfile = join(tempdir, 'js.pot')
+ execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s'
+ % (tmppotfile, ' '.join(jsfiles)))
+ # no pot file created if there are no string to translate
+ if exists(tmppotfile):
potfiles.append(tmppotfile)
- potfile = join(tempdir, 'cube.pot')
- print '******** merging .pot files'
- execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
- print '******** merging main pot file with existing translations'
- chdir('i18n')
- for lang in LANGS:
- print '****', lang
- cubepo = '%s.po' % lang
- if not exists(cubepo):
- shutil.copy(potfile, cubepo)
- else:
- execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo))
- ensure_fs_mode(cubepo)
- shutil.move('%snew' % cubepo, cubepo)
- toedit.append(abspath(cubepo))
- # cleanup
- rm(tempdir)
- # instructions pour la suite
- print '*' * 72
- print 'you can now edit the following files:'
- print '* ' + '\n* '.join(toedit)
+ print '******** create cube specific catalog'
+ tmppotfile = join(tempdir, 'generated.pot')
+ cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
+ cubefiles.append(tali18nfile)
+ execute('xgettext --no-location --omit-header -k_ -o %s %s'
+ % (tmppotfile, ' '.join(cubefiles)))
+ if exists(tmppotfile): # doesn't exists of no translation string found
+ potfiles.append(tmppotfile)
+ potfile = join(tempdir, 'cube.pot')
+ print '******** merging .pot files'
+ execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
+ print '******** merging main pot file with existing translations'
+ chdir('i18n')
+ for lang in LANGS:
+ print '****', lang
+ cubepo = '%s.po' % lang
+ if not exists(cubepo):
+ shutil.copy(potfile, cubepo)
+ else:
+ execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo))
+ ensure_fs_mode(cubepo)
+ shutil.move('%snew' % cubepo, cubepo)
+ toedit.append(abspath(cubepo))
+ # cleanup
+ rm(tempdir)
+ return toedit
class LiveServerCommand(Command):
@@ -368,7 +415,7 @@
name = 'live-server'
arguments = ''
options = ()
-
+
def run(self, args):
"""run the command with its specific arguments"""
from cubicweb.devtools.livetest import runserver
@@ -385,6 +432,11 @@
arguments = ''
options = (
+ ("directory",
+ {'short': 'd', 'type' : 'string', 'metavar': '',
+ 'help': 'directory where the new cube should be created',
+ }
+ ),
("verbose",
{'short': 'v', 'type' : 'yn', 'metavar': '',
'default': 'n',
@@ -411,7 +463,7 @@
),
)
-
+
def run(self, args):
if len(args) != 1:
raise BadCommandUsage("exactly one argument (cube name) is expected")
@@ -419,14 +471,20 @@
#if ServerConfiguration.mode != "dev":
# self.fail("you can only create new cubes in development mode")
verbose = self.get('verbose')
- cubedir = ServerConfiguration.CUBES_DIR
- if not isdir(cubedir):
- print "creating apps directory", cubedir
+ cubesdir = self.get('directory')
+ if not cubesdir:
+ cubespath = ServerConfiguration.cubes_search_path()
+ if len(cubespath) > 1:
+ raise BadCommandUsage("can't guess directory where to put the new cube."
+ " Please specify it using the --directory option")
+ cubesdir = cubespath[0]
+ if not isdir(cubesdir):
+ print "creating cubes directory", cubesdir
try:
- mkdir(cubedir)
+ mkdir(cubesdir)
except OSError, err:
- self.fail("failed to create directory %r\n(%s)" % (cubedir, err))
- cubedir = join(cubedir, cubename)
+ self.fail("failed to create directory %r\n(%s)" % (cubesdir, err))
+ cubedir = join(cubesdir, cubename)
if exists(cubedir):
self.fail("%s already exists !" % (cubedir))
skeldir = join(BASEDIR, 'skeleton')
@@ -439,7 +497,7 @@
distname = 'cubicweb-' + distname
else:
distname = 'cubicweb-%s' % cubename.lower()
-
+
longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
if verbose:
longdesc = raw_input('Enter a long description (or nothing if you want to reuse the short one): ')
@@ -451,14 +509,13 @@
dependancies = ', '.join(repr(cube) for cube in includes)
else:
dependancies = ''
- from mx.DateTime import now
context = {'cubename' : cubename,
'distname' : distname,
'shortdesc' : shortdesc,
'longdesc' : longdesc or shortdesc,
'dependancies' : dependancies,
'version' : cubicwebversion,
- 'year' : str(now().year),
+ 'year' : str(datetime.now().year),
'author': self['author'],
'author-email': self['author-email'],
'author-web-site': self['author-web-site'],
@@ -478,7 +535,7 @@
elif ans == 's':
break
return includes
-
+
class ExamineLogCommand(Command):
"""Examine a rql log file.
@@ -497,23 +554,28 @@
name = 'exlog'
options = (
)
-
+
def run(self, args):
if args:
raise BadCommandUsage("no argument expected")
import re
requests = {}
- for line in sys.stdin:
+ for lineno, line in enumerate(sys.stdin):
if not ' WHERE ' in line:
continue
#sys.stderr.write( line )
- rql, time = line.split('--')
- rql = re.sub("(\'\w+': \d*)", '', rql)
- req = requests.setdefault(rql, [])
- time.strip()
- chunks = time.split()
- cputime = float(chunks[-3])
- req.append( cputime )
+ try:
+ rql, time = line.split('--')
+ rql = re.sub("(\'\w+': \d*)", '', rql)
+ if '{' in rql:
+ rql = rql[:rql.index('{')]
+ req = requests.setdefault(rql, [])
+ time.strip()
+ chunks = time.split()
+ cputime = float(chunks[-3])
+ req.append( cputime )
+ except Exception, exc:
+ sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
stat = []
for rql, times in requests.items():
@@ -521,9 +583,12 @@
stat.sort()
stat.reverse()
+
+ total_time = sum(time for time, occ, rql in stat)*0.01
+ print 'Percentage;Cumulative Time;Occurences;Query'
for time, occ, rql in stat:
- print time, occ, rql
-
+ print '%.2f;%.2f;%s;%s' % (time/total_time, time, occ, rql)
+
register_commands((UpdateCubicWebCatalogCommand,
UpdateTemplateCatalogCommand,
LiveServerCommand,
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/fake.py
--- a/devtools/fake.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/fake.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""Fake objects to ease testing of cubicweb without a fully working environment
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -24,13 +25,13 @@
self.apphome = apphome
self._cubes = cubes
self['auth-mode'] = 'cookie'
- self['uid'] = None
+ self['uid'] = None
self['base-url'] = BASE_URL
self['rql-cache-size'] = 100
-
+
def cubes(self, expand=False):
return self._cubes
-
+
def sources(self):
return {}
@@ -41,7 +42,7 @@
self.properties = {'ui.encoding': 'UTF8',
'ui.language': 'en',
}
-
+
def property_value(self, key):
return self.properties[key]
@@ -51,10 +52,10 @@
'views' : [Mock(id='primary'), Mock(id='secondary'),
Mock(id='oneline'), Mock(id='list')],
}
-
+
def registry_objects(self, name, oid=None):
return self._registries[name]
-
+
def etype_class(self, etype):
class Entity(dict):
e_schema = self.schema[etype]
@@ -112,15 +113,15 @@
def set_header(self, header, value):
"""set an output HTTP header"""
pass
-
+
def add_header(self, header, value):
"""set an output HTTP header"""
pass
-
+
def remove_header(self, header):
"""remove an output HTTP header"""
pass
-
+
def get_header(self, header, default=None):
"""return the value associated with the given input header,
raise KeyError if the header is not set
@@ -169,7 +170,7 @@
self.is_internal_session = False
self.is_super_session = self.user.eid == -1
self._query_data = {}
-
+
def execute(self, *args):
pass
def commit(self, *args):
@@ -186,7 +187,7 @@
def set_entity_cache(self, entity):
pass
-
+
class FakeRepo(object):
querier = None
def __init__(self, schema, vreg=None, config=None):
@@ -199,8 +200,9 @@
def internal_session(self):
return FakeSession(self)
-
- def extid2eid(self, source, extid, etype, session, insert=True):
+
+ def extid2eid(self, source, extid, etype, session, insert=True,
+ recreate=False):
try:
return self.extids[extid]
except KeyError:
@@ -213,7 +215,7 @@
self.eids[eid] = extid
source.after_entity_insertion(session, extid, entity)
return eid
-
+
def eid2extid(self, source, eid, session=None):
return self.eids[eid]
@@ -228,7 +230,7 @@
def __init__(self, uri):
self.uri = uri
-
+
class FakePool(object):
def source(self, uri):
return FakeSource(uri)
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/fill.py
--- a/devtools/fill.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/fill.py Wed Jun 03 19:50:34 2009 +0200
@@ -2,16 +2,17 @@
"""This modules defines func / methods for creating test repositories
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
from random import randint, choice
from copy import deepcopy
+from datetime import datetime, date, timedelta
+from decimal import Decimal
-from mx.DateTime import DateTime, DateTimeDelta
-from decimal import Decimal
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
IntervalBoundConstraint)
from rql.utils import decompose_b26 as base_decompose_b26
@@ -33,7 +34,7 @@
if isinstance(cst, StaticVocabularyConstraint):
return cst.vocabulary()
return None
-
+
def get_max_length(eschema, attrname):
"""returns the maximum length allowed for 'attrname'"""
@@ -75,7 +76,7 @@
value = self.__generate_value(attrname, index, **kwargs)
_GENERATED_VALUES.setdefault((self.e_schema.type, attrname), set()).add(value)
return value
-
+
def __generate_value(self, attrname, index, **kwargs):
"""generates a consistent value for 'attrname'"""
attrtype = str(self.e_schema.destination(attrname)).lower()
@@ -100,7 +101,7 @@
if choices is None:
return None
return unicode(choice(choices)) # FIXME
-
+
def generate_string(self, attrname, index, format=None):
"""generates a consistent value for 'attrname' if it's a string"""
# First try to get choices
@@ -133,7 +134,7 @@
def generate_password(self, attrname, index):
"""generates a consistent value for 'attrname' if it's a password"""
return u'toto'
-
+
def generate_integer(self, attrname, index):
"""generates a consistent value for 'attrname' if it's an integer"""
choosed = self.generate_choice(attrname, index)
@@ -145,29 +146,29 @@
else:
maxvalue = maxvalue or index
return randint(minvalue or 0, maxvalue)
-
+
generate_int = generate_integer
-
+
def generate_float(self, attrname, index):
"""generates a consistent value for 'attrname' if it's a float"""
return float(randint(-index, index))
-
+
def generate_decimal(self, attrname, index):
"""generates a consistent value for 'attrname' if it's a float"""
return Decimal(str(self.generate_float(attrname, index)))
-
+
def generate_date(self, attrname, index):
"""generates a random date (format is 'yyyy-mm-dd')"""
- return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28))
+ return date(randint(2000, 2004), randint(1, 12), randint(1, 28))
def generate_time(self, attrname, index):
"""generates a random time (format is ' HH:MM')"""
- return DateTimeDelta(0, 11, index%60) #'11:%02d' % (index % 60)
-
+ return timedelta(0, 11, index%60) #'11:%02d' % (index % 60)
+
def generate_datetime(self, attrname, index):
"""generates a random date (format is 'yyyy-mm-dd HH:MM')"""
- return DateTime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
-
+ return datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
+
def generate_bytes(self, attrname, index, format=None):
# modpython way
@@ -175,7 +176,7 @@
fakefile.filename = "file_%s" % attrname
fakefile.value = fakefile.getvalue()
return fakefile
-
+
def generate_boolean(self, attrname, index):
"""generates a consistent value for 'attrname' if it's a boolean"""
return index % 2 == 0
@@ -185,7 +186,7 @@
# need this method else stupid values will be set which make mtconverter
# raise exception
return u'application/octet-stream'
-
+
def generate_Any_content_format(self, index, **kwargs):
# content_format attribute of EmailPart has no vocabulary constraint, we
# need this method else stupid values will be set which make mtconverter
@@ -236,7 +237,7 @@
returns acceptable values for this attribute
"""
# XXX HACK, remove or fix asap
- if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'EGroup', 'EUser')):
+ if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
return []
queries = []
for index in xrange(entity_num):
@@ -250,7 +251,7 @@
args))
assert not 'eid' in args, args
else:
- queries.append(('INSERT %s X' % etype, {}))
+ queries.append(('INSERT %s X' % etype, {}))
return queries
@@ -365,7 +366,7 @@
continue
subjcard, objcard = rschema.rproperty(subj, obj, 'cardinality')
# process mandatory relations first
- if subjcard in '1+' or objcard in '1+':
+ if subjcard in '1+' or objcard in '1+':
queries += self.make_relation_queries(sedict, oedict,
rschema, subj, obj)
else:
@@ -374,7 +375,7 @@
queries += self.make_relation_queries(sedict, oedict, rschema,
subj, obj)
return queries
-
+
def qargs(self, subjeids, objeids, subjcard, objcard, subjeid, objeid):
if subjcard in '?1':
subjeids.remove(subjeid)
@@ -411,7 +412,7 @@
subjeids.remove(subjeid)
if not subjeids:
check_card_satisfied(objcard, objeids, subj, rschema, obj)
- return
+ return
if not objeids:
check_card_satisfied(subjcard, subjeids, subj, rschema, obj)
return
@@ -452,7 +453,7 @@
used.add( (subjeid, objeid) )
yield q, self.qargs(subjeids, objeids, subjcard, objcard,
subjeid, objeid)
-
+
def check_card_satisfied(card, remaining, subj, rschema, obj):
if card in '1+' and remaining:
raise Exception("can't satisfy cardinality %s for relation %s %s %s"
@@ -466,8 +467,8 @@
while objeid == avoid: # avoid infinite recursion like in X comment X
objeid = choice(values)
return objeid
-
-
+
+
# UTILITIES FUNCS ##############################################################
def make_tel(num_tel):
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/htmlparser.py
--- a/devtools/htmlparser.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/htmlparser.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,20 +1,23 @@
-"""defines a validating HTML parser used in web application tests"""
+"""defines a validating HTML parser used in web application tests
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
import re
-from StringIO import StringIO
from lxml import etree
-from lxml.builder import E
-from cubicweb.common.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()
+from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE
+STRICT_DOCTYPE = str(STRICT_DOCTYPE)
+TRANSITIONAL_DOCTYPE = str(TRANSITIONAL_DOCTYPE)
ERR_COUNT = 0
class Validator(object):
-
+
def parse_string(self, data, sysid=None):
try:
data = self.preprocess_data(data)
@@ -55,24 +58,11 @@
for blockquote in blockquotes:
parent = blockquote.getparent()
parent.remove(blockquote)
-## # for each blockquote, wrap unauthorized child in a div
-## for blockquote in blockquotes:
-## if len(blockquote):
-## needs_wrap = [(index, child) for index, child in enumerate(blockquote)
-## if child.tag not in expected]
-## for index, child in needs_wrap:
-## # the child is automatically popped from blockquote when
-## # its parent is changed
-## div = E.div(child)
-## blockquote.insert(index, div)
-## elif blockquote.text:
-## div = E.div(blockquote.text)
-## blockquote.text = None
-## blockquote.append(div)
data = etree.tostring(tree)
- return '%s\n%s' % (STRICT_DOCTYPE, data)
+ return '%s\n%s' % (
+ STRICT_DOCTYPE, data)
-
+
class SaxOnlyValidator(Validator):
def __init__(self):
@@ -85,7 +75,7 @@
Validator.__init__(self)
self.parser = etree.HTMLParser()
-
+
class PageInfo(object):
"""holds various informations on the view's output"""
@@ -103,24 +93,24 @@
self.h4_tags = self.find_tag('h4')
self.input_tags = self.find_tag('input')
self.title_tags = [self.h1_tags, self.h2_tags, self.h3_tags, self.h4_tags]
-
- def find_tag(self, tag):
+
+ def find_tag(self, tag, gettext=True):
"""return a list which contains text of all "tag" elements """
if self.default_ns is None:
iterstr = ".//%s" % tag
else:
iterstr = ".//{%s}%s" % (self.default_ns, tag)
- if tag in ('a', 'input'):
+ if not gettext or tag in ('a', 'input'):
return [(elt.text, elt.attrib) for elt in self.etree.iterfind(iterstr)]
return [u''.join(elt.xpath('.//text()')) for elt in self.etree.iterfind(iterstr)]
-
+
def appears(self, text):
"""returns True if appears in the page"""
return text in self.raw_text
def __contains__(self, text):
return text in self.source
-
+
def has_title(self, text, level=None):
"""returns True if text
@@ -150,7 +140,7 @@
if sre.match(title):
return True
return False
-
+
def has_link(self, text, url=None):
"""returns True if text was found in the page"""
for link_text, attrs in self.a_tags:
@@ -164,7 +154,7 @@
except KeyError:
continue
return False
-
+
def has_link_regexp(self, pattern, url=None):
"""returns True if pattern was found in the page"""
sre = re.compile(pattern)
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/livetest.py
--- a/devtools/livetest.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/livetest.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,4 +1,10 @@
-"""provide utilies for web (live) unit testing"""
+"""provide utilies for web (live) unit testing
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
import socket
import logging
@@ -36,17 +42,14 @@
"""Indicate which resource to use to process down the URL's path"""
if len(segments) and segments[0] == 'data':
# Anything in data/ is treated as static files
- dirlist = [self.data_dir, join(dirname(cubicweb.web.__file__), 'data')]
- for alternative in dirlist:
- filepath = join(alternative, *segments[1:])
- if exists(filepath):
- self.info('publish static file: %s', '/'.join(segments))
- return static.File(filepath), ()
+ datadir = self.config.locate_resource(segments[1])
+ if datadir:
+ return static.File(str(datadir), segments[1:])
# Otherwise we use this single resource
return self, ()
-
-
-
+
+
+
def make_site(cube, options=None):
from cubicweb.etwist import twconfig # trigger configuration registration
sourcefile = options.sourcefile
@@ -78,7 +81,7 @@
def saveconf(templhome, port, user, passwd):
import pickle
conffile = file(join(templhome, 'test', 'livetest.conf'), 'w')
-
+
pickle.dump((port, user, passwd, get_starturl(port, user, passwd)),
conffile)
conffile.close()
@@ -102,8 +105,8 @@
from twill import browser as twb
twc.OUT = new_output
twb.OUT = new_output
-
-
+
+
class LiveTestCase(TestCase):
sourcefile = None
@@ -121,7 +124,7 @@
def tearDown(self):
self.teardown_db(self.cnx)
-
+
def setup_db(self, cnx):
"""override setup_db() to setup your environment"""
@@ -144,5 +147,3 @@
if __name__ == '__main__':
runserver()
-
-
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/migrtest.py
--- a/devtools/migrtest.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/migrtest.py Wed Jun 03 19:50:34 2009 +0200
@@ -27,11 +27,12 @@
The permissions on .pgpass must disallow any access to world or group;
achieve this by the command chmod 0600 ~/.pgpass. If the permissions
-are less strict than this, the file will be ignored.
+are less strict than this, the file will be ignored.
:organization: Logilab
-:copyright: 2001-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/pkginfo.py
--- a/devtools/pkginfo.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/pkginfo.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,4 +1,10 @@
-"""distutils / __pkginfo__ helpers for cubicweb applications"""
+"""distutils / __pkginfo__ helpers for cubicweb applications
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
import os
from os.path import isdir, join
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/repotest.py
--- a/devtools/repotest.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/repotest.py Wed Jun 03 19:50:34 2009 +0200
@@ -3,8 +3,9 @@
This module contains functions to initialize a new repository.
:organization: Logilab
-:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -43,7 +44,7 @@
'expected %s queries, got %s' % (len(equeries), len(queries)))
for i, (rql, sol) in enumerate(queries):
self.assertEquals(rql, equeries[i][0])
- self.assertEquals(sol, equeries[i][1])
+ self.assertEquals(sorted(sol), sorted(equeries[i][1]))
idx = 2
else:
idx = 1
@@ -72,7 +73,7 @@
def __contains__(self, key):
return key in self.iterkeys()
def __getitem__(self, key):
- for key_, value in self.iteritems():
+ for key_, value in list.__iter__(self):
if key == key_:
return value
raise KeyError(key)
@@ -80,6 +81,17 @@
return (x for x, y in list.__iter__(self))
def iteritems(self):
return (x for x in list.__iter__(self))
+ def items(self):
+ return [x for x in list.__iter__(self)]
+
+class DumbOrderedDict2(object):
+ def __init__(self, origdict, sortkey):
+ self.origdict = origdict
+ self.sortkey = sortkey
+ def __getattr__(self, attr):
+ return getattr(self.origdict, attr)
+ def __iter__(self):
+ return iter(sorted(self.origdict, key=self.sortkey))
from logilab.common.testlib import TestCase
@@ -92,7 +104,7 @@
class RQLGeneratorTC(TestCase):
schema = None # set this in concret test
-
+
def setUp(self):
self.rqlhelper = RQLHelper(self.schema, special_relations={'eid': 'uid',
'has_text': 'fti'})
@@ -103,7 +115,7 @@
def tearDown(self):
ExecutionPlan._check_permissions = _orig_check_permissions
rqlannotation._select_principal = _orig_select_principal
-
+
def _prepare(self, rql):
#print '******************** prepare', rql
union = self.rqlhelper.parse(rql)
@@ -122,7 +134,7 @@
class BaseQuerierTC(TestCase):
repo = None # set this in concret test
-
+
def setUp(self):
self.o = self.repo.querier
self.session = self.repo._sessions.values()[0]
@@ -137,7 +149,7 @@
return self.session.unsafe_execute('Any MAX(X)')[0][0]
def cleanup(self):
self.session.unsafe_execute('DELETE Any X WHERE X eid > %s' % self.maxeid)
-
+
def tearDown(self):
undo_monkey_patch()
self.session.rollback()
@@ -148,7 +160,7 @@
def set_debug(self, debug):
set_debug(debug)
-
+
def _rqlhelper(self):
rqlhelper = self.o._rqlhelper
# reset uid_func so it don't try to get type from eids
@@ -164,8 +176,8 @@
for select in rqlst.children:
select.solutions.sort()
return self.o.plan_factory(rqlst, kwargs, self.session)
-
- def _prepare(self, rql, kwargs=None):
+
+ def _prepare(self, rql, kwargs=None):
plan = self._prepare_plan(rql, kwargs)
plan.preprocess(plan.rqlst)
rqlst = plan.rqlst.children[0]
@@ -184,10 +196,10 @@
def execute(self, rql, args=None, eid_key=None, build_descr=True):
return self.o.execute(self.session, rql, args, eid_key, build_descr)
-
+
def commit(self):
self.session.commit()
- self.session.set_pool()
+ self.session.set_pool()
class BasePlannerTC(BaseQuerierTC):
@@ -217,10 +229,10 @@
variantes = _orig_build_variantes(self, newsolutions)
sortedvariantes = []
for variante in variantes:
- orderedkeys = sorted((k[1], k[2], v) for k,v in variante.iteritems())
+ orderedkeys = sorted((k[1], k[2], v) for k, v in variante.iteritems())
variante = DumbOrderedDict(sorted(variante.iteritems(),
- lambda a,b: cmp((a[0][1],a[0][2],a[1]),
- (b[0][1],b[0][2],b[1]))))
+ lambda a, b: cmp((a[0][1],a[0][2],a[1]),
+ (b[0][1],b[0][2],b[1]))))
sortedvariantes.append( (orderedkeys, variante) )
return [v for ok, v in sorted(sortedvariantes)]
@@ -230,7 +242,7 @@
def _check_permissions(*args, **kwargs):
res, restricted = _orig_check_permissions(*args, **kwargs)
- res = DumbOrderedDict(sorted(res.iteritems(), lambda a,b: cmp(a[1], b[1])))
+ res = DumbOrderedDict(sorted(res.iteritems(), lambda a, b: cmp(a[1], b[1])))
return res, restricted
def _dummy_check_permissions(self, rqlst):
@@ -256,17 +268,17 @@
from cubicweb.server.msplanner import PartPlanInformation
except ImportError:
class PartPlanInformation(object):
- def merge_input_maps(*args):
+ def merge_input_maps(self, *args):
pass
- def _choose_var(self, sourcevars):
- pass
+ def _choose_term(self, sourceterms):
+ pass
_orig_merge_input_maps = PartPlanInformation.merge_input_maps
-_orig_choose_var = PartPlanInformation._choose_var
+_orig_choose_term = PartPlanInformation._choose_term
def _merge_input_maps(*args):
return sorted(_orig_merge_input_maps(*args))
-def _choose_var(self, sourcevars):
+def _choose_term(self, sourceterms):
# predictable order for test purpose
def get_key(x):
try:
@@ -279,17 +291,7 @@
except AttributeError:
# const
return x.value
- varsinorder = sorted(sourcevars, key=get_key)
- if len(self._sourcesvars) > 1:
- for var in varsinorder:
- if not var.scope is self.rqlst:
- return var, sourcevars.pop(var)
- else:
- for var in varsinorder:
- if var.scope is self.rqlst:
- return var, sourcevars.pop(var)
- var = varsinorder[0]
- return var, sourcevars.pop(var)
+ return _orig_choose_term(self, DumbOrderedDict2(sourceterms, get_key))
def do_monkey_patch():
@@ -299,7 +301,7 @@
ExecutionPlan.tablesinorder = None
ExecutionPlan.init_temp_table = _init_temp_table
PartPlanInformation.merge_input_maps = _merge_input_maps
- PartPlanInformation._choose_var = _choose_var
+ PartPlanInformation._choose_term = _choose_term
def undo_monkey_patch():
RQLRewriter.insert_snippets = _orig_insert_snippets
@@ -307,5 +309,4 @@
ExecutionPlan._check_permissions = _orig_check_permissions
ExecutionPlan.init_temp_table = _orig_init_temp_table
PartPlanInformation.merge_input_maps = _orig_merge_input_maps
- PartPlanInformation._choose_var = _orig_choose_var
-
+ PartPlanInformation._choose_term = _orig_choose_term
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/stresstester.py
--- a/devtools/stresstester.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/stresstester.py Wed Jun 03 19:50:34 2009 +0200
@@ -5,13 +5,13 @@
OPTIONS:
-h / --help
Display this help message and exit.
-
+
-u / --user
Connect as instead of being prompted to give it.
-p / --password
Automatically give for authentication instead of being prompted
to give it.
-
+
-n / --nb-times
Repeat queries times.
-t / --nb-threads
@@ -21,8 +21,9 @@
-o / --report-output
Write profiler report into rather than on stdout
-Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__revision__ = "$Id: stresstester.py,v 1.3 2006-03-05 14:35:27 syt Exp $"
@@ -49,7 +50,7 @@
self._times = times
self._queries = queries
self._reporter = reporter
-
+
def run(self):
cursor = self._cursor
times = self._times
@@ -80,7 +81,7 @@
threads and can write a report that summarizes all profile informations
"""
profiler_lock = threading.Lock()
-
+
def __init__(self, queries):
self._queries = tuple(queries)
self._profile_results = [(0., 0)] * len(self._queries)
@@ -111,8 +112,8 @@
table_layout = Table(3, rheaders = True, children = table_elems)
TextWriter().format(table_layout, output)
# output.write('\n'.join(tmp_output))
-
-
+
+
def run(args):
"""run the command line tool"""
try:
@@ -150,7 +151,7 @@
user = raw_input('login: ')
if password is None:
password = getpass('password: ')
- from cubicweb.cwconfig import application_configuration
+ from cubicweb.cwconfig import application_configuration
config = application_configuration(args[0])
# get local access to the repository
print "Creating repo", prof_file
@@ -176,7 +177,7 @@
else:
QueryExecutor(repo_cursor, repeat, queries, reporter = reporter).run()
reporter.dump_report(report_output)
-
-
+
+
if __name__ == '__main__':
run(sys.argv[1:])
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/data/schema/custom.py
--- a/devtools/test/data/schema/custom.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/data/schema/custom.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,2 +1,9 @@
+"""
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
Person = import_erschema('Person')
Person.add_relation(Date(), 'birthday')
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/data/schema/relations.rel
--- a/devtools/test/data/schema/relations.rel Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/data/schema/relations.rel Wed Jun 03 19:50:34 2009 +0200
@@ -23,11 +23,11 @@
Project uses Project
Version version_of Project inline
-Version todo_by EUser
+Version todo_by CWUser
Comment about Bug inline
Comment about Story inline
Comment about Comment inline
-EUser interested_in Project
+CWUser interested_in Project
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/data/views/bug.py
--- a/devtools/test/data/views/bug.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/data/views/bug.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,6 +1,13 @@
-"""only for unit tests !"""
+"""only for unit tests !
-from cubicweb.common.view import EntityView
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
+
+from cubicweb.view import EntityView
+from cubicweb.selectors import implements
HTML_PAGE = u"""
@@ -11,7 +18,7 @@
class SimpleView(EntityView):
id = 'simple'
- accepts = ('Bug',)
+ __select__ = implements('Bug',)
def call(self, **kwargs):
self.cell_call(0, 0)
@@ -21,7 +28,7 @@
class RaisingView(EntityView):
id = 'raising'
- accepts = ('Bug',)
+ __select__ = implements('Bug',)
def cell_call(self, row, col):
raise ValueError()
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/runtests.py
--- a/devtools/test/runtests.py Wed Jun 03 19:49:44 2009 +0200
+++ /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 '.')
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/unittest_dbfill.py
--- a/devtools/test/unittest_dbfill.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/unittest_dbfill.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,5 +1,11 @@
# -*- coding: iso-8859-1 -*-
-"""unit tests for database value generator"""
+"""unit tests for database value generator
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
import os.path as osp
import re
@@ -31,7 +37,7 @@
return getattr(self, '_available_%s_%s' % (etype, attrname))(etype, attrname)
except AttributeError:
return None
-
+
def _available_Person_firstname(self, etype, attrname):
return [f.strip() for f in file(osp.join(DATADIR, 'firstnames.txt'))]
@@ -51,11 +57,11 @@
year = date.year
month = date.month
day = date.day
- self.failUnless(day in range(1, 29), '%s not in [0;28]' % day)
+ self.failUnless(day in range(1, 29), '%s not in [0;28]' % day)
self.failUnless(month in range(1, 13), '%s not in [1;12]' % month)
self.failUnless(year in range(2000, 2005),
'%s not in [2000;2004]' % year)
-
+
def test_string(self):
"""test string generation"""
@@ -89,7 +95,7 @@
for index in range(5):
date_value = self.person_valgen._generate_value('birthday', index)
self._check_date(date_value)
-
+
def test_phone(self):
"""tests make_tel utility"""
self.assertEquals(make_tel(22030405), '22 03 04 05')
@@ -102,14 +108,14 @@
u'yo')
self.assertEquals(self.person_valgen._generate_value('description', 12),
u'yo')
-
-
+
+
class ConstraintInsertionTC(TestCase):
def test_writeme(self):
self.skip('Test automatic insertion / Schema Constraints')
-
+
if __name__ == '__main__':
unittest_main()
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/unittest_fill.py
--- a/devtools/test/unittest_fill.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/unittest_fill.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,4 +1,10 @@
-"""unit tests for cubicweb.devtools.fill module"""
+"""unit tests for cubicweb.devtools.fill module
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
from logilab.common.testlib import TestCase, unittest_main
@@ -20,7 +26,7 @@
for attrname in attrvalues - set(self.attrvalues):
delattr(_ValueGenerator, attrname)
-
+
def test_autoextend(self):
self.failIf('generate_server' in dir(ValueGenerator))
class MyValueGenerator(ValueGenerator):
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/test/unittest_testlib.py
--- a/devtools/test/unittest_testlib.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/test/unittest_testlib.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,4 +1,10 @@
-"""unittests for gct.apptest module"""
+"""unittests for gct.apptest module
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
+"""
from cStringIO import StringIO
from unittest import TestSuite
@@ -22,14 +28,14 @@
def test_error_view(self):
self.add_entity('Bug', title=u"bt")
self.view('raising', self.execute('Bug B'), template=None)
-
+
def test_correct_view(self):
- self.view('primary', self.execute('EUser U'), template=None)
-
+ self.view('primary', self.execute('CWUser U'), template=None)
+
tests = [MyWebTest('test_error_view'), MyWebTest('test_correct_view')]
result = self.runner.run(TestSuite(tests))
self.assertEquals(result.testsRun, 2)
- self.assertEquals(len(result.errors), 0)
+ self.assertEquals(len(result.errors), 0)
self.assertEquals(len(result.failures), 1)
@@ -97,13 +103,13 @@
def test_source1(self):
"""make sure source is stored correctly"""
self.assertEquals(self.page_info.source, HTML_PAGE2)
-
+
def test_source2(self):
"""make sure source is stored correctly - raise exception"""
parser = htmlparser.DTDValidator()
self.assertRaises(AssertionError, parser.parse_string, HTML_PAGE_ERROR)
-
+
def test_has_title_no_level(self):
"""tests h? tags information"""
self.assertEquals(self.page_info.has_title('Test'), True)
@@ -128,7 +134,7 @@
self.assertEquals(self.page_info.has_title_regexp('h[23] title', 2), True)
self.assertEquals(self.page_info.has_title_regexp('h[23] title', 3), True)
self.assertEquals(self.page_info.has_title_regexp('h[23] title', 4), False)
-
+
def test_appears(self):
"""tests PageInfo.appears()"""
self.assertEquals(self.page_info.appears('CW'), True)
@@ -151,4 +157,3 @@
if __name__ == '__main__':
unittest_main()
-
diff -r 292b7989b166 -r ddf4f2d8d51c devtools/testlib.py
--- a/devtools/testlib.py Wed Jun 03 19:49:44 2009 +0200
+++ b/devtools/testlib.py Wed Jun 03 19:50:34 2009 +0200
@@ -1,8 +1,9 @@
"""this module contains base classes for web tests
:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
@@ -13,8 +14,6 @@
from logilab.common.testlib import InnerTest
from logilab.common.pytest import nocoverage
-from rql import parse
-
from cubicweb.devtools import VIEW_VALIDATORS
from cubicweb.devtools.apptest import EnvBasedTC
from cubicweb.devtools._apptest import unprotected_entities, SYSTEM_RELATIONS
@@ -24,8 +23,6 @@
from cubicweb.sobjects.notification import NotificationView
from cubicweb.vregistry import NoSelectableObject
-from cubicweb.web.action import Action
-from cubicweb.web.views.basetemplates import TheMainTemplate
## TODO ###############
@@ -114,16 +111,16 @@
# maps vid : validator name (override content_type_validators)
vid_validators = dict((vid, VALMAP[valkey])
for vid, valkey in VIEW_VALIDATORS.iteritems())
-
+
no_auto_populate = ()
- ignored_relations = ()
-
+ ignored_relations = ()
+
def custom_populate(self, how_many, cursor):
pass
-
+
def post_populate(self, cursor):
pass
-
+
@nocoverage
def auto_populate(self, how_many):
"""this method populates the database with `how_many` entities
@@ -149,7 +146,7 @@
if rschema.is_final() or rschema in ignored_relations:
continue
rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
- existingrels.setdefault(rschema.type, set()).update((x,y) for x, y in rset)
+ existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
q = make_relations_queries(self.schema, edict, cu, ignored_relations,
existingrels=existingrels)
for rql, args in q:
@@ -158,7 +155,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 +172,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
@@ -184,12 +181,12 @@
:returns: an instance of `cubicweb.devtools.htmlparser.PageInfo`
encapsulation the generated HTML
"""
- req = req or rset.req
+ req = req or rset and rset.req or self.request()
# print "testing ", vid,
# if rset:
# print rset, len(rset), id(rset)
# else:
- # print
+ # print
req.form['vid'] = vid
view = self.vreg.select_view(vid, req, rset, **kwargs)
# set explicit test description
@@ -197,24 +194,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
+ viewfunc = view.render
+ 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
@@ -249,21 +238,23 @@
output = '\n'.join(line_template % (idx + 1, line)
for idx, line in enumerate(output)
if line_context_filter(idx+1, position))
- msg+= '\nfor output:\n%s' % output
+ msg += '\nfor output:\n%s' % output
raise AssertionError, msg, tcbk
def to_test_etypes(self):
return unprotected_entities(self.schema, strict=True)
-
- def iter_automatic_rsets(self):
+
+ def iter_automatic_rsets(self, limit=10):
"""generates basic resultsets for each entity type"""
etypes = self.to_test_etypes()
for etype in etypes:
- yield self.execute('Any X WHERE X is %s' % etype)
-
+ yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype))
etype1 = etypes.pop()
- etype2 = etypes.pop()
+ try:
+ etype2 = etypes.pop()
+ except KeyError:
+ etype2 = etype1
# test a mixed query (DISTINCT/GROUP to avoid getting duplicate
# X which make muledit view failing for instance (html validation fails
# because of some duplicate "id" attributes)
@@ -272,7 +263,7 @@
for rql in self.application_rql:
yield self.execute(rql)
-
+
def list_views_for(self, rset):
"""returns the list of views that can be applied on `rset`"""
req = rset.req
@@ -309,7 +300,7 @@
req = rset.req
for box in self.vreg.possible_objects('boxes', req, rset):
yield box
-
+
def list_startup_views(self):
"""returns the list of startup views"""
req = self.request()
@@ -318,7 +309,7 @@
yield view.id
else:
not_selected(self.vreg, view)
-
+
def _test_everything_for(self, rset):
"""this method tries to find everything that can be tested
for `rset` and yields a callable test (as needed in generative tests)
@@ -332,40 +323,36 @@
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
for action in self.list_actions_for(rset):
- # XXX this seems a bit dummy
- #yield InnerTest(self._testname(rset, action.id, 'action'),
- # self.failUnless,
- # isinstance(action, Action))
yield InnerTest(self._testname(rset, action.id, 'action'), action.url)
for box in self.list_boxes_for(rset):
- yield InnerTest(self._testname(rset, box.id, 'box'), box.dispatch)
+ yield InnerTest(self._testname(rset, box.id, 'box'), box.render)
@staticmethod
def _testname(rset, objid, objtype):
return '%s_%s_%s' % ('_'.join(rset.column_types(0)), objid, objtype)
-
+
class AutomaticWebTest(WebTest):
"""import this if you wan automatic tests to be ran"""
## one each
def test_one_each_config(self):
self.auto_populate(1)
- for rset in self.iter_automatic_rsets():
+ for rset in self.iter_automatic_rsets(limit=1):
for testargs in self._test_everything_for(rset):
yield testargs
## ten each
def test_ten_each_config(self):
self.auto_populate(10)
- for rset in self.iter_automatic_rsets():
+ for rset in self.iter_automatic_rsets(limit=10):
for testargs in self._test_everything_for(rset):
yield testargs
-
+
## startup views
def test_startup_views(self):
for vid in self.list_startup_views():
@@ -390,7 +377,7 @@
vreg._selected[vobject.__class__] -= 1
except (KeyError, AttributeError):
pass
-
+
def vreg_instrumentize(testclass):
from cubicweb.devtools.apptest import TestEnvironment
env = testclass._env = TestEnvironment('data', configcls=testclass.configcls,
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/README Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,43 @@
+====
+Book
+====
+
+----
+Part
+----
+
+Chapter
+=======
+
+Level 1 section
+---------------
+
+Level 2 section
+~~~~~~~~~~~~~~~
+
+Level 3 section
+```````````````
+
+
+
+*CubicWeb*
+
+
+inline directives:
+ :file:
+ :envvar:
+ :command:
+
+ :ref:, :mod:
+
+
+XXX
+* lien vers cw.cwconfig.CW_CUBES_PATH par ex.
+
+
+.. sourcecode:: python
+
+ class SomePythonCode:
+ ...
+
+.. XXX a comment
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/_maybe_to_integrate/D050-architecture.en.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/D050-architecture.en.txt Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,14 @@
+.. -*- coding: utf-8 -*-
+
+
+Server Architecture
+-------------------
+
+.. image:: images/server-class-diagram.png
+
+`Diagramme ArgoUML`_
+
+[FIXME]
+Make a downloadable source of zargo file.
+
+.. _`Diagramme ArgoUML`: cubicweb.zargo
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/_maybe_to_integrate/rss-xml.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/rss-xml.rst Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,45 @@
+.. -*- coding: utf-8 -*-
+
+RSS Channel
+-----------
+
+Assuming you have several blog entries, click on the title of the
+search box in the left column. A larger search box should appear. Enter::
+
+ Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
+
+and you get a list of blog entries.
+
+Click on your login at the top right corner. Chose "user preferences",
+then "boxes", then "possible views box" and check "visible = yes"
+before validating your changes.
+
+Enter the same query in the search box and you will see the same list,
+plus a box titled "possible views" in the left column. Click on
+"entityview", then "RSS".
+
+You just applied the "RSS" view to the RQL selection you requested.
+
+That's it, you have a RSS channel for your blog.
+
+Try again with::
+
+ Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
+ X entry_of B, B title "MyLife"
+
+Another RSS channel, but a bit more focused.
+
+A last one for the road::
+
+ Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
+
+displayed with the RSS view, that's a channel for the last fifteen
+comments posted.
+
+[WRITE ME]
+
+* show that the RSS view can be used to display an ordered selection
+ of blog entries, thus providing a RSS channel
+
+* show that a different selection (by category) means a different channel
+
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/_maybe_to_integrate/template.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/template.rst Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,20 @@
+
+
+Templates
+---------
+
+*Templates* are specific views that do not depend on a result set. The basic
+class `Template` (`cubicweb.common.view`) is derived from the class `View`.
+
+To build a HTML page, a *main template* is used. In general, the template of
+identifier `main` is the one to use (it is not used in case an error is raised or for
+the login form for example). This template uses other templates in addition
+to the views which depends on the content to generate the HTML page to return.
+
+A *template* is responsible for:
+
+1. executing RQL query of data to render if necessary
+2. identifying the view to use to render data if it is not specified
+3. composing the HTML page to return
+
+You will find out more about templates in :ref:`templates`.
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/_maybe_to_integrate/treemixin.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/_maybe_to_integrate/treemixin.rst Wed Jun 03 19:50:34 2009 +0200
@@ -0,0 +1,100 @@
+
+Class `TreeMixIn`
+-----------------
+
+This class provides a tree interface. This mixin has to be inherited
+explicitly and configured using the tree_attribute, parent_target and
+children_target class attribute to benefit from this default implementation.
+
+This class provides the following methods:
+
+ * `different_type_children(entities=True)`, returns children entities
+ of different type as this entity. According to the `entities` parameter,
+ returns entity objects (if entity=True) or the equivalent result set.
+
+ * `same_type_children(entities=True)`, returns children entities of
+ the same type as this entity. According to the `entities` parameter,
+ return entity objects (if entity=True) or the equivalent result set.
+
+ * `iterchildren( _done=None)`, iters on the children of the entity.
+
+ * `prefixiter( _done=None)`
+
+ * `path()`, returns the list of eids from the root object to this object.
+
+ * `iterparents()`, iters on the parents of the entity.
+
+ * `notification_references(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.
+
+`TreeMixIn` implements also the ITree interface (``cubicweb.interfaces``):
+
+ * `parent()`, returns the parent entity if any, else None (e.g. if we are on the
+ root)
+
+ * `children(entities=True, sametype=False)`, returns children entities
+ according to the `entities` parameter, return entity objects or the
+ equivalent result set.
+
+ * `children_rql()`, returns the RQL query corresponding to the children
+ of the entity.
+
+ * `is_leaf()`, returns True if the entity does not have any children.
+
+ * `is_root()`, returns True if the entity does not have any parent.
+
+ * `root()`, returns the root object of the tree representation of
+ the entity and its related entities.
+
+Example of use
+``````````````
+
+Imagine you defined three types of entities in your schema, and they
+relates to each others as follows in ``schema.py``::
+
+ class Entity1(EntityType):
+ title = String()
+ is_related_to = SubjectRelation('Entity2', 'subject')
+
+ class Entity2(EntityType):
+ title = String()
+ belongs_to = SubjectRelation('Entity3', 'subject')
+
+ class Entity3(EntityType):
+ name = String()
+
+You would like to create a view that applies to both entity types
+`Entity1` and `Entity2` and which lists the entities they are related to.
+That means when you view `Entity1` you want to list all `Entity2`, and
+when you view `Entity2` you want to list all `Entity3`.
+
+In ``entities.py``::
+
+ class Entity1(TreeMixIn, AnyEntity):
+ id = 'Entity1'
+ __implements__ = AnyEntity.__implements__ + (ITree,)
+ __rtags__ = {('is_related_to', 'Entity2', 'object'): 'link'}
+ tree_attribute = 'is_related_to'
+
+ def children(self, entities=True):
+ return self.different_type_children(entities)
+
+ class Entity2(TreeMixIn, AnyEntity):
+ id = 'Entity2'
+ __implements__ = AnyEntity.__implements__ + (ITree,)
+ __rtags__ = {('belongs_to', 'Entity3', 'object'): 'link'}
+ tree_attribute = 'belongs_to'
+
+ def children(self, entities=True):
+ return self.different_type_children(entities)
+
+Once this is done, you can define your common view as follows::
+
+ class E1E2CommonView(baseviews.PrimaryView):
+ accepts = ('Entity11, 'Entity2')
+
+ def render_entity_relations(self, entity, siderelations):
+ self.wview('list', entity.children(entities=False))
+
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A000-introduction.en.txt
--- a/doc/book/en/A000-introduction.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-
-===================================
-Part I - Introduction to `CubicWeb`
-===================================
-
-This first part of the book will offer different reading path to
-present you with the `CubicWeb` framework, provide a tutorial to get a quick
-overview of its features and list its key concepts.
-
-
-.. toctree::
- :maxdepth: 2
-
- A010-book-map.en.txt
- A020-tutorial.en.txt
- A030-foundation.en.txt
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A010-book-map.en.txt
--- a/doc/book/en/A010-book-map.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Book map
-=========
-
-[WRITE ME]
-
-* explain how to use this book and what chapters to read in what order depending on the
- objectives of the reader
-
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A020-tutorial.en.txt
--- a/doc/book/en/A020-tutorial.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _Overview:
-
-Quick overview of `CubicWeb`
-============================
-
-`CubicWeb` is a semantic web application framework that favors reuse and
-object-oriented design.
-
-A `cube` is a component that includes a model defining the data types and a set of
-views to display the data.
-
-An application is a `cube`, but usually an application is built by assembling
-a few smaller cubes.
-
-An `instance` is a specific installation of an application and includes
-configuration files.
-
-This tutorial will show how to create a `cube` and how to use it as an
-application to run an `instance`.
-
-.. include:: A02a-create-cube.en.txt
-.. include:: A02b-components.en.txt
-.. include:: A02c-maintemplate.en.txt
-.. include:: A02d-conclusion.en.txt
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A02a-create-cube.en.txt
--- a/doc/book/en/A02a-create-cube.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Create your cube
-----------------
-
-Once your `CubicWeb` development environment is set up, you can create a new
-cube::
-
- cubicweb-ctl newcube blog
-
-This will create in the cubes directory (``/path/to/forest/cubes`` for Mercurial
-installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
-a directory named ``blog`` reflecting the structure described in :ref:`cubesConcepts`.
-
-.. _DefineDataModel:
-
-Define your data model
-----------------------
-
-The data model or schema is the core of your `CubicWeb` application.
-It defines the type of content your application will handle.
-
-The data model of your cube ``blog`` is defined in the file ``schema.py``:
-
-::
-
- from cubicweb.schema import format_constraint
-
- class Blog(EntityType):
- title = String(maxsize=50, required=True)
- description = String()
-
- class BlogEntry(EntityType):
- title = String(required=True, fulltextindexed=True, maxsize=256)
- publish_date = Date(default='TODAY')
- content = String(required=True, fulltextindexed=True)
- entry_of = SubjectRelation('Blog', cardinality='?*')
-
-
-A Blog has a title and a description. The title is a string that is
-required by the class EntityType and must be less than 50 characters.
-The description is a string that is not constrained.
-
-A BlogEntry has a title, a publish_date and a content. The title is a
-string that is required and must be less than 100 characters. The
-publish_date is a Date with a default value of TODAY, meaning that
-when a BlogEntry is created, its publish_date will be the current day
-unless it is modified. The content is a string that will be indexed in
-the full-text index and has no constraint.
-
-A BlogEntry also has a relationship ``entry_of`` that links it to a
-Blog. The cardinality ``?*`` means that a BlogEntry can be part of
-zero or one Blog (``?`` means `zero or one`) and that a Blog can
-have any number of BlogEntry (``*`` means `any number including
-zero`). For completeness, remember that ``+`` means `one or more`.
-
-
-Create your instance
---------------------
-
-To use this cube as an application and create a new instance named ``blogdemo``, do::
-
- cubicweb-ctl create blog blogdemo
-
-
-This command will create a directory ``~/etc/cubicweb.d/blogdemo``
-which will contain all the configuration files required to start
-you web application.
-
-Welcome to your web application
--------------------------------
-
-Start your application in debug mode with the following command: ::
-
- cubicweb-ctl start -D blogdemo
-
-
-You can now access your web application to create blogs and post messages
-by visiting the URL http://localhost:8080/.
-
-A login form will appear. By default, the application will not allow anonymous
-users to enter the application. To login, you need then use the admin account
-you created at the time you initialized the database with ``cubicweb-ctl
-create``.
-
-.. image:: images/login-form.png
-
-
-Once authenticated, you can start playing with your application
-and create entities.
-
-.. image:: images/blog-demo-first-page.png
-
-Please notice that so far, the `CubicWeb` franework managed all aspects of
-the web application based on the schema provided at first.
-
-
-Add entities
-------------
-
-We will now add entities in our web application.
-
-Add a Blog
-~~~~~~~~~~
-
-Let us create a few of these entities. Click on the `[+]` at the left of the
-link Blog on the home page. Call this new Blog ``Tech-blog`` and type in
-``everything about technology`` as the description, then validate the form by
-clicking on ``Validate``.
-
-.. image:: images/cbw-create-blog.en.png
- :alt: from to create blog
-
-Click on the logo at top left to get back to the home page, then
-follow the Blog link that will list for you all the existing Blog.
-You should be seeing a list with a single item ``Tech-blog`` you
-just created.
-
-.. image:: images/cbw-list-one-blog.en.png
- :alt: displaying a list of a single blog
-
-Clicking on this item will get you to its detailed description except
-that in this case, there is not much to display besides the name and
-the phrase ``everything about technology``.
-
-Now get back to the home page by clicking on the top-left logo, then
-create a new Blog called ``MyLife`` and get back to the home page
-again to follow the Blog link for the second time. The list now
-has two items.
-
-.. image:: images/cbw-list-two-blog.en.png
- :alt: displaying a list of two blogs
-
-Add a BlogEntry
-~~~~~~~~~~~~~~~
-
-Get back to the home page and click on [+] at the left of the link
-BlogEntry. Call this new entry ``Hello World`` and type in some text
-before clicking on ``Validate``. You added a new blog entry without
-saying to what blog it belongs. There is a box on the left entitled
-``actions``, click on the menu item ``modify``. You are back to the form
-to edit the blog entry you just created, except that the form now has
-another section with a combobox titled ``add relation``. Chose
-``entry_of`` in this menu and a second combobox appears where you pick
-``MyLife``.
-
-You could also have, at the time you started to fill the form for a
-new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
-combobox titled ``add relation`` would have showed up.
-
-
-.. image:: images/cbw-add-relation-entryof.en.png
- :alt: editing a blog entry to add a relation to a blog
-
-Validate the changes by clicking ``Validate``. The entity BlogEntry
-that is displayed now includes a link to the entity Blog named
-``MyLife``.
-
-.. image:: images/cbw-detail-one-blogentry.en.png
- :alt: displaying the detailed view of a blogentry
-
-Note that all of this was handled by the framework and that the only input
-that was provided so far is the schema. To get a graphical view of the schema,
-point your browser to the URL http://localhost:8080/schema
-
-.. image:: images/cbw-schema.en.png
- :alt: graphical view of the schema (aka data-model)
-
-
-.. _DefineViews:
-
-Define your entities views
---------------------------
-
-Each entity defined in a model inherits defaults views allowing
-different rendering of the data. You can redefine each of them
-according to your needs and preferences. If you feel like it then
-you have to know how a view is defined.
-
-
-The views selection principle
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A view is defined by a Python class which includes:
-
- - an identifier (all objects in `CubicWeb` are entered in a registry
- and this identifier will be used as a key)
-
- - a filter to select the resulsets it can be applied to
-
-A view has a set of methods complying
-with the `View` class interface (`cubicweb.common.view`).
-
-`CubicWeb` provides a lot of standard views for the type
-`EntityView`, for a complete list, you
-will have to read the code in directory ``cubicweb/web/views/``
-
-A view is applied on a `result set` which contains a set of
-entities we are trying to display. `CubicWeb` uses a selector
-mechanism which computes a score used to identify which view
-is the best to apply for the `result set` we are trying to
-display. The standard library of selectors is in
-``cubicweb.common.selector`` and a library of methods used to
-compute scores is available in ``cubicweb.vregistry.vreq``.
-
-It is possible to define multiple views for the same identifier
-and to associate selectors and filters to allow the application
-to find the best way to render the data. We will see more details
-on this in :ref:`DefinitionVues`.
-
-For example, the view named ``primary`` is the one used to display
-a single entity. We will now show you hos to customize this view.
-
-
-View customization
-~~~~~~~~~~~~~~~~~~
-
-If you wish to modify the way a `BlogEntry` is rendered, you will have to
-overwrite the `primary` view defined in the module ``views`` of the cube
-``cubes/blog/views.py``.
-
-We can for example add in front of the pulication date a prefix specifying
-the date we see is the publication date.
-
-To do so, please apply the following changes:
-
-::
-
- from cubicweb.web.views import baseviews
-
-
- class BlogEntryPrimaryView(baseviews.PrimaryView):
-
- accepts = ('BlogEntry',)
-
- def render_entity_title(self, entity):
- self.w(u'
' % entity.content)
-
- # display relations
- siderelations = []
- if self.main_related_section:
- self.render_entity_relations(entity, siderelations)
-
-.. note::
- When a view is modified, it is not required to restart the application
- server. Save the Python file and reload the page in your web browser
- to view the changes.
-
-You can now see that the publication date has a prefix.
-
-.. image:: images/cbw-update-primary-view.en.png
- :alt: modified primary view
-
-
-The above source code defines a new primary view for
-``BlogEntry``.
-
-Since views are applied to resultsets and resulsets can be tables of
-data, it is needed to recover the entity from its (row,col)
-coordinates. We will get to this in more detail later.
-
-The view has a ``self.w()`` method that is used to output data. In our
-example we use it to output HTML tags and values of the entity's attributes.
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A02b-components.en.txt
--- a/doc/book/en/A02b-components.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _cubes:
-
-Cubes
------
-
-Standard library
-~~~~~~~~~~~~~~~~
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities are:
-
-* addressbook: PhoneNumber and PostalAddress
-
-* basket: Basket (like a shopping cart)
-
-* blog: Blog (a *very* basic blog)
-
-* classfolder: Folder (to organize things but grouping them in folders)
-
-* classtags: Tag (to tag anything)
-
-* file: File (to allow users to upload and store binary or text files)
-
-* link: Link (to collect links to web resources)
-
-* mailinglist: MailingList (to reference a mailing-list and the URLs
- for its archives and its admin interface)
-
-* person: Person (easily mixed with addressbook)
-
-* task: Task (something to be done between start and stop date)
-
-* zone: Zone (to define places within larger places, for example a
- city in a state in a country)
-
-The available system entities are:
-
-* comment: Comment (to attach comment threads to entities)
-
-
-Adding comments to BlogDemo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To import a cube in your application just change the line in the
-``__pkginfo__.py`` file and verify that the cube you are planning
-to use is listed by the command ``cubicweb-ctl list``.
-For example::
-
- __use__ = ('comment',)
-
-will make the ``Comment`` entity available in your ``BlogDemo``
-application.
-
-Change the schema to add a relationship between ``BlogEntry`` and
-``Comment`` and you are done. Since the comment cube defines the
-``comments`` relationship, adding the line::
-
- comments = ObjectRelation('Comment', cardinality='1*', composite='object')
-
-to the definition of a ``BlogEntry`` will be enough.
-
-Synchronize the data model
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once you modified your data model, you need to synchronize the
-database with your model. For this purpose, `CubicWeb` provides
-a very usefull command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive migration Python shell. (see
-:ref:`cubicweb-ctl-shell` for more details))
-As you modified a relation from the `BlogEntry` schema,
-run the following command:
-::
-
- synchronize_rschema('BlogEntry')
-
-You can now start your application and add comments to each
-`BlogEntry`.
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A02c-maintemplate.en.txt
--- a/doc/book/en/A02c-maintemplate.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Templates
----------
-
-Look at ``cubicweb/web/views/basetemplates.py`` and you will
-find the base templates used to generate HTML for your application.
-
-A page is composed as indicated on the schema below:
-
-.. image:: images/lax-book.06-main-template-layout.en.png
-
-In this section we will demonstrate a change in one of the main
-interesting template from the three you will look for,
-that is to say, the HTMLPageHeader, the HTMLPageFooter
-and the TheMainTemplate.
-
-
-Customize a template
-~~~~~~~~~~~~~~~~~~~~
-
-Based on the diagram below, each template can be overriden
-by your customized template. To do so, we recommand you create
-a Python module ``blog.views.templates`` to keep it organized.
-In this module you will have to import the parent class you are
-interested as follows: ::
-
- from cubicweb.web.views.basetemplates import HTMLPageHeader, \
- HTMLPageFooter, TheMainTemplate
-
-and then create your sub-class::
-
- class MyBlogHTMLPageHeader(HTMLPageHeader):
- ...
-
-Customize header
-`````````````````
-
-Let's now move the search box in the header and remove the login form
-from the header. We'll show how to move it to the left column of the application.
-
-Let's say we do not want anymore the login menu to be in the header
-
-First, to remove the login menu, we just need to comment out the display of the
-login graphic component such as follows: ::
-
- class MyBlogHTMLPageHeader(HTMLPageHeader):
-
- def main_header(self, view):
- """build the top menu with authentification info and the rql box"""
- self.w(u'
')
- helpcomp = self.vreg.select_component('help', self.req, self.rset)
- if helpcomp: # may not be available if Card is not defined in the schema
- helpcomp.dispatch(w=self.w)
- self.w(u'
')
- # lastcolumn
- self.w(u'
')
- self.w(u'
\n')
- self.w(u'
\n')
- self.template('logform', rset=self.rset, id='popupLoginBox', klass='hidden',
- title=False, message=False)
-
-
-
-.. image:: images/lax-book.06-header-no-login.en.png
-
-Customize footer
-````````````````
-
-If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in: ::
-
- from cubicweb.web.views.basetemplates import HTMLPageFooter
-
- class MyHTMLPageFooter(HTMLPageFooter):
-
- def call(self, **kwargs):
- self.w(u'')
-
-Updating a view does not require any restart of the server. By reloading
-the page you can see your new page footer.
-
-
-TheMainTemplate
-```````````````
-
-.. _TheMainTemplate:
-
-The MainTemplate is a bit complex as it tries to accomodate many
-different cases. We are now about to go through it and cutomize entirely
-our application.
-
-TheMainTemplate is responsible for the general layout of the entire application.
-It defines the template of ``id = main`` that is used by the application. Is
-also defined in ``cubicweb/web/views/basetemplates.py`` another template that can
-be used based on TheMainTemplate called SimpleMainTemplate which does not have
-a top section.
-
-.. image:: images/lax-book.06-simple-main-template.en.png
-
-XXX
-[WRITE ME]
-
-* customize MainTemplate and show that everything in the user
- interface can be changed
-
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A02d-conclusion.en.txt
--- a/doc/book/en/A02d-conclusion.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-What's next?
-------------
-
-We demonstrated how from a straight out of the box `CubicWeb`
-installation, you can build your web-application based on a
-schema. It's all already there: views, templates, permissions,
-etc. The step forward is now for you to customize according
-to your needs.
-
-More than a web application, many features are available to
-extend your application, for example: RSS channel integration
-(:ref:`rss`), hooks (:ref:`hooks`), support of sources such as
-Google App Engine (:ref:`gaecontents`) and lots of others to
-discover through our book.
-
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A02d-rss-xml.en.txt
--- a/doc/book/en/A02d-rss-xml.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-RSS Channel
------------
-
-Assuming you have several blog entries, click on the title of the
-search box in the left column. A larger search box should appear. Enter::
-
- Any X ORDERBY D WHERE X is BlogEntry, X creation_date D
-
-and you get a list of blog entries.
-
-Click on your login at the top right corner. Chose "user preferences",
-then "boxes", then "possible views box" and check "visible = yes"
-before validating your changes.
-
-Enter the same query in the search box and you will see the same list,
-plus a box titled "possible views" in the left column. Click on
-"entityview", then "RSS".
-
-You just applied the "RSS" view to the RQL selection you requested.
-
-That's it, you have a RSS channel for your blog.
-
-Try again with::
-
- Any X ORDERBY D WHERE X is BlogEntry, X creation_date D,
- X entry_of B, B title "MyLife"
-
-Another RSS channel, but a bit more focused.
-
-A last one for the road::
-
- Any C ORDERBY D WHERE C is Comment, C creation_date D LIMIT 15
-
-displayed with the RSS view, that's a channel for the last fifteen
-comments posted.
-
-[WRITE ME]
-
-* show that the RSS view can be used to display an ordered selection
- of blog entries, thus providing a RSS channel
-
-* show that a different selection (by category) means a different channel
-
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A030-foundation.en.txt
--- a/doc/book/en/A030-foundation.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-`CubicWeb` Foundations
-======================
-
-A little history...
--------------------
-
-`CubicWeb` is a web application framework developped by Logilab_ since 2001.
-
-Entirely written in Python, `CubicWeb` publishes data from all sorts
-of sources such as SQL database, LDAP directory and versioning system such
-as subversion.
-
-`CubicWeb` user interface was designed to let the final user a huge flexibility
-on how to select and how to display content. It allows to browse the knowledge
-database and to display the results with the best rendering according to
-the context.
-This interface flexibility gives back the user the control of the
-rendering parameters that are usually reserved for developpers.
-
-
-We can list a couple of web applications developped with `CubicWeb`, an online
-public phone directory (see http://www.118000.fr/), a system for managing
-digital studies and simulations for a research lab, a tool for shared children
-babysitting (see http://garde-partagee.atoukontact.fr/), a tool to manage
-software developpment (see http://www.logilab.org), an application for
-managing museums collections (see
-http://collections.musees-haute-normandie.fr/collections/), etc.
-
-In 2008, `CubicWeb` was ported for a new type of source : the datastore
-from `GoogleAppEngine`_.
-
-.. include:: A03a-concepts.en.txt
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/A03a-concepts.en.txt
--- a/doc/book/en/A03a-concepts.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,536 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Concepts
---------
-
-This section aims to provide you the keys of success with `CubicWeb`
-by clarifying the terms specific to our framework.
-
-Global architecture
-~~~~~~~~~~~~~~~~~~~
-.. image:: images/archi_globale.en.png
-
-
-`CubicWeb` framework is a server/client application framework. Those two
-parties communicates through RQL (`CubicWeb` query language implementation)
-and ResultSet (which will be explained in :ref:`TermsVocabulary`).
-
-The server manages all interactions with sources.
-
-
-.. note::
- For real, the client and server sides are integrated in the same
- process and interact directly, without the needs for distants
- calls using Pyro. It is important to note down that those two
- sides, client/server, are disjointed and it is possible to execute
- a couple of calls in distincts processes to balance the load of
- your web site on one or more machines.
-
-.. _TermsVocabulary:
-
-Terms and vocabulary
-~~~~~~~~~~~~~~~~~~~~~
-
-`CubicWeb` defines its own terminology. To make sure there is no confusion
-while reading this book, we strongly recommand you take time to go through
-the following definitions that are the basics to understand while
-developing with `CubicWeb`.
-
-*schema*
- The schema defines the data model of an application based on entities
- and relations, modeled with a comprehensive language made of Python
- classes based on `yams`_ library. This is the core piece
- of an application. It is initially defined in the file system and is
- stored in the database at the time an instance is created. `CubicWeb`
- provides a certain number of system entities included automatically as
- it is necessary for the core of `CubicWeb` and a library of
- cubes (which defined application entities) that can be explicitely
- included if necessary.
-
-*entity type*
- An entity type is a set of attributes; the essential attribute of
- an entity is its key, named eid.
-
-*relation type*
- Entities are linked to each others by relations. In `CubicWeb`
- relations are binary: by convention we name the first item of
- a relation the `subject` and the second the `object`.
-
-*final entity type*
- Final types corresponds to the basic types such as string of characters,
- integers... Those types have a main property which is that they can
- only be used as `object` of a relation. The attributes of an entity
- (non final) are entities (finals).
-
-*final relation type*
- A relation is said final if its `object` is a final type. This is equivalent
- to an entity attribute.
-
-*relation definition*
- A relation definition is a 3-uple (subject entity type, relation type, object
- entity type), with an associated set of property such as cardinality, constraints...
-
-*repository*
- This is the RQL server side of `CubicWeb`. Be carefull not to get
- confused with a Mercurial repository or a debian repository.
-
-*source*
- A data source is a container of data (SGBD, LDAP directory, `Google
- App Engine`'s datastore ...) integrated in the
- `CubicWeb` repository. This repository has at least one source, `system` which
- contains the schema of the application, plain-text index and others
- vital informations for the system.
-
-*configuration*
- It is possible to create differents configurations for an instance:
-
- - ``repository`` : repository only, accessible for clients using Pyro
- - ``twisted`` : web interface only, access the repository using Pyro
- - ``all-in-one`` : web interface and repository in a single process.
- The repository could be or not accessible using Pyro.
-
-*cube*
- A cube is a model grouping one or multiple data types and/or views
- to provide a specific functionnality or a complete `CubicWeb` application
- potentially using other cubes. The available cubes are located in the file
- system at `/path/to/forest/cubicweb/cubes` for a Mercurial forest installation,
- for a debian packages installation they will be located in
- `/usr/share/cubicweb/cubes`.
- Larger applications can be built faster by importing cubes,
- adding entities and relationships and overriding the
- views that need to display or edit informations not provided by
- cubes.
-
-*instance*
- An instance is a specific installation of one or multiple cubes. All the required
- configuration files necessary for the well being of your web application
- are grouped in an instance. This will refer to the cube(s) your application
- is based on.
- For example logilab.org and our intranet are two instances of a single
- cube jpl, developped internally.
- The instances are defined in the directory `/etc/cubicweb.d`.
-
-*application*
- The term application is sometime used to talk about an instance
- and sometimes to talk of a cube depending on the context.
- So we would like to avoid using this term and try to use *cube* and
- *instance* instead.
-
-*result set*
- This object contains the results of an RQL query sent to the source
- and informations on the query.
-
-*Pyro*
- `Python Remote Object`_, distributed objects system similar to Java's RMI
- (Remote Method Invocation), which can be used for the dialog between the web
- side of the framework and the RQL repository.
-
-*query language*
- A full-blown query language named RQL is used to formulate requests
- to the database or any sources such as LDAP or `Google App Engine`'s
- datastore.
-
-*views*
- A view is applied to a `result set` to present it as HTML, XML,
- JSON, CSV, etc. Views are implemented as Python classes. There is no
- templating language.
-
-*generated user interface*
- A user interface is generated on-the-fly from the schema definition:
- entities can be created, displayed, updated and deleted. As display
- views are not very fancy, it is usually necessary to develop your
- own. Any generated view can be overridden by defining a new one with
- the same identifier.
-
-*rql*
- Relation Query Language in order to empasize the way of browsing relations.
- This query language is inspired by SQL but is highest level, its implementation
- generates SQL.
-
-
-.. _`Python Remote Object`: http://pyro.sourceforge.net/
-.. _`yams`: http://www.logilab.org/project/yams/
-
-
-`CubicWeb` engine
-~~~~~~~~~~~~~~~~~
-
-The engine in `CubicWeb` is a set of classes managing a set of objects loaded
-dynamically at the startup of `CubicWeb` (*appobjects*). Those dynamics objects,
-based on the schema or the library, are building the final application.
-The differents dymanic components are for example:
-
-* client and server side
-
- - entities definition, containing the logic which enables application data manipulation
-
-* client side
-
- - *views*, or more specifically
-
- - boxes
- - header and footer
- - forms
- - page templates
-
- - *actions*
- - *controllers*
-
-* server side
-
- - notification hooks
- - notification views
-
-The components of the engine are:
-
-* a frontal web (only twisted is available so far), transparent for dynamic objects
-* an object that encapsulates the configuration
-* a `registry` (`cubicweb.cwvreg`) containing the dynamic objects loaded automatically
-
-Every *appobject* may access to the instance configuration using its *config* attribute
-and to the registry using its *vreg* attribute.
-
-API Python/RQL
-~~~~~~~~~~~~~~
-
-The Python API developped to interface with RQL is inspired from the standard db-api,
-with a Connection object having the methods cursor, rollback and commit essentially.
-The most important method is the `execute` method of a cursor :
-
-`execute(rqlstring, args=None, eid_key=None, build_descr=True)`
-
-:rqlstring: the RQL query to execute (unicode)
-:args: if the query contains substitutions, a dictionnary containing the values to use
-:eid_key:
- an implementation detail of the RQL queries cache implies that if a substitution
- is used to introduce an eid *susceptible to raise the ambiguities in the query
- type resolution*, then we have to specify the correponding key in the dictionnary
- through this argument
-
-
-The `Connection` object owns the methods `commit` and `rollback`. You *should
-never need to use them* during the development of the web interface based on
-the `CubicWeb` framework as it determines the end of the transaction depending
-on the query execution success.
-
-.. note::
- While executing updates queries (SET, INSERT, DELETE), if a query generates
- an error related to security, a rollback is automatically done on the current
- transaction.
-
-
-The `Request` class (`cubicweb.web`)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A request instance is created when an HTTP request is sent to the web server.
-It contains informations such as forms parameters, user authenticated, etc.
-
-**Globally, a request represents a user query, either through HTTP or not
-(we also talk about RQL queries on the server side for example).**
-
-An instance of `Request` has the following attributes:
-
-* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
- user
-* `form`, dictionnary containing the values of a web form
-* `encoding`, characters encoding to use in the response
-
-But also:
-
-:Session data handling:
- * `session_data()`, returns a dictionnary containing all the session data
- * `get_session_data(key, default=None)`, returns a value associated to the given
- key or the value `default` if the key is not defined
- * `set_session_data(key, value)`, assign a value to a key
- * `del_session_data(key)`, suppress the value associated to a key
-
-
-:Cookies handling:
- * `get_cookie()`, returns a dictionnary containing the value of the header
- HTTP 'Cookie'
- * `set_cookie(cookie, key, maxage=300)`, adds a header HTTP `Set-Cookie`,
- with a minimal 5 minutes length of duration by default (`maxage` = None
- returns a *session* cookie which will expire when the user closes the browser
- window)
- * `remove_cookie(cookie, key)`, forces a value to expire
-
-:URL handling:
- * `url()`, returns the full URL of the HTTP request
- * `base_url()`, returns the root URL of the web application
- * `relative_path()`, returns the relative path of the request
-
-:And more...:
- * `set_content_type(content_type, filename=None)`, adds the header HTTP
- 'Content-Type'
- * `get_header(header)`, returns the value associated to an arbitrary header
- of the HTTP request
- * `set_header(header, value)`, adds an arbitrary header in the response
- * `cursor()` returns a RQL cursor on the session
- * `execute(*args, **kwargs)`, shortcut to ``.cursor().execute()``
- * `property_value(key)`, properties management (`EProperty`)
- * dictionnary `data` to store data to share informations between components
- *while a request is executed*
-
-Please note that this class is abstract and that a concrete implementation
-will be provided by the *frontend* web used (in particular *twisted* as of
-today). For the views or others that are executed on the server side,
-most of the interface of `Request` is defined in the session associated
-to the client.
-
-The `AppObject` class
-~~~~~~~~~~~~~~~~~~~~~
-
-In general:
-
-* we do not inherit directly from this class but from a more specific
- class such as `AnyEntity`, `EntityView`, `AnyRsetView`,
- `Action`...
-
-* to be recordable, a subclass has to define its own register (attribute
- `__registry__`) and its identifier (attribute `id`). Usually we do not have
- to take care of the register, only the identifier `id`.
-
-We can find a certain number of attributes and methods defined in this class
-and common to all the application objects.
-
-At the recording, the following attributes are dynamically added to
-the *subclasses*:
-
-* `vreg`, the `vregistry` of the application
-* `schema`, the application schema
-* `config`, the application configuration
-
-We also find on instances, the following attributes:
-
-* `req`, `Request` instance
-* `rset`, the *result set* associated to the object if necessary
-* `cursor`, rql cursor on the session
-
-
-:URL handling:
- * `build_url(method=None, **kwargs)`, returns an absolute URL based on
- the given arguments. The *controller* supposed to handle the response,
- can be specified through the special parameter `method` (the connection
- is theoretically done automatically :).
-
- * `datadir_url()`, returns the directory of the application data
- (contains static files such as images, css, js...)
-
- * `base_url()`, shortcut to `req.base_url()`
-
- * `url_quote(value)`, version *unicode safe* of the function `urllib.quote`
-
-:Data manipulation:
-
- * `etype_rset(etype, size=1)`, shortcut to `vreg.etype_rset()`
-
- * `eid_rset(eid, rql=None, descr=True)`, returns a *result set* object for
- the given eid
- * `entity(row, col=0)`, returns the entity corresponding to the data position
- in the *result set* associated to the object
-
- * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but
- also call the method `complete()` on the entity before returning it
-
-:Data formatting:
- * `format_date(date, date_format=None, time=False)` returns a string for a
- mx date time according to application's configuration
- * `format_time(time)` returns a string for a mx date time according to
- application's configuration
-
-:And more...:
-
- * `external_resource(rid, default=_MARKER)`, access to a value defined in the
- configuration file `external_resource`
-
- * `tal_render(template, variables)`, renders a precompiled page template with
- variables in the given dictionary as context
-
-.. note::
- When we inherit from `AppObject` (even not directly), you *always* have to use
- **super()** to get the methods and attributes of the superclasses, and not
- use the class identifier.
- For example, instead of writting: ::
-
- class Truc(PrimaryView):
- def f(self, arg1):
- PrimaryView.f(self, arg1)
-
- You'd better write: ::
-
- class Truc(PrimaryView):
- def f(self, arg1):
- super(Truc, self).f(arg1)
-
-.. _cubesConcepts:
-
-Cubes
-~~~~~
-
-What is a cube ?
-````````````````
-
-A cube is a model grouping one or more entity types and/or views associated
-in order to provide a specific feature or even a complete application using
-others cubes.
-
-You can decide to write your own set of cubes if you wish to re-use the
-entity types you develop. Lots of cubes are available from the `CubicWeb
-Forge`_ under a free software license.
-
-.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
-
-.. _foundationsCube:
-
-Standard structure for a cube
-`````````````````````````````
-
-A cube is structured as follows:
-
-::
-
- mycube/
- |
- |-- data/
- | |-- cubes.mycube.css
- | |-- cubes.mycube.js
- | `-- external_resources
- |
- |-- debian/
- | |-- changelog
- | |-- compat
- | |-- control
- | |-- copyright
- | |-- cubicweb-mycube.prerm
- | `-- rules
- |
- |-- entities.py
- |
- |-- i18n/
- | |-- en.po
- | `-- fr.po
- |
- |-- __init__.py
- |
- |-- MANIFEST.in
- |
- |-- migration/
- | |-- postcreate.py
- | `-- precreate.py
- |
- |-- __pkginfo__.py
- |
- |-- schema.py
- |
- |-- setup.py
- |
- |-- site_cubicweb.py
- |
- |-- hooks.py
- |
- |-- test/
- | |-- data/
- | | `-- bootstrap_cubes
- | |-- pytestconf.py
- | |-- realdb_test_mycube.py
- | `-- test_mycube.py
- |
- `-- views.py
-
-
-We can use subpackages instead of python modules for ``views.py``, ``entities.py``,
-``schema.py`` or ``hooks.py``. For example, we could have:
-
-::
-
- mycube/
- |
- |-- entities.py
- |-- hooks.py
- `-- views/
- |-- forms.py
- |-- primary.py
- `-- widgets.py
-
-
-where :
-
-* ``schema`` contains the schema definition (server side only)
-* ``entities`` contains the entities definition (server side and web interface)
-* ``sobjects`` contains hooks and/or views notifications (server side only)
-* ``views`` contains the web interface components (web interface only)
-* ``test`` contains tests related to the application (not installed)
-* ``i18n`` contains messages catalogs for supported languages (server side and
- web interface)
-* ``data`` contains data files for static content (images, css, javascripts)
- ...(web interface only)
-* ``migration`` contains initialization file for new instances (``postcreate.py``)
- and a file containing dependencies of the component depending on the version
- (``depends.map``)
-* ``debian`` contains all the files managing debian packaging (you will find
- the usual files ``control``, ``rules``, ``changelog``... not installed)
-* file ``__pkginfo__.py`` provides component meta-data, especially the distribution
- and the current version (server side and web interface) or sub-cubes used by
- the cube.
-
-
-At least you should have:
-
-* the file ``__pkginfo__.py``
-* the schema definition
- XXX false, we may want to have cubes which are only adding a service,
- no persistent data (eg embeding for instance)
-
-
-Standard library
-````````````````
-
-A library of standard cubes are available from `CubicWeb Forge`_
-Cubes provide entities and views.
-
-The available application entities are:
-
-* addressbook_: PhoneNumber and PostalAddress
-
-* basket_: Basket (like a shopping cart)
-
-* blog_: Blog (a *very* basic blog)
-
-* comment_: Comment (to attach comment threads to entities)
-
-* event_: Event (define events, display them in calendars)
-
-* file_: File (to allow users to upload and store binary or text files)
-
-* folder_: Folder (to organize things but grouping them in folders)
-
-* keyword_: Keyword (to define classification schemes)
-
-* link_: Link (to collect links to web resources)
-
-* mailinglist_: MailingList (to reference a mailing-list and the URLs
- for its archives and its admin interface)
-
-* person_: Person (easily mixed with addressbook)
-
-* tag_: Tag (to tag anything)
-
-* task_: Task (something to be done between start and stop date)
-
-* zone_: Zone (to define places within larger places, for example a
- city in a state in a country)
-
-.. _addressbook: http://www.cubicweb.org/project/cubicweb-addressbook
-.. _basket: http://www.cubicweb.org/project/cubicweb-basket
-.. _blog: http://www.cubicweb.org/project/cubicweb-blog
-.. _comment: http://www.cubicweb.org/project/cubicweb-comment
-.. _event: http://www.cubicweb.org/project/cubicweb-event
-.. _file: http://www.cubicweb.org/project/cubicweb-file
-.. _folder: http://www.cubicweb.org/project/cubicweb-folder
-.. _keyword: http://www.cubicweb.org/project/cubicweb-keyword
-.. _link: http://www.cubicweb.org/project/cubicweb-link
-.. _mailinglist: http://www.cubicweb.org/project/cubicweb-mailinglist
-.. _person: http://www.cubicweb.org/project/cubicweb-person
-.. _tag: http://www.cubicweb.org/project/cubicweb-tag
-.. _task: http://www.cubicweb.org/project/cubicweb-task
-.. _zone: http://www.cubicweb.org/project/cubicweb-zone
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/B0-data-model.en.txt
--- a/doc/book/en/B0-data-model.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-The data model
-++++++++++++++
-
-.. toctree::
- :maxdepth: 1
-
- B0010-define-schema.en.txt
- B0020-define-workflows.en.txt
- B0030-data-as-objects.en.txt
- B0040-migration.en.txt
-
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/B000-development.en.txt
--- a/doc/book/en/B000-development.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ b/doc/book/en/B000-development.en.txt Wed Jun 03 19:50:34 2009 +0200
@@ -1,5 +1,6 @@
.. -*- coding: utf-8 -*-
+.. _Part2:
=====================
Part II - Development
diff -r 292b7989b166 -r ddf4f2d8d51c doc/book/en/B0010-define-schema.en.txt
--- a/doc/book/en/B0010-define-schema.en.txt Wed Jun 03 19:49:44 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-Data model definition (*schema*)
-================================
-
-The schema is the core piece of a `CubicWeb` application as it defines
-the data model handled. It is based on entities types already defined
-in the `CubicWeb` standard library and others, more specific, we would
-expect to find in one or more Python files under the `schema` directory.
-
-At this point, it is important to make clear the difference between
-relation type and relation definition: a relation type is only a relation
-name with potentially other additionnal properties (see XXXX), whereas a
-relation definition is a complete triplet
-"