--- a/.hgtags Tue Aug 04 11:43:03 2009 +0200
+++ b/.hgtags Tue Aug 04 15:06:09 2009 +0200
@@ -40,3 +40,13 @@
4003d24974f15f17bd03b7efd6a5047cad4e4c41 cubicweb-debian-version-3_2_3-1
2d7d3062ca03d4b4144100013dc4ab7f9d9cb25e cubicweb-version-3_3_0
07214e923e75c8f0490e609e9bee0f4964b87114 cubicweb-debian-version-3_3_0-1
+a356da3e725bfcb59d8b48a89d04be05ea261fd3 3.3.1
+e3aeb6e6c3bb5c18e8dcf61bae9d654beda6c036 cubicweb-version-3_3_2
+bef5e74e53f9de8220451dca4b5863a24a0216fb cubicweb-debian-version-3_3_2-1
+1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
+81973c897c9e78e5e52643e03628654916473196 cubicweb-debian-version-3_3_3-1
+1cf9e44e2f1f4415253b8892a0adfbd3b69e84fd cubicweb-version-3_3_3
+47b5236774a0cf3b1cfe75f6d4bd2ec989644ace cubicweb-version-3_3_3
+2ba27ce8ecd9828693ec53c517e1c8810cbbe33e cubicweb-debian-version-3_3_3-2
+d46363eac5d71bc1570d69337955154dfcd8fcc8 cubicweb-version-3.3.4
+7dc22caa7640bf70fcae55afb6d2326829dacced cubicweb-debian-version-3.3.4-1
--- a/README Tue Aug 04 11:43:03 2009 +0200
+++ b/README Tue Aug 04 15:06:09 2009 +0200
@@ -1,18 +1,27 @@
-CubicWeb semantic web framework
+CubicWeb semantic web framework
===============================
-
+
Install
-------
-From the source distribution, extract the tarball and run ::
-
- python setup.py install
-
-For deb and rpm packages, use the tools recommended by your distribution.
+
+More details at http://www.cubicweb.org/doc/en/admin/setup
+
+Getting started
+---------------
+
+Execute:
-
+ apt-get install cubicweb cubicweb-dev cubicweb-blog
+ cubicweb-ctl create blog myblog
+ cubicweb-ctl start -D myblog
+ sensible-browser http://localhost:8080/
+
+Details at http://www.cubicweb.org/doc/en/intro/tutorial/blog-in-five-minutes
+
Documentation
-------------
-Look in the doc/ subdirectory.
+
+Look in the doc/ subdirectory or read http://www.cubicweb.org/doc/en/
--- a/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -100,13 +100,27 @@
[(etype,)])
return self.decorate_rset(rset)
+ def empty_rset(self):
+ """return a result set for the given eid without doing actual query
+ (we have the eid, we can suppose it exists and user has access to the
+ entity)
+ """
+ from cubicweb.rset import ResultSet
+ return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1'))
+
def entity_from_eid(self, eid, etype=None):
- rset = self.eid_rset(eid, etype)
- if rset:
- return rset.get_entity(0, 0)
- else:
- return None
+ try:
+ return self.entity_cache(eid)
+ except KeyError:
+ rset = self.eid_rset(eid, etype)
+ entity = rset.get_entity(0, 0)
+ self.set_entity_cache(entity)
+ return entity
+ def entity_cache(self, eid):
+ raise KeyError
+ def set_entity_cache(self, entity):
+ pass
# url generation methods ##################################################
def build_url(self, *args, **kwargs):
@@ -198,7 +212,7 @@
# abstract methods to override according to the web front-end #############
def base_url(self):
- """return the root url of the application"""
+ """return the root url of the instance"""
raise NotImplementedError
def decorate_rset(self, rset):
@@ -300,3 +314,6 @@
except AttributeError:
return neg_role(obj.role)
+def underline_title(title, car='-'):
+ return title+'\n'+(car*len(title))
+
--- a/__pkginfo__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/__pkginfo__.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
distname = "cubicweb"
modname = "cubicweb"
-numversion = (3, 3, 0)
+numversion = (3, 4, 0)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL v2'
@@ -32,6 +32,13 @@
ftp = 'ftp://ftp.logilab.org/pub/cubicweb'
pyversions = ['2.4', '2.5']
+classifiers = [
+ 'Environment :: Web Environment',
+ 'Framework :: CubicWeb',
+ 'Programming Language :: Python',
+ 'Programming Language :: JavaScript',
+]
+
import sys
from os import listdir, environ
--- a/_exceptions.py Tue Aug 04 11:43:03 2009 +0200
+++ b/_exceptions.py Tue Aug 04 15:06:09 2009 +0200
@@ -129,6 +129,9 @@
class UnknownProperty(RegistryException):
"""property found in database but unknown in registry"""
+class RegistryOutOfDate(RegistryException):
+ """raised when a source file modification is detected"""
+
# query exception #############################################################
class QueryError(CubicWebRuntimeError):
--- a/appobject.py Tue Aug 04 11:43:03 2009 +0200
+++ b/appobject.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from datetime import datetime, timedelta, time
from logilab.common.decorators import classproperty
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
from rql.nodes import VariableRef, SubQuery
from rql.stmts import Union, Select
@@ -25,8 +25,9 @@
class Cache(dict):
def __init__(self):
super(Cache, self).__init__()
- self.cache_creation_date = None
- self.latest_cache_lookup = datetime.now()
+ _now = datetime.now()
+ self.cache_creation_date = _now
+ self.latest_cache_lookup = _now
CACHE_REGISTRY = {}
@@ -39,11 +40,11 @@
At registration time, the following attributes are set on the class:
:vreg:
- the application's registry
+ the instance's registry
:schema:
- the application's schema
+ the instance's schema
:config:
- the application's configuration
+ the instance's configuration
At instantiation time, the following attributes are set on the instance:
:req:
@@ -100,7 +101,7 @@
return '%s.%s.%s' % (cls.__registry__, cls.id, propid)
@classproperty
- @obsolete('use __select__ and & or | operators')
+ @deprecated('use __select__ and & or | operators')
def __selectors__(cls):
selector = cls.__select__
if isinstance(selector, AndSelector):
@@ -127,8 +128,7 @@
if cachename in CACHE_REGISTRY:
cache = CACHE_REGISTRY[cachename]
else:
- cache = Cache()
- CACHE_REGISTRY[cachename] = cache
+ cache = CACHE_REGISTRY[cachename] = Cache()
_now = datetime.now()
if _now > cache.latest_cache_lookup + ONESECOND:
ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T',
@@ -276,7 +276,7 @@
return output.getvalue()
def format_date(self, date, date_format=None, time=False):
- """return a string for a date time according to application's
+ """return a string for a date time according to instance's
configuration
"""
if date:
@@ -289,7 +289,7 @@
return u''
def format_time(self, time):
- """return a string for a time according to application's
+ """return a string for a time according to instance's
configuration
"""
if time:
@@ -297,7 +297,7 @@
return u''
def format_float(self, num):
- """return a string for floating point number according to application's
+ """return a string for floating point number according to instance's
configuration
"""
if num:
--- a/common/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -18,9 +18,9 @@
rtype = 'String'
@classmethod
- def st_description(cls, funcnode):
- return ', '.join(term.get_description()
- for term in iter_funcnode_variables(funcnode))
+ def st_description(cls, funcnode, mainindex, tr):
+ return ', '.join(sorted(term.get_description(mainindex, tr)
+ for term in iter_funcnode_variables(funcnode)))
register_function(COMMA_JOIN) # XXX do not expose?
@@ -41,8 +41,8 @@
rtype = 'String'
@classmethod
- def st_description(cls, funcnode):
- return funcnode.children[0].get_description()
+ def st_description(cls, funcnode, mainindex, tr):
+ return funcnode.children[0].get_description(mainindex, tr)
register_function(LIMIT_SIZE)
--- a/common/i18n.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/i18n.py Tue Aug 04 15:06:09 2009 +0200
@@ -63,7 +63,7 @@
"""generate .mo files for a set of languages into the `destdir` i18n directory
"""
from logilab.common.fileutils import ensure_fs_mode
- print 'compiling %s catalogs...' % destdir
+ print '-> compiling %s catalogs...' % destdir
errors = []
for lang in langs:
langdir = join(destdir, lang, 'LC_MESSAGES')
@@ -73,7 +73,7 @@
pofiles = [pof for pof in pofiles if exists(pof)]
mergedpo = join(destdir, '%s_merged.po' % lang)
try:
- # merge application messages' catalog with the stdlib's one
+ # merge instance/cubes messages catalogs with the stdlib's one
execute('msgcat --use-first --sort-output --strict %s > %s'
% (' '.join(pofiles), mergedpo))
# make sure the .mo file is writeable and compile with *msgfmt*
--- a/common/mail.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/mail.py Tue Aug 04 15:06:09 2009 +0200
@@ -18,8 +18,8 @@
def addrheader(uaddr, uname=None):
# even if an email address should be ascii, encode it using utf8 since
- # application tests may generate non ascii email address
- addr = uaddr.encode('UTF-8')
+ # automatic tests may generate non ascii email address
+ addr = uaddr.encode('UTF-8')
if uname:
return '%s <%s>' % (header(uname).encode(), addr)
return addr
--- a/common/migration.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/migration.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,5 +1,4 @@
-"""utility to ease migration of application version to newly installed
-version
+"""utilities for instances migration
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -11,29 +10,14 @@
import sys
import os
import logging
-from tempfile import mktemp
+import tempfile
from os.path import exists, join, basename, splitext
from logilab.common.decorators import cached
from logilab.common.configuration import REQUIRED, read_old_config
-
+from logilab.common.shellutils import ASK
-def migration_files(config, toupgrade):
- """return an orderer list of path of scripts to execute to upgrade
- an installed application according to installed cube and cubicweb versions
- """
- merged = []
- for cube, fromversion, toversion in toupgrade:
- if cube == 'cubicweb':
- migrdir = config.migration_scripts_dir()
- else:
- migrdir = config.cube_migration_scripts_dir(cube)
- scripts = filter_scripts(config, migrdir, fromversion, toversion)
- merged += [s[1] for s in scripts]
- if config.accept_mode('Any'):
- migrdir = config.migration_scripts_dir()
- merged.insert(0, join(migrdir, 'bootstrapmigration_repository.py'))
- return merged
+from cubicweb import ConfigurationError
def filter_scripts(config, directory, fromversion, toversion, quiet=True):
@@ -86,11 +70,10 @@
ability to show the script's content
"""
while True:
- confirm = raw_input('** execute %r (Y/n/s[how]) ?' % scriptpath)
- confirm = confirm.strip().lower()
- if confirm in ('n', 'no'):
+ answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
+ if answer == 'n':
return False
- elif confirm in ('s', 'show'):
+ elif answer == 'show':
stream = open(scriptpath)
scriptcontent = stream.read()
stream.close()
@@ -124,6 +107,18 @@
'interactive_mode': interactive,
}
+ def __getattribute__(self, name):
+ try:
+ return object.__getattribute__(self, name)
+ except AttributeError:
+ cmd = 'cmd_%s' % name
+ if hasattr(self, cmd):
+ meth = getattr(self, cmd)
+ return lambda *args, **kwargs: self.interact(args, kwargs,
+ meth=meth)
+ raise
+ raise AttributeError(name)
+
def repo_connect(self):
return self.config.repository()
@@ -142,34 +137,42 @@
return False
return orig_accept_mode(mode)
self.config.accept_mode = accept_mode
- scripts = migration_files(self.config, toupgrade)
- if scripts:
- vmap = dict( (pname, (fromver, tover)) for pname, fromver, tover in toupgrade)
- self.__context.update({'applcubicwebversion': vcconf['cubicweb'],
- 'cubicwebversion': self.config.cubicweb_version(),
- 'versions_map': vmap})
- self.scripts_session(scripts)
- else:
- print 'no migration script to execute'
+ # may be an iterator
+ toupgrade = tuple(toupgrade)
+ vmap = dict( (cube, (fromver, tover)) for cube, fromver, tover in toupgrade)
+ ctx = self.__context
+ ctx['versions_map'] = vmap
+ if self.config.accept_mode('Any') and 'cubicweb' in vmap:
+ migrdir = self.config.migration_scripts_dir()
+ self.process_script(join(migrdir, 'bootstrapmigration_repository.py'))
+ for cube, fromversion, toversion in toupgrade:
+ if cube == 'cubicweb':
+ migrdir = self.config.migration_scripts_dir()
+ else:
+ migrdir = self.config.cube_migration_scripts_dir(cube)
+ scripts = filter_scripts(self.config, migrdir, fromversion, toversion)
+ if scripts:
+ prevversion = None
+ for version, script in scripts:
+ # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call
+ # cube_upgraded once all script of X.Y.Z have been executed
+ if prevversion is not None and version != prevversion:
+ self.cube_upgraded(cube, version)
+ prevversion = version
+ self.process_script(script)
+ self.cube_upgraded(cube, toversion)
+ else:
+ self.cube_upgraded(cube, toversion)
+
+ def cube_upgraded(self, cube, version):
+ pass
def shutdown(self):
pass
- def __getattribute__(self, name):
- try:
- return object.__getattribute__(self, name)
- except AttributeError:
- cmd = 'cmd_%s' % name
- if hasattr(self, cmd):
- meth = getattr(self, cmd)
- return lambda *args, **kwargs: self.interact(args, kwargs,
- meth=meth)
- raise
- raise AttributeError(name)
-
def interact(self, args, kwargs, meth):
"""execute the given method according to user's confirmation"""
- msg = 'execute command: %s(%s) ?' % (
+ msg = 'Execute command: %s(%s) ?' % (
meth.__name__[4:],
', '.join([repr(arg) for arg in args] +
['%s=%r' % (n,v) for n,v in kwargs.items()]))
@@ -185,27 +188,24 @@
if `retry` is true the r[etry] answer may return 2
"""
- print question,
- possibleanswers = 'Y/n'
+ possibleanswers = ['Y','n']
if abort:
- possibleanswers += '/a[bort]'
+ possibleanswers.append('abort')
if shell:
- possibleanswers += '/s[hell]'
+ possibleanswers.append('shell')
if retry:
- possibleanswers += '/r[etry]'
+ possibleanswers.append('retry')
try:
- confirm = raw_input('(%s): ' % ( possibleanswers, ))
- answer = confirm.strip().lower()
+ answer = ASK.ask(question, possibleanswers, 'Y')
except (EOFError, KeyboardInterrupt):
answer = 'abort'
- if answer in ('n', 'no'):
+ if answer == 'n':
return False
- if answer in ('r', 'retry'):
+ if answer == 'retry':
return 2
- if answer in ('a', 'abort'):
- self.rollback()
+ if answer == 'abort':
raise SystemExit(1)
- if shell and answer in ('s', 'shell'):
+ if shell and answer == 'shell':
self.interactive_shell()
return self.confirm(question)
return True
@@ -282,16 +282,6 @@
return None
return func(*args, **kwargs)
- def scripts_session(self, migrscripts):
- """execute some scripts in a transaction"""
- try:
- for migrscript in migrscripts:
- self.process_script(migrscript)
- self.commit()
- except:
- self.rollback()
- raise
-
def cmd_option_renamed(self, oldname, newname):
"""a configuration option has been renamed"""
self._option_changes.append(('renamed', oldname, newname))
@@ -328,18 +318,18 @@
self.config.add_cubes(newcubes)
return newcubes
- def cmd_remove_cube(self, cube):
+ def cmd_remove_cube(self, cube, removedeps=False):
+ if removedeps:
+ toremove = self.config.expand_cubes([cube])
+ else:
+ toremove = (cube,)
origcubes = self.config._cubes
- basecubes = list(origcubes)
- for pkg in self.config.expand_cubes([cube]):
- try:
- basecubes.remove(pkg)
- except ValueError:
- continue
+ basecubes = [c for c in origcubes if not c in toremove]
self.config._cubes = tuple(self.config.expand_cubes(basecubes))
removed = [p for p in origcubes if not p in self.config._cubes]
- assert cube in removed, \
- "can't remove cube %s, used as a dependancy" % cube
+ if not cube in removed:
+ raise ConfigurationError("can't remove cube %s, "
+ "used as a dependency" % cube)
return removed
def rewrite_configuration(self):
@@ -348,7 +338,7 @@
configfile = self.config.main_config_file()
if self._option_changes:
read_old_config(self.config, self._option_changes, configfile)
- newconfig = mktemp()
+ _, newconfig = tempfile.mkstemp()
for optdescr in self._option_changes:
if optdescr[0] == 'added':
optdict = self.config.get_option_def(optdescr[1])
--- a/common/mixins.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/mixins.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
from logilab.common.decorators import cached
from cubicweb import typed_eid
@@ -191,11 +191,18 @@
return rset.get_entity(0, 0)
return None
- def change_state(self, stateeid, trcomment=None, trcommentformat=None):
+ def change_state(self, state, 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 isinstance(state, basestring):
+ state = self.wf_state(state)
+ assert state is not None, 'not a %s state: %s' % (self.id, state)
+ if hasattr(state, 'eid'):
+ stateeid = state.eid
+ else:
+ stateeid = state
+ stateeid = typed_eid(stateeid)
if trcomment:
self.req.set_shared_data('trcomment', trcomment)
if trcommentformat:
@@ -235,7 +242,7 @@
# specific vocabulary methods #############################################
- @obsolete('use EntityFieldsForm.subject_in_state_vocabulary')
+ @deprecated('use EntityFieldsForm.subject_in_state_vocabulary')
def subject_in_state_vocabulary(self, rschema, limit=None):
form = self.vreg.select('forms', 'edition', self.req, entity=self)
return form.subject_in_state_vocabulary(rschema, limit)
--- a/common/mttransforms.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/mttransforms.py Tue Aug 04 15:06:09 2009 +0200
@@ -46,8 +46,8 @@
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')
+ from cubicweb import schema
+ schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template')
else:
HAS_TAL = True
--- a/common/tags.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/tags.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.common.uilib import simple_sgml_tag
+from cubicweb.common.uilib import simple_sgml_tag, sgml_attributes
class tag(object):
def __init__(self, name, escapecontent=True):
@@ -38,8 +38,7 @@
if id:
attrs['id'] = id
attrs['name'] = name
- html = [u'<select %s>' % ' '.join('%s="%s"' % kv
- for kv in sorted(attrs.items()))]
+ html = [u'<select %s>' % sgml_attributes(attrs)]
html += options
html.append(u'</select>')
return u'\n'.join(html)
--- a/common/test/unittest_migration.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/test/unittest_migration.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,7 +13,8 @@
from cubicweb.devtools.apptest import TestEnvironment
from cubicweb.cwconfig import CubicWebConfiguration
-from cubicweb.common.migration import migration_files, filter_scripts
+from cubicweb.common.migration import MigrationHelper, filter_scripts
+from cubicweb.server.migractions import ServerMigrationHelper
class Schema(dict):
@@ -39,82 +40,53 @@
self.config.__class__.cubicweb_vobject_path = frozenset()
self.config.__class__.cube_vobject_path = frozenset()
- def test_migration_files_base(self):
- self.assertListEquals(migration_files(self.config, [('cubicweb', (2,3,0), (2,4,0)),
- ('TEMPLATE', (0,0,2), (0,0,3))]),
- [SMIGRDIR+'bootstrapmigration_repository.py',
- TMIGRDIR+'0.0.3_Any.py'])
- self.assertListEquals(migration_files(self.config, [('cubicweb', (2,4,0), (2,5,0)),
- ('TEMPLATE', (0,0,2), (0,0,3))]),
- [SMIGRDIR+'bootstrapmigration_repository.py',
- SMIGRDIR+'2.5.0_Any.sql',
- TMIGRDIR+'0.0.3_Any.py'])
- self.assertListEquals(migration_files(self.config, [('cubicweb', (2,5,0), (2,6,0)),
- ('TEMPLATE', (0,0,3), (0,0,4))]),
- [SMIGRDIR+'bootstrapmigration_repository.py',
- SMIGRDIR+'2.6.0_Any.sql',
- TMIGRDIR+'0.0.4_Any.py'])
+ def test_filter_scripts_base(self):
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
+ [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
+ ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
+ [])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
+ ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+ self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
+ [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
-## def test_migration_files_overlap(self):
-## self.assertListEquals(migration_files(self.config, (2,4,0), (2,10,2),
-## (0,0,2), (0,1,2)),
-## [SMIGRDIR+'bootstrapmigration_repository.py',
-## TMIGRDIR+'0.0.3_Any.py',
-## TMIGRDIR+'0.0.4_Any.py',
-## SMIGRDIR+'2.4.0_2.5.0_Any.sql',
-## SMIGRDIR+'2.5.1_2.6.0_Any.sql',
-## TMIGRDIR+'0.1.0_Any.py',
-## TMIGRDIR+'0.1.0_common.py',
-## TMIGRDIR+'0.1.0_repository.py',
-## TMIGRDIR+'0.1.2_Any.py',
-## SMIGRDIR+'2.10.1_2.10.2_Any.sql'])
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,3)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py')])
+ self.assertListEquals(filter_scripts(self.config, TMIGRDIR, (0,0,2), (0,0,4)),
+ [((0, 0, 3), TMIGRDIR+'0.0.3_Any.py'),
+ ((0, 0, 4), TMIGRDIR+'0.0.4_Any.py')])
- def test_migration_files_for_mode(self):
- from cubicweb.server.migractions import ServerMigrationHelper
+ def test_filter_scripts_for_mode(self):
self.assertIsInstance(self.config.migration_handler(), ServerMigrationHelper)
- from cubicweb.common.migration import MigrationHelper
config = CubicWebConfiguration('data')
config.verbosity = 0
self.assert_(not isinstance(config.migration_handler(), ServerMigrationHelper))
self.assertIsInstance(config.migration_handler(), MigrationHelper)
config = self.config
config.__class__.name = 'twisted'
- self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
- [TMIGRDIR+'0.1.0_common.py',
- TMIGRDIR+'0.1.0_web.py'])
- config.__class__.name = 'repository'
- self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
- [SMIGRDIR+'bootstrapmigration_repository.py',
- TMIGRDIR+'0.1.0_Any.py',
- TMIGRDIR+'0.1.0_common.py',
- TMIGRDIR+'0.1.0_repository.py'])
- config.__class__.name = 'all-in-one'
- self.assertListEquals(migration_files(config, [('TEMPLATE', (0,0,4), (0,1,0))]),
- [SMIGRDIR+'bootstrapmigration_repository.py',
- TMIGRDIR+'0.1.0_Any.py',
- TMIGRDIR+'0.1.0_common.py',
- TMIGRDIR+'0.1.0_repository.py',
- TMIGRDIR+'0.1.0_web.py'])
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
config.__class__.name = 'repository'
-
- def test_filter_scripts(self):
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,5,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,4,0), (2,6,0)),
- [((2, 5, 0), SMIGRDIR+'2.5.0_Any.sql'),
- ((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,5,1)),
- [])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,0), (2,10,2)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
- ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,6,0)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql')])
- self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,5,1), (2,10,2)),
- [((2, 6, 0), SMIGRDIR+'2.6.0_Any.sql'),
- ((2, 10, 2), SMIGRDIR+'2.10.2_Any.sql')])
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py')])
+ config.__class__.name = 'all-in-one'
+ self.assertListEquals(filter_scripts(config, TMIGRDIR, (0,0,4), (0,1,0)),
+ [((0, 1 ,0), TMIGRDIR+'0.1.0_Any.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_common.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_repository.py'),
+ ((0, 1 ,0), TMIGRDIR+'0.1.0_web.py')])
+ config.__class__.name = 'repository'
from cubicweb.devtools import ApptestConfiguration, init_test_database, cleanup_sqlite
--- a/common/uilib.py Tue Aug 04 11:43:03 2009 +0200
+++ b/common/uilib.py Tue Aug 04 15:06:09 2009 +0200
@@ -15,7 +15,7 @@
from urllib import quote as urlquote
from StringIO import StringIO
-from logilab.mtconverter import html_escape, html_unescape
+from logilab.mtconverter import xml_escape, html_unescape
from cubicweb.utils import ustrftime
@@ -66,7 +66,7 @@
except ImportError:
def rest_publish(entity, data):
"""default behaviour if docutils was not found"""
- return html_escape(data)
+ return xml_escape(data)
TAG_PROG = re.compile(r'</?.*?>', re.U)
def remove_html_tags(text):
@@ -92,7 +92,9 @@
# fallback implementation, nicer one defined below if lxml is available
def soup2xhtml(data, encoding):
- return data
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ return u'\n'.join(data.splitlines())
# fallback implementation, nicer one defined below if lxml> 2.0 is available
def safe_cut(text, length):
@@ -106,7 +108,7 @@
if len(text_nohtml) <= length:
return text
# else if un-tagged text is too long, cut it
- return html_escape(text_nohtml[:length] + u'...')
+ return xml_escape(text_nohtml[:length] + u'...')
fallback_safe_cut = safe_cut
@@ -123,6 +125,10 @@
Note: the function considers a string with no surrounding tag as valid
if <div>`data`</div> can be parsed by an XML parser
"""
+ # normalize line break
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
+ data = u'\n'.join(data.splitlines())
+ # XXX lxml 1.1 support still needed ?
xmltree = etree.HTML('<div>%s</div>' % data)
# NOTE: lxml 1.1 (etch platforms) doesn't recognize
# the encoding=unicode parameter (lxml 2.0 does), this is
@@ -203,10 +209,18 @@
# HTML generation helper functions ############################################
+HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param',
+ 'img', 'area', 'input', 'col'))
+
+def sgml_attributes(attrs):
+ return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value)))
+ for attr, value in sorted(attrs.items())
+ if value is not None)
+
def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs):
"""generation of a simple sgml tag (eg without children tags) easier
- content and attributes will be escaped
+ content and attri butes will be escaped
"""
value = u'<%s' % tag
if attrs:
@@ -214,15 +228,16 @@
attrs['class'] = attrs.pop('klass')
except KeyError:
pass
- value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value)))
- for attr, value in sorted(attrs.items())
- if value is not None)
+ value += u' ' + sgml_attributes(attrs)
if content:
if escapecontent:
- content = html_escape(unicode(content))
+ content = xml_escape(unicode(content))
value += u'>%s</%s>' % (content, tag)
else:
- value += u'/>'
+ if tag in HTML4_EMPTY_TAGS:
+ value += u' />'
+ else:
+ value += u'></%s>' % tag
return value
def tooltipize(text, tooltip, url=None):
@@ -400,9 +415,9 @@
strings.append(body)
strings.append(u'</div>')
if title:
- strings.append(u'<h1 class="error">%s</h1>'% html_escape(title))
+ strings.append(u'<h1 class="error">%s</h1>'% xml_escape(title))
try:
- strings.append(u'<p class="error">%s</p>' % html_escape(str(exception)).replace("\n","<br />"))
+ strings.append(u'<p class="error">%s</p>' % xml_escape(str(exception)).replace("\n","<br />"))
except UnicodeError:
pass
strings.append(u'<div class="error_traceback">')
@@ -410,9 +425,9 @@
strings.append(u'<b>File</b> <b class="file">%s</b>, <b>line</b> '
u'<b class="line">%s</b>, <b>function</b> '
u'<b class="function">%s</b>:<br/>'%(
- html_escape(stackentry[0]), stackentry[1], html_escape(stackentry[2])))
+ xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2])))
if stackentry[3]:
- string = html_escape(stackentry[3]).decode('utf-8', 'replace')
+ string = xml_escape(stackentry[3]).decode('utf-8', 'replace')
strings.append(u' %s<br/>\n' % (string))
# add locals info for each entry
try:
@@ -420,7 +435,7 @@
html_info = []
chars = 0
for name, value in local_context.iteritems():
- value = html_escape(repr(value))
+ value = xml_escape(repr(value))
info = u'<span class="name">%s</span>=%s, ' % (name, value)
line_length = len(name) + len(value)
chars += line_length
@@ -485,5 +500,5 @@
def newfunc(*args, **kwargs):
ret = function(*args, **kwargs)
assert isinstance(ret, basestring)
- return html_escape(ret)
+ return xml_escape(ret)
return newfunc
--- a/cwconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/cwconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -17,9 +17,12 @@
import sys
import os
import logging
+from smtplib import SMTP
+from threading import Lock
from os.path import exists, join, expanduser, abspath, normpath, basename, isdir
from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
from logilab.common.logging_ext import set_log_methods, init_log
from logilab.common.configuration import (Configuration, Method,
ConfigurationMixIn, merge_options)
@@ -29,6 +32,8 @@
CONFIGURATIONS = []
+SMTP_LOCK = Lock()
+
class metaconfiguration(type):
"""metaclass to automaticaly register configuration"""
@@ -145,7 +150,7 @@
CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ
# create __init__ file
file(join(CUBES_DIR, '__init__.py'), 'w').close()
- elif exists(join(CW_SOFTWARE_ROOT, '.hg')):
+ elif exists(join(CW_SOFTWARE_ROOT, '.hg')) or os.environ.get('CW_MODE') == 'user':
mode = 'dev'
CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes')))
else:
@@ -190,6 +195,12 @@
'help': 'web server root url',
'group': 'main', 'inputlevel': 1,
}),
+ ('allow-email-login',
+ {'type' : 'yn',
+ 'default': False,
+ 'help': 'allow users to login with their primary email if set',
+ 'group': 'main', 'inputlevel': 2,
+ }),
('use-request-subdomain',
{'type' : 'yn',
'default': None,
@@ -211,7 +222,7 @@
'group': 'appobjects', 'inputlevel': 2,
}),
)
- # static and class methods used to get application independant resources ##
+ # static and class methods used to get instance independant resources ##
@staticmethod
def cubicweb_version():
@@ -237,7 +248,7 @@
@classmethod
def i18n_lib_dir(cls):
- """return application's i18n directory"""
+ """return instance's i18n directory"""
if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'):
return join(CW_SOFTWARE_ROOT, 'i18n')
return join(cls.shared_dir(), 'i18n')
@@ -414,7 +425,7 @@
@classmethod
def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None):
"""given a list of directories, return a list of sub files and
- directories that should be loaded by the application objects registry.
+ directories that should be loaded by the instance objects registry.
:param evobjpath:
optional list of sub-directories (or files without the .py ext) of
@@ -529,8 +540,10 @@
# for some commands (creation...) we don't want to initialize gettext
set_language = True
- # set this to true to avoid false error message while creating an application
+ # set this to true to avoid false error message while creating an instance
creating = False
+ # set this to true to allow somethings which would'nt be possible
+ repairing = False
options = CubicWebNoAppConfiguration.options + (
('log-file',
@@ -554,14 +567,14 @@
}),
('sender-name',
{'type' : 'string',
- 'default': Method('default_application_id'),
+ 'default': Method('default_instance_id'),
'help': 'name used as HELO name for outgoing emails from the \
repository.',
'group': 'email', 'inputlevel': 2,
}),
('sender-addr',
{'type' : 'string',
- 'default': 'devel@logilab.fr',
+ 'default': 'cubicweb@mydomain.com',
'help': 'email address used as HELO address for outgoing emails from \
the repository',
'group': 'email', 'inputlevel': 1,
@@ -571,49 +584,49 @@
@classmethod
def runtime_dir(cls):
"""run time directory for pid file..."""
- return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time')
+ return env_path('CW_RUNTIME_DIR', cls.RUNTIME_DIR, 'run time')
@classmethod
def registry_dir(cls):
"""return the control directory"""
- return env_path('CW_REGISTRY', cls.REGISTRY_DIR, 'registry')
+ return env_path('CW_INSTANCES_DIR', cls.REGISTRY_DIR, 'registry')
@classmethod
def instance_data_dir(cls):
"""return the instance data directory"""
- return env_path('CW_INSTANCE_DATA',
+ return env_path('CW_INSTANCES_DATA_DIR',
cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR,
'additional data')
@classmethod
def migration_scripts_dir(cls):
"""cubicweb migration scripts directory"""
- return env_path('CW_MIGRATION', cls.MIGRATION_DIR, 'migration')
+ return env_path('CW_MIGRATION_DIR', cls.MIGRATION_DIR, 'migration')
@classmethod
def config_for(cls, appid, config=None):
- """return a configuration instance for the given application identifier
+ """return a configuration instance for the given instance identifier
"""
- config = config or guess_configuration(cls.application_home(appid))
+ config = config or guess_configuration(cls.instance_home(appid))
configcls = configuration_cls(config)
return configcls(appid)
@classmethod
def possible_configurations(cls, appid):
"""return the name of possible configurations for the given
- application id
+ instance id
"""
- home = cls.application_home(appid)
+ home = cls.instance_home(appid)
return possible_configurations(home)
@classmethod
- def application_home(cls, appid):
- """return the home directory of the application with the given
- application id
+ def instance_home(cls, appid):
+ """return the home directory of the instance with the given
+ instance id
"""
home = join(cls.registry_dir(), appid)
if not exists(home):
- raise ConfigurationError('no such application %s (check it exists with "cubicweb-ctl list")' % appid)
+ raise ConfigurationError('no such instance %s (check it exists with "cubicweb-ctl list")' % appid)
return home
MODES = ('common', 'repository', 'Any', 'web')
@@ -627,14 +640,14 @@
# default configuration methods ###########################################
- def default_application_id(self):
- """return the application identifier, useful for option which need this
+ def default_instance_id(self):
+ """return the instance identifier, useful for option which need this
as default value
"""
return self.appid
def default_log_file(self):
- """return default path to the log file of the application'server"""
+ """return default path to the log file of the instance'server"""
if self.mode == 'dev':
basepath = '/tmp/%s-%s' % (basename(self.appid), self.name)
path = basepath + '.log'
@@ -650,10 +663,10 @@
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 default path to the pid file of the instance'server"""
return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name))
- # instance methods used to get application specific resources #############
+ # instance methods used to get instance specific resources #############
def __init__(self, appid):
self.appid = appid
@@ -712,7 +725,7 @@
self._cubes = self.reorder_cubes(list(self._cubes) + cubes)
def main_config_file(self):
- """return application's control configuration file"""
+ """return instance's control configuration file"""
return join(self.apphome, '%s.conf' % self.name)
def save(self):
@@ -729,7 +742,7 @@
return md5.new(';'.join(infos)).hexdigest()
def load_site_cubicweb(self):
- """load (web?) application's specific site_cubicweb file"""
+ """load instance's specific site_cubicweb file"""
for path in reversed([self.apphome] + self.cubes_path()):
sitefile = join(path, 'site_cubicweb.py')
if exists(sitefile) and not sitefile in self._site_loaded:
@@ -743,7 +756,7 @@
self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
def _load_site_cubicweb(self, sitefile):
- context = {}
+ context = {'__file__': sitefile}
execfile(sitefile, context, context)
self.info('%s loaded', sitefile)
# cube specific options
@@ -752,7 +765,7 @@
self.load_defaults()
def load_configuration(self):
- """load application's configuration files"""
+ """load instance's configuration files"""
super(CubicWebConfiguration, self).load_configuration()
if self.apphome and self.set_language:
# init gettext
@@ -764,14 +777,14 @@
return
self._logging_initialized = True
CubicWebNoAppConfiguration.init_log(self, logthreshold, debug,
- logfile=self.get('log-file'))
+ logfile=self.get('log-file'))
# read a config file if it exists
logconfig = join(self.apphome, 'logging.conf')
if exists(logconfig):
logging.fileConfig(logconfig)
def available_languages(self, *args):
- """return available translation for an application, by looking for
+ """return available translation for an instance, by looking for
compiled catalog
take *args to be usable as a vocabulary method
@@ -827,8 +840,30 @@
sourcedirs.append(self.i18n_lib_dir())
return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs)
+ def sendmails(self, msgs):
+ """msgs: list of 2-uple (message object, recipients)"""
+ server, port = self['smtp-host'], self['smtp-port']
+ SMTP_LOCK.acquire()
+ try:
+ try:
+ smtp = SMTP(server, port)
+ except Exception, ex:
+ self.exception("can't connect to smtp server %s:%s (%s)",
+ server, port, ex)
+ return
+ heloaddr = '%s <%s>' % (self['sender-name'], self['sender-addr'])
+ for msg, recipients in msgs:
+ try:
+ smtp.sendmail(heloaddr, recipients, msg.as_string())
+ except Exception, ex:
+ self.exception("error sending mail to %s (%s)",
+ recipients, ex)
+ smtp.close()
+ finally:
+ SMTP_LOCK.release()
+
set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))
-# alias to get a configuration instance from an application id
-application_configuration = CubicWebConfiguration.config_for
-
+# alias to get a configuration instance from an instance id
+instance_configuration = CubicWebConfiguration.config_for
+application_configuration = deprecated('use instance_configuration')(instance_configuration)
--- a/cwctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/cwctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,6 +1,6 @@
"""%%prog %s [options] %s
-CubicWeb main applications controller.
+CubicWeb main instances controller.
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
%s"""
@@ -9,10 +9,11 @@
from os.path import exists, join, isfile, isdir
from logilab.common.clcommands import register_commands, pop_arg
+from logilab.common.shellutils import ASK
-from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
+from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title
from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
-from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm
+from cubicweb.toolsutils import Command, main_run, rm, create_dir
def wait_process_end(pid, maxtry=10, waittime=1):
"""wait for a process to actually die"""
@@ -45,11 +46,11 @@
return modes
-class ApplicationCommand(Command):
- """base class for command taking 0 to n application id as arguments
- (0 meaning all registered applications)
+class InstanceCommand(Command):
+ """base class for command taking 0 to n instance id as arguments
+ (0 meaning all registered instances)
"""
- arguments = '[<application>...]'
+ arguments = '[<instance>...]'
options = (
("force",
{'short': 'f', 'action' : 'store_true',
@@ -84,7 +85,7 @@
return allinstances
def run(self, args):
- """run the <command>_method on each argument (a list of application
+ """run the <command>_method on each argument (a list of instance
identifiers)
"""
if not args:
@@ -102,29 +103,29 @@
for appid in args:
if askconfirm:
print '*'*72
- if not confirm('%s application %r ?' % (self.name, appid)):
+ if not confirm('%s instance %r ?' % (self.name, appid)):
continue
self.run_arg(appid)
def run_arg(self, appid):
- cmdmeth = getattr(self, '%s_application' % self.name)
+ cmdmeth = getattr(self, '%s_instance' % self.name)
try:
cmdmeth(appid)
except (KeyboardInterrupt, SystemExit):
print >> sys.stderr, '%s aborted' % self.name
sys.exit(2) # specific error code
except (ExecutionError, ConfigurationError), ex:
- print >> sys.stderr, 'application %s not %s: %s' % (
+ print >> sys.stderr, 'instance %s not %s: %s' % (
appid, self.actionverb, ex)
except Exception, ex:
import traceback
traceback.print_exc()
- print >> sys.stderr, 'application %s not %s: %s' % (
+ print >> sys.stderr, 'instance %s not %s: %s' % (
appid, self.actionverb, ex)
-class ApplicationCommandFork(ApplicationCommand):
- """Same as `ApplicationCommand`, but command is forked in a new environment
+class InstanceCommandFork(InstanceCommand):
+ """Same as `InstanceCommand`, but command is forked in a new environment
for each argument
"""
@@ -136,22 +137,21 @@
for appid in args:
if askconfirm:
print '*'*72
- if not confirm('%s application %r ?' % (self.name, appid)):
+ if not confirm('%s instance %r ?' % (self.name, appid)):
continue
if forkcmd:
status = system('%s %s' % (forkcmd, appid))
if status:
- sys.exit(status)
+ print '%s exited with status %s' % (forkcmd, status)
else:
self.run_arg(appid)
# base commands ###############################################################
class ListCommand(Command):
- """List configurations, componants and applications.
+ """List configurations, cubes and instances.
- list available configurations, installed web and server componants, and
- registered applications
+ list available configurations, installed cubes, and registered instances
"""
name = 'list'
options = (
@@ -206,30 +206,30 @@
try:
regdir = cwcfg.registry_dir()
except ConfigurationError, ex:
- print 'No application available:', ex
+ print 'No instance available:', ex
print
return
instances = list_instances(regdir)
if instances:
- print 'Available applications (%s):' % regdir
+ print 'Available instances (%s):' % regdir
for appid in instances:
modes = cwcfg.possible_configurations(appid)
if not modes:
- print '* %s (BROKEN application, no configuration found)' % appid
+ print '* %s (BROKEN instance, no configuration found)' % appid
continue
print '* %s (%s)' % (appid, ', '.join(modes))
try:
config = cwcfg.config_for(appid, modes[0])
except Exception, exc:
- print ' (BROKEN application, %s)' % exc
+ print ' (BROKEN instance, %s)' % exc
continue
else:
- print 'No application available in %s' % regdir
+ print 'No instance available in %s' % regdir
print
-class CreateApplicationCommand(Command):
- """Create an application from a cube. This is an unified
+class CreateInstanceCommand(Command):
+ """Create an instance from a cube. This is an unified
command which can handle web / server / all-in-one installation
according to available parts of the software library and of the
desired cube.
@@ -238,11 +238,11 @@
the name of cube to use (list available cube names using
the "list" command). You can use several cubes by separating
them using comma (e.g. 'jpl,eemail')
- <application>
- an identifier for the application to create
+ <instance>
+ an identifier for the instance to create
"""
name = 'create'
- arguments = '<cube> <application>'
+ arguments = '<cube> <instance>'
options = (
("config-level",
{'short': 'l', 'type' : 'int', 'metavar': '<level>',
@@ -255,7 +255,7 @@
{'short': 'c', 'type' : 'choice', 'metavar': '<install type>',
'choices': ('all-in-one', 'repository', 'twisted'),
'default': 'all-in-one',
- 'help': 'installation type, telling which part of an application \
+ 'help': 'installation type, telling which part of an instance \
should be installed. You can list available configurations using the "list" \
command. Default to "all-in-one", e.g. an installation embedding both the RQL \
repository and the web server.',
@@ -265,15 +265,16 @@
def run(self, args):
"""run the command with its specific arguments"""
- from logilab.common.textutils import get_csv
+ from logilab.common.textutils import splitstrip
configname = self.config.config
- cubes = get_csv(pop_arg(args, 1))
+ cubes = splitstrip(pop_arg(args, 1))
appid = pop_arg(args)
# get the configuration and helper
cwcfg.creating = True
config = cwcfg.config_for(appid, configname)
config.set_language = False
- config.init_cubes(config.expand_cubes(cubes))
+ cubes = config.expand_cubes(cubes)
+ config.init_cubes(cubes)
helper = self.config_helper(config)
# check the cube exists
try:
@@ -284,21 +285,23 @@
print '\navailable cubes:',
print ', '.join(cwcfg.available_cubes())
return
- # create the registry directory for this application
+ # create the registry directory for this instance
+ print '\n'+underline_title('Creating the instance %s' % appid)
create_dir(config.apphome)
# load site_cubicweb from the cubes dir (if any)
config.load_site_cubicweb()
# cubicweb-ctl configuration
- print '** application\'s %s configuration' % configname
- print '-' * 72
+ print '\n'+underline_title('Configuring the instance (%s.conf)' % configname)
config.input_config('main', self.config.config_level)
# configuration'specific stuff
print
helper.bootstrap(cubes, self.config.config_level)
# write down configuration
config.save()
+ print '-> generated %s' % config.main_config_file()
# handle i18n files structure
# in the first cube given
+ print '-> preparing i18n catalogs'
from cubicweb.common import i18n
langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
errors = config.i18ncompile(langs)
@@ -308,35 +311,31 @@
'continue anyway ?'):
print 'creation not completed'
return
- # create the additional data directory for this application
+ # create the additional data directory for this instance
if config.appdatahome != config.apphome: # true in dev mode
create_dir(config.appdatahome)
+ create_dir(join(config.appdatahome, 'backup'))
if config['uid']:
from logilab.common.shellutils import chown
# this directory should be owned by the uid of the server process
print 'set %s as owner of the data directory' % config['uid']
chown(config.appdatahome, config['uid'])
- print
- print
- print '*' * 72
- print 'application %s (%s) created in %r' % (appid, configname,
- config.apphome)
- print
+ print '\n-> creation done for %r.\n' % config.apphome
helper.postcreate()
-class DeleteApplicationCommand(Command):
- """Delete an application. Will remove application's files and
+class DeleteInstanceCommand(Command):
+ """Delete an instance. Will remove instance's files and
unregister it.
"""
name = 'delete'
- arguments = '<application>'
+ arguments = '<instance>'
options = ()
def run(self, args):
"""run the command with its specific arguments"""
- appid = pop_arg(args, msg="No application specified !")
+ appid = pop_arg(args, msg="No instance specified !")
configs = [cwcfg.config_for(appid, configname)
for configname in cwcfg.possible_configurations(appid)]
if not configs:
@@ -355,16 +354,16 @@
if ex.errno != errno.ENOENT:
raise
confignames = ', '.join([config.name for config in configs])
- print 'application %s (%s) deleted' % (appid, confignames)
+ print '-> instance %s (%s) deleted.' % (appid, confignames)
-# application commands ########################################################
+# instance commands ########################################################
-class StartApplicationCommand(ApplicationCommand):
- """Start the given applications. If no application is given, start them all.
+class StartInstanceCommand(InstanceCommand):
+ """Start the given instances. If no instance is given, start them all.
- <application>...
- identifiers of the applications to start. If no application is
+ <instance>...
+ identifiers of the instances to start. If no instance is
given, start them all.
"""
name = 'start'
@@ -376,22 +375,32 @@
("force",
{'short': 'f', 'action' : 'store_true',
'default': False,
- 'help': 'start the application even if it seems to be already \
+ 'help': 'start the instance even if it seems to be already \
running.'}),
('profile',
{'short': 'P', 'type' : 'string', 'metavar': '<stat file>',
'default': None,
'help': 'profile code and use the specified file to store stats',
}),
+ ('loglevel',
+ {'short': 'l', 'type' : 'choice', 'metavar': '<log level>',
+ 'default': None, 'choices': ('debug', 'info', 'warning', 'error'),
+ 'help': 'debug if -D is set, error otherwise',
+ }),
)
- def start_application(self, appid):
- """start the application's server"""
+ def start_instance(self, appid):
+ """start the instance's server"""
# use get() since start may be used from other commands (eg upgrade)
# without all options defined
debug = self.get('debug')
force = self.get('force')
+ loglevel = self.get('loglevel')
config = cwcfg.config_for(appid)
+ if loglevel is not None:
+ loglevel = 'LOG_%s' % loglevel.upper()
+ config.global_set_option('log-threshold', loglevel)
+ config.init_log(loglevel, debug=debug, force=True)
if self.get('profile'):
config.global_set_option('profile', self.config.profile)
helper = self.config_helper(config, cmdname='start')
@@ -400,36 +409,27 @@
msg = "%s seems to be running. Remove %s by hand if necessary or use \
the --force option."
raise ExecutionError(msg % (appid, pidf))
- command = helper.start_command(config, debug)
- if debug:
- print "starting server with command :"
- print command
- if system(command):
- print 'an error occured while starting the application, not started'
- print
- return False
- if not debug:
- print 'application %s started' % appid
+ helper.start_command(config, debug)
return True
-class StopApplicationCommand(ApplicationCommand):
- """Stop the given applications.
+class StopInstanceCommand(InstanceCommand):
+ """Stop the given instances.
- <application>...
- identifiers of the applications to stop. If no application is
+ <instance>...
+ identifiers of the instances to stop. If no instance is
given, stop them all.
"""
name = 'stop'
actionverb = 'stopped'
def ordered_instances(self):
- instances = super(StopApplicationCommand, self).ordered_instances()
+ instances = super(StopInstanceCommand, self).ordered_instances()
instances.reverse()
return instances
- def stop_application(self, appid):
- """stop the application's server"""
+ def stop_instance(self, appid):
+ """stop the instance's server"""
config = cwcfg.config_for(appid)
helper = self.config_helper(config, cmdname='stop')
helper.poststop() # do this anyway
@@ -460,15 +460,15 @@
except OSError:
# already removed by twistd
pass
- print 'application %s stopped' % appid
+ print 'instance %s stopped' % appid
-class RestartApplicationCommand(StartApplicationCommand,
- StopApplicationCommand):
- """Restart the given applications.
+class RestartInstanceCommand(StartInstanceCommand,
+ StopInstanceCommand):
+ """Restart the given instances.
- <application>...
- identifiers of the applications to restart. If no application is
+ <instance>...
+ identifiers of the instances to restart. If no instance is
given, restart them all.
"""
name = 'restart'
@@ -478,18 +478,18 @@
regdir = cwcfg.registry_dir()
if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
# no specific startorder
- super(RestartApplicationCommand, self).run_args(args, askconfirm)
+ super(RestartInstanceCommand, self).run_args(args, askconfirm)
return
print ('some specific start order is specified, will first stop all '
- 'applications then restart them.')
+ 'instances then restart them.')
# get instances in startorder
stopped = []
for appid in args:
if askconfirm:
print '*'*72
- if not confirm('%s application %r ?' % (self.name, appid)):
+ if not confirm('%s instance %r ?' % (self.name, appid)):
continue
- self.stop_application(appid)
+ self.stop_instance(appid)
stopped.append(appid)
forkcmd = [w for w in sys.argv if not w in args]
forkcmd[1] = 'start'
@@ -499,46 +499,46 @@
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)
+ def restart_instance(self, appid):
+ self.stop_instance(appid)
+ if self.start_instance(appid):
+ print 'instance %s %s' % (appid, self.actionverb)
-class ReloadConfigurationCommand(RestartApplicationCommand):
- """Reload the given applications. This command is equivalent to a
+class ReloadConfigurationCommand(RestartInstanceCommand):
+ """Reload the given instances. This command is equivalent to a
restart for now.
- <application>...
- identifiers of the applications to reload. If no application is
+ <instance>...
+ identifiers of the instances to reload. If no instance is
given, reload them all.
"""
name = 'reload'
- def reload_application(self, appid):
- self.restart_application(appid)
+ def reload_instance(self, appid):
+ self.restart_instance(appid)
-class StatusCommand(ApplicationCommand):
- """Display status information about the given applications.
+class StatusCommand(InstanceCommand):
+ """Display status information about the given instances.
- <application>...
- identifiers of the applications to status. If no application is
- given, get status information about all registered applications.
+ <instance>...
+ identifiers of the instances to status. If no instance is
+ given, get status information about all registered instances.
"""
name = 'status'
options = ()
@staticmethod
- def status_application(appid):
- """print running status information for an application"""
+ def status_instance(appid):
+ """print running status information for an instance"""
for mode in cwcfg.possible_configurations(appid):
config = cwcfg.config_for(appid, mode)
print '[%s-%s]' % (appid, mode),
try:
pidf = config['pid-file']
except KeyError:
- print 'buggy application, pid file not specified'
+ print 'buggy instance, pid file not specified'
continue
if not exists(pidf):
print "doesn't seem to be running"
@@ -553,22 +553,22 @@
print "running with pid %s" % (pid)
-class UpgradeApplicationCommand(ApplicationCommandFork,
- StartApplicationCommand,
- StopApplicationCommand):
- """Upgrade an application after cubicweb and/or component(s) upgrade.
+class UpgradeInstanceCommand(InstanceCommandFork,
+ StartInstanceCommand,
+ StopInstanceCommand):
+ """Upgrade an instance after cubicweb and/or component(s) upgrade.
For repository update, you will be prompted for a login / password to use
to connect to the system database. For some upgrades, the given user
should have create or alter table permissions.
- <application>...
- identifiers of the applications to upgrade. If no application is
+ <instance>...
+ identifiers of the instances to upgrade. If no instance is
given, upgrade them all.
"""
name = 'upgrade'
actionverb = 'upgraded'
- options = ApplicationCommand.options + (
+ options = InstanceCommand.options + (
('force-componant-version',
{'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z',
'default': None,
@@ -586,7 +586,7 @@
('nostartstop',
{'short': 'n', 'action' : 'store_true',
'default': False,
- 'help': 'don\'t try to stop application before migration and to restart it after.'}),
+ 'help': 'don\'t try to stop instance before migration and to restart it after.'}),
('verbosity',
{'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
@@ -597,7 +597,7 @@
('backup-db',
{'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
'default': None,
- 'help': "Backup the application database before upgrade.\n"\
+ 'help': "Backup the instance database before upgrade.\n"\
"If the option is ommitted, confirmation will be ask.",
}),
@@ -612,25 +612,24 @@
)
def ordered_instances(self):
- # need this since mro return StopApplicationCommand implementation
- return ApplicationCommand.ordered_instances(self)
+ # need this since mro return StopInstanceCommand implementation
+ return InstanceCommand.ordered_instances(self)
- def upgrade_application(self, appid):
+ def upgrade_instance(self, appid):
+ print '\n' + underline_title('Upgrading the instance %s' % appid)
from logilab.common.changelog import Version
config = cwcfg.config_for(appid)
- config.creating = True # notice we're not starting the server
+ config.repairing = True # notice we're not starting the server
config.verbosity = self.config.verbosity
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...'
+ # get instance and installed versions for the server and the componants
mih = config.migration_handler()
repo = mih.repo_connect()
vcconf = repo.get_versions()
- print 'done'
if self.config.force_componant_version:
packversions = {}
for vdef in self.config.force_componant_version:
@@ -654,15 +653,15 @@
else:
applcubicwebversion = vcconf.get('cubicweb')
if cubicwebversion > applcubicwebversion:
- toupgrade.append( ('cubicweb', applcubicwebversion, cubicwebversion) )
+ toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
if not self.config.fs_only and not toupgrade:
- print 'no software migration needed for application %s' % appid
+ print '-> no software migration needed for instance %s.' % appid
return
for cube, fromversion, toversion in toupgrade:
- print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
+ print '-> migration needed from %s to %s for %s' % (fromversion, toversion, cube)
# only stop once we're sure we have something to do
if not (cwcfg.mode == 'dev' or self.config.nostartstop):
- self.stop_application(appid)
+ self.stop_instance(appid)
# run cubicweb/componants migration scripts
mih.migrate(vcconf, reversed(toupgrade), self.config)
# rewrite main configuration file
@@ -677,16 +676,15 @@
errors = config.i18ncompile(langs)
if errors:
print '\n'.join(errors)
- if not confirm('error while compiling message catalogs, '
+ if not confirm('Error while compiling message catalogs, '
'continue anyway ?'):
- print 'migration not completed'
+ print '-> migration not completed.'
return
- mih.rewrite_vcconfiguration()
mih.shutdown()
print
- print 'application migrated'
+ print '-> instance migrated.'
if not (cwcfg.mode == 'dev' or self.config.nostartstop):
- self.start_application(appid)
+ self.start_instance(appid)
print
@@ -696,11 +694,11 @@
argument may be given corresponding to a file containing commands to
execute in batch mode.
- <application>
- the identifier of the application to connect.
+ <instance>
+ the identifier of the instance to connect.
"""
name = 'shell'
- arguments = '<application> [batch command file]'
+ arguments = '<instance> [batch command file]'
options = (
('system-only',
{'short': 'S', 'action' : 'store_true',
@@ -720,7 +718,7 @@
)
def run(self, args):
- appid = pop_arg(args, 99, msg="No application specified !")
+ appid = pop_arg(args, 99, msg="No instance specified !")
config = cwcfg.config_for(appid)
if self.config.ext_sources:
assert not self.config.system_only
@@ -732,24 +730,25 @@
config.set_sources_mode(sources)
mih = config.migration_handler()
if args:
- mih.scripts_session(args)
+ for arg in args:
+ mih.process_script(arg)
else:
mih.interactive_shell()
mih.shutdown()
-class RecompileApplicationCatalogsCommand(ApplicationCommand):
- """Recompile i18n catalogs for applications.
+class RecompileInstanceCatalogsCommand(InstanceCommand):
+ """Recompile i18n catalogs for instances.
- <application>...
- identifiers of the applications to consider. If no application is
- given, recompile for all registered applications.
+ <instance>...
+ identifiers of the instances to consider. If no instance is
+ given, recompile for all registered instances.
"""
name = 'i18ninstance'
@staticmethod
- def i18ninstance_application(appid):
- """recompile application's messages catalogs"""
+ def i18ninstance_instance(appid):
+ """recompile instance's messages catalogs"""
config = cwcfg.config_for(appid)
try:
config.bootstrap_cubes()
@@ -758,8 +757,8 @@
if ex.errno != errno.ENOENT:
raise
# bootstrap_cubes files doesn't exist
- # set creating to notify this is not a regular start
- config.creating = True
+ # notify this is not a regular start
+ config.repairing = True
# create an in-memory repository, will call config.init_cubes()
config.repository()
except AttributeError:
@@ -793,16 +792,16 @@
print cube
register_commands((ListCommand,
- CreateApplicationCommand,
- DeleteApplicationCommand,
- StartApplicationCommand,
- StopApplicationCommand,
- RestartApplicationCommand,
+ CreateInstanceCommand,
+ DeleteInstanceCommand,
+ StartInstanceCommand,
+ StopInstanceCommand,
+ RestartInstanceCommand,
ReloadConfigurationCommand,
StatusCommand,
- UpgradeApplicationCommand,
+ UpgradeInstanceCommand,
ShellCommand,
- RecompileApplicationCatalogsCommand,
+ RecompileInstanceCatalogsCommand,
ListInstancesCommand, ListCubesCommand,
))
--- a/cwvreg.py Tue Aug 04 11:43:03 2009 +0200
+++ b/cwvreg.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,11 +9,12 @@
_ = unicode
from logilab.common.decorators import cached, clear_cache
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
from rql import RQLHelper
-from cubicweb import ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid
+from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid,
+ RegistryOutOfDate)
from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject
from cubicweb.rtags import RTAGS
@@ -40,15 +41,15 @@
class CubicWebRegistry(VRegistry):
- """Central registry for the cubicweb application, extending the generic
+ """Central registry for the cubicweb instance, extending the generic
VRegistry with some cubicweb specific stuff.
- This is one of the central object in cubicweb application, coupling
+ This is one of the central object in cubicweb instance, coupling
dynamically loaded objects with the schema and the configuration objects.
It specializes the VRegistry by adding some convenience methods to access to
stored objects. Currently we have the following registries of objects known
- by the web application (library may use some others additional registries):
+ by the web instance (library may use some others additional registries):
* etypes
* views
@@ -91,11 +92,16 @@
self.register_property(key, **propdef)
def set_schema(self, schema):
- """set application'schema and load application objects"""
+ """set instance'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())
+ # map lowered entity type names to their actual name
+ self.case_insensitive_etypes = {}
+ for etype in self.schema.entities():
+ etype = str(etype)
+ self.case_insensitive_etypes[etype.lower()] = etype
def update_schema(self, schema):
"""update .schema attribute on registered objects, necessary for some
@@ -134,6 +140,15 @@
def register_objects(self, path, force_reload=None):
"""overriden to remove objects requiring a missing interface"""
+ try:
+ self._register_objects(path, force_reload)
+ except RegistryOutOfDate:
+ # modification detected, reset and reload
+ self.reset()
+ self._register_objects(path, force_reload)
+
+ def _register_objects(self, path, force_reload=None):
+ """overriden to remove objects requiring a missing interface"""
extrapath = {}
for cubesdir in self.config.cubes_search_path():
if cubesdir != self.config.CUBES_DIR:
@@ -279,22 +294,22 @@
self.exception('error while trying to select %s view for %s',
vid, rset)
- @obsolete("use .select_object('boxes', ...)")
+ @deprecated("use .select_object('boxes', ...)")
def select_box(self, oid, *args, **kwargs):
"""return the most specific view according to the result set"""
return self.select_object('boxes', oid, *args, **kwargs)
- @obsolete("use .select_object('components', ...)")
+ @deprecated("use .select_object('components', ...)")
def select_component(self, cid, *args, **kwargs):
"""return the most specific component according to the result set"""
return self.select_object('components', cid, *args, **kwargs)
- @obsolete("use .select_object('actions', ...)")
+ @deprecated("use .select_object('actions', ...)")
def select_action(self, oid, *args, **kwargs):
"""return the most specific view according to the result set"""
return self.select_object('actions', oid, *args, **kwargs)
- @obsolete("use .select('views', ...)")
+ @deprecated("use .select('views', ...)")
def select_view(self, __vid, req, rset=None, **kwargs):
"""return the most specific view according to the result set"""
return self.select('views', __vid, req, rset=rset, **kwargs)
@@ -303,9 +318,10 @@
def user_property_keys(self, withsitewide=False):
if withsitewide:
- return sorted(self['propertydefs'])
+ return sorted(k for k in self['propertydefs']
+ if not k.startswith('sources.'))
return sorted(k for k, kd in self['propertydefs'].iteritems()
- if not kd['sitewide'])
+ if not kd['sitewide'] and not k.startswith('sources.'))
def register_property(self, key, type, help, default=None, vocabulary=None,
sitewide=False):
@@ -408,7 +424,7 @@
"""return an instance of the most specific object according
to parameters
- raise NoSelectableObject if not object apply
+ raise NoSelectableObject if no object apply
"""
for vobjectcls in vobjects:
self._fix_cls_attrs(vobjectcls)
--- a/dbapi.py Tue Aug 04 11:43:03 2009 +0200
+++ b/dbapi.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,6 +13,7 @@
from logging import getLogger
from time import time, clock
+from itertools import count
from logilab.common.logging_ext import set_log_methods
from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn
@@ -21,6 +22,12 @@
_MARKER = object()
+def _fake_property_value(self, name):
+ try:
+ return super(dbapi.DBAPIRequest, self).property_value(name)
+ except KeyError:
+ return ''
+
class ConnectionProperties(object):
def __init__(self, cnxtype=None, lang=None, close=True, log=False):
self.cnxtype = cnxtype or 'pyro'
@@ -60,22 +67,22 @@
raise ConnectionError('Could not get repository for %s '
'(not registered in Pyro), '
'you may have to restart your server-side '
- 'application' % nsid)
+ 'instance' % nsid)
return core.getProxyForURI(uri)
-def repo_connect(repo, user, password, cnxprops=None):
+def repo_connect(repo, login, password, cnxprops=None):
"""Constructor to create a new connection to the CubicWeb repository.
Returns a Connection instance.
"""
cnxprops = cnxprops or ConnectionProperties('inmemory')
- cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops)
+ cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops)
cnx = Connection(repo, cnxid, cnxprops)
if cnxprops.cnxtype == 'inmemory':
cnx.vreg = repo.vreg
return cnx
-def connect(database=None, user=None, password=None, host=None,
+def connect(database=None, login=None, password=None, host=None,
group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True,
initlog=True):
"""Constructor for creating a connection to the CubicWeb repository.
@@ -111,11 +118,11 @@
vreg.set_schema(schema)
else:
vreg = None
- cnx = repo_connect(repo, user, password, cnxprops)
+ cnx = repo_connect(repo, login, password, cnxprops)
cnx.vreg = vreg
return cnx
-def in_memory_cnx(config, user, password):
+def in_memory_cnx(config, login, password):
"""usefull method for testing and scripting to get a dbapi.Connection
object connected to an in-memory repository instance
"""
@@ -128,7 +135,7 @@
repo = get_repository('inmemory', config=config, vreg=vreg)
# connection to the CubicWeb repository
cnxprops = ConnectionProperties('inmemory')
- cnx = repo_connect(repo, user, password, cnxprops=cnxprops)
+ cnx = repo_connect(repo, login, password, cnxprops=cnxprops)
return repo, cnx
@@ -176,7 +183,7 @@
except KeyError:
# this occurs usually during test execution
self._ = self.__ = unicode
- self.debug('request language: %s', self.lang)
+ self.debug('request default language: %s', self.lang)
def decorate_rset(self, rset):
rset.vreg = self.vreg
@@ -245,7 +252,7 @@
@property
def user(self):
if self._user is None and self.cnx:
- self.set_user(self.cnx.user(self))
+ self.set_user(self.cnx.user(self, {'lang': self.lang}))
return self._user
def set_user(self, user):
@@ -367,6 +374,10 @@
"""raise `BadSessionId` if the connection is no more valid"""
self._repo.check_session(self.sessionid)
+ def set_session_props(self, **props):
+ """raise `BadSessionId` if the connection is no more valid"""
+ self._repo.set_session_props(self.sessionid, props)
+
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)
@@ -390,7 +401,8 @@
raise ProgrammingError('Closed connection')
return self._repo.get_schema()
- def load_vobjects(self, cubes=_MARKER, subpath=None, expand=True, force_reload=None):
+ def load_vobjects(self, cubes=_MARKER, subpath=None, expand=True,
+ force_reload=None):
config = self.vreg.config
if cubes is _MARKER:
cubes = self._repo.get_cubes()
@@ -418,10 +430,35 @@
hm, config = self._repo.hm, self._repo.config
hm.set_schema(hm.schema) # reset structure
hm.register_system_hooks(config)
- # application specific hooks
- if self._repo.config.application_hooks:
+ # instance specific hooks
+ if self._repo.config.instance_hooks:
hm.register_hooks(config.load_hooks(self.vreg))
+ def use_web_compatible_requests(self, baseurl, sitetitle=None):
+ """monkey patch DBAPIRequest to fake a cw.web.request, so you should
+ able to call html views using rset from a simple dbapi connection.
+
+ You should call `load_vobjects` at some point to register those views.
+ """
+ from cubicweb.web.request import CubicWebRequestBase as cwrb
+ DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func
+ DBAPIRequest.list_form_param = cwrb.list_form_param.im_func
+ DBAPIRequest.property_value = _fake_property_value
+ DBAPIRequest.next_tabindex = count().next
+ DBAPIRequest.form = {}
+ DBAPIRequest.data = {}
+ fake = lambda *args, **kwargs: None
+ DBAPIRequest.relative_path = fake
+ DBAPIRequest.url = fake
+ DBAPIRequest.next_tabindex = fake
+ DBAPIRequest.add_js = fake #cwrb.add_js.im_func
+ DBAPIRequest.add_css = fake #cwrb.add_css.im_func
+ # XXX could ask the repo for it's base-url configuration
+ self.vreg.config.set_option('base-url', baseurl)
+ # XXX why is this needed? if really needed, could be fetched by a query
+ if sitetitle is not None:
+ self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle}
+
def source_defs(self):
"""Return the definition of sources used by the repository.
@@ -434,7 +471,8 @@
def user(self, req=None, props=None):
"""return the User object associated to this connection"""
# cnx validity is checked by the call to .user_info
- eid, login, groups, properties = self._repo.user_info(self.sessionid, props)
+ eid, login, groups, properties = self._repo.user_info(self.sessionid,
+ props)
if req is None:
req = self.request()
rset = req.eid_rset(eid, 'CWUser')
--- a/debian/changelog Tue Aug 04 11:43:03 2009 +0200
+++ b/debian/changelog Tue Aug 04 15:06:09 2009 +0200
@@ -1,3 +1,40 @@
+cubicweb (3.3.4-2) unstable; urgency=low
+
+ * fix conflicting test files
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 21 Jul 2009 23:09:03 +0200
+
+cubicweb (3.3.4-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 21 Jul 2009 13:11:38 +0200
+
+cubicweb (3.3.3-2) unstable; urgency=low
+
+ * re-release with "from __future__ import with_statement" commented out to
+ avoid broken installation if 2.4 is installed
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 06 Jul 2009 17:33:15 +0200
+
+cubicweb (3.3.3-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 06 Jul 2009 13:24:29 +0200
+
+cubicweb (3.3.2-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 25 Jun 2009 07:58:14 +0200
+
+cubicweb (3.3.1-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Aurélien Campéas <aurelien.campeas@logilab.fr> Mon, 22 Jun 2009 12:00:00 +0200
+
cubicweb (3.3.0-1) unstable; urgency=low
* new upstream release
--- a/debian/control Tue Aug 04 11:43:03 2009 +0200
+++ b/debian/control Tue Aug 04 15:06:09 2009 +0200
@@ -62,7 +62,7 @@
Architecture: all
XB-Python-Version: ${python:Versions}
Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree
-Recommends: python-docutils, python-vobject, fckeditor
+Recommends: python-docutils, python-vobject, fckeditor, python-fyzz
Description: web interface library for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -76,8 +76,8 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.0)
-Recommends: python-simpletal (>= 4.0), python-lxml
+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.1), python-lxml
+Recommends: python-simpletal (>= 4.0)
Conflicts: cubicweb-core
Replaces: cubicweb-core
Description: common library for the CubicWeb framework
--- a/debian/cubicweb-dev.install.in Tue Aug 04 11:43:03 2009 +0200
+++ b/debian/cubicweb-dev.install.in Tue Aug 04 15:06:09 2009 +0200
@@ -1,2 +1,11 @@
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/entities/test usr/lib/PY_VERSION/site-packages/cubicweb/entities/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/ext/test usr/lib/PY_VERSION/site-packages/cubicweb/ext/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/server/test usr/lib/PY_VERSION/site-packages/cubicweb/server/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/test usr/lib/PY_VERSION/site-packages/cubicweb/sobjects/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/web/test usr/lib/PY_VERSION/site-packages/cubicweb/web/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/etwist/test usr/lib/PY_VERSION/site-packages/cubicweb/etwist/
+debian/tmp/usr/lib/PY_VERSION/site-packages/cubicweb/goa/test usr/lib/PY_VERSION/site-packages/cubicweb/goa/
--- a/debian/rules Tue Aug 04 11:43:03 2009 +0200
+++ b/debian/rules Tue Aug 04 15:06:09 2009 +0200
@@ -47,12 +47,13 @@
dh_lintian
# Remove unittests directory (should be available in cubicweb-dev only)
- 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
+ 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-twisted/usr/lib/${PY_VERSION}/site-packages/cubicweb/etwist/test
+ rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/ext/test
+ rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/common/test
+ rm -rf debian/cubicweb-common/usr/lib/${PY_VERSION}/site-packages/cubicweb/entities/test
# cubes directory must be managed as a valid python module
touch debian/cubicweb-common/usr/share/cubicweb/cubes/__init__.py
--- a/devtools/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -43,7 +43,7 @@
class TestServerConfiguration(ServerConfiguration):
mode = 'test'
set_language = False
- read_application_schema = False
+ read_instance_schema = False
bootstrap_schema = False
init_repository = True
options = merge_options(ServerConfiguration.options + (
@@ -77,12 +77,12 @@
def apphome(self):
if exists(self.appid):
return abspath(self.appid)
- # application cube test
+ # cube test
return abspath('..')
appdatahome = apphome
def main_config_file(self):
- """return application's control configuration file"""
+ """return instance's control configuration file"""
return join(self.apphome, '%s.conf' % self.name)
def instance_md5_version(self):
@@ -149,7 +149,7 @@
return ('en', 'fr', 'de')
def ext_resources_file(self):
- """return application's external resources file"""
+ """return instance's external resources file"""
return join(self.apphome, 'data', 'external_resources')
def pyro_enabled(self):
@@ -235,14 +235,14 @@
# XXX I'm afraid this test will prevent to run test from a production
# environment
self._sources = None
- # application cube test
+ # instance cube test
if cube is not None:
self.apphome = self.cube_dir(cube)
elif 'web' in os.getcwd().split(os.sep):
# web test
self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
else:
- # application cube test
+ # cube test
self.apphome = abspath('..')
self.sourcefile = sourcefile
self.global_set_option('realm', '')
--- a/devtools/apptest.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/apptest.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-"""This module provides misc utilities to test applications
+"""This module provides misc utilities to test instances
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -15,7 +15,7 @@
from logilab.common.pytest import nocoverage
from logilab.common.umessage import message_from_string
-from logilab.common.deprecation import deprecated_function
+from logilab.common.deprecation import deprecated
from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration
from cubicweb.devtools._apptest import TestEnvironment
@@ -34,6 +34,14 @@
def message(self):
return message_from_string(self.msg)
+ @property
+ def subject(self):
+ return self.message.get('Subject')
+
+ @property
+ def content(self):
+ return self.message.get_payload(decode=True)
+
def __repr__(self):
return '<Email to %s with subject %s>' % (','.join(self.recipients),
self.message.get('Subject'))
@@ -46,12 +54,12 @@
def sendmail(self, helo_addr, recipients, msg):
MAILBOX.append(Email(recipients, msg))
-from cubicweb.server import hookhelper
-hookhelper.SMTP = MockSMTP
+from cubicweb import cwconfig
+cwconfig.SMTP = MockSMTP
def get_versions(self, checkversions=False):
- """return the a dictionary containing cubes used by this application
+ """return the a dictionary containing cubes used by this instance
as key with their version as value, including cubicweb version. This is a
public method, not requiring a session id.
@@ -226,7 +234,7 @@
return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset=rset)
if a.category in categories]
- paddrelactions = deprecated_function(pactions_by_cats)
+ paddrelactions = deprecated()(pactions_by_cats)
def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')):
res = {}
@@ -398,7 +406,7 @@
rset.vreg = self.vreg
rset.req = self.session
# call to set_pool is necessary to avoid pb when using
- # application entities for convenience
+ # instance entities for convenience
self.session.set_pool()
return rset
@@ -449,7 +457,6 @@
pactionsdict = EnvBasedTC.pactionsdict.im_func
# default test setup and teardown #########################################
- copy_schema = False
def _prepare(self):
MAILBOX[:] = [] # reset mailbox
@@ -462,17 +469,6 @@
self.__close = repo.close
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)
- try:
- origschema = repo.__schema
- except AttributeError:
- origschema = repo.schema
- repo.__schema = origschema
- if self.copy_schema:
- repo.schema = deepcopy(origschema)
- repo.set_schema(repo.schema) # reset hooks
- repo.vreg.update_schema(repo.schema)
self.cnxs = []
# reset caches, they may introduce bugs among tests
repo._type_source_cache = {}
--- a/devtools/devctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/devctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,20 +12,20 @@
from datetime import datetime
from os import mkdir, chdir
from os.path import join, exists, abspath, basename, normpath, split, isdir
-
+from warnings import warn
from logilab.common import STD_BLACKLIST
from logilab.common.modutils import get_module_files
-from logilab.common.textutils import get_csv
+from logilab.common.textutils import splitstrip
+from logilab.common.shellutils import ASK
from logilab.common.clcommands import register_commands
-from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
+from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage, underline_title
from cubicweb.__pkginfo__ import version as cubicwebversion
-from cubicweb.toolsutils import Command, confirm, copy_skeleton
+from cubicweb.toolsutils import Command, copy_skeleton
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration
-
class DevCubeConfiguration(ServerConfiguration, WebConfiguration):
"""dummy config to get full library schema and entities"""
creating = True
@@ -254,15 +254,14 @@
if args:
raise BadCommandUsage('Too much arguments')
import shutil
- from tempfile import mktemp
+ import tempfile
import yams
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import globfind, find, rm
from cubicweb.common.i18n import extract_from_tal, execute
- tempdir = mktemp()
- mkdir(tempdir)
- potfiles = [join(I18NDIR, 'entities.pot')]
- print '******** extract schema messages'
+ tempdir = tempdir.mkdtemp()
+ potfiles = [join(I18NDIR, 'static-messages.pot')]
+ print '-> extract schema messages.'
schemapot = join(tempdir, 'schema.pot')
potfiles.append(schemapot)
# explicit close necessary else the file may not be yet flushed when
@@ -270,10 +269,10 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir=None)
schemapotstream.close()
- print '******** extract TAL messages'
+ print '-> extract TAL messages.'
tali18nfile = join(tempdir, 'tali18n.py')
extract_from_tal(find(join(BASEDIR, 'web'), ('.py', '.pt')), tali18nfile)
- print '******** .pot files generation'
+ print '-> generate .pot files.'
for id, files, lang in [('pycubicweb', get_module_files(BASEDIR) + list(globfind(join(BASEDIR, 'misc', 'migration'), '*.py')), None),
('schemadescr', globfind(join(BASEDIR, 'schemas'), '*.py'), None),
('yams', get_module_files(yams.__path__[0]), None),
@@ -288,11 +287,11 @@
if exists(potfile):
potfiles.append(potfile)
else:
- print 'WARNING: %s file not generated' % potfile
- print '******** merging .pot files'
+ print '-> WARNING: %s file was not generated' % potfile
+ print '-> merging %i .pot files' % len(potfiles)
cubicwebpot = join(tempdir, 'cubicweb.pot')
execute('msgcat %s > %s' % (' '.join(potfiles), cubicwebpot))
- print '******** merging main pot file with existing translations'
+ print '-> merging main pot file with existing translations.'
chdir(I18NDIR)
toedit = []
for lang in LANGS:
@@ -304,11 +303,10 @@
# cleanup
rm(tempdir)
# instructions pour la suite
- print '*' * 72
- print 'you can now edit the following files:'
+ print '-> regenerated CubicWeb\'s .po catalogs.'
+ print '\nYou can now edit the following files:'
print '* ' + '\n* '.join(toedit)
- print
- print "then you'll have to update cubes catalogs using the i18ncube command"
+ print 'when you are done, run "cubicweb-ctl i18ncube yourcube".'
class UpdateTemplateCatalogCommand(Command):
@@ -329,39 +327,45 @@
def update_cubes_catalogs(cubes):
- toedit = []
for cubedir in cubes:
+ toedit = []
if not isdir(cubedir):
- print 'not a directory', cubedir
+ print '-> ignoring %s that is 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)
-
+ print '-> error while updating catalogs for cube', cubedir
+ else:
+ # instructions pour la suite
+ print '-> regenerated .po catalogs for cube %s.' % cubedir
+ print '\nYou can now edit the following files:'
+ print '* ' + '\n* '.join(toedit)
+ print ('When you are done, run "cubicweb-ctl i18ninstance '
+ '<yourinstance>" to see changes in your instances.')
def update_cube_catalogs(cubedir):
import shutil
- from tempfile import mktemp
+ import tempfile
from logilab.common.fileutils import ensure_fs_mode
from logilab.common.shellutils import find, rm
from cubicweb.common.i18n import extract_from_tal, execute
toedit = []
cube = basename(normpath(cubedir))
- tempdir = mktemp()
- mkdir(tempdir)
- print '*' * 72
- print 'updating %s cube...' % cube
+ tempdir = tempfile.mkdtemp()
+ print underline_title('Updating i18n catalogs for cube %s' % cube)
chdir(cubedir)
- potfiles = [join('i18n', scfile) for scfile in ('entities.pot',)
- if exists(join('i18n', scfile))]
- print '******** extract schema messages'
+ if exists(join('i18n', 'entities.pot')):
+ warn('entities.pot is deprecated, rename file to static-messages.pot (%s)'
+ % join('i18n', 'entities.pot'), DeprecationWarning)
+ potfiles = [join('i18n', 'entities.pot')]
+ elif exists(join('i18n', 'static-messages.pot')):
+ potfiles = [join('i18n', 'static-messages.pot')]
+ else:
+ potfiles = []
+ print '-> extract schema messages'
schemapot = join(tempdir, 'schema.pot')
potfiles.append(schemapot)
# explicit close necessary else the file may not be yet flushed when
@@ -369,10 +373,10 @@
schemapotstream = file(schemapot, 'w')
generate_schema_pot(schemapotstream.write, cubedir)
schemapotstream.close()
- print '******** extract TAL messages'
+ print '-> extract TAL messages'
tali18nfile = join(tempdir, 'tali18n.py')
extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
- print '******** extract Javascript messages'
+ print '-> extract Javascript messages'
jsfiles = [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
if jsfiles:
tmppotfile = join(tempdir, 'js.pot')
@@ -381,7 +385,7 @@
# no pot file created if there are no string to translate
if exists(tmppotfile):
potfiles.append(tmppotfile)
- print '******** create cube specific catalog'
+ print '-> create cube-specific catalog'
tmppotfile = join(tempdir, 'generated.pot')
cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
cubefiles.append(tali18nfile)
@@ -390,12 +394,12 @@
if exists(tmppotfile): # doesn't exists of no translation string found
potfiles.append(tmppotfile)
potfile = join(tempdir, 'cube.pot')
- print '******** merging .pot files'
+ print '-> merging %i .pot files:' % len(potfiles)
execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
- print '******** merging main pot file with existing translations'
+ print '-> merging main pot file with existing translations:'
chdir('i18n')
for lang in LANGS:
- print '****', lang
+ print '-> language', lang
cubepo = '%s.po' % lang
if not exists(cubepo):
shutil.copy(potfile, cubepo)
@@ -479,7 +483,7 @@
" Please specify it using the --directory option")
cubesdir = cubespath[0]
if not isdir(cubesdir):
- print "creating cubes directory", cubesdir
+ print "-> creating cubes directory", cubesdir
try:
mkdir(cubesdir)
except OSError, err:
@@ -488,19 +492,20 @@
if exists(cubedir):
self.fail("%s already exists !" % (cubedir))
skeldir = join(BASEDIR, 'skeleton')
+ default_name = 'cubicweb-%s' % cubename.lower()
if verbose:
- distname = raw_input('Debian name for your cube (just type enter to use the cube name): ').strip()
+ distname = raw_input('Debian name for your cube ? [%s]): ' % default_name).strip()
if not distname:
- distname = 'cubicweb-%s' % cubename.lower()
+ distname = default_name
elif not distname.startswith('cubicweb-'):
- if confirm('do you mean cubicweb-%s ?' % distname):
+ if ASK.confirm('Do you mean cubicweb-%s ?' % distname):
distname = 'cubicweb-' + distname
else:
- distname = 'cubicweb-%s' % cubename.lower()
+ distname = default_name
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): ')
+ longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ')
if verbose:
includes = self._ask_for_dependancies()
if len(includes) == 1:
@@ -525,14 +530,14 @@
def _ask_for_dependancies(self):
includes = []
for stdtype in ServerConfiguration.available_cubes():
- ans = raw_input("Depends on cube %s? (N/y/s(kip)/t(ype)"
- % stdtype).lower().strip()
- if ans == 'y':
+ answer = ASK.ask("Depends on cube %s? " % stdtype,
+ ('N','y','skip','type'), 'N')
+ if answer == 'y':
includes.append(stdtype)
- if ans == 't':
- includes = get_csv(raw_input('type dependancies: '))
+ if answer == 'type':
+ includes = splitstrip(raw_input('type dependancies: '))
break
- elif ans == 's':
+ elif answer == 'skip':
break
return includes
@@ -572,22 +577,25 @@
req = requests.setdefault(rql, [])
time.strip()
chunks = time.split()
+ clocktime = float(chunks[0][1:])
cputime = float(chunks[-3])
- req.append( cputime )
+ req.append( (clocktime, cputime) )
except Exception, exc:
sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
stat = []
for rql, times in requests.items():
- stat.append( (sum(times), len(times), rql) )
+ stat.append( (sum(time[0] for time in times),
+ sum(time[1] for time in times),
+ len(times), rql) )
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 '%.2f;%.2f;%s;%s' % (time/total_time, time, occ, rql)
+ total_time = sum(clocktime for clocktime, cputime, occ, rql in stat)*0.01
+ print 'Percentage;Cumulative Time (clock);Cumulative Time (CPU);Occurences;Query'
+ for clocktime, cputime, occ, rql in stat:
+ print '%.2f;%.2f;%.2f;%s;%s' % (clocktime/total_time, clocktime, cputime, occ, rql)
register_commands((UpdateCubicWebCatalogCommand,
UpdateTemplateCatalogCommand,
--- a/devtools/fake.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/fake.py Tue Aug 04 15:06:09 2009 +0200
@@ -49,8 +49,7 @@
_registries = {
'controllers' : [Mock(id='view'), Mock(id='login'),
Mock(id='logout'), Mock(id='edit')],
- 'views' : [Mock(id='primary'), Mock(id='secondary'),
- Mock(id='oneline'), Mock(id='list')],
+ 'views' : [Mock(id='primary'), Mock(id='oneline'), Mock(id='list')],
}
def registry_objects(self, name, oid=None):
@@ -88,12 +87,12 @@
return None
def base_url(self):
- """return the root url of the application"""
+ """return the root url of the instance"""
return BASE_URL
def relative_path(self, includeparams=True):
"""return the normalized path of the request (ie at least relative
- to the application's root, but some other normalization may be needed
+ to the instance's root, but some other normalization may be needed
so that the returned path may be used to compare to generated urls
"""
if self._url.startswith(BASE_URL):
@@ -188,12 +187,14 @@
self.user = user or FakeUser()
self.is_internal_session = False
self.is_super_session = self.user.eid == -1
- self._query_data = {}
+ self.transaction_data = {}
def execute(self, *args):
pass
+ unsafe_execute = execute
+
def commit(self, *args):
- self._query_data.clear()
+ self.transaction_data.clear()
def close(self, *args):
pass
def system_sql(self, sql, args=None):
--- a/devtools/fill.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/fill.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from random import randint, choice
from copy import deepcopy
-from datetime import datetime, date, timedelta
+from datetime import datetime, date, time#timedelta
from decimal import Decimal
from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
@@ -163,7 +163,7 @@
def generate_time(self, attrname, index):
"""generates a random time (format is ' HH:MM')"""
- return timedelta(0, 11, index%60) #'11:%02d' % (index % 60)
+ return time(11, index%60) #'11:%02d' % (index % 60)
def generate_datetime(self, attrname, index):
"""generates a random date (format is 'yyyy-mm-dd HH:MM')"""
@@ -225,7 +225,7 @@
:param etype: the entity's type
:type schema: cubicweb.schema.Schema
- :param schema: the application schema
+ :param schema: the instance schema
:type entity_num: int
:param entity_num: the number of entities to insert
@@ -325,7 +325,7 @@
def make_relations_queries(schema, edict, cursor, ignored_relations=(),
existingrels=None):
"""returns a list of generated RQL queries for relations
- :param schema: The application schema
+ :param schema: The instance schema
:param e_dict: mapping between etypes and eids
--- a/devtools/migrtest.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/migrtest.py Tue Aug 04 15:06:09 2009 +0200
@@ -42,14 +42,14 @@
from logilab.common.shellutils import cp, rm
from cubicweb.toolsutils import read_config
-from cubicweb.server.serverctl import generate_sources_file
+from cubicweb.server.utils import generate_sources_file
# XXXX use db-copy instead
# test environment configuration
chrootpath = '/sandbox/cubicwebtest'
tmpdbhost = 'crater'
-tmpdbuser = 'syt'
+tmpdbuser = 'syt'
tmpdbpasswd = 'syt'
def play_migration(applhome, applhost='', sudo=False):
--- a/devtools/pkginfo.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
-"""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
-
-
-def get_distutils_datafiles(cube, i18n=True, recursive=False):
- """
- :param cube: application cube's name
- """
- data_files = []
- data_files += get_basepyfiles(cube)
- data_files += get_webdatafiles(cube)
- if i18n:
- data_files += get_i18nfiles(cube)
- data_files += get_viewsfiles(cube, recursive=recursive)
- data_files += get_migrationfiles(cube)
- data_files += get_schemafiles(cube)
- return data_files
-
-
-
-## listdir filter funcs ################################################
-def nopyc_and_nodir(fname):
- if isdir(fname) or fname.endswith('.pyc') or fname.endswith('~'):
- return False
- return True
-
-def no_version_control(fname):
- if fname in ('CVS', '.svn', '.hg'):
- return False
- if fname.endswith('~'):
- return False
- return True
-
-def basepy_files(fname):
- if fname.endswith('.py') and fname != 'setup.py':
- return True
- return False
-
-def chain(*filters):
- def newfilter(fname):
- for filterfunc in filters:
- if not filterfunc(fname):
- return False
- return True
- return newfilter
-
-def listdir_with_path(path='.', filterfunc=None):
- if filterfunc:
- return [join(path, fname) for fname in os.listdir(path) if filterfunc(join(path, fname))]
- else:
- return [join(path, fname) for fname in os.listdir(path)]
-
-
-## data_files helpers ##################################################
-CUBES_DIR = join('share', 'cubicweb', 'cubes')
-
-def get_i18nfiles(cube):
- """returns i18n files in a suitable format for distutils's
- data_files parameter
- """
- i18ndir = join(CUBES_DIR, cube, 'i18n')
- potfiles = [(i18ndir, listdir_with_path('i18n', chain(no_version_control, nopyc_and_nodir)))]
- return potfiles
-
-
-def get_viewsfiles(cube, recursive=False):
- """returns views files in a suitable format for distutils's
- data_files parameter
-
- :param recursive: include views' subdirs recursively if True
- """
- if recursive:
- datafiles = []
- for dirpath, dirnames, filenames in os.walk('views'):
- filenames = [join(dirpath, fname) for fname in filenames
- if nopyc_and_nodir(join(dirpath, fname))]
- dirpath = join(CUBES_DIR, cube, dirpath)
- datafiles.append((dirpath, filenames))
- return datafiles
- else:
- viewsdir = join(CUBES_DIR, cube, 'views')
- return [(viewsdir,
- listdir_with_path('views', filterfunc=nopyc_and_nodir))]
-
-
-def get_basepyfiles(cube):
- """returns cube's base python scripts (tali18n.py, etc.)
- in a suitable format for distutils's data_files parameter
- """
- return [(join(CUBES_DIR, cube),
- [fname for fname in os.listdir('.')
- if fname.endswith('.py') and fname != 'setup.py'])]
-
-
-def get_webdatafiles(cube):
- """returns web's data files (css, png, js, etc.) in a suitable
- format for distutils's data_files parameter
- """
- return [(join(CUBES_DIR, cube, 'data'),
- listdir_with_path('data', filterfunc=no_version_control))]
-
-
-def get_migrationfiles(cube):
- """returns cube's migration scripts
- in a suitable format for distutils's data_files parameter
- """
- return [(join(CUBES_DIR, cube, 'migration'),
- listdir_with_path('migration', no_version_control))]
-
-
-def get_schemafiles(cube):
- """returns cube's schema files
- in a suitable format for distutils's data_files parameter
- """
- return [(join(CUBES_DIR, cube, 'schema'),
- listdir_with_path('schema', no_version_control))]
-
-
--- a/devtools/repotest.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/repotest.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,6 +11,8 @@
from pprint import pprint
+from logilab.common.decorators import clear_cache
+
def tuplify(list):
for i in range(len(list)):
if type(list[i]) is not type(()):
@@ -116,6 +118,9 @@
ExecutionPlan._check_permissions = _orig_check_permissions
rqlannotation._select_principal = _orig_select_principal
+ def set_debug(self, debug):
+ set_debug(debug)
+
def _prepare(self, rql):
#print '******************** prepare', rql
union = self.rqlhelper.parse(rql)
@@ -203,6 +208,34 @@
class BasePlannerTC(BaseQuerierTC):
+ newsources = 0
+ def setup(self):
+ clear_cache(self.repo, 'rel_type_sources')
+ clear_cache(self.repo, 'rel_type_sources')
+ clear_cache(self.repo, 'can_cross_relation')
+ clear_cache(self.repo, 'is_multi_sources_relation')
+ # XXX source_defs
+ self.o = self.repo.querier
+ self.session = self.repo._sessions.values()[0]
+ self.pool = self.session.set_pool()
+ self.schema = self.o.schema
+ self.sources = self.o._repo.sources
+ self.system = self.sources[-1]
+ do_monkey_patch()
+
+ def add_source(self, sourcecls, uri):
+ self.sources.append(sourcecls(self.repo, self.o.schema,
+ {'uri': uri}))
+ self.repo.sources_by_uri[uri] = self.sources[-1]
+ setattr(self, uri, self.sources[-1])
+ self.newsources += 1
+
+ def tearDown(self):
+ while self.newsources:
+ source = self.sources.pop(-1)
+ del self.repo.sources_by_uri[source.uri]
+ self.newsources -= 1
+ undo_monkey_patch()
def _prepare_plan(self, rql, kwargs=None):
rqlst = self.o.parse(rql, annotate=True)
--- a/devtools/stresstester.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/stresstester.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-""" Usage: %s [OPTIONS] <application id> <queries file>
+""" Usage: %s [OPTIONS] <instance id> <queries file>
Stress test a CubicWeb repository
@@ -151,8 +151,8 @@
user = raw_input('login: ')
if password is None:
password = getpass('password: ')
- from cubicweb.cwconfig import application_configuration
- config = application_configuration(args[0])
+ from cubicweb.cwconfig import instance_configuration
+ config = instance_configuration(args[0])
# get local access to the repository
print "Creating repo", prof_file
repo = Repository(config, prof_file)
--- a/devtools/test/data/bootstrap_cubes Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/test/data/bootstrap_cubes Tue Aug 04 15:06:09 2009 +0200
@@ -1,1 +1,1 @@
-person, comment
+person
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/devtools/test/data/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,20 @@
+"""
+
+: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 yams.buildobjs import EntityType, SubjectRelation, String, Int, Date
+
+from cubes.person.schema import Person
+
+Person.add_relation(Date(), 'birthday')
+
+class Bug(EntityType):
+ title = String(maxsize=64, required=True, fulltextindexed=True)
+ severity = String(vocabulary=('important', 'normal', 'minor'), default='normal')
+ cost = Int()
+ description = String(maxsize=4096, fulltextindexed=True)
+ identical_to = SubjectRelation('Bug', symetric=True)
+
--- a/devtools/test/data/schema/Bug.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-title ivarchar(64) not null
-state CHOICE('open', 'rejected', 'validation pending', 'resolved') default 'open'
-severity CHOICE('important', 'normal', 'minor') default 'normal'
-cost integer
-description ivarchar(4096)
--- a/devtools/test/data/schema/Project.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-name ivarchar(64) not null
-summary ivarchar(128)
-vcsurl varchar(256)
-reporturl varchar(256)
-description ivarchar(1024)
-url varchar(128)
--- a/devtools/test/data/schema/Story.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-title ivarchar(64) not null
-state CHOICE('open', 'rejected', 'validation pending', 'resolved') default 'open'
-priority CHOICE('minor', 'normal', 'important') default 'normal'
-cost integer
-description ivarchar(4096)
--- a/devtools/test/data/schema/Version.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-num varchar(16) not null
-diem date
-status CHOICE('planned', 'dev', 'published') default 'planned'
--- a/devtools/test/data/schema/custom.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-"""
-
-: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')
--- a/devtools/test/data/schema/relations.rel Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-Bug concerns Project inline
-Story concerns Project inline
-
-Bug corrected_in Version inline CONSTRAINT E concerns P, X version_of P
-Story done_in Version inline CONSTRAINT E concerns P, X version_of P
-
-Bug identical_to Bug symetric
-Bug identical_to Story symetric
-Story identical_to Story symetric
-
-Story depends_on Story
-Story depends_on Bug
-Bug depends_on Story
-Bug depends_on Bug
-
-Bug see_also Bug symetric
-Bug see_also Story symetric
-Bug see_also Project symetric
-Story see_also Story symetric
-Story see_also Project symetric
-Project see_also Project symetric
-
-Project uses Project
-
-Version version_of Project inline
-Version todo_by CWUser
-
-Comment about Bug inline
-Comment about Story inline
-Comment about Comment inline
-
-CWUser interested_in Project
-
--- a/devtools/testlib.py Tue Aug 04 11:43:03 2009 +0200
+++ b/devtools/testlib.py Tue Aug 04 15:06:09 2009 +0200
@@ -44,7 +44,7 @@
# compute how many entities by type we need to be able to satisfy relation constraint
relmap = {}
for rschema in schema.relations():
- if rschema.meta or rschema.is_final(): # skip meta relations
+ if rschema.is_final():
continue
for subj, obj in rschema.iter_rdefs():
card = rschema.rproperty(subj, obj, 'cardinality')
@@ -249,6 +249,8 @@
def iter_automatic_rsets(self, limit=10):
"""generates basic resultsets for each entity type"""
etypes = self.to_test_etypes()
+ if not etypes:
+ return
for etype in etypes:
yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype))
etype1 = etypes.pop()
@@ -387,7 +389,7 @@
vreg._selected = {}
orig_select_best = vreg.__class__.select_best
def instr_select_best(self, *args, **kwargs):
- selected = orig_select(self, *args, **kwargs)
+ selected = orig_select_best(self, *args, **kwargs)
try:
self._selected[selected.__class__] += 1
except KeyError:
--- a/doc/book/en/.templates/layout.html Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/.templates/layout.html Tue Aug 04 15:06:09 2009 +0200
@@ -4,7 +4,7 @@
{%- endblock %}
{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
-{%- macro relbar %}
+{%- macro relbar() %}
<div class="related">
<h3>Navigation</h3>
<ul>
@@ -24,7 +24,7 @@
</ul>
</div>
{%- endmacro %}
-{%- macro sidebar %}
+{%- macro sidebar() %}
{%- if builder != 'htmlhelp' %}
<div class="sphinxsidebar">
<div class="sphinxsidebarwrapper">
--- a/doc/book/en/B000-development.en.txt Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/B000-development.en.txt Tue Aug 04 15:06:09 2009 +0200
@@ -1,12 +1,10 @@
.. -*- coding: utf-8 -*-
-.. _Part2:
-
=====================
Part II - Development
=====================
-This part is about developing web applications with the `CubicWeb` framework.
+This part is about developing web applications with the *CubicWeb* framework.
.. toctree::
:maxdepth: 1
--- a/doc/book/en/B0015-define-permissions.en.txt Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/B0015-define-permissions.en.txt Tue Aug 04 15:06:09 2009 +0200
@@ -11,7 +11,7 @@
* permissions (read, update, create, delete)
* permissions are assigned to groups (and not to users)
-For `CubicWeb` in particular:
+For *CubicWeb* in particular:
* we associate rights at the enttities/relations schema level
* for each entity, we distinguish four kind of permissions: read,
--- a/doc/book/en/MERGE_ME-tut-create-gae-app.en.txt Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/MERGE_ME-tut-create-gae-app.en.txt Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
Ce tutoriel va vous guider pas à pas a construire une apllication web
de gestion de Blog afin de vous faire découvrir les fonctionnalités de
-`CubicWeb`.
+*CubicWeb*.
Nous supposons que vous avec déjà suivi le guide :ref:`installationGAE`.
@@ -23,7 +23,7 @@
cubicweb-ctl newgapp blogdemo
-`newgapp` est la commande permettant de créer une instance `CubicWeb` pour
+`newgapp` est la commande permettant de créer une instance *CubicWeb* pour
le datastore.
Assurez-vous que votre variable d'environnement ``PYTHONPATH`` est correctement
@@ -32,7 +32,7 @@
Définissez un schéma
--------------------
-Le modèle de données ou schéma est au coeur d'une application `CubicWeb`.
+Le modèle de données ou schéma est au coeur d'une application *CubicWeb*.
C'est là où vous allez devoir définir le type de contenu que votre application
devra gérer.
@@ -180,7 +180,7 @@
:alt: displaying the detailed view of a blogentry
Rappelez-vous que pour le moment, tout a été géré par la plate-forme
-`CubicWeb` et que la seule chose qui a été fournie est le schéma de
+*CubicWeb* et que la seule chose qui a été fournie est le schéma de
données. D'ailleurs pour obtenir une vue graphique du schéma, exécutez
la commande ``laxctl genschema blogdemo`` et vous pourrez visualiser
votre schéma a l'URL suivante : http://localhost:8080/schema
--- a/doc/book/en/Z010-beginners.en.txt Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/Z010-beginners.en.txt Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
.. _QuickInstall:
-Quick Installation of a `CubicWeb` instance
+Quick Installation of a *CubicWeb* instance
===========================================
.. include:: C010-setup.en.txt
--- a/doc/book/en/Z012-create-instance.en.txt Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/Z012-create-instance.en.txt Tue Aug 04 15:06:09 2009 +0200
@@ -7,15 +7,15 @@
What is an instance?
--------------------
-A `CubicWeb` instance is a container that
-refers to cubes and configuration parameters for your web application.
+A *CubicWeb* instance is a container that
+refers to cubes and configuration parameters for your web instance.
Each instance is stored as a directory in ``~/etc/cubicweb.d`` which enables
-us to run your application.
+us to run your instance.
What is a cube?
---------------
-Cubes represent data and basic building bricks of your web applications :
+Cubes represent data and basic building bricks of your web instances :
blogs, person, date, addressbook and a lot more.
.. XXX They related to each other by a 'Schema' which is also the PostGres representation.
@@ -26,16 +26,16 @@
a debian package installation. For example, the 'blog' cube defines the entities
blogs and blogentries.
-When an `CubicWeb` instance is created, you list the cubes that you want to use.
+When an *CubicWeb* instance is created, you list the cubes that you want to use.
Using a cube means having the entities defined in your cube's schema
available in your instance as well as their views and workflows.
-Creating a basic `CubicWeb` Instance
+Creating a basic *CubicWeb* Instance
------------------------------------
We can create an instance to view our
-application in a web browser. ::
+instance in a web browser. ::
cubicweb-ctl create blog myblog
@@ -52,17 +52,17 @@
sufficient. You can allways modify the parameters later by editing
configuration files. When a user/psswd is requested to access the database
please use the login you create at the time you configured the database
-(:ref:`ConfigurationPostgres`).
+(:ref:`ConfigurationPostgresql`).
It is important to distinguish here the user used to access the database and
-the user used to login to the cubicweb application. When a `CubicWeb` application
+the user used to login to the cubicweb instance. When a *CubicWeb* instance
starts, it uses the login/psswd for the database to get the schema and handle
low level transaction. But, when ``cubicweb-ctl create`` asks for
-a manager login/psswd of `CubicWeb`, it refers to an application user
-to administrate your web application.
+a manager login/psswd of *CubicWeb*, it refers to an instance user
+to administrate your web instance.
The configuration files are stored in *~/etc/cubicweb.d/myblog/*.
-To launch the web application, you just type ::
+To launch the web instance, you just type ::
cubicweb-ctl start myblog
--- a/doc/book/en/admin/create-instance.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/create-instance.rst Tue Aug 04 15:06:09 2009 +0200
@@ -6,9 +6,8 @@
Instance creation
-----------------
-Now that we created our cube, we can create an instance to view our
-application in a web browser. To do so we will use a `all-in-one`
-configuration to simplify things ::
+Now that we created a cube, we can create an instance and access it via a web
+browser. We will use a `all-in-one` configuration to simplify things ::
cubicweb-ctl create -c all-in-one mycube myinstance
@@ -21,15 +20,15 @@
sufficient. You can anyway modify the configuration later on by editing
configuration files. When a user/psswd is requested to access the database
please use the login you create at the time you configured the database
-(:ref:`ConfigurationPostgres`).
+(:ref:`ConfigurationPostgresql`).
It is important to distinguish here the user used to access the database and the
-user used to login to the cubicweb application. When an instance starts, it uses
+user used to login to the cubicweb instance. When an instance starts, it uses
the login/psswd for the database to get the schema and handle low level
transaction. But, when :command:`cubicweb-ctl create` asks for a manager
-login/psswd of `CubicWeb`, it refers to the user you will use during the
-development to administrate your web application. It will be possible, later on,
-to use this user to create others users for your final web application.
+login/psswd of *CubicWeb*, it refers to the user you will use during the
+development to administrate your web instance. It will be possible, later on,
+to use this user to create others users for your final web instance.
Instance administration
@@ -45,8 +44,8 @@
The option `-D` specify the *debug mode* : the instance is not running in
server mode and does not disconnect from the termnial, which simplifies debugging
in case the instance is not properly launched. You can see how it looks by
-visiting the URL `http://localhost:8080` (the port number depends of your
-configuration). To login, please use the cubicweb administrator login/psswd you
+visiting the URL `http://localhost:8080` (the port number depends of your
+configuration). To login, please use the cubicweb administrator login/psswd you
defined when you created the instance.
To shutdown the instance, Crtl-C in the terminal window is enough.
@@ -59,5 +58,10 @@
upgrade
~~~~~~~
-XXX feed me
+
+The command is::
+ cubicweb-ctl upgrade myinstance
+
+XXX write me
+
--- a/doc/book/en/admin/gae.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/gae.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,5 +1,7 @@
.. -*- coding: utf-8 -*-
+.. _GoogleAppEngineSource:
+
CubicWeb in Google AppEngine
============================
@@ -9,9 +11,9 @@
`Google AppEngine`_ is provided with a partial port of the `Django`
framework, but Google stated at Google IO 2008 that it would not
support a specific Python web framework and that all
-community-supported frameworks would be more than welcome [1]_.
+community-supported frameworks would be more than welcome [1]_.
-Therefore `Logilab`_ ported `CubicWeb` to run on top of `Google AppEngine`'s
+Therefore `Logilab`_ ported *CubicWeb* to run on top of `Google AppEngine`'s
datastore.
.. _`Google AppEngine`: http://code.google.com/appengine/docs/whatisgoogleappengine.html
@@ -25,8 +27,8 @@
http://code.google.com/appengine/downloads.html
-Please follow instructions on how to install `CubicWeb` framework
-(:ref:`CubicWebInstallation`).
+Please follow instructions on how to install *CubicWeb* framework
+(:ref:`SetUpEnv`).
Installation
------------
@@ -37,7 +39,7 @@
cubicweb-ctl newgapp <myapp>
This will create a directory containing ::
-
+
`-- myapp/
|-- app.conf
|-- app.yaml
@@ -73,17 +75,17 @@
|-- yams/
`-- yapps/
-
+
This skeleton directory is a working `AppEngine` application. You will
recognize the files ``app.yaml`` and ``main.py``. All the rest is the
-`CubicWeb` framework and its third-party libraries. You will notice that
+*CubicWeb* framework and its third-party libraries. You will notice that
the directory ``cubes`` is a library of reusable cubes.
The main directories that you should know about are:
- - ``cubes`` : this is a library of reusable yams cubes. To use
- those cubes you will list them in the variable
- `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`.
+ - ``cubes`` : this is a library of reusable yams cubes. To use
+ those cubes you will list them in the variable
+ `included-yams-cubes` of ``app.conf``. See also :ref:`cubes`.
- [WHICH OTHER ONES SHOULD BE LISTED HERE?]
Dependencies
@@ -91,7 +93,7 @@
Before starting anything, please make sure the following packages are installed:
- yaml : by default google appengine is providing yaml; make sure you can
- import it. We recommend you create a symbolic link yaml instead of installing
+ import it. We recommend you create a symbolic link yaml instead of installing
and using python-yaml:
yaml -> full/path/to/google_appengine/lib/yaml/lib/yaml/
- gettext
@@ -99,13 +101,13 @@
Setup
~~~~~
-Once you executed ``cubicweb-ctl newgapp <myapp>``, you can use that ``myapp/``
+Once you executed ``cubicweb-ctl newgapp <myapp>``, you can use that ``myapp/``
as an application directory and do as follows.
-This installation directory provides a configuration for an instance of `CubicWeb`
-ported for Google App Engine. It is installed with its own command ``laxctl``
-which is a port of the command tool ``cubicweb-ctl`` originally developped for
-`CubicWeb`.
+This installation directory provides a configuration for an instance of *CubicWeb*
+ported for Google App Engine. It is installed with its own command ``laxctl``
+which is a port of the command tool ``cubicweb-ctl`` originally developped for
+*CubicWeb*.
You can have the details of available commands by running ::
@@ -115,25 +117,25 @@
Generating translation files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-`CubicWeb` is fully internationalized. Translation catalogs are found in
+*CubicWeb* is fully internationalized. Translation catalogs are found in
``myapp/i18n``. To compile the translation files, use the `gettext` tools
or the ``laxctl`` command ::
- $ python myapp/bin/laxctl i18ncube
+ $ python myapp/bin/laxctl i18ncube
$ python myapp/bin/laxctl i18ninstance
Ignore the errors that print "No translation file found for domain
'cubicweb'". They disappear after the first run of i18ninstance.
.. note:: The command myapp/bin/laxctl i18ncube needs to be executed
- only if your application is using cubes from cubicweb-apps.
+ only if your instance is using cubes from cubicweb-apps.
Otherwise, please skip it.
-You will never need to add new entries in the translation catalog. Instead we would
-recommand you to use ``self.req._("msgId")`` in your application code
-to flag new message id to add to the catalog, where ``_`` refers to
-xgettext that is used to collect new strings to translate.
-While running ``laxctl i18ncube``, new string will be added to the catalogs.
+You will never need to add new entries in the translation catalog. Instead we
+would recommand you to use ``self.req._("msgId")`` in your code to flag new
+message id to add to the catalog, where ``_`` refers to xgettext that is used to
+collect new strings to translate. While running ``laxctl i18ncube``, new string
+will be added to the catalogs.
Generating the data directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -152,20 +154,20 @@
$ python myapp/bin/laxctl genschema
-Application configuration
+Instance configuration
-------------------------
Authentication
~~~~~~~~~~~~~~
-You have the option of using or not google authentication for your application.
+You have the option of using or not google authentication for your instance.
This has to be define in ``app.conf`` and ``app.yaml``.
In ``app.conf`` modify the following variable::
- # does this application rely on google authentication service or not.
+ # does this instance rely on google authentication service or not.
use-google-auth=no
-
+
In ``app.yaml`` comment the `login: required` set by default in the handler::
- url: .*
@@ -177,7 +179,7 @@
-Quickstart : launch the application
+Quickstart : launch the instance
-----------------------------------
On Mac OS X platforms, drag that directory on the
@@ -187,9 +189,9 @@
$ python /path/to/google_appengine/dev_appserver.py /path/to/myapp/
-Once the local server is started, visit `http://MYAPP_URL/_load <http://localhost:8080/_load>`_ and sign in as administrator.
-This will initialize the repository and enable you to log in into
-the application and continue the installation.
+Once the local server is started, visit `http://MYAPP_URL/_load <http://localhost:8080/_load>`_ and sign in as administrator.
+This will initialize the repository and enable you to log in into
+the instance and continue the installation.
You should be redirected to a page displaying a message `content initialized`.
@@ -197,7 +199,7 @@
~~~~~~~~~~~~~~~~~~~~~~~~
You, then, want to visit `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
-If you selected not to use google authentication, you will be prompted to a
+If you selected not to use google authentication, you will be prompted to a
login form where you should initialize the administrator login (recommended
to use admin/admin at first). You will then be redirected to a page providing
you the value to provide to ``./bin/laxctl --cookie``.
@@ -212,21 +214,21 @@
:alt: displaying the detailed view of the cookie values returned
-.. note:: In case you are not redirected to a page providing the
- option --cookie value, please visit one more time
+.. note:: In case you are not redirected to a page providing the
+ option --cookie value, please visit one more time
`http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_ .
Once, you have this value, then return to the shell and execute ::
-
+
$ python myapp/bin/laxctl db-init --cookie='dev_appserver_login=test@example.com:True; __session=7bbe973a6705bc5773a640f8cf4326cc' localhost:8080
.. note:: In the case you are not using google authentication, the value returned
- by `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_
+ by `http://MYAPP_URL/?vid=authinfo <http://localhost:8080/?vid=authinfo>`_
will look like :
--cookie='__session=2b45d1a9c36c03d2a30cedb04bc37b6d'
Log out by clicking in the menu at the top right corner
-and restart browsing from `http://MYAPP_URL/ <http://localhost:8080>`_
+and restart browsing from `http://MYAPP_URL/ <http://localhost:8080>`_
as a normal user.
Unless you did something to change it, http://MYAPP_URL should be
--- a/doc/book/en/admin/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -6,8 +6,8 @@
Part III - Administration
-------------------------
-This part is for installation and administration of the `CubicWeb` framework and
-applications based on that framework.
+This part is for installation and administration of the *CubicWeb* framework and
+instances based on that framework.
.. toctree::
:maxdepth: 1
@@ -25,11 +25,11 @@
RQL logs
--------
-You can configure the `CubicWeb` application to keep a log
+You can configure the *CubicWeb* instance to keep a log
of the queries executed against your database. To do so,
-edit the configuration file of your application
+edit the configuration file of your instance
``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the
variable ``query-log-file``::
- # web application query log file
+ # web instance query log file
query-log-file=/tmp/rql-myapp.log
--- a/doc/book/en/admin/instance-config.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/instance-config.rst Tue Aug 04 15:06:09 2009 +0200
@@ -6,11 +6,11 @@
While creating an instance, a configuration file is generated in::
- $ (CW_REGISTRY) / <instance> / <configuration name>.conf
+ $ (CW_INSTANCES_DIR) / <instance> / <configuration name>.conf
For example::
- /etc/cubicweb.d/JPL/all-in-one.conf
+ /etc/cubicweb.d/myblog/all-in-one.conf
It is a simple text file format INI. In the following description,
each option name is prefixed with its own section and followed by its
@@ -22,7 +22,7 @@
:`web.auth-model` [cookie]:
authentication mode, cookie or http
:`web.realm`:
- realm of the application in http authentication mode
+ realm of the instance in http authentication mode
:`web.http-session-time` [0]:
period of inactivity of an HTTP session before it closes automatically.
Duration in seconds, 0 meaning no expiration (or more exactly at the
@@ -51,7 +51,7 @@
RewriteCond %(REQUEST_URI) ^/demo
RewriteRule ^/demo$ /demo/
RewriteRule ^/demo/(.*) http://127.0.0.1:8080/$1 [L,P]
-
+
and for the https:::
RewriteCond %(REQUEST_URI) ^/ demo
@@ -70,7 +70,7 @@
regular expression matching sites which could be "embedded" in
the site (controllers 'embed')
:`web.submit-url`:
- url where the bugs encountered in the application can be mailed to
+ url where the bugs encountered in the instance can be mailed to
RQL server configuration
@@ -92,7 +92,7 @@
-----------------------------------
Web server side:
-:`pyro-client.pyro-application-id`:
+:`pyro-client.pyro-instance-id`:
pyro identifier of RQL server (e.g. the instance name)
RQL server side:
@@ -107,7 +107,7 @@
hostname hosting pyro server name. If no value is
specified, it is located by a request from broadcast
:`pyro-name-server.pyro-ns-group` [cubicweb]:
- pyro group in which to save the application
+ pyro group in which to save the instance
Configuring e-mail
@@ -125,9 +125,9 @@
:`email.smtp-port [25]`:
SMTP server port to use for outgoing mail
:`email.sender-name`:
- name to use for outgoing mail of the application
+ name to use for outgoing mail of the instance
:`email.sender-addr`:
- address for outgoing mail of the application
+ address for outgoing mail of the instance
:`email.default dest-addrs`:
destination addresses by default, if used by the configuration of the
dissemination of the model (separated by commas)
--- a/doc/book/en/admin/multisources.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/multisources.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,6 @@
-Integrating some data from another instance
-===========================================
+Multiple sources of data
+========================
-XXX feed me
\ No newline at end of file
+Data sources include SQL, LDAP, RQL, mercurial and subversion.
+
+XXX feed me
--- a/doc/book/en/admin/setup.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/setup.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,13 +3,13 @@
.. _SetUpEnv:
===================================================
-Installation and set-up of a `CubicWeb` environment
+Installation and set-up of a *CubicWeb* environment
===================================================
Installation of `Cubicweb` and its dependencies
-----------------------------------------------
-`CubicWeb` is packaged for Debian and Ubuntu, but can be installed from source
+*CubicWeb* is packaged for Debian and Ubuntu, but can be installed from source
using a tarball or the Mercurial version control system.
.. _DebianInstallation:
@@ -35,17 +35,24 @@
You can now install the required packages with the following command::
- apt-get update
+ apt-get update
apt-get install cubicweb cubicweb-dev
`cubicweb` installs the framework itself, allowing you to create
-new applications.
+new instances.
`cubicweb-dev` installs the development environment allowing you to
develop new cubes.
There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball.
+The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update":
+1. become root using sudo
+2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget
+3. run "apt-key add logilab-dists-key.asc"
+4. re-run apt-get update (manually or through the package manager, whichever you prefer)
+
+.. _`Logilab's gnupg key`: http://ftp.logilab.org/dists/logilab-dists-key.asc
Install from source
```````````````````
@@ -62,19 +69,29 @@
hg fclone http://www.logilab.org/hg/forests/cubicweb
See :ref:`MercurialPresentation` for more details about Mercurial.
+When cloning a repository, you might be set in a development branch
+(the 'default' branch). You should check that the branches of the
+repositories are set to 'stable' (using `hg up stable` for each one)
+if you do not intend to develop the framework itself.
-Postgres installation
-`````````````````````
+In both cases, make sure you have installed the dependencies (see appendixes for
+the list).
-Please refer to the `Postgresql project online documentation`_.
+PostgreSQL installation
+```````````````````````
+
+Please refer to the `PostgreSQL project online documentation`_.
-.. _`Postgresql project online documentation`: http://www.postgresql.org/
+.. _`PostgreSQL project online documentation`: http://www.postgresql.org/
-You need to install the three following packages: `postgres-8.3`,
-`postgres-contrib-8.3` and `postgresql-plpython-8.3`.
+You need to install the three following packages: `postgresql-8.3`,
+`postgresql-contrib-8.3` and `postgresql-plpython-8.3`.
-Then you can install:
+Other dependencies
+``````````````````
+
+You can also install:
* `pyro` if you wish the repository to be accessible through Pyro
or if the client and the server are not running on the same machine
@@ -88,26 +105,27 @@
Environment configuration
-------------------------
-If you installed `CubicWeb` by cloning the Mercurial forest, then you
-will need to update the environment variable PYTHONPATH by adding
+If you installed *CubicWeb* by cloning the Mercurial forest, then you
+will need to update the environment variable PYTHONPATH by adding
the path to the forest ``cubicweb``:
Add the following lines to either `.bashrc` or `.bash_profile` to configure
your development environment ::
-
- export PYTHONPATH=/full/path/to/cubicweb-forest
+
+ export PYTHONPATH=/full/path/to/cubicweb-forest
-If you installed the debian packages, no configuration is required.
-Your new cubes will be placed in `/usr/share/cubicweb/cubes` and
-your applications will be placed in `/etc/cubicweb.d`.
+If you installed *CubicWeb* with packages, no configuration is required and your
+new cubes will be placed in `/usr/share/cubicweb/cubes` and your instances
+will be placed in `/etc/cubicweb.d`.
-To use others directories then you will have to configure the
-following environment variables as follows::
+You may run a system-wide install of *CubicWeb* in "user mode" and use it for
+development by setting the following environment variable::
+ export CW_MODE=user
export CW_CUBES_PATH=~/lib/cubes
- export CW_REGISTRY=~/etc/cubicweb.d/
- export CW_INSTANCE_DATA=$CW_REGISTRY
- export CW_RUNTIME=/tmp
+ export CW_INSTANCES_DIR=~/etc/cubicweb.d/
+ export CW_INSTANCES_DATA_DIR=$CW_INSTANCES_DIR
+ export CW_RUNTIME_DIR=/tmp
.. note::
The values given above are our suggestions but of course
@@ -119,54 +137,54 @@
-.. _ConfigurationPostgres:
+.. _ConfigurationPostgresql:
-Postgres configuration
-``````````````````````
+PostgreSQL configuration
+````````````````````````
.. note::
- If you already have an existing cluster and postgres server
+ If you already have an existing cluster and PostgreSQL server
running, you do not need to execute the initilization step
- of your Postgres database.
+ of your PostgreSQL database.
-* First, initialize the database Postgres with the command ``initdb``.
+* First, initialize the database PostgreSQL with the command ``initdb``.
::
$ initdb -D /path/to/pgsql
- Once initialized, start the database server Postgres
+ Once initialized, start the database server PostgreSQL
with the command::
-
+
$ postgres -D /path/to/psql
If you cannot execute this command due to permission issues, please
make sure that your username has write access on the database.
::
-
+
$ chown username /path/to/pgsql
* The database authentication can be either set to `ident sameuser`
- or `md5`.
+ or `md5`.
If set to `md5`, make sure to use an existing user
of your database.
If set to `ident sameuser`, make sure that your
client's operating system user name has a matching user in
the database. If not, please do as follow to create a user::
-
+
$ su
$ su - postgres
$ createuser -s -P username
The option `-P` (for password prompt), will encrypt the password with
- the method set in the configuration file ``pg_hba.conf``.
+ the method set in the configuration file ``pg_hba.conf``.
If you do not use this option `-P`, then the default value will be null
and you will need to set it with::
-
+
$ su postgres -c "echo ALTER USER username WITH PASSWORD 'userpasswd' | psql"
This login/password will be requested when you will create an
instance with `cubicweb-ctl create` to initialize the database of
- your application.
+ your instance.
.. note::
The authentication method can be configured in ``pg_hba.conf``.
@@ -184,13 +202,17 @@
MySql configuration
```````````````````
-Yout must add the following lines in /etc/mysql/my.cnf file::
+Yout must add the following lines in ``/etc/mysql/my.cnf`` file::
transaction-isolation = READ-COMMITTED
default-storage-engine=INNODB
default-character-set=utf8
max_allowed_packet = 128M
+.. note::
+ It is unclear whether mysql supports indexed string of arbitrary lenght or
+ not.
+
Pyro configuration
------------------
--- a/doc/book/en/admin/site-config.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/admin/site-config.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,7 @@
.. image:: ../images/lax-book.03-site-config-panel.en.png
-This panel allows you to configure the appearance of your application site.
+This panel allows you to configure the appearance of your instance site.
Six menus are available and we will go through each of them to explain how
to use them.
@@ -14,12 +14,12 @@
This menu provides you a way to adjust some navigation options depending on
your needs, such as the number of entities to display by page of results.
Follows the detailled list of available options :
-
+
* navigation.combobox-limit : maximum number of entities to display in related
combo box (sample format: 23)
-* navigation.page-size : maximum number of objects displayed by page of results
+* navigation.page-size : maximum number of objects displayed by page of results
(sample format: 23)
-* navigation.related-limit : maximum number of related entities to display in
+* navigation.related-limit : maximum number of related entities to display in
the primary view (sample format: 23)
* navigation.short-line-size : maximum number of characters in short description
(sample format: 23)
@@ -47,17 +47,17 @@
Actions
~~~~~~~
This menu provides a way to configure the context in which you expect the actions
-to be displayed to the user and if you want the action to be visible or not.
-You must have notice that when you view a list of entities, an action box is
-available on the left column which display some actions as well as a drop-down
-menu for more actions.
+to be displayed to the user and if you want the action to be visible or not.
+You must have notice that when you view a list of entities, an action box is
+available on the left column which display some actions as well as a drop-down
+menu for more actions.
The context available are :
* mainactions : actions listed in the left box
* moreactions : actions listed in the `more` menu of the left box
* addrelated : add actions listed in the left box
-* useractions : actions listed in the first section of drop-down menu
+* useractions : actions listed in the first section of drop-down menu
accessible from the right corner user login link
* siteactions : actions listed in the second section of drop-down menu
accessible from the right corner user login link
@@ -65,15 +65,15 @@
Boxes
~~~~~
-The application has already a pre-defined set of boxes you can use right away.
+The instance has already a pre-defined set of boxes you can use right away.
This configuration section allows you to place those boxes where you want in the
-application interface to customize it.
+instance interface to customize it.
The available boxes are :
* actions box : box listing the applicable actions on the displayed data
-* boxes_blog_archives_box : box listing the blog archives
+* boxes_blog_archives_box : box listing the blog archives
* possible views box : box listing the possible views for the displayed data
@@ -81,8 +81,8 @@
* search box : search box
-* startup views box : box listing the configuration options available for
- the application site, such as `Preferences` and `Site Configuration`
+* startup views box : box listing the configuration options available for
+ the instance site, such as `Preferences` and `Site Configuration`
Components
~~~~~~~~~~
--- a/doc/book/en/annexes/cookbook.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/cookbook.rst Tue Aug 04 15:06:09 2009 +0200
@@ -7,11 +7,14 @@
life easier.
-* How to import LDAP users in `CubicWeb`?
+* How to import LDAP users in *CubicWeb*?
+
+ [XXX distribute this script with cubicweb instead]
Here is a very useful script which enables you to import LDAP users
- into your `CubicWeb` application by running the following: ::
+ into your *CubicWeb* instance by running the following:
+.. sourcecode:: python
import os
import pwd
@@ -66,15 +69,17 @@
* How to load data from a script?
The following script aims at loading data within a script assuming pyro-nsd is
- running and your application is configured with ``pyro-server=yes``, otherwise
- you would not be able to use dbapi. ::
+ running and your instance is configured with ``pyro-server=yes``, otherwise
+ you would not be able to use dbapi.
+
+.. sourcecode:: python
from cubicweb import dbapi
-
+
cnx = dbapi.connection(database='instance-id', user='admin', password='admin')
cur = cnx.cursor()
for name in ('Personal', 'Professional', 'Computers'):
cur.execute('INSERT Blog B: B name %s', name)
cnx.commit()
-
+
--- a/doc/book/en/annexes/cubicweb-ctl.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/cubicweb-ctl.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,7 @@
``cubicweb-ctl`` tool
=====================
-`cubicweb-ctl` is the swiss knife to manage `CubicWeb` instances.
+`cubicweb-ctl` is the swiss knife to manage *CubicWeb* instances.
The general syntax is ::
cubicweb-ctl <command> [options command] <arguments commands>
@@ -15,7 +15,7 @@
cubicweb-ctl
cubicweb-ctl --help
-Please note that the commands available depends on the `CubicWeb` packages
+Please note that the commands available depends on the *CubicWeb* packages
and cubes that have been installed.
To view the help menu on specific command ::
@@ -26,9 +26,9 @@
------------------------
* ``newcube``, create a new cube on the file system based on the name
- given in the parameters. This command create a cube from an application
- skeleton that includes default files required for debian packaging.
-
+ given in the parameters. This command create a cube from a skeleton
+ that includes default files required for debian packaging.
+
Command to create an instance
-----------------------------
@@ -62,20 +62,20 @@
Commands to maintain instances
------------------------------
* ``upgrade``, launches the existing instances migration when a new version
- of `CubicWeb` or the cubes installed is available
+ of *CubicWeb* or the cubes installed is available
* ``shell``, opens a migration shell for manual maintenance of the instance
* ``db-dump``, creates a dump of the system database
* ``db-restore``, restores a dump of the system database
* ``db-check``, checks data integrity of an instance. If the automatic correction
is activated, it is recommanded to create a dump before this operation.
* ``schema-sync``, synchronizes the persistent schema of an instance with
- the application schema. It is recommanded to create a dump before this operation.
+ the instance schema. It is recommanded to create a dump before this operation.
Commands to maintain i18n catalogs
----------------------------------
-* ``i18ncubicweb``, regenerates messages catalogs of the `CubicWeb` library
+* ``i18ncubicweb``, regenerates messages catalogs of the *CubicWeb* library
* ``i18ncube``, regenerates the messages catalogs of a cube
-* ``i18ninstance``, recompiles the messages catalogs of an instance.
+* ``i18ninstance``, recompiles the messages catalogs of an instance.
This is automatically done while upgrading.
See also chapter :ref:`internationalisation`.
@@ -116,7 +116,7 @@
This will create a new cube in ``/path/to/forest/cubicweb/cubes/<mycube>``
for a Mercurial forest installation, or in ``/usr/share/cubicweb/cubes``
-for a debian packages installation, and then create an instance as
+for a debian packages installation, and then create an instance as
explained just above.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/annexes/depends.rst Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,51 @@
+.. -*- coding: utf-8 -*-
+
+.. _dependencies:
+
+Dependencies
+============
+
+When you run CubicWeb from source, either by downloading the tarball or
+cloning the mercurial forest, here is the list of tools and libraries you need
+to have installed in order for CubicWeb to work:
+
+* mxDateTime - http://www.egenix.com/products/python/mxBase/mxDateTime/ - http://pypi.python.org/pypi/egenix-mx-base
+
+* pyro - http://pyro.sourceforge.net/ - http://pypi.python.org/pypi/Pyro
+
+* yapps - http://theory.stanford.edu/~amitp/yapps/ -
+ http://pypi.python.org/pypi/Yapps2
+
+* pygraphviz - http://networkx.lanl.gov/pygraphviz/ -
+ http://pypi.python.org/pypi/pygraphviz
+
+* simplejson - http://code.google.com/p/simplejson/ -
+ http://pypi.python.org/pypi/simplejson
+
+* lxml - http://codespeak.net/lxml - http://pypi.python.org/pypi/lxml
+
+* twisted - http://twistedmatrix.com/ - http://pypi.python.org/pypi/Twisted
+
+* logilab-common - http://www.logilab.org/project/logilab-common -
+ http://pypi.python.org/pypi/logilab-common/ - included in the forest
+
+* logilab-constraint - http://www.logilab.org/project/logilab-constraint -
+ http://pypi.python.org/pypi/constraint/ - included in the forest
+
+* logilab-mtconverter - http://www.logilab.org/project/logilab-mtconverter -
+ http://pypi.python.org/pypi/logilab-mtconverter - included in the forest
+
+* rql - http://www.logilab.org/project/rql - http://pypi.python.org/pypi/rql -
+ included in the forest
+
+* yams - http://www.logilab.org/project/yams - http://pypi.python.org/pypi/yams
+ - included in the forest
+
+* indexer - http://www.logilab.org/project/indexer -
+ http://pypi.python.org/pypi/indexer - included in the forest
+
+* fyzz - http://www.logilab.org/project/fyzz - http://pypi.python.org/pypi/fyzz
+ - included in the forest
+
+Any help with the packaging of CubicWeb for more than Debian/Ubuntu (including
+eggs, buildouts, etc) will be greatly appreciated.
--- a/doc/book/en/annexes/faq.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/faq.rst Tue Aug 04 15:06:09 2009 +0200
@@ -14,132 +14,137 @@
Why does not CubicWeb have a template language ?
------------------------------------------------
- There are enough template languages out there. You can use your
- preferred template language if you want. [explain how to use a
- template language]
+There are enough template languages out there. You can use your
+preferred template language if you want. [explain how to use a
+template language]
- `CubicWeb` does not define its own templating language as this was
- not our goal. Based on our experience, we realized that
- we could gain productivity by letting designers use design tools
- and developpers develop without the use of the templating language
- as an intermediary that could not be anyway efficient for both parties.
- Python is the templating language that we use in `CubicWeb`, but again,
- it does not prevent you from using a templating language.
+*CubicWeb* does not define its own templating language as this was
+not our goal. Based on our experience, we realized that
+we could gain productivity by letting designers use design tools
+and developpers develop without the use of the templating language
+as an intermediary that could not be anyway efficient for both parties.
+Python is the templating language that we use in *CubicWeb*, but again,
+it does not prevent you from using a templating language.
- The reason template languages are not used in this book is that
- experience has proved us that using pure python was less cumbersome.
+The reason template languages are not used in this book is that
+experience has proved us that using pure python was less cumbersome.
Why do you think using pure python is better than using a template language ?
-----------------------------------------------------------------------------
- Python is an Object Oriented Programming language and as such it
- already provides a consistent and strong architecture and syntax
- a templating language would not reach.
+Python is an Object Oriented Programming language and as such it
+already provides a consistent and strong architecture and syntax
+a templating language would not reach.
- When doing development, you need a real language and template
- languages are not real languages.
+When doing development, you need a real language and template
+languages are not real languages.
- Using Python enables developing applications for which code is
- easier to maintain with real functions/classes/contexts
- without the need of learning a new dialect. By using Python,
- we use standard OOP techniques and this is a key factor in a
- robust application.
+Using Python instead of a template langage for describing the user interface
+makes it to maintain with real functions/classes/contexts without the need of
+learning a new dialect. By using Python, we use standard OOP techniques and
+this is a key factor in a robust application.
Why do you use the LGPL license to prevent me from doing X ?
------------------------------------------------------------
+------------------------------------------------------------
- LGPL means that *if* you redistribute your application, you need to
- redistribute the changes you made to CubicWeb under the LGPL licence.
+LGPL means that *if* you redistribute your application, you need to
+redistribute the changes you made to CubicWeb under the LGPL licence.
- Publishing a web site has nothing to do with redistributing
- source code. A fair amount of companies use modified LGPL code
- for internal use. And someone could publish a `CubicWeb` component
- under a BSD licence for others to plug into a LGPL framework without
- any problem. The only thing we are trying to prevent here is someone
- taking the framework and packaging it as closed source to his own
- clients.
+Publishing a web site has nothing to do with redistributing
+source code. A fair amount of companies use modified LGPL code
+for internal use. And someone could publish a *CubicWeb* component
+under a BSD licence for others to plug into a LGPL framework without
+any problem. The only thing we are trying to prevent here is someone
+taking the framework and packaging it as closed source to his own
+clients.
CubicWeb looks pretty recent. Is it stable ?
--------------------------------------------
- It is constantly evolving, piece by piece. The framework has evolved since
- 2001 and data has been migrated from one schema to the other ever since. There
- is a well-defined way to handle data and schema migration.
+It is constantly evolving, piece by piece. The framework has evolved since
+2001 and data has been migrated from one schema to the other ever since. There
+is a well-defined way to handle data and schema migration.
Why is the RQL query language looking similar to X ?
-----------------------------------------------------
- It may remind you of SQL but it is higher level than SQL, more like
- SPARQL. Except that SPARQL did not exist when we started the project.
- Having SPARQL has a query language has been in our backlog for years.
+It may remind you of SQL but it is higher level than SQL, more like
+SPARQL. Except that SPARQL did not exist when we started the project.
+With version 3.4, CubicWeb has support for SPARQL.
- That RQL language is what is going to make a difference with django-
- like frameworks for several reasons.
+That RQL language is what is going to make a difference with django-
+like frameworks for several reasons.
- 1. accessing data is *much* easier with it. One can write complex
- queries with RQL that would be tedious to define and hard to maintain
- using an object/filter suite of method calls.
+1. accessing data is *much* easier with it. One can write complex
+ queries with RQL that would be tedious to define and hard to maintain
+ using an object/filter suite of method calls.
- 2. it offers an abstraction layer allowing your applications to run
- on multiple back-ends. That means not only various SQL backends
- (postgresql, sqlite, mysql), but also multiple databases at the
- same time, and also non-SQL data stores like LDAP directories and
- subversion/mercurial repositories (see the `vcsfile`
- component). Google App Engine is yet another supported target for
- RQL.
+2. it offers an abstraction layer allowing your applications to run
+ on multiple back-ends. That means not only various SQL backends
+ (postgresql, sqlite, mysql), but also multiple databases at the
+ same time, and also non-SQL data stores like LDAP directories and
+ subversion/mercurial repositories (see the `vcsfile`
+ component). Google App Engine is yet another supported target for
+ RQL.
[copy answer from forum, explain why similar to sparql and why better
than django and SQL]
-which ajax library
-------------------
-[we use jquery and things on top of that]
+which ajax library is CubicWeb using ?
+--------------------------------------
+
+[CubicWeb uses jQuery and adds a thin layer on top of that]
-How to implement security?
---------------------------
+How is security implemented ?
+------------------------------
- This is an example of how it works in our framework::
+This is an example of how it works in our framework:
+
+.. sourcecode:: python
class Version(EntityType):
- """a version is defining the content of a particular project's
- release"""
- # definition of attributes is voluntarily missing
- permissions = {'read': ('managers', 'users', 'guests',),
- 'update': ('managers', 'logilab', 'owners',),
- 'delete': ('managers', ),
- 'add': ('managers', 'logilab',
- ERQLExpression('X version_of PROJ, U in_group G, PROJ
- require_permission P, P name "add_version", P require_group G'),)}
+ """a version is defining the content of a particular project's
+ release"""
+ # definition of attributes is voluntarily missing
+ permissions = {'read': ('managers', 'users', 'guests',),
+ 'update': ('managers', 'logilab', 'owners',),
+ 'delete': ('managers', ),
+ 'add': ('managers', 'logilab',
+ ERQLExpression('X version_of PROJ, U in_group G, '
+ 'PROJ require_permission P, '
+ 'P name "add_version", P require_group G'),)}
- The above means that permission to read a Version is granted to any
- user that is part of one of the groups 'managers', 'users', 'guests'.
- The 'add' permission is granted to users in group 'managers' or
- 'logilab' and to users in group G, if G is linked by a permission
- entity named "add_version" to the version's project.
- ::
+The above means that permission to read a Version is granted to any
+user that is part of one of the groups 'managers', 'users', 'guests'.
+The 'add' permission is granted to users in group 'managers' or
+'logilab' and to users in group G, if G is linked by a permission
+entity named "add_version" to the version's project.
+
+.. sourcecode:: python
class version_of(RelationType):
"""link a version to its project. A version is necessarily linked
to one and only one project. """
# some lines voluntarily missing
- permissions = {'read': ('managers', 'users', 'guests',),
+ permissions = {'read': ('managers', 'users', 'guests',),
'delete': ('managers', ),
'add': ('managers', 'logilab',
- RRQLExpression('O require_permission P, P name "add_version",
- 'U in_group G, P require_group G'),) }
+ RRQLExpression('O require_permission P, P name "add_version", '
+ 'U in_group G, P require_group G'),) }
- You can find additional information in the section :ref:`security`.
+You can find additional information in the section :ref:`security`.
- [XXX what does the second example means in addition to the first one?]
+[XXX what does the second example means in addition to the first one?]
-`Error while publishing rest text ...`
---------------------------------------
- While modifying the description of an entity, you get an error message in
- the application `Error while publishing ...` for Rest text and plain text.
- The server returns a traceback like as follows ::
+What is `Error while publishing rest text ...` ?
+------------------------------------------------
+
+While modifying the description of an entity, you get an error message in
+the instance `Error while publishing ...` for Rest text and plain text.
+The server returns a traceback like as follows ::
2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text
Traceback (most recent call last):
@@ -148,73 +153,69 @@
file = __builtin__.open(filename, mode, buffering)
TypeError: __init__() takes at most 3 arguments (4 given)
+This can be fixed by applying the patch described in :
+http://code.google.com/p/googleappengine/issues/detail?id=48
- This can be fixed by applying the patch described in :
- http://code.google.com/p/googleappengine/issues/detail?id=48
+What are hooks used for ?
+-------------------------
-What are hooks used for?
-------------------------
-
- Hooks are executed around (actually before or after) events. The
- most common events are data creation, update and deletion. They
- permit additional constraint checking (those not expressible at the
- schema level), pre and post computations depending on data
- movements.
+Hooks are executed around (actually before or after) events. The
+most common events are data creation, update and deletion. They
+permit additional constraint checking (those not expressible at the
+schema level), pre and post computations depending on data
+movements.
- As such, they are a vital part of the framework.
+As such, they are a vital part of the framework.
- Other kinds of hooks, called Operations, are available
- for execution just before commit.
+Other kinds of hooks, called Operations, are available
+for execution just before commit.
-When should you define an HTML template rather than define a graphical component?
----------------------------------------------------------------------------------
+When should you define an HTML template rather than define a graphical component ?
+----------------------------------------------------------------------------------
- An HTML template cannot contain code, hence it is only about static
- content. A component is made of code and operations that apply on a
- well defined context (request, result set). It enables much more
- dynamic views.
+An HTML template cannot contain code, hence it is only about static
+content. A component is made of code and operations that apply on a
+well defined context (request, result set). It enables much more
+dynamic views.
What is the difference between `AppRsetObject` and `AppObject` ?
----------------------------------------------------------------
- `AppRsetObject` instances are selected on a request and a result
- set. `AppObject` instances are directly selected by id.
+`AppRsetObject` instances are selected on a request and a result
+set. `AppObject` instances are directly selected by id.
-How to update a database after a schema modification?
------------------------------------------------------
-
- It depends on what has been modified in the schema.
+How to update a database after a schema modification ?
+------------------------------------------------------
- * Update of an attribute permissions and properties:
- ``synchronize_eschema('MyEntity')``.
+It depends on what has been modified in the schema.
- * Update of a relation permissions and properties:
- ``synchronize_rschema('MyRelation')``.
+* Update the permissions and properties of an entity or a relation:
+ ``sync_schema_props_perms('MyEntityOrRelation')``.
- * Add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
+* Add an attribute: ``add_attribute('MyEntityType', 'myattr')``.
- * Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
+* Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``.
-How to create an anonymous user?
---------------------------------
+How to create an anonymous user ?
+---------------------------------
- This allows to bypass authentication for your site. In the
- ``all-in-one.conf`` file of your instance, define the anonymous user
- as follows ::
+This allows to bypass authentication for your site. In the
+``all-in-one.conf`` file of your instance, define the anonymous user
+as follows ::
- # login of the CubicWeb user account to use for anonymous user (if you want to
- # allow anonymous)
- anonymous-user=anon
+ # login of the CubicWeb user account to use for anonymous user (if you want to
+ # allow anonymous)
+ anonymous-user=anon
- # password of the CubicWeb user account matching login
- anonymous-password=anon
+ # password of the CubicWeb user account matching login
+ anonymous-password=anon
- You also must ensure that this `anon` user is a registered user of
- the DB backend. If not, you can create through the administation
- interface of your instance by adding a user with the role `guests`.
- This could be the admin account (for development
- purposes, of course).
+You also must ensure that this `anon` user is a registered user of
+the DB backend. If not, you can create through the administation
+interface of your instance by adding a user with the role `guests`.
+This could be the admin account (for development
+purposes, of course).
.. note::
While creating a new instance, you can decide to allow access
@@ -222,94 +223,111 @@
decribed above.
-How to change the application logo?
------------------------------------
+How to change the instance logo ?
+------------------------------------
- There are two ways of changing the logo.
+There are two ways of changing the logo.
- 1. The easiest way to use a different logo is to replace the existing
- ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
- By default all application will look for a ``logo.png`` to be
- rendered in the logo section.
+1. The easiest way to use a different logo is to replace the existing
+ ``logo.png`` in ``myapp/data`` by your prefered icon and refresh.
+ By default all instance will look for a ``logo.png`` to be
+ rendered in the logo section.
- .. image:: ../images/lax-book.06-main-template-logo.en.png
+ .. image:: ../images/lax-book.06-main-template-logo.en.png
- 2. In your cube directory, you can specify which file to use for the logo.
- This is configurable in ``mycube/data/external_resources``: ::
+2. In your cube directory, you can specify which file to use for the logo.
+ This is configurable in ``mycube/data/external_resources``: ::
- LOGO = DATADIR/path/to/mylogo.gif
+ LOGO = DATADIR/path/to/mylogo.gif
- where DATADIR is ``mycubes/data``.
+ where DATADIR is ``mycube/data``.
-How to configure LDAP source?
--------------------------------
+How to configure a LDAP source ?
+--------------------------------
- Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
- Configuring an LDAP source is about declaring that source in your
- instance configuration file such as: ::
+Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``.
+Configuring an LDAP source is about declaring that source in your
+instance configuration file such as: ::
- [ldapuser]
- adapter=ldapuser
- # ldap host
- host=myhost
- # base DN to lookup for usres
- user-base-dn=ou=People,dc=mydomain,dc=fr
- # user search scope
- user-scope=ONELEVEL
- # classes of user
- user-classes=top,posixAccount
- # attribute used as login on authentication
- user-login-attr=uid
- # name of a group in which ldap users will be by default
- user-default-group=users
- # map from ldap user attributes to cubicweb attributes
- user-attrs-map=gecos:email,uid:login
+ [ldapuser]
+ adapter=ldapuser
+ # ldap host
+ host=myhost
+ # base DN to lookup for usres
+ user-base-dn=ou=People,dc=mydomain,dc=fr
+ # user search scope
+ user-scope=ONELEVEL
+ # classes of user
+ user-classes=top,posixAccount
+ # attribute used as login on authentication
+ user-login-attr=uid
+ # name of a group in which ldap users will be by default
+ user-default-group=users
+ # map from ldap user attributes to cubicweb attributes
+ user-attrs-map=gecos:email,uid:login
- Any change applied to configuration file requires to restart your
- application.
+Any change applied to configuration file requires to restart your
+instance.
-I get NoSelectableObject exceptions: how do I debug selectors ?
+I get NoSelectableObject exceptions, how do I debug selectors ?
---------------------------------------------------------------
- You just need to put the appropriate context manager around view/component
- selection: ::
+You just need to put the appropriate context manager around view/component
+selection (one standard place in in vreg.py):
+
+.. sourcecode:: python
+
+ def possible_objects(self, registry, *args, **kwargs):
+ """return an iterator on possible objects in a registry for this result set
- from cubicweb.common.selectors import traced_selection
- with traced_selection():
- comp = self.vreg.select_object('contentnavigation', 'wfhistory',
- self.req, rset, context='navcontentbottom')
+ actions returned are classes, not instances
+ """
+ from cubicweb.selectors import traced_selection
+ with traced_selection():
+ for vobjects in self.registry(registry).values():
+ try:
+ yield self.select(vobjects, *args, **kwargs)
+ except NoSelectableObject:
+ continue
- This will yield additional WARNINGs, like this: ::
+Don't forget the 'from __future__ import with_statement' at the module
+top-level.
+
+This will yield additional WARNINGs, like this::
2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
-How to format an entity date attribute?
----------------------------------------
+How to format an entity date attribute ?
+----------------------------------------
- If your schema has an attribute of type Date or Datetime, you might
- want to format it. First, you should define your preferred format using
- the site configuration panel ``http://appurl/view?vid=systempropertiesform``
- and then set ``ui.date`` and/or ``ui.datetime``.
- Then in the view code, use::
-
+If your schema has an attribute of type Date or Datetime, you might
+want to format it. First, you should define your preferred format using
+the site configuration panel ``http://appurl/view?vid=systempropertiesform``
+and then set ``ui.date`` and/or ``ui.datetime``.
+Then in the view code, use:
+
+.. sourcecode:: python
+
self.format_date(entity.date_attribute)
Can PostgreSQL and CubicWeb authentication work with kerberos ?
----------------------------------------------------------------
- If you have postgresql set up to accept kerberos authentication, you can set
- the db-host, db-name and db-user parameters in the `sources` configuration
- file while leaving the password blank. It should be enough for your instance
- to connect to postgresql with a kerberos ticket.
+If you have PostgreSQL set up to accept kerberos authentication, you can set
+the db-host, db-name and db-user parameters in the `sources` configuration
+file while leaving the password blank. It should be enough for your
+instance to connect to postgresql with a kerberos ticket.
+
-
-How to load data from a script?
--------------------------------
+How to load data from a script ?
+--------------------------------
- The following script aims at loading data within a script assuming pyro-nsd is
- running and your application is configured with ``pyro-server=yes``, otherwise
- you would not be able to use dbapi. ::
+The following script aims at loading data within a script assuming pyro-nsd is
+running and your instance is configured with ``pyro-server=yes``, otherwise
+you would not be able to use dbapi.
+
+.. sourcecode:: python
from cubicweb import dbapi
@@ -319,36 +337,51 @@
cur.execute('INSERT Blog B: B name %s', name)
cnx.commit()
-What is the CubicWeb datatype corresponding to GAE datastore's UserProperty?
-----------------------------------------------------------------------------
+What is the CubicWeb datatype corresponding to GAE datastore's UserProperty ?
+-----------------------------------------------------------------------------
- If you take a look at your application schema and
- click on "display detailed view of metadata" you will see that there
- is a Euser entity in there. That's the one that is modeling users. The
- thing that corresponds to a UserProperty is a relationship between
- your entity and the Euser entity. As in ::
+If you take a look at your instance schema and
+click on "display detailed view of metadata" you will see that there
+is a Euser entity in there. That's the one that is modeling users. The
+thing that corresponds to a UserProperty is a relationship between
+your entity and the Euser entity. As in:
+
+.. sourcecode:: python
class TodoItem(EntityType):
text = String()
todo_by = SubjectRelation('Euser')
- [XXX check that cw handle users better by
- mapping Google Accounts to local Euser entities automatically]
+[XXX check that cw handle users better by mapping Google Accounts to local Euser
+entities automatically]
-How to reset the password for user joe?
----------------------------------------
+How to reset the password for user joe ?
+----------------------------------------
- You need to generate a new encrypted password::
+If you want to reset the admin password for ``myinstance``, do::
+
+ $ cubicweb-ctl reset-admin-pwd myinstance
+
+You need to generate a new encrypted password::
$ python
>>> from cubicweb.server.utils import crypt_password
>>> crypt_password('joepass')
'qHO8282QN5Utg'
- >>>
+ >>>
- and paste it in the database::
+and paste it in the database::
$ psql mydb
mydb=> update cw_cwuser set cw_upassword='qHO8282QN5Utg' where cw_login='joe';
- UPDATE 1
\ No newline at end of file
+ UPDATE 1
+
+I've just created a user in a group and it doesn't work !
+---------------------------------------------------------
+
+You are probably getting errors such as ::
+
+ remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost
+
+This is because you have to put your user in the "users" group. The user has to be in both groups.
--- a/doc/book/en/annexes/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
--------------------
The following chapters are reference material.
-
+
.. toctree::
:maxdepth: 1
@@ -16,6 +16,7 @@
cubicweb-ctl
rql/index
mercurial
+ depends
(X)HTML tricks to apply
-----------------------
--- a/doc/book/en/annexes/mercurial.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/mercurial.rst Tue Aug 04 15:06:09 2009 +0200
@@ -12,7 +12,7 @@
next, and so on). Locally, we have a repository containing revisions
tree, and a working directory. It is possible
to put in its working directory, one of the versions of its local repository,
-modify and then push it in its repository.
+modify and then push it in its repository.
It is also possible to get revisions from another repository or to export
its own revisions from the local repository to another repository.
@@ -83,7 +83,7 @@
hg incoming ssh://myhost//home/src/repo
-* See what is the revision of the local repository which has been taken out
+* See what is the revision of the local repository which has been taken out
from the working directory and amended::
hg parent
@@ -114,8 +114,8 @@
Installation of the forest extension
````````````````````````````````````
-Set up the forest extension by getting a copy of the sources
-from http://hg.akoha.org/hgforest/ and adding the following
+Set up the forest extension by getting a copy of the sources
+from http://hg.akoha.org/hgforest/ and adding the following
lines to your ``~/.hgrc``: ::
[extensions]
--- a/doc/book/en/annexes/rql/implementation.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/rql/implementation.rst Tue Aug 04 15:06:09 2009 +0200
@@ -53,7 +53,7 @@
relation ::= 'NOT'? VARIABLE R_TYPE COMP_OP? expression
| 'NOT'? R_TYPE VARIABLE 'IN' '(' expression (',' expression)* ')'
-
+
expression ::= var_or_func_or_const (MATH_OP var_or_func_or_const) *
| '(' expression ')'
@@ -83,7 +83,7 @@
Internal representation (syntactic tree)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The tree research does not contain the selected variables
+The tree research does not contain the selected variables
(e.g. there is only what follows "WHERE").
The insertion tree does not contain the variables inserted or relations
@@ -156,7 +156,7 @@
Document class Type <-> Document occurence_of Fiche class Type
Sheet class Type <-> Form collection Collection class Type
-
+
Therefore 1. becomes::
Document X where
--- a/doc/book/en/annexes/rql/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/rql/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -8,5 +8,4 @@
intro
language
- dbapi
implementation
--- a/doc/book/en/annexes/rql/intro.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/rql/intro.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,41 +5,38 @@
Goals of RQL
~~~~~~~~~~~~
-The goal is to have a language emphasizing the way of browsing
-relations. As such, attributes will be regarded as cases of
-special relations (in terms of implementation, the language
-user should see virtually no difference between an attribute and a
+The goal is to have a language emphasizing the way of browsing relations. As
+such, attributes will be regarded as cases of special relations (in terms of
+implementation, the user should see no difference between an attribute and a
relation).
-RQL is inspired by SQL but is the highest level. A knowledge of the
-`CubicWeb` schema defining the application is necessary.
-
Comparison with existing languages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL
```
-RQL builds on the features of SQL but is at a higher level
-(the current implementation of RQL generates SQL). For that it is limited
-to the way of browsing relations and introduces variables.
-The user does not need to know the model underlying SQL, but the `CubicWeb`
-schema defining the application.
+
+RQL may remind of SQL but works at a higher abstraction level (the *CubicWeb*
+framework generates SQL from RQL to fetch data from relation databases). RQL is
+focused on browsing relations. The user needs only to know about the *CubicWeb*
+data model he is querying, but not about the underlying SQL model.
+
+Sparql
+``````
+
+The query language most similar to RQL is SPARQL_, defined by the W3C to serve
+for the semantic web.
Versa
`````
-We should look in more detail, but here are already some ideas for
-the moment ... Versa_ is the language most similar to what we wanted
-to do, but the model underlying data being RDF, there is some
-number of things such as namespaces or handling of the RDF types which
-does not interest us. On the functionality level, Versa_ is very comprehensive
-including through many functions of conversion and basic types manipulation,
-which may need to be guided at one time or another.
-Finally, the syntax is a little esoteric.
-Sparql
-``````
-The query language most similar to RQL is SPARQL_, defined by the W3C to serve
-for the semantic web.
+We should look in more detail, but here are already some ideas for the moment
+... Versa_ is the language most similar to what we wanted to do, but the model
+underlying data being RDF, there is some number of things such as namespaces or
+handling of the RDF types which does not interest us. On the functionality
+level, Versa_ is very comprehensive including through many functions of
+conversion and basic types manipulation, which may need to be guided at one time
+or another. Finally, the syntax is a little esoteric.
The different types of queries
@@ -59,7 +56,7 @@
Delete entities or relationship (`DELETE`)
Remove entities or relations existing in the database.
-
+
.. _Versa: http://uche.ogbuji.net/tech/rdf/versa/
--- a/doc/book/en/annexes/rql/language.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/annexes/rql/language.rst Tue Aug 04 15:06:09 2009 +0200
@@ -67,7 +67,7 @@
of logical operators (see :ref:`PriorityOperators`).
Mathematical Operators
-````````````````````
+```````````````````````
::
+, -, *, /
@@ -81,7 +81,7 @@
* The operator `=` is the default operator.
* The operator `LIKE` equivalent to `~=` can be used with the
- special character `%` in a string to indicate that the chain
+ special character `%` in a string to indicate that the chain
must start or finish by a prefix/suffix:
::
@@ -90,11 +90,11 @@
* The operator `IN` provides a list of possible values:
::
-
+
Any X WHERE X name IN ( 'chauvat', 'fayolle', 'di mascio', 'thenault')
-XXX nico: "A trick <> 'bar'" wouldn't it be more convenient than
+XXX nico: "A trick <> 'bar'" wouldn't it be more convenient than
"NOT A trick 'bar'" ?
.. _PriorityOperators:
@@ -130,7 +130,7 @@
Type of selected variables.
The special type `Any` is equivalent to not specify a type.
:restriction:
- list of conditions to test successively
+ list of conditions to test successively
`V1 relation V2 | <static value>`
:orderterms:
Definition of the selection order: variable or column number followed by
@@ -167,7 +167,7 @@
Identity
````````
-You can use the special relation `identity` in a query to
+You can use the special relation `identity` in a query to
add an identity constraint between two variables. This is equivalent
to ``is`` in python::
@@ -181,23 +181,23 @@
Limit / offset
``````````````
::
-
+
Any P ORDERBY N LIMIT 5 OFFSET 10 WHERE P is Person, P firstname N
Function calls
``````````````
::
-
+
Any UPPER(N) WHERE P firstname N
Functions on string: UPPER, LOWER
-
+
Exists
``````
::
-
+
Any X ORDERBY PN,N
- WHERE X num N, X version_of P, P name PN,
+ WHERE X num N, X version_of P, P name PN,
EXISTS(X in_state S, S name IN ("dev", "ready"))
OR EXISTS(T tags X, T name "priority")
@@ -219,12 +219,12 @@
Any C, P WHERE C is Card, P? documented_by C
Any T,P,V WHERE T is Ticket, T concerns P, T done_in V?
-
-
+
+
Having
``````
::
-
+
Any X GROUPBY X WHERE X knows Y HAVING COUNT(Y) > 10
Subqueries
@@ -232,14 +232,14 @@
::
(Any X WHERE X is Person) UNION (Any X WHERE X is Company)
-
+
DISTINCT Any W, REF
- WITH W, REF BEING
+ WITH W, REF BEING
(
- (Any W, REF WHERE W is Workcase, W ref REF,
+ (Any W, REF WHERE W is Workcase, W ref REF,
W concerned_by D, D name "Logilab")
- UNION
+ UNION
(Any W, REF WHERE W is Workcase, W ref REF, '
W split_into WP, WP name "WP1")
)
@@ -317,7 +317,7 @@
The restriction can define variables used in assignments.
-Caution, if a restriction is specified, the insertion is done for
+Caution, if a restriction is specified, the insertion is done for
*each line result returned by the restriction*.
- *Insert a new person named 'foo'*
@@ -331,7 +331,7 @@
INSERT Person X, Person Y: X name 'foo', Y name 'nice', X friend Y
-- *Insert a new person named 'foo' and a 'friend' relation with an existing
+- *Insert a new person named 'foo' and a 'friend' relation with an existing
person called 'nice'*
::
@@ -350,7 +350,7 @@
SET X name 'bar', X first_name 'original' WHERE X is Person, X name 'foo'
-- *Insert a relation of type 'know' between objects linked by
+- *Insert a relation of type 'know' between objects linked by
the relation of type 'friend'*
::
--- a/doc/book/en/conf.py Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/conf.py Tue Aug 04 15:06:09 2009 +0200
@@ -51,7 +51,7 @@
# The short X.Y version.
version = '0.54'
# The full version, including alpha/beta/rc tags.
-release = '3.2'
+release = '3.4'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
--- a/doc/book/en/development/cubes/available-cubes.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/cubes/available-cubes.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
Available cubes
---------------
-An application is based on several basic cubes. In the set of available
+An instance is based on several basic cubes. In the set of available
basic cubes we can find for example :
Base entity types
--- a/doc/book/en/development/cubes/cc-newcube.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/cubes/cc-newcube.rst Tue Aug 04 15:06:09 2009 +0200
@@ -7,22 +7,21 @@
cubicweb-ctl newcube mycube
- # answer questions
+ # answer questions
hg init moncube
cd mycube
hg add .
hg ci
-If all went well, you should see the cube you just create in the list
-returned by `cubicweb-ctl list` in the section *Available components*,
+If all went well, you should see the cube you just created in the list
+returned by ``cubicweb-ctl list`` in the section *Available components*,
and if it is not the case please refer to :ref:`ConfigurationEnv`.
-To use a cube, you have to list it in the variable ``__use__``
-of the file ``__pkginfo__.py`` of the instance.
-This variable is used for the instance packaging (dependencies
-handled by system utility tools such as APT) and the usable cubes
-at the time the base is created (import_erschema('MyCube') will
-not properly work otherwise).
+To reuse an existing cube, add it to the list named ``__use__`` and defined in
+:file:`__pkginfo__.py`. This variable is used for the instance packaging
+(dependencies handled by system utility tools such as APT) and the usable cubes
+at the time the base is created (import_erschema('MyCube') will not properly
+work otherwise).
.. note::
Please note that if you do not wish to use default directory
@@ -31,12 +30,12 @@
the source code of your cube:
``cubicweb-ctl newcube --directory=/path/to/cubes/library cube_name``
-
+
Usage of :command:`cubicweb-ctl liveserver`
-------------------------------------------
To quickly test a new cube, you can also use the `liveserver` command for cubicweb-ctl
-which allows to create an instance in memory (using an SQLite database by
+which allows to create an instance in memory (using an SQLite database by
default) and make it accessible through a web server ::
cubicweb-ctl live-server mycube
--- a/doc/book/en/development/cubes/layout.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/cubes/layout.rst Tue Aug 04 15:06:09 2009 +0200
@@ -80,7 +80,7 @@
* ``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)
+* ``test`` contains tests related to the cube (not installed)
* ``i18n`` contains message catalogs for supported languages (server side and
web interface)
* ``data`` contains data files for static content (images, css, javascripts)
--- a/doc/book/en/development/datamodel/baseschema.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/datamodel/baseschema.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,9 +1,9 @@
-Pre-defined schemas in the library
-----------------------------------
+Pre-defined entities in the library
+-----------------------------------
The library defines a set of entity schemas that are required by the system
-or commonly used in `CubicWeb` applications.
+or commonly used in *CubicWeb* instances.
Entity types used to store the schema
@@ -18,21 +18,21 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* `CWUser`, system users
* `CWGroup`, users groups
-* `CWPermission`, used to configure the security of the application
+* `CWPermission`, used to configure the security of the instance
Entity types used to manage workflows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* `State`, workflow state
* `Transition`, workflow transition
-* `TrInfo`, record of a transition trafic for an entity
+* `TrInfo`, record of a transition trafic for an entity
Other entity types
~~~~~~~~~~~~~~~~~~
-* `CWCache`
-* `CWProperty`, used to configure the application
+* `CWCache`, cache entities used to improve performances
+* `CWProperty`, used to configure the instance
* `EmailAddress`, email address, used by the system to send notifications
to the users and also used by others optionnals schemas
* `Bookmark`, an entity type used to allow a user to customize his links within
- the application
+ the instance
--- a/doc/book/en/development/datamodel/define-workflows.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/datamodel/define-workflows.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,36 +2,36 @@
.. _Workflow:
-An Example: Workflow definition
-===============================
+Define a Workflow
+=================
General
-------
-A workflow describes how certain entities have to evolve between
-different states. Hence we have a set of states, and a "transition graph",
+A workflow describes how certain entities have to evolve between
+different states. Hence we have a set of states, and a "transition graph",
i.e. a list of possible transitions from one state to another state.
-We will define a simple workflow for a blog, with only the following
-two states: `submitted` and `published`. So first, we create a simple
-`CubicWeb` in ten minutes (see :ref:`BlogTenMinutes`).
+We will define a simple workflow for a blog, with only the following
+two states: `submitted` and `published`. So first, we create a simple
+*CubicWeb* in ten minutes (see :ref:`BlogFiveMinutes`).
Set-up a workflow
-----------------
-We want to create a workflow to control the quality of the BlogEntry
-submitted on your application. When a BlogEntry is created by a user
+We want to create a workflow to control the quality of the BlogEntry
+submitted on your instance. When a BlogEntry is created by a user
its state should be `submitted`. To be visible to all, it has to
be in the state `published`. To move it from `submitted` to `published`,
we need a transition that we can call `approve_blogentry`.
A BlogEntry state should not be modifiable by every user.
-So we have to define a group of users, `moderators`, and
+So we have to define a group of users, `moderators`, and
this group will have appropriate permissions to publish a BlogEntry.
There are two ways to create a workflow: from the user interface,
-or by defining it in ``migration/postcreate.py``.
-This script is executed each time a new ``cubicweb-ctl db-init`` is done.
+or by defining it in ``migration/postcreate.py``.
+This script is executed each time a new ``cubicweb-ctl db-init`` is done.
We strongly recommand to create the workflow in ``migration/postcreate.py``
and we will now show you how. Read `Under the hood`_ to understand why.
@@ -60,13 +60,13 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``postcreate.py`` script is executed in a special environment, adding
-several `CubicWeb` primitives that can be used.
+several *CubicWeb* primitives that can be used.
They are all defined in the ``class ServerMigrationHelper``.
We will only discuss the methods we use to create a workflow in this example.
To define our workflow for BlogDemo, please add the following lines
to ``migration/postcreate.py``::
-
+
_ = unicode
moderators = add_entity('CWGroup', name=u"moderators")
@@ -88,12 +88,12 @@
add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
-``add_transition`` expects
+``add_transition`` expects
* as the first argument the name of the
transition, then the entity type on which the transition can be applied,
* then the list of states on which the transition can be trigged,
- * the target state of the transition,
+ * the target state of the transition,
* and the permissions
(e.g. a list of user groups who can apply the transition; the user
has to belong to at least one of the listed group to perform the action).
@@ -106,11 +106,11 @@
Do not forget to add the `_()` in front of all states and transitions names while creating
a workflow so that they will be identified by the i18n catalog scripts.
-In addition to the user group condition, we could have added a RQL condition.
-In this case, the user can only perform the action if
-the two conditions are satisfied.
+In addition to the user group condition, we could have added a RQL condition.
+In this case, the user can only perform the action if
+the two conditions are satisfied.
-If we use a RQL condition on a transition, we can use the following
+If we use a RQL condition on a transition, we can use the following
variables:
* `%(eid)s`, object's eid
@@ -118,12 +118,12 @@
* `%(seid)s`, the object's current state eid
-.. image:: images/lax-book.03-transitions-view.en.png
+.. image:: ../../images/lax-book.03-transitions-view.en.png
You can notice that in the action box of a BlogEntry, the state
is now listed as well as the possible transitions defined by the workflow.
The transitions will only be displayed for users having the right permissions.
-In our example, the transition `approve_blogentry` will only be displayed
+In our example, the transition `approve_blogentry` will only be displayed
for the users belonging to the group `moderators` or `managers`.
@@ -131,7 +131,7 @@
~~~~~~~~~~~~~~
A workflow is a collection of entities of type ``State`` and of type ``Transition``
-which are standard `CubicWeb` entity types.
+which are standard *CubicWeb* entity types.
For instance, the following lines::
submitted = add_state(_('submitted'), 'BlogEntry', initial=True)
@@ -141,7 +141,7 @@
with name 'published'. Whereas::
add_transition(_('approve_blogentry'), 'BlogEntry', (submitted,), published, ('moderators', 'managers'),)
-
+
will create an entity of type ``Transition`` with name 'approve_blogentry' which will
be linked to the ``State`` entities created before.
As a consequence, we could use the administration interface to do these operations.
@@ -151,8 +151,8 @@
Indeed, if you create the states and transitions through the user interface,
next time you initialize the database
-you will have to re-create all the entities.
-The user interface should only be a reference for you to view the states
+you will have to re-create all the entities.
+The user interface should only be a reference for you to view the states
and transitions, but is not the appropriate interface to define your
application workflow.
--- a/doc/book/en/development/datamodel/definition.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/datamodel/definition.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,30 +3,29 @@
Yams *schema*
-------------
-The **schema** is the core piece of a `CubicWeb` application as it defines
+The **schema** is the core piece of a *CubicWeb* instance as it defines
the handled data model. It is based on entity types that are either already
-defined in the `CubicWeb` standard library; or more specific types, that
-`CubicWeb` expects to find in one or more Python files under the directory
+defined in the *CubicWeb* standard library; or more specific types, that
+*CubicWeb* expects to find in one or more Python files under the directory
`schema`.
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
-"<subject entity type> <relation type> <object entity type>".
-A relation type could have been implied if none is related to a
+name with potentially other additionnal properties (see XXXX), whereas a
+*relation definition* is a complete triplet
+"<subject entity type> <relation type> <object entity type>".
+A relation type could have been implied if none is related to a
relation definition of the schema.
-All `CubicWeb` built-in types are available : `String`, `Int`, `Float`,
-`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte`
+All *CubicWeb* built-in types are available : `String`, `Int`, `Float`,
+`Decimal`, `Boolean`, `Date`, `Datetime`, `Time`, `Interval`, `Byte`
and `Password`.
They are implicitely imported (as well as the special the function "_"
for translation :ref:`internationalization`).
-The instance schema of an application is defined on all appobjects by a .schema
-class attribute set on registration. It's an instance of
-:class:`yams.schema.Schema`.
+The instance schema is defined on all appobjects by a .schema class attribute set
+on registration. It's an instance of :class:`yams.schema.Schema`.
Entity type
~~~~~~~~~~~
@@ -67,7 +66,7 @@
RelationSchema.rproperties()
RelationSchema.rproperty(subjtype, objtype, property name)
-* Optional properties for attributes and relations :
+* Optional properties for attributes and relations :
- `description` : a string describing an attribute or a relation. By default
this string will be used in the editing form of the entity, which means
@@ -79,7 +78,7 @@
- `cardinality` : a two character string which specify the cardinality of the
relation. The first character defines the cardinality of the relation on
- the subject, and the second on the object. When a relation can have
+ the subject, and the second on the object. When a relation can have
multiple subjects or objects, the cardinality applies to all,
not on a one-to-one basis (so it must be consistent...). The possible
values are inspired from regular expression syntax :
@@ -92,24 +91,24 @@
- `meta` : boolean indicating that the relation is a meta-relation (false by
default)
-* optional properties for attributes :
+* optional properties for attributes :
- `required` : boolean indicating if the attribute is required (false by default)
- `unique` : boolean indicating if the value of the attribute has to be unique
or not within all entities of the same type (false by default)
- - `indexed` : boolean indicating if an index needs to be created for this
+ - `indexed` : boolean indicating if an index needs to be created for this
attribute in the database (false by default). This is useful only if
you know that you will have to run numerous searches on the value of this
attribute.
- `default` : default value of the attribute. In case of date types, the values
which could be used correspond to the RQL keywords `TODAY` and `NOW`.
-
+
- `vocabulary` : specify static possible values of an attribute
-* optional properties of type `String` :
+* optional properties of type `String` :
- `fulltextindexed` : boolean indicating if the attribute is part of
the full text index (false by default) (*applicable on the type `Byte`
@@ -120,11 +119,11 @@
- `maxsize` : integer providing the maximum size of the string (no limit by default)
-* optional properties for relations :
+* optional properties for relations :
- `composite` : string indicating that the subject (composite == 'subject')
is composed of the objects of the relations. For the opposite case (when
- the object is composed of the subjects of the relation), we just set
+ the object is composed of the subjects of the relation), we just set
'object' as value. The composition implies that when the relation
is deleted (so when the composite is deleted), the composed are also deleted.
@@ -137,7 +136,7 @@
* `SizeConstraint` : allows to specify a minimum and/or maximum size on
string (generic case of `maxsize`)
-* `BoundConstraint` : allows to specify a minimum and/or maximum value on
+* `BoundConstraint` : allows to specify a minimum and/or maximum value on
numeric types
* `UniqueConstraint` : identical to "unique=True"
@@ -146,7 +145,7 @@
* `RQLConstraint` : allows to specify a RQL query that has to be satisfied
by the subject and/or the object of the relation. In this query the variables
- `S` and `O` are reserved for the entities subject and object of the
+ `S` and `O` are reserved for the entities subject and object of the
relation.
* `RQLVocabularyConstraint` : similar to the previous type of constraint except
@@ -160,7 +159,7 @@
The security model
~~~~~~~~~~~~~~~~~~
-The security model of `cubicWeb` is based on `Access Control List`.
+The security model of `cubicWeb` is based on `Access Control List`.
The main principles are:
* users and groups of users
@@ -168,7 +167,7 @@
* permissions (read, update, create, delete)
* permissions are assigned to groups (and not to users)
-For `CubicWeb` in particular:
+For *CubicWeb* in particular:
* we associate rights at the enttities/relations schema level
* for each entity, we distinguish four kind of permissions: read,
@@ -213,13 +212,13 @@
This can only be used for the actions `update` and `delete` of an entity
type.
-It is also possible to use specific groups if they are defined in the precreate
+It is also possible to use specific groups if they are defined in the precreate
of the cube (``migration/precreate.py``).
Use of RQL expression for writing rights
`````````````````````````````````````````
-It is possible to define RQL expression to provide update permission
+It is possible to define RQL expression to provide update permission
(`add`, `delete` and `update`) on relation and entity types.
RQL expression for entity type permission :
@@ -232,13 +231,13 @@
respectively on the current entity (on which the action is verified) and
on the user who send the request
-* it is possible to use, in this expression, a special relation
- "has_<ACTION>_permission" where the subject is the user and the
+* it is possible to use, in this expression, a special relation
+ "has_<ACTION>_permission" where the subject is the user and the
object is a any variable, meaning that the user needs to have
permission to execute the action <ACTION> on the entities related
- to this variable
+ to this variable
-For RQL expressions on a relation type, the principles are the same except
+For RQL expressions on a relation type, the principles are the same except
for the following :
* you have to use the class `RQLExpression` in the case of a non-final relation
@@ -248,7 +247,7 @@
which the action is being verified) and the user who executed the query
* we can also defined rights on attributes of an entity (non-final relation),
- knowing that :
+ knowing that :
- to defines RQL expression, we have to use the class `RQLExpression`
in which X represents the entity the attribute belongs to
@@ -260,17 +259,17 @@
Potentially, the use of an RQL expression to add an entity or a relation
can cause problems for the user interface, because if the expression uses
- the entity or the relation to create, then we are not able to verify the
+ the entity or the relation to create, then we are not able to verify the
permissions before we actually add the entity (please note that this is
not a problem for the RQL server at all, because the permissions checks are
- done after the creation). In such case, the permission check methods
- (check_perm, has_perm) can indicate that the user is not allowed to create
- this entity but can obtain the permission.
+ done after the creation). In such case, the permission check methods
+ (check_perm, has_perm) can indicate that the user is not allowed to create
+ this entity but can obtain the permission.
To compensate this problem, it is usually necessary, for such case,
to use an action that reflects the schema permissions but which enables
to check properly the permissions so that it would show up if necessary.
-
+
Use of RQL expression for reading rights
````````````````````````````````````````
@@ -315,20 +314,20 @@
`Company` through the semantic `works_for`.
The name of the Python attribute corresponds to the name of the attribute
-or the relation in `CubicWeb` application.
+or the relation in *CubicWeb* application.
An attribute is defined in the schema as follows::
-
+
attr_name = attr_type(properties*)
where `attr_type` is one of the type listed above and `properties` is
a list of the attribute needs to statisfy (see :ref:`properties`
-for more details).
+for more details).
* relations can be defined by using `ObjectRelation` or `SubjectRelation`.
The first argument of `SubjectRelation` or `ObjectRelation` gives respectively
- the object/subject entity type of the relation. This could be :
+ the object/subject entity type of the relation. This could be :
* a string corresponding to an entity type
@@ -337,7 +336,7 @@
* special string such as follows :
- "**" : all types of entities
- - "*" : all types of non-meta entities
+ - "*" : all types of non-meta entities
- "@" : all types of meta entities but not system entities (e.g. used for
the basic schema description)
@@ -356,9 +355,9 @@
A relation is defined by a Python class heriting `RelationType`. The name
of the class corresponds to the name of the type. The class then contains
-a description of the properties of this type of relation, and could as well
+a description of the properties of this type of relation, and could as well
contain a string for the subject and a string for the object. This allows to create
-new definition of associated relations, (so that the class can have the
+new definition of associated relations, (so that the class can have the
definition properties from the relation) for example ::
class locked_by(RelationType):
--- a/doc/book/en/development/datamodel/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/datamodel/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -9,5 +9,5 @@
definition
metadata
baseschema
-
-.. define-workflows
+ define-workflows
+ inheritance
--- a/doc/book/en/development/datamodel/inheritance.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/datamodel/inheritance.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,1 +1,8 @@
-XXX WRITME
\ No newline at end of file
+
+Inheritance
+-----------
+
+When describing a data model, entities can inherit from other entities as is
+common in object-oriented programming.
+
+XXX WRITME
--- a/doc/book/en/development/devcore/appobject.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devcore/appobject.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,5 +1,5 @@
-
+
The `AppObject` class
~~~~~~~~~~~~~~~~~~~~~
@@ -19,9 +19,9 @@
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
+* `vreg`, the `vregistry` of the instance
+* `schema`, the instance schema
+* `config`, the instance configuration
We also find on instances, the following attributes:
@@ -36,7 +36,7 @@
can be specified through the special parameter `method` (the connection
is theoretically done automatically :).
- * `datadir_url()`, returns the directory of the application data
+ * `datadir_url()`, returns the directory of the instance data
(contains static files such as images, css, js...)
* `base_url()`, shortcut to `req.base_url()`
@@ -57,9 +57,9 @@
:Data formatting:
* `format_date(date, date_format=None, time=False)` returns a string for a
- mx date time according to application's configuration
+ mx date time according to instance's configuration
* `format_time(time)` returns a string for a mx date time according to
- application's configuration
+ instance's configuration
:And more...:
--- a/doc/book/en/development/devcore/dbapi.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devcore/dbapi.rst Tue Aug 04 15:06:09 2009 +0200
@@ -20,7 +20,7 @@
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
+the *CubicWeb* framework as it determines the end of the transaction depending
on the query execution success.
.. note::
--- a/doc/book/en/development/devcore/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devcore/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,12 +5,11 @@
:maxdepth: 1
vreg.rst
- selection.rst
appobject.rst
selectors.rst
dbapi.rst
-
+
:mod:`Configuration <cubicweb.cwconfig>`
----------------------------------------
--- a/doc/book/en/development/devcore/selectors.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devcore/selectors.rst Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
essential part of the construction of well behaved cubes.
-`CubicWeb` provides its own set of selectors that you can use and here is a
+*CubicWeb* provides its own set of selectors that you can use and here is a
description of some of the most common used:
Of course you will write your own set of selectors as you get familiar with the
--- a/doc/book/en/development/devcore/vreg.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devcore/vreg.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,3 +1,5 @@
+.. -*- coding: utf-8 -*-
+
The VRegistry
--------------
@@ -7,23 +9,25 @@
Details of the recording process
````````````````````````````````
-* par défaut on enregistre automatiquement tout les objets
+XXX this part needs to be updated and checked
+
+* by default all objects are registered automatically
-* si certains objets doivent remplacer d'autres objets ou être inclus
- conditionnellement,
- - enregistrement explicite en définissant la fonction `registration_callback(vreg)`
- - appel des méthodes d'enregistrement des objets sur le vreg
+* if some objects have to replace other objects or be included only if a
+ condition is true,
+ - explicitly register the object by defining `registration_callback(vreg)`
+ - call registration methods on objects listed in the vreg registry
+
.. note::
Once the function `registration_callback(vreg)` is implemented, all the objects
- need to be explicitly registered as it disables the automatic object registering.
-
-* suppression de l'ancien système quand il ne restera plus de réference au
- module registerers dans le code des cubes existants.
+ have to be explicitly registered as it disables the automatic object registering.
+* the old registration mechanism will be removed when there will be no reference
+ left to the registerers module in cubicweb and the library of cubes.
-Examples
+Examples:
-.. code-block:: python
+.. sourcecode:: python
# web/views/basecomponents.py
def registration_callback(vreg):
@@ -40,7 +44,8 @@
API d'enregistrement des objets
```````````````````````````````
-.. code-block:: python
+
+.. sourcecode:: python
register(obj, registryname=None, oid=None, clear=False)
@@ -58,7 +63,8 @@
Defining selectors
``````````````````
-The object's selector is defined by itsd `__select__` class attribute.
+
+The object's selector is defined by its `__select__` class attribute.
When two selectors are combined using the `&` operator (former `chainall`), it
means that both should return a positive score. On success, the sum of scores is returned.
@@ -86,18 +92,20 @@
Example
````````
-Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe
-vers les entrées de ce blog, non vers l'entité blog elle-même.
+XXX this part needs to be translated
-L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité
+Le but final : quand on est sur un Blog, on veut que le lien rss de celui-ci pointe
+vers les entrées de ce blog, non vers l'entité blog elle-même.
+
+L'idée générale pour résoudre ça : on définit une méthode sur les classes d'entité
qui renvoie l'url du flux rss pour l'entité en question. Avec une implémentation
-par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce
+par défaut sur AnyEntity et une implémentation particulière sur Blog qui fera ce
qu'on veut.
-La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient
+La limitation : on est embêté dans le cas ou par ex. on a un result set qui contient
plusieurs entités Blog (ou autre chose), car on ne sait pas sur quelle entité appeler
la méthode sus-citée. Dans ce cas, on va conserver le comportement actuel (eg appel
-à limited_rql)
+Ã limited_rql)
Donc : on veut deux cas ici, l'un pour un rset qui contient une et une seule entité,
l'autre pour un rset qui contient plusieurs entité.
@@ -116,7 +124,7 @@
pour voir le détail)
* non_final_entity, qui filtre sur des rset contenant une liste d'entité non finale
-ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique
+ça correspond donc à notre 2eme cas. Reste à fournir un composant plus spécifique
pour le 1er cas ::
class EntityRSSIconBox(RSSIconBox):
@@ -130,11 +138,11 @@
non sélectionnable). Donc ici, sur un rset avec plusieurs entités, onelinerset_selector
rendra la classe EntityRSSIconBox non sélectionnable, et on obtiendra bien la
classe RSSIconBox. Pour un rset avec une entité, la classe EntityRSSIconBox aura un
-score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée.
+score supérieur à RSSIconBox et c'est donc bien elle qui sera sélectionnée.
-Voili voilou, il reste donc pour finir tout ça :
+Voili voilou, il reste donc pour finir tout ça :
-* à définir le contenu de la méthode call de EntityRSSIconBox
+* Ã définir le contenu de la méthode call de EntityRSSIconBox
* fournir l'implémentation par défaut de la méthode renvoyant l'url du flux rss sur
AnyEntity
* surcharger cette methode dans blog.Blog
@@ -144,8 +152,8 @@
```````````````````````
Il faut utiliser les sélecteurs pour faire des choses différentes en
-fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la
-nature de `self.rset` dans un objet, il faut très sérieusement se
+fonction de ce qu'on a en entrée. Dès qu'on a un "if" qui teste la
+nature de `self.rset` dans un objet, il faut très sérieusement se
poser la question s'il ne vaut pas mieux avoir deux objets différent
avec des sélecteurs approprié.
--- a/doc/book/en/development/devrepo/hooks.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devrepo/hooks.rst Tue Aug 04 15:06:09 2009 +0200
@@ -10,8 +10,8 @@
*Hooks* are executed before or after updating an entity or a relation in the
repository.
-Their prototypes are as follows:
-
+Their prototypes are as follows:
+
* after_add_entity (session, entity)
* after_update_entity (session, entity)
* after_delete_entity (session, eid)
@@ -23,10 +23,10 @@
* after_delete_relation (session, fromeid, rtype, toeid)
* before_add_relation (session, fromeid, rtype, toeid)
* before_delete_relation (session, fromeid, rtype, toeid)
-
+
* server_startup
* server_shutdown
-
+
* session_open
* session_close
--- a/doc/book/en/development/devrepo/sessions.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devrepo/sessions.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,7 +3,25 @@
Sessions
========
+There are three kinds of sessions.
+
+* `user sessions` are the most common: they are related to users and
+ carry security checks coming with user credentials
+
+* `super sessions` are children of ordinary user sessions and allow to
+ bypass security checks (they are created by calling unsafe_execute
+ on a user session); this is often convenient in hooks which may
+ touch data that is not directly updatable by users
+
+* `internal sessions` have all the powers; they are also used in only a
+ few situations where you don't already have an adequate session at
+ hand, like: user authentication, data synchronisation in
+ multi-source contexts
+
+.. note::
+ Do not confuse the session type with their connection mode, for
+ instance : 'in memory' or 'pyro'.
+
[WRITE ME]
* authentication and management of sessions
-
--- a/doc/book/en/development/devweb/form.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devweb/form.rst Tue Aug 04 15:06:09 2009 +0200
@@ -9,9 +9,9 @@
* `vocabulary(rtype, x='subject', limit=None)`, called by the
editing views, it returns a list of couples (label, eid) of entities
that could be related to the entity by the relation `rtype`
- * `subject_relation_vocabulary(rtype, limit=None)`, called internally
+ * `subject_relation_vocabulary(rtype, limit=None)`, called internally
by `vocabulary` in the case of a subject relation
- * `object_relation_vocabulary(rtype, limit=None)`, called internally
+ * `object_relation_vocabulary(rtype, limit=None)`, called internally
by `vocabulary` in the case of an object relation
* `relation_vocabulary(rtype, targettype, x, limit=None)`, called
internally by `subject_relation_vocabulary` and `object_relation_vocabulary`
--- a/doc/book/en/development/devweb/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devweb/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,7 +1,7 @@
Web development
===============
-In this chapter, we will core api for web development in the CubicWeb framework.
+In this chapter, we will describe the core api for web development in the *CubicWeb* framework.
.. toctree::
:maxdepth: 1
--- a/doc/book/en/development/devweb/internationalization.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devweb/internationalization.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,12 +1,12 @@
.. -*- coding: utf-8 -*-
-.. _internationalisation:
+.. _internationalization:
-Internationalisation
+Internationalization
---------------------
-Cubicweb fully supports the internalization of it's content and interface.
+Cubicweb fully supports the internalization of its content and interface.
Cubicweb's interface internationalization is based on the translation project `GNU gettext`_.
@@ -16,7 +16,7 @@
* in your Python code and cubicweb-tal templates : mark translatable strings
-* in your application : handle the translation catalog
+* in your instance : handle the translation catalog
String internationalization
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -66,16 +66,18 @@
.. note::
We dont need to mark the translation strings of entities/relations
- used by a particular application's schema as they are generated
+ used by a particular instance's schema as they are generated
automatically.
+If you need to add messages on top of those that can be found in the source,
+you can create a file named `i18n/static-messages.pot`.
Handle the translation catalog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Once the internationalization is done in your application's code, you need
-to populate and update the translation catalog. Cubicweb provides the
-following commands for this purpose:
+Once the internationalization is done in your code, you need to populate and
+update the translation catalog. Cubicweb provides the following commands for this
+purpose:
* `i18ncubicweb` updates Cubicweb framework's translation
@@ -83,28 +85,28 @@
need to use this command.
* `i18ncube` updates the translation catalogs of *one particular
- component* (or of all components). After this command is
+ cube* (or of all cubes). After this command is
executed you must update the translation files *.po* in the "i18n"
directory of your template. This command will of course not remove
existing translations still in use.
* `i18ninstance` recompile the translation catalogs of *one particular
instance* (or of all instances) after the translation catalogs of
- its components have been updated. This command is automatically
+ its cubes have been updated. This command is automatically
called every time you create or update your instance. The compiled
catalogs (*.mo*) are stored in the i18n/<lang>/LC_MESSAGES of
- application where `lang` is the language identifier ('en' or 'fr'
+ instance where `lang` is the language identifier ('en' or 'fr'
for exemple).
Example
```````
-You have added and/or modified some translation strings in your application
-(after creating a new view or modifying the application's schema for exemple).
+You have added and/or modified some translation strings in your cube
+(after creating a new view or modifying the cube's schema for exemple).
To update the translation catalogs you need to do:
-1. `cubicweb-ctl i18ncube <component>`
-2. Edit the <component>/xxx.po files and add missing translations (empty `msgstr`)
+1. `cubicweb-ctl i18ncube <cube>`
+2. Edit the <cube>/i18n/xxx.po files and add missing translations (empty `msgstr`)
3. `hg ci -m "updated i18n catalogs"`
-4. `cubicweb-ctl i18ninstance <myapplication>`
+4. `cubicweb-ctl i18ninstance <myinstance>`
--- a/doc/book/en/development/devweb/views.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/devweb/views.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,13 +1,16 @@
+
+.. _Views:
+
Views
-----
This chapter aims to describe the concept of a `view` used all along
the development of a web application and how it has been implemented
-in `CubicWeb`.
+in *CubicWeb*.
We'll start with a description of the interface providing you with a basic
understanding of the classes and methods available, then detail the view
-selection principle which makes `CubicWeb` web interface very flexible.
+selection principle which makes *CubicWeb* web interface very flexible.
A `View` is an object applied to another object such as an entity.
@@ -42,19 +45,19 @@
* `dispatch(**context)`, render the view by calling `call` or
`cell_call` depending on the given parameters
-* `call(**kwargs)`, call the view for a complete result set or null (default
+* `call(**kwargs)`, call the view for a complete result set or null (default
implementation calls `cell_call()` on each cell of the result set)
* `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set
* `url()`, returns the URL enabling us to get the view with the current
- result set
-* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier
+ result set
+* `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier
`__vid` on the given result set. It is possible to give a view identifier
of fallback that will be used if the view requested is not applicable to the
result set
-
+
* `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except
the flow is automatically passed in the parameters
-
+
* `html_headers()`, returns a list of HTML headers to set by the main template
* `page_title()`, returns the title to use in the HTML header `title`
@@ -67,17 +70,16 @@
* `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
* `StartupView`, start view that does not require a result set to apply to
-* `AnyRsetView`, view applied to any result set
+* `AnyRsetView`, view applied to any result set
* `EmptyRsetView`, view applied to an empty result set
-Examples of views class
+Examples of views class
-----------------------
- Using `templatable`, `content_type` and HTTP cache configuration
-.. code-block:: python
-
+.. sourcecode:: python
class RSSView(XMLView):
id = 'rss'
@@ -86,13 +88,11 @@
content_type = 'text/xml'
http_cache_manager = MaxAgeHTTPCacheManager
cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
- Using custom selector
-.. code-block:: python
-
+.. sourcecode:: python
class SearchForAssociationView(EntityView):
"""view called by the edition view when the user asks
@@ -111,18 +111,18 @@
We'll show you now an example of a ``primary`` view and how to customize it.
-If you want to change the way a ``BlogEntry`` is displayed, just override
-the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` ::
+If you want to change the way a ``BlogEntry`` is displayed, just override
+the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``:
-.. code-block:: python
+.. sourcecode:: python
from cubicweb.view import EntityView
from cubicweb.selectors import implements
-
+
class BlogEntryPrimaryView(EntityView):
id = 'primary'
__select__ =implements('Blog')
-
+
def cell_call(self, row, col):
entity = self.entity(row, col)
self.w(u'<h1>%s</h1>' % entity.title)
@@ -131,7 +131,7 @@
self.w(u'<p>%s</p>' % entity.text)
The above source code defines a new primary view (`line 03`) for
-``BlogEntry`` (`line 05`).
+``BlogEntry`` (`line 05`).
Since views are applied to result sets which can be tables of
data, we have to recover the entity from its (row,col)-coordinates (`line 08`).
@@ -148,7 +148,7 @@
Let us now improve the primary view of a blog
-.. code-block:: python
+.. sourcecode:: python
class BlogPrimaryView(EntityView):
id = 'primary'
@@ -170,9 +170,9 @@
about the schema and infer that such entities have to be of the
``BlogEntry`` kind and retrieves them.
-The request returns a selection of data called a result set. At
+The request returns a selection of data called a result set. At
`line 10` the view 'primary' is applied to this result set to output
-HTML.
+HTML.
**This is to be compared to interfaces and protocols in object-oriented
languages. Applying a given view called 'a_view' to all the entities
@@ -186,7 +186,7 @@
:alt: a blog and all its entries
**Before we move forward, remember that the selection/view principle is
-at the core of `CubicWeb`. Everywhere in the engine, data is requested
+at the core of *CubicWeb*. Everywhere in the engine, data is requested
using the RQL language, then HTML/XML/text/PNG is output by applying a
view to the result set returned by the query. That is where most of the
flexibility comes from.**
@@ -202,7 +202,7 @@
* create view "blogentry table" with title, publish_date, category
-We will show that by default the view that displays
+We will show that by default the view that displays
"Any E,D,C WHERE E publish_date D, E category C" is the table view.
Of course, the same can be obtained by calling
self.wview('table',rset)
@@ -215,9 +215,9 @@
[FILLME]
-
XML views, binaries...
----------------------
+
For views generating other formats than HTML (an image generated dynamically
for example), and which can not simply be included in the HTML page generated
by the main template (see above), you have to:
@@ -226,7 +226,7 @@
* set, through the attribute `content_type` of the class, the MIME type generated
by the view to `application/octet-stream`
-For views dedicated to binary content creation (like dynamically generated
+For views dedicated to binary content creation (like dynamically generated
images), we have to set the attribute `binary` of the class to `True` (which
implies that `templatable == False`, so that the attribute `w` of the view could be
replaced by a binary flow instead of unicode).
--- a/doc/book/en/development/entityclasses/data-as-objects.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/entityclasses/data-as-objects.rst Tue Aug 04 15:06:09 2009 +0200
@@ -19,18 +19,18 @@
* `absolute_url(**kwargs)`, returns an absolute URL to access the primary view
of an entity
-
+
* `rest_path()`, returns a relative REST URL to get the entity
* `format(attr)`, returns the format (MIME type) of the field given un parameter
- * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`,
+ * `printable_value(attr, value=_marker, attrtype=None, format='text/html')`,
returns a string enabling the display of an attribute value in a given format
(the value is automatically recovered if necessary)
:Data handling:
- * `as_rset()`, converts the entity into an equivalent result set simulating the
+ * `as_rset()`, converts the entity into an equivalent result set simulating the
request `Any X WHERE X eid _eid_`
* `complete(skip_bytes=True)`, executes a request that recovers in one time
@@ -57,21 +57,21 @@
* `delete()` allows to delete the entity
-
+
Tne :class:`AnyEntity` class
----------------------------
-
-To provide a specific behavior for each entity, we have to define
-a class inheriting from `cubicweb.entities.AnyEntity`. In general, we
-define this class in a module of `mycube.entities` package of an application
-so that it will be available on both server and client side.
-The class `AnyEntity` is loaded dynamically from the class `Entity`
+To provide a specific behavior for each entity, we have to define a class
+inheriting from `cubicweb.entities.AnyEntity`. In general, we define this class
+in `mycube.entities` module (or in a submodule if we want to split code among
+multiple files) so that it will be available on both server and client side.
+
+The class `AnyEntity` is loaded dynamically from the class `Entity`
(`cubciweb.entity`). We define a sub-class to add methods or to
specialize the handling of a given entity type
The methods defined for `AnyEntity` or `Entity` are the following ones:
-
+
:Standard meta-data (Dublin Core):
* `dc_title()`, returns a unicode string corresponding to the meta-data
@@ -81,15 +81,15 @@
* `dc_long_title()`, same as dc_title but can return a more
detailled title
- * `dc_description(format='text/plain')`, returns a unicode string
+ * `dc_description(format='text/plain')`, returns a unicode string
corresponding to the meta-data `Description` (look for a description
attribute by default)
- * `dc_authors()`, returns a unicode string corresponding to the meta-data
+ * `dc_authors()`, returns a unicode string corresponding to the meta-data
`Authors` (owners by default)
- * `dc_date(date_format=None)`, returns a unicode string corresponding to
+ * `dc_date(date_format=None)`, returns a unicode string corresponding to
the meta-data `Date` (update date by default)
- * `dc_type(form='')`, returns a string to display the entity type by
+ * `dc_type(form='')`, returns a string to display the entity type by
specifying the preferred form (`plural` for a plural form)
--- a/doc/book/en/development/entityclasses/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/entityclasses/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
===============
In this chapter, we will introduce the objects that are used to handle
-the data stored in the database.
+the logic associated to the data stored in the database.
.. toctree::
:maxdepth: 1
@@ -10,4 +10,4 @@
data-as-objects
load-sort
interfaces
- more
\ No newline at end of file
+ more
--- a/doc/book/en/development/entityclasses/interfaces.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/entityclasses/interfaces.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,16 +1,19 @@
Interfaces
----------
+Same thing as object-oriented programming interfaces.
+
XXX how to define a cw interface
Declaration of interfaces implemented by a class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
XXX __implements__
Interfaces defined in the library
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. automodule:: cubicweb.interface
- :members:
-`````````````
+automodule:: cubicweb.interface :members:
+
+
--- a/doc/book/en/development/entityclasses/load-sort.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/entityclasses/load-sort.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,26 +1,28 @@
+
+.. _FetchAttrs:
Loaded attributes and default sorting management
````````````````````````````````````````````````
-* The class attribute `fetch_attrs` allows to defined in an entity class
- a list of names of attributes or relations that should be automatically
- loaded when we recover the entities of this type. In the case of relations,
+* The class attribute `fetch_attrs` allows to define in an entity class a list
+ of names of attributes or relations that should be automatically loaded when
+ entities of this type are fetched from the database. In the case of relations,
we are limited to *subject of cardinality `?` or `1`* relations.
* The class method `fetch_order(attr, var)` expects an attribute (or relation)
name as a parameter and a variable name, and it should return a string
- to use in the requirement `ORDER BY` of an RQL query to automatically
+ to use in the requirement `ORDERBY` of an RQL query to automatically
sort the list of entities of such type according to this attribute, or
`None` if we do not want to sort on the attribute given in the parameter.
By default, the entities are sorted according to their creation date.
-* The class method `fetch_unrelated_order(attr, var)` is similar to the
+* The class method `fetch_unrelated_order(attr, var)` is similar to the
method `fetch_order` except that it is essentially used to control
- the sorting of drop-down lists enabling relations creation in
+ the sorting of drop-down lists enabling relations creation in
the editing view of an entity.
The function `fetch_config(fetchattrs, mainattr=None)` simplifies the
-definition of the attributes to load and the sorting by returning a
+definition of the attributes to load and the sorting by returning a
list of attributes to pre-load (considering automatically the attributes
of `AnyEntity`) and a sorting function based on the main attribute
(the second parameter if specified otherwisethe first attribute from
--- a/doc/book/en/development/entityclasses/more.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/entityclasses/more.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,12 +1,14 @@
Navigation on deletion
----------------------
+
XXX after_deletion_path, pre_web_edit
Controlling output url
----------------------
-XXX
+-----------------------
+
+XXX write me
Controling notification references
----------------------------------
-XXX
+XXX write me
--- a/doc/book/en/development/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -4,7 +4,7 @@
Part II - Development
---------------------
-This part is about developing web applications with the `CubicWeb` framework.
+This part is about developing web applications with the *CubicWeb* framework.
.. toctree::
:maxdepth: 2
@@ -18,3 +18,4 @@
testing/index
migration/index
webstdlib/index
+ profiling/index
--- a/doc/book/en/development/migration/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/migration/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,29 +5,29 @@
Migration
=========
-One of the main concept in `CubicWeb` is to create incremental applications.
-For this purpose, multiple actions are provided to facilitate the improvement
-of an application, and in particular to handle the changes to be applied
-to the data model, without loosing existing data.
+One of the main design goals of *CubicWeb* was to support iterative and agile
+development. For this purpose, multiple actions are provided to facilitate the
+improvement of an instance, and in particular to handle the changes to be
+applied to the data model, without loosing existing data.
-The current version of an application model is provided in the file
+The current version of a cube (and of cubicweb itself) is provided in the file
`__pkginfo__.py` as a tuple of 3 integers.
Migration scripts management
----------------------------
Migration scripts has to be located in the directory `migration` of your
-application and named accordingly:
+cube and named accordingly:
::
<version n° X.Y.Z>[_<description>]_<mode>.py
-in which :
+in which :
* X.Y.Z is the model version number to which the script enables to migrate.
-* *mode* (between the last "_" and the extension ".py") is used for
+* *mode* (between the last "_" and the extension ".py") is used for
distributed installation. It indicates to which part
of the application (RQL server, web server) the script applies.
Its value could be :
@@ -44,10 +44,10 @@
(schema and data migration for example).
Again in the directory `migration`, the file `depends.map` allows to indicate
-that for the migration to a particular model version, you always have to first
-migrate to a particular `CubicWeb` version. This file can contain comments (lines
+that for the migration to a particular model version, you always have to first
+migrate to a particular *CubicWeb* version. This file can contain comments (lines
starting by `#`) and a dependancy is listed as follows: ::
-
+
<model version n° X.Y.Z> : <cubicweb version n° X.Y.Z>
For example: ::
@@ -65,13 +65,10 @@
* `config`, instance configuration
* `interactive_mode`, boolean indicating that the script is executed in
- an interactive mode or not
-
-* `appltemplversion`, application model version of the instance
+ an interactive mode or not
-* `templversion`, installed application model version
-
-* `cubicwebversion`, installed cubicweb version
+* `versions_map`, dictionary of migrated versions (key are cubes
+ names, including 'cubicweb', values are (from version, to version)
* `confirm(question)`, function asking the user and returning true
if the user answers yes, false otherwise (always returns true in
@@ -84,41 +81,39 @@
* `checkpoint`, request confirming and executing a "commit" at checking point
-* `repo_schema`, instance persisting schema (e.g. instance schema of the
- current migration)
+* `schema`, instance schema (readen from the database)
-* `newschema`, installed schema on the file system (e.g. schema of
+* `fsschema`, installed schema on the file system (e.g. schema of
the updated model and cubicweb)
-* `sqlcursor`, SQL cursor for very rare cases where it is really
- necessary or beneficial to go through the sql
-
* `repo`, repository object
-
+* `session`, repository session object
+
+
Schema migration
----------------
The following functions for schema migration are available in `repository`
scripts:
* `add_attribute(etype, attrname, attrtype=None, commit=True)`, adds a new
- attribute to an existing entity type. If the attribute type is not specified,
+ attribute to an existing entity type. If the attribute type is not specified,
then it is extracted from the updated schema.
-
+
* `drop_attribute(etype, attrname, commit=True)`, removes an attribute from an
existing entity type.
* `rename_attribute(etype, oldname, newname, commit=True)`, renames an attribute
-
+
* `add_entity_type(etype, auto=True, commit=True)`, adds a new entity type.
If `auto` is True, all the relations using this entity type and having a known
entity type on the other hand will automatically be added.
-* `drop_entity_type(etype, commit=True)`, removes an entity type and all the
+* `drop_entity_type(etype, commit=True)`, removes an entity type and all the
relations using it.
* `rename_entity_type(oldname, newname, commit=True)`, renames an entity type
-
+
* `add_relation_type(rtype, addrdef=True, commit=True)`, adds a new relation
type. If `addrdef` is True, all the relations definitions of this type will
be added.
@@ -134,19 +129,12 @@
* `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes
a relation definition.
-* `synchronize_permissions(ertype, commit=True)`, synchronizes permissions on
- an entity type or relation type.
-
-* `synchronize_rschema(rtype, commit=True)`, synchronizes properties and permissions
- on a relation type.
-
-* `synchronize_eschema(etype, commit=True)`, synchronizes properties and persmissions
- on an entity type.
-
-* `synchronize_schema(commit=True)`, synchronizes the persisting schema with the
- updated schema (but without adding or removing new entity types, relations types
- or even relations definitions).
-
+* `sync_schema_props_perms(ertype=None, syncperms=True, syncprops=True, syncrdefs=True, commit=True)`,
+ synchronizes properties and/or permissions on:
+ - the whole schema if ertype is None
+ - an entity or relation type schema if ertype is a string
+ - a relation definition if ertype is a 3-uple (subject, relation, object)
+
* `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes
properties of a relation definition by using the named parameters of the properties
to change.
@@ -162,7 +150,7 @@
The following functions for data migration are available in `repository` scripts:
* `rql(rql, kwargs=None, cachekey=None, ask_confirm=True)`, executes an arbitrary RQL
- query, either to interrogate or update. A result set object is returned.
+ query, either to interrogate or update. A result set object is returned.
* `add_entity(etype, *args, **kwargs)`, adds a nes entity type of the given
type. The attribute and relation values are specified using the named and
@@ -176,8 +164,8 @@
* `add_state(name, stateof, initial=False, commit=False, **kwargs)`, adds a new state
in the workflow.
-
-* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`,
+
+* `add_transition(name, transitionof, fromstates, tostate, requiredgroups=(), commit=False, **kwargs)`,
adds a new transition in the workflow.
You can find more details about workflows in the chapter :ref:`Workflow` .
@@ -185,7 +173,7 @@
Configuration migration
-----------------------
-The following functions for configuration migration are available in all
+The following functions for configuration migration are available in all
scripts:
* `option_renamed(oldname, newname)`, indicates that an option has been renamed
@@ -200,11 +188,11 @@
Others migration functions
--------------------------
-Those functions are only used for low level operations that could not be
-accomplished otherwise or to repair damaged databases during interactive
+Those functions are only used for low level operations that could not be
+accomplished otherwise or to repair damaged databases during interactive
session. They are available in `repository` scripts:
-* `sqlexec(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query
+* `sql(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query on the system source
* `add_entity_type_table(etype, commit=True)`
* `add_relation_type_table(rtype, commit=True)`
* `uninline_relation(rtype, commit=True)`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/development/profiling/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,55 @@
+Profiling and performance
+=========================
+
+If you feel that one of your pages takes more time than it should to be
+generated, chances are that you're making too many RQL queries. Obviously,
+there are other reasons but experience tends to show this is the first thing to
+track down. Luckily, CubicWeb provides a configuration option to log RQL
+queries. In your ``all-in-one.conf`` file, set the **query-log-file** option::
+
+ # web application query log file
+ query-log-file=~/myapp-rql.log
+
+Then restart your application, reload your page and stop your application.
+The file ``myapp-rql.log`` now contains the list of RQL queries that were
+executed during your test. It's a simple text file containing lines such as::
+
+ Any A WHERE X eid %(x)s, X lastname A {'x': 448} -- (0.002 sec, 0.010 CPU sec)
+ Any A WHERE X eid %(x)s, X firstname A {'x': 447} -- (0.002 sec, 0.000 CPU sec)
+
+The structure of each line is::
+
+ <RQL QUERY> <QUERY ARGS IF ANY> -- <TIME SPENT>
+
+CubicWeb also provides the **exlog** command to examine and summarize data found
+in such a file:
+
+.. sourcecode:: sh
+
+ $ cubicweb-ctl exlog < ~/myapp-rql.log
+ 0.07 50 Any A WHERE X eid %(x)s, X firstname A {}
+ 0.05 50 Any A WHERE X eid %(x)s, X lastname A {}
+ 0.01 1 Any X,AA ORDERBY AA DESC WHERE E eid %(x)s, E employees X, X modification_date AA {}
+ 0.01 1 Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s {, }
+ 0.01 1 Any B,T,P ORDERBY lower(T) WHERE B is Bookmark,B title T, B path P, B bookmarked_by U, U eid %(x)s {}
+ 0.01 1 Any A,B,C,D WHERE A eid %(x)s,A name B,A creation_date C,A modification_date D {}
+
+This command sorts and uniquifies queries so that it's easy to see where
+is the hot spot that needs optimization.
+
+Do not neglect to set the **fetch_attrs** attribute you can define in your
+entity classes because it can greatly reduce the number of queries executed (see
+:ref:`FetchAttrs`).
+
+You should also know about the **profile** option in the ``all-in-on.conf``. If
+set, this option will make your application run in an `hotshot`_ session and
+store the results in the specified file.
+
+.. _hotshot: http://docs.python.org/library/hotshot.html#module-hotshot
+
+Last but no least, if you're using the PostgreSQL database backend, VACUUMing
+your database can significantly improve the performance of the queries (by
+updating the statistics used by the query optimizer). Nowadays, this is done
+automatically from time to time, but if you've just imported a large amount of
+data in your db, you will want to vacuum it (with the analyse option on). Read
+the documentation of your database for more information.
--- a/doc/book/en/development/testing/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/testing/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
Unit tests
----------
-`CubicWeb` framework provides essentially two Python test classes in the
+*CubicWeb* framework provides essentially two Python test classes in the
module `cubicweb.devtools.apptest`:
* `EnvBasedTC`, to simulate a complete environment (web + repository)
@@ -28,11 +28,11 @@
Email notifications tests
-------------------------
When running tests potentially generated e-mails are not really
-sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`.
+sent but is found in the list `MAILBOX` of module `cubicweb.devtools.apptest`.
This list is reset at each test *setUp* (by the setUp of classes `EnvBasedTC`
and `RepositoryBasedTC`).
-
+
You can test your notifications by analyzing the contents of this list, which
contains objects with two attributes:
* `recipients`, the list of recipients
--- a/doc/book/en/development/webstdlib/autoform.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/autoform.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
---------------------------------------------------------------
It is possible to manage attributes/relations in the simple or multiple
-editing form thanks to the following *rtags*:
+editing form thanks to the following *rtags*:
* `primary`, indicates that an attribute or a relation has to be
inserted **in the simple or multiple editing forms**. In the case of
@@ -26,6 +26,6 @@
* `generated`, indicates that an attribute is dynamically computed
or other, and that it should not be displayed in the editing form.
-If necessary, it is possible to overwrite the method
+If necessary, it is possible to overwrite the method
`relation_category(rtype, x='subject')` to dynamically compute
a relation editing category.
--- a/doc/book/en/development/webstdlib/basetemplates.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/basetemplates.rst Tue Aug 04 15:06:09 2009 +0200
@@ -32,15 +32,15 @@
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, but we
+Let's say we do not want anymore the login menu to be in the header, but we
prefer it to be in the left column just below the logo. As the left column is
-rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_.
+rendered by ``TheMainTemplate``, we will show how to do it in TheMainTemplate_.
First, to remove the login menu, we just need to comment out the display of the
login component such as follows : ::
class MyHTMLPageHeader(HTMLPageHeader):
-
+
def main_header(self, view):
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
@@ -101,13 +101,13 @@
if comp and comp.propval('visible'):
comp.dispatch(w=self.w, view=view)
self.w(u'</td>')
-
+
# logged user and help
#self.w(u'<td>\n')
#comp = self.vreg.select_component('loggeduserlink', self.req, self.rset)
#comp.dispatch(w=self.w)
#self.w(u'</td><td>')
-
+
# search box
self.w(u'<td>')
self.get_searchbox(view, 'left')
@@ -133,14 +133,14 @@
if box.id == 'search_box':
box.dispatch(w=self.w, view=view)
-
+
HTMLPageFooter
--------------
If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in :
+for HTMLPageFooter and override it in your views file as in :
::
form cubicweb.web.views.basetemplates import HTMLPageFooter
@@ -158,8 +158,8 @@
---------------
.. _TheMainTemplate:
-TheMainTemplate is responsible for the general layout of the entire application.
-It defines the template of ``id = main`` that is used by the application.
+TheMainTemplate is responsible for the general layout of the entire application.
+It defines the template of ``id = main`` that is used by the instance.
The default main template (`cubicweb.web.views.basetemplates.TheMainTemplate`)
builds the page based on the following pattern:
@@ -168,7 +168,7 @@
The rectangle containing `view.dispatch()` represents the area where the content
view has to be displayed. The others represents sub-templates called to complete
-the page. A default implementation of those is provided in
+the page. A default implementation of those is provided in
`cubicweb.views.basetemplates`. You can, of course, overload those sub-templates
to implement your own customization of the HTML page.
@@ -177,9 +177,9 @@
* `__notemplate`, if present (whatever the value assigned), only the content view
is returned
-* `__force_display`, if present and its value is not null, no navigation
+* `__force_display`, if present and its value is not null, no navigation
whatever the number of entities to display
-* `__method`, if the result set to render contains only one entity and this
+* `__method`, if the result set to render contains only one entity and this
parameter is set, it refers to a method to call on the entity by passing it
the dictionary of the forms parameters, before going the classic way (through
step 1 and 2 described juste above)
--- a/doc/book/en/development/webstdlib/baseviews.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/baseviews.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,7 +3,7 @@
Base views (:mod:`cubicweb.web.views.baseviews`)
------------------------------------------------
-`CubicWeb` provides a lot of standard views. You can find them in
+*CubicWeb* provides a lot of standard views. You can find them in
``cubicweb/web/views/``.
A certain number of views are used to build the web interface, which apply
@@ -12,33 +12,6 @@
HTML views
~~~~~~~~~~
-*oneline*
- This is a hyper linked *text* view. Similar to the `secondary` view,
- but called when we want the view to stand on a single line, or just
- get a brief view. By default this view uses the
- parameter `MAX_LINE_CHAR` to control the result size.
-
-*secondary*
- This is a combinaison of an icon and a *oneline* view.
- By default it renders the two first attributes of the entity as a
- clickable link redirecting to the primary view.
-
-*incontext, outofcontext*
- Similar to the `secondary` view, but called when an entity is considered
- as in or out of context. By default it respectively returns the result of
- `textincontext` and `textoutofcontext` wrapped in a link leading to
- the primary view of the entity.
-
-List
-`````
-*list*
- This view displays a list of entities by creating a HTML list (`<ul>`)
- and call the view `listitem` for each entity of the result set.
-
-*listitem*
- This view redirects by default to the `outofcontext` view.
-
-
Special views
`````````````
@@ -54,13 +27,43 @@
This view is the default view used when nothing needs to be rendered.
It is always applicable and it does not return anything
-Text views
-~~~~~~~~~~
+Entity views
+````````````
+*incontext, outofcontext*
+ Those are used to display a link to an entity, depending if the entity is
+ considered as displayed in or out of context (of another entity). By default
+ it respectively returns the result of `textincontext` and `textoutofcontext`
+ wrapped in a link leading to the primary view of the entity.
+
+*oneline*
+ This view is used when we can't tell if the entity should be considered as
+ displayed in or out of context. By default it returns the result of `text`
+ in a link leading to the primary view of the entity.
+
+List
+`````
+*list*
+ This view displays a list of entities by creating a HTML list (`<ul>`)
+ and call the view `listitem` for each entity of the result set.
+
+*listitem*
+ This view redirects by default to the `outofcontext` view.
+
+*adaptedlist*
+ This view displays a list of entities of the same type, in HTML section (`<div>`)
+ and call the view `adaptedlistitem` for each entity of the result set.
+
+*adaptedlistitem*
+ This view redirects by default to the `outofcontext` view.
+
+Text entity views
+~~~~~~~~~~~~~~~~~
*text*
- This is the simplest text view for an entity. It displays the
- title of an entity. It should not contain HTML.
+ This is the simplest text view for an entity. By default it returns the
+ result of the `.dc_title` method, which is cut to fit the
+ `navigation.short-line-size` property if necessary.
*textincontext, textoutofcontext*
Similar to the `text` view, but called when an entity is considered out or
- in context. By default it returns respectively the result of the
+ in context. By default it returns respectively the result of the
methods `.dc_title` and `.dc_long_title` of the entity.
--- a/doc/book/en/development/webstdlib/boxes.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/boxes.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
---------------------------------------------------------------
*sidebox*
- This view displays usually a side box of some related entities
+ This view displays usually a side box of some related entities
in a primary view.
The action box
@@ -23,9 +23,9 @@
to new entities and that we should display a link to create a new
entity and link to it automatically
-
+
-If necessary, it is possible to overwrite the method
+If necessary, it is possible to overwrite the method
`relation_mode(rtype, targettype, x='subject')` to dynamically
compute a relation creation category.
--- a/doc/book/en/development/webstdlib/editcontroller.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/editcontroller.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,5 @@
.. -*- coding: utf-8 -*-
+
The 'edit' controller (:mod:`cubicweb.web.views.editcontroller`)
----------------------------------------------------------------
--- a/doc/book/en/development/webstdlib/primary.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/primary.rst Tue Aug 04 15:06:09 2009 +0200
@@ -2,23 +2,21 @@
---------------------------------------------------------------
The primary view of an entity is the view called by default when a single
-entity is in the result set and needs to be displayed.
+entity is in the result set and needs to be displayed.
This view is supposed to render a maximum of informations about the entity.
-
-
Rendering methods and attributes for ``PrimaryView``
----------------------------------------------------
-By default, `CubicWeb` provides a primary view for each new entity type
+By default, *CubicWeb* provides a primary view for each new entity type
you create. The first view you might be interested in modifying.
Let's have a quick look at the EntityView ``PrimaryView`` as well as
its rendering method
-.. code-block:: python
-
+.. sourcecode:: python
+
class PrimaryView(EntityView):
"""the full view of an non final entity"""
id = 'primary'
@@ -64,8 +62,8 @@
The methods you want to modify while customizing a ``PrimaryView`` are:
-*render_entity_title(self, entity)*
- Renders the entity title based on the assumption that the method
+*render_entity_title(self, entity)*
+ Renders the entity title based on the assumption that the method
``def content_title(self)`` is implemented for the given entity type.
*render_entity_metadata(self, entity)*
@@ -77,7 +75,7 @@
of type `Password` and `Bytes`.
*content_navigation_components(self, context)*
- This method is applicable only for entity type implementing the interface
+ This method is applicable only for entity type implementing the interface
`IPrevNext`. This interface is for entities which can be linked to a previous
and/or next entity. This methods will render the navigation links between
entities of this type, either at the top or at the bottom of the page
@@ -85,7 +83,7 @@
*render_entity_relations(self, entity, siderelations)*
Renders all the relations of the entity in the main section of the page.
-
+
*render_side_related(self, entity, siderelations)*
Renders all the relations of the entity in a side box. This is equivalent
to *render_entity_relations* in addition to render the relations
@@ -98,7 +96,7 @@
Renders the attribute label next to the attribute value if set to True.
Otherwise, does only display the attribute value.
-*show_rel_label*
+*show_rel_label*
Renders the relation label next to the relation value if set to True.
Otherwise, does only display the relation value.
@@ -114,4 +112,4 @@
overwrite ``render_entity`` as you might potentially loose the benefits of the side
boxes handling.
-.. XXX talk about uicfg.rdisplay
\ No newline at end of file
+.. XXX talk about uicfg.rdisplay
--- a/doc/book/en/development/webstdlib/startup.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/startup.rst Tue Aug 04 15:06:09 2009 +0200
@@ -9,5 +9,5 @@
a result set to apply to.
*schema*
- A view dedicated to the display of the schema of the application
+ A view dedicated to the display of the schema of the instance
--- a/doc/book/en/development/webstdlib/urlpublish.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/urlpublish.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,6 +1,7 @@
.. -*- coding: utf-8 -*-
+
URL Rewriting (:mod:`cubicweb.web.views.urlpublish`) and (:mod:`cubicweb.web.views.urlrewrite`)
------------------------------------------------------------------------------------------------
XXX feed me
-show how urls are mapped to selections and views and explain URLRewriting
+show how urls are mapped to selections and views and explain URLRewriting
--- a/doc/book/en/development/webstdlib/xmlrss.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/development/webstdlib/xmlrss.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,3 +1,5 @@
+.. _XmlAndRss:
+
XML and RSS views (:mod:`cubicweb.web.views.xmlrss`)
----------------------------------------------------
--- a/doc/book/en/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,25 +3,25 @@
.. _contents:
=====================================================
-`CubicWeb` - The Semantic Web is a construction game!
+*CubicWeb* - The Semantic Web is a construction game!
=====================================================
-`CubicWeb` is a semantic web application framework, licensed under the LGPL,
+*CubicWeb* is a semantic web application framework, licensed under the LGPL,
that empowers developers to efficiently build web applications by reusing
components (called `cubes`) and following the well known object-oriented design
principles.
Its main features are:
-* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application,
-* a query language name :ref:`RQL <RQL>` similar to W3C's SPARQL,
-* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation,
+* an engine driven by the explicit :ref:`data model <DefineDataModel>` of the application,
+* a query language named :ref:`RQL <RQL>` similar to W3C's SPARQL,
+* a :ref:`selection+view <DefineViews>` mechanism for semi-automatic XHTML/XML/JSON/text generation,
* a library of reusable :ref:`components <cubes>` (data model and views) that fulfill common needs,
* the power and flexibility of the Python_ programming language,
* the reliability of SQL databases, LDAP directories, Subversion and Mercurial for storage backends.
Built since 2000 from an R&D effort still continued, supporting 100,000s of
-daily visits at some production sites, `CubicWeb` is a proven end to end solution
+daily visits at some production sites, *CubicWeb* is a proven end to end solution
for semantic web application development that promotes quality, reusability and
efficiency.
@@ -29,14 +29,19 @@
The hacker will join development at the forge_.
-The impatient developper will move right away to :ref:`SetUpEnv`.
+The impatient developer will move right away to :ref:`SetUpEnv`.
+
+The chatter lover will join the `jabber forum`_, the `mailing-list`_ and the blog_.
.. _Logilab: http://www.logilab.fr/
.. _forge: http://www.cubicweb.org/project/
.. _Python: http://www.python.org/
+.. _`jabber forum`: http://www.logilab.org/blogentry/6718
+.. _`mailing-list`: http://lists.cubicweb.org/mailman/listinfo/cubicweb
+.. _blog: http://www.cubicweb.org/blog/1238
-The book
-========
+Table of Contents
+=================
.. toctree::
:maxdepth: 2
@@ -46,17 +51,10 @@
admin/index
annexes/index
-
-
-Table of Contents
------------------
-
-Complete :ref:`TOC`.
+See also:
-Indices and tables
-==================
+* the complete :ref:`TOC`,
+* the :ref:`genindex`,
+* the :ref:`modindex`,
+* and the :ref:`search`.
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
--- a/doc/book/en/intro/concepts/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/concepts/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -1,177 +1,161 @@
.. -*- coding: utf-8 -*-
+.. _Concepts:
+
The Core Concepts of CubicWeb
=============================
-.. toctree::
- :maxdepth: 1
-
-------------------------------
-
-This section aims to provide you the keys of success with *CubicWeb*
-by clarifying the terms specific to our framework. If you want to do anything
-serious with CubicWeb, you should understand concepts in those lines.
-
-*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*.
-
+This section defines some terms and core concepts of the *CubicWeb*
+framework. To avoid confusion while reading this book, take time to go through
+the following definitions and use this section as a reference during your
+reading.
.. _Cube:
Cubes
-----
-** Construct your application by assembling cubes **
+
+A cube is a software component made of three parts: its data model
+(:file:`schema`), its logic (:file:`entities`) and its user interface
+(:file:`views`).
-A cube provides a specific functionality, or a complete *CubicWeb*
-application usually by assembling other cubes.
-
-It's usually composed of a data model, some logic to manipulate it and some parts
-of web interface.
+A cube can use other cubes as building blocks and assemble them to provide
+a whole with richer functionnalities than its parts. The cubes `cubicweb-blog`_
+and `cubicweb-comment`_ could be used to make a cube named *myblog* with
+commentable blog entries.
-You can decide to write your own set of cubes if you wish to re-use the
-entity types you develop or/and if you have specific needs not covered by
-cubes are available from the `CubicWeb Forge`_ under a free software license.
+The `CubicWeb Forge`_ offers a large number of cubes developed by the community
+and available under a free software license.
+
+The command ``cubicweb-ctl list`` displays the list of cubes installed on your
+system.
-Available cubes on your system are defined in the directory
-:file:`/usr/share/cubicweb/cubes` when using a system wide installation. For people
-using the mercurial repository of cubicweb, the :file:`/path/to/forest/cubicweb/cubes`
-directory is used. You can specify additional location using the :envvar:`CW_CUBES_PATH`
-environment variable, using ':' as separator.
+On a Unix system, the available cubes are usually stored in the directory
+:file:`/usr/share/cubicweb/cubes`. During development, the cubes are commonly
+found in the directory :file:`/path/to/cubicweb_forest/cubes`. The environment
+variable :envvar:`CW_CUBES_PATH` gives additionnal locations where to search for
+cubes.
.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
-
+.. _`cubicweb-blog`: http://www.cubicweb.org/project/cubicweb-blog
+.. _`cubicweb-comment`: http://www.cubicweb.org/project/cubicweb-comment
Instances
-----------
-** *CubicWeb* framework is a server/client application framework**
+---------
-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`
+An instance is a runnable application installed on a computer and based on a
+cube.
-We recommand not to define schema, entities or views in the instance
-file system itself but in the cube, in order to maintain re-usability of
-entities and their views. We strongly recommand to develop cubes which
-could be used in other instances (modular approach).
+The instance directory contains the configuration files. Several instances can
+be created and based on the same cube. For exemple, several software forges can
+be set up on one computer system based on the `cubicweb-forge`_ cube.
-An instance usually usually consists into a web interface which is talking to a
-rql repository, itself connected to a SQL database, all into a single
-process. You can have some more complicated configurations using several web
-front-ends talking to a rql repository using `Pyro`_, databases replication...
+.. _`cubicweb-forge`: http://www.cubicweb.org/project/cubicweb-forge
+
+Instances can be of three different types: all-in-one, web engine or data
+repository. For applications that support high traffic, several web (front-end)
+and data (back-end) instances can be set-up to share the load.
.. image:: ../../images/archi_globale.en.png
-The term application is sometimes 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.
+The command ``cubicweb-ctl list`` displays the list of instances installed on
+your system.
+
+On a Unix system, the instances are usually stored in the directory
+:file:`/etc/cubicweb.d/`. During development, the :file:`~/etc/cubicweb.d/`
+directory is looked up, as well as the paths in :envvar:`CW_INSTANCES_DIR`
+environment variable.
+
+The term application is used to refer at "something that should do something as a
+whole", eg more like a project and so can refer to an instance or to a cube,
+depending on the context. This book will try to use *application*, *cube* and
+*instance* as appropriate.
Data Repository
-~~~~~~~~~~~~~~~
-The repository (Be carefull not to get confused with a Mercurial repository or a
-debian repository!) manages all interactions with various data sources by
-providing access to them using uniformly using the Relation Query Language (RQL). The
-web interface and the repository communicate using this language.
+---------------
+
+The data repository [#]_ provides access to one or more data sources (including
+SQL databases, LDAP repositories, Mercurial or Subversion version control
+systems, other CubicWeb repositories, GAE's DataStore, etc).
-Usually, the web server and repository sides are integrated in the same process and
-interact directly, without the need for distant calls using Pyro. But, it is
-important to note that those two sides, client/server, are disjointed and it is
-possible to execute a couple of calls in distinct processes to balance the load
-of your web site on one or more machines.
-
+All interactions with the repository are done using the Relation Query Language
+(RQL). The repository federates the data sources and hides them from the
+querier, which does not realize when a query spans accross several data sources
+and requires running sub-queries and merges to complete.
-A data source is a container of data integrated in the *CubicWeb* repository. A
-repository has at least one source, named `system`, which contains the schema of
-the application, plain-text index and other vital informations for the
-system. You'll find source for SQL databases, LDAP servers, other RQL
-repositories and even mercurial /svn repositories or `Google App Engine`'s
-datastore.
+It is common to run the web engine and the repository in the same process (see
+instances of type all-in-one above), but this is not a requirement. A repository
+can be set up to be accessed remotely using Pyro (`Python Remote Objects`_) and
+act as a server.
-Web interface
-~~~~~~~~~~~~~
-By default the web server provides a generated interface based on its schema.
-Entities can be created, displayed, updated and deleted. As display views are not
-very fancy, it is usually necessary to develop your own.
+Some logic can be attached to events that happen in the repository, like
+creation of entities, deletion of relations, etc. This is used for example to
+send email notifications when the state of an object changes. See `Hooks` below.
-Instances are defined on your system in the directory :file:`/etc/cubicweb.d` when
-using a system wide installation. For people using the mercurial repository of
-cubicweb, the :file:`etc` directory is searched in the user home directory. You can
-also specify an alternative directory using the :envvar:`CW_REGISTRY` environment
-variable.
+.. _[#]: not to be confused with a Mercurial repository or a Debian repository.
+.. _`Python Remote Objects`: http://pyro.sourceforge.net/
-
-
-Schema
-------
-** *CubicWeb* is schema driven **
+Web Engine
+----------
-The schema describes the persistent data model using entities and
-relations. It is modeled with a comprehensive language made of Python classes based on
-the `yams`_ library.
+The web engine replies to http requests and runs the user interface and most of
+the application logic.
-When you create a new cubicweb instance, the schema is stored in the database,
-and it will usually evolves as you upgrade cubicweb and used cubes.
+By default the web engine provides a generated user interface based on the data
+model of the instance. Entities can be created, displayed, updated and
+deleted. As the default user interface is not very fancy, it is usually
+necessary to develop your own.
-*CubicWeb* provides a certain number of system entities included
-sytematically (necessary for the core of *CubicWeb*, notably the schema itself).
-You will also find a library of cubes which defines more piece of schema for standard needs.
-necessary.
+Schema (Data Model)
+-------------------
-*CubicWeb* add some metadata to every entity type, such as the eid (a global
- identifier, unique into an instance), entity's creation date...
+The data model of a cube is described as an entity-relationship schema using a
+comprehensive language made of Python classes imported from the yams_ library.
+.. _yams: http://www.logilab.org/project/yams/
-Attributes may be of the following types:
- `String`, `Int`, `Float`, `Boolean`, `Date`, `Time`, `Datetime`,
- `Interval`, `Password`, `Bytes`.
-
-New in 3.2: RichString
+An `entity type` defines a set of attributes and is used in some relations.
+Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`,
+`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See
+:ref:`yams.BASE_TYPES` for details.
-see :ref:`yams.BASE_TYPES`
-
-Data level security is defined by setting permissions on entity and relation types.
-
-A schema consist of parts detailed below.
-
+A `relation type` is used to define a binary oriented relation between two
+entity types. The left-hand part of a relation is named the `subject` and the
+right-hand part is named the `object`.
-Entity type
-~~~~~~~~~~~
-An *entity type* defines set of attributes and is used in some relations. It may
-have some permissions telling who can read/add/update/delete entities of this type.
+A `relation definition` is a triple (*subject entity type*, *relation type*, *object
+entity type*) associated with a set of properties such as cardinality,
+constraints, etc.
-Relation type
-~~~~~~~~~~~~~
-A *relation type* is used to define a semantic relation between two entity types.
-It may have some permissions telling who can read/add/delete relation of this type.
+Permissions can be set on entity types and relation types to control who will be
+able to create, read, update or delete entities and relations.
-In *CubicWeb* relations are ordered and binary: by convention we name the first
-item of a relation the `subject` and the second the `object`.
+Some meta-data necessary to the system is added to the data model. That includes
+entities like users and groups, the entities used to store the data model
+itself and attributes like unique identifier, creation date, creator, etc.
-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...
+When you create a new *CubicWeb* instance, the schema is stored in the database.
+When the cubes the instance is based on evolve, they may change their data model
+and provide migration scripts that will be executed when the administrator will
+run the upgrade process for the instance.
-
+Registries and Objects
+----------------------
-Dynamic objects for reusable components
----------------------------------------
-** Dynamic objects management or how CubicWeb provides really reusable components **
+XXX registry, register, registries, registers ???
Application objects
~~~~~~~~~~~~~~~~~~~
+
Beside a few core functionalities, almost every feature of the framework is
-acheived by dynamic objects (`application objects` or `appobjects`) stored in a
+achieved by dynamic objects (`application objects` or `appobjects`) stored in a
two-levels registry (the `vregistry`). Each object is affected to a registry with
an identifier in this registry. You may have more than one object sharing an
identifier in the same registry, At runtime, appobjects are selected in the
vregistry according to the context.
-Application objects are stored in the registry using a two level hierarchy :
+Application objects are stored in the registry using a two-level hierarchy :
object's `__registry__` : object's `id` : [list of app objects]
@@ -179,30 +163,30 @@
The `vregistry`
~~~~~~~~~~~~~~~
+
At startup, the `registry` or registers base, inspects a number of directories
looking for compatible classes definition. After a recording process, the objects
are assigned to registers so that they can be selected dynamically while the
-application is running.
+instance is running.
Selectors
~~~~~~~~~
-Each appobject has a selector, which is used to score how well it suits to a
-given context by returning a score. A score of 0 means the object doesn't apply
-to the context. The score is used to choose the most pertinent object: the "more"
-the appobject suits the context the higher the score.
+
+Each appobject has a selector, that is used to compute how well the object fits
+a given context. The better the object fits the context, the higher the score.
-CubicWeb provides a set of basic selectors which may be parametrized and combined
-using binary `&` and `|` operators to provide a custom selector (which can be
-itself reused...).
+CubicWeb provides a set of basic selectors that may be parametrized. Selectors
+can be combined with the binary operators `&` and `|` to build more complex
+selector that can be combined too.
-There is 3 current ways to retreive some appobject from the repository:
+There are three common ways to retrieve some appobject from the repository:
* get the most appropriate objects by specifying a registry and an identifier. In
that case, the object with the greatest score is selected. There should always
be a single appobject with a greater score than others.
* get all appobjects applying to a context by specifying a registry.In
- that case, every objects with the a postive score are selected.
+ that case, every object with the a postive score is selected.
* get the object within a particular registry/identifier. In that case no
selection process is involved, the vregistry will expect to find a single
@@ -215,7 +199,7 @@
When no score is higher than the others, an exception is raised in development
mode to let you know that the engine was not able to identify the view to
apply. This error is silented in production mode and one of the objects with the
-higher score is picked.
+higher score is picked.
If no object has a positive score, ``NoSelectableObject`` exception is raised.
@@ -229,9 +213,10 @@
The RQL query language
----------------------
-**No needs for a complicated ORM when you've a powerful query language**
-All the persistant data in a CubicWeb application is retreived and modified by using the
+**No need for a complicated ORM when you have a powerful query language**
+
+All the persistent data in a CubicWeb instance is retrieved and modified by using the
Relation Query Language.
This query language is inspired by SQL but is on a higher level in order to
@@ -239,16 +224,19 @@
db-api
~~~~~~
+
The repository exposes a `db-api`_ like api but using the RQL instead of SQL.
XXX feed me
Result set
~~~~~~~~~~
+
XXX feed me
Views
-----
+
** *CubicWeb* is data driven **
XXX feed me.
@@ -259,7 +247,3 @@
** *CubicWeb* provides an extensible data repository **
XXX feed me.
-
-
-.. _`Python Remote Object`: http://pyro.sourceforge.net/
-.. _`yams`: http://www.logilab.org/project/yams/
--- a/doc/book/en/intro/foundations/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +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`_.
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/history.rst Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,30 @@
+.. -*- coding: utf-8 -*-
+
+A little history...
+======================
+
+*CubicWeb* is a semantic web application framework that Logilab_ started
+developing in 2001 as an offspring of its Narval_ research project. *CubicWeb*
+is written in Python and includes a data server and a web engine.
+
+Its data server publishes data federated from different sources like SQL
+databases, LDAP directories and versioning systems (such as subversion or
+mercurial).
+
+Its web engine was designed to let the final user control what content to select
+and how to display it. It allows one to browse the federated data sources and
+display the results with the rendering that best fits the context. This
+flexibility of the user interface gives back to the user some capabilities
+usually only accessible to application developers.
+
+*CubicWeb* has been developed by Logilab_ and used in-house for many years
+before it was first installed for its clients in 2006 as version 2.
+
+In 2008, *CubicWeb* version 3 became downloadable for free under the terms of
+the LGPL license. Its community is now steadily growing as changes can occur
+rapidly thanks to the time and energy originally put in the design of the
+framework.
+
+
+.. _Narval: http://www.logilab.org/project/narval
+.. _Logilab: http://www.logilab.fr/
--- a/doc/book/en/intro/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,18 +3,18 @@
.. _Part1:
-----------------------------------
-Part I - Introduction to `CubicWeb`
+Part I - Introduction to *CubicWeb*
-----------------------------------
-This first part of the book will present different reading path to
-discover the `CubicWeb` framework, provide a tutorial to get a quick
-overview of its features and list its key concepts.
+This first part of the book offers different reading path to
+discover the *CubicWeb* framework, provides a tutorial to get a quick
+overview of its features and lists its key concepts.
-
+
.. toctree::
:maxdepth: 2
book-map
- foundations/index
+ history
concepts/index
tutorial/index
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/intro/tutorial/blog-in-five-minutes.rst Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,25 @@
+.. -*- coding: utf-8 -*-
+
+.. _BlogFiveMinutes:
+
+Get a blog running in five minutes!
+-----------------------------------
+
+First install the following packages (:ref:`DebianInstallation`)::
+
+ cubicweb, cubicweb-dev, cubicweb-blog
+
+Then create and initialize your instance::
+
+ cubicweb-ctl create blog myblog
+
+And start it::
+
+ cubicweb-ctl start -D myblog
+
+This is it. Your blog is running. Visit http://localhost:8080 and enjoy it!
+
+As a developer, you'll want to know more about developing new cubes and
+customizing the look of your instance. This is what the next section is about.
+
+
--- a/doc/book/en/intro/tutorial/blog-less-ten-minutes.rst Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-.. -*- coding: utf-8 -*-
-
-.. _BlogTenMinutes:
-
-Get a Blog running in less than ten minutes!
---------------------------------------------
-
-You need to install the following packages (:ref:`DebianInstallation`)::
-
- cubicweb, cubicweb-dev, cubicweb-blog
-
-Creation and initialization of your application by running::
-
- cubicweb-ctl create blog myblog
-
-Your application is now ready to go::
-
- cubicweb-ctl start -D myblog
-
-This is it. Your blog is ready to you. Go to http://localhost:8080 and enjoy!
-
-As a developper, you'll want to know more about how to develop new
-cubes and cutomize the look of your application and this is what we
-talk about now.
-
-
--- a/doc/book/en/intro/tutorial/components.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/tutorial/components.rst Tue Aug 04 15:06:09 2009 +0200
@@ -11,7 +11,7 @@
A library of standard cubes are available from `CubicWeb Forge`_
Cubes provide entities and views.
-The available application entities are:
+The available application entities in standard cubes are:
* addressbook: PhoneNumber and PostalAddress
@@ -23,6 +23,8 @@
* classtags: Tag (to tag anything)
+* comment: Comment (to attach comment threads to entities)
+
* file: File (to allow users to upload and store binary or text files)
* link: Link (to collect links to web resources)
@@ -37,15 +39,12 @@
* 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)
-
+.. _`CubicWeb Forge`: http://www.cubicweb.org/project/
Adding comments to BlogDemo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To import a cube in your application just change the line in the
+To import a cube in your instance just change the line in the
``__pkginfo__.py`` file and verify that the cube you are planning
to use is listed by the command ``cubicweb-ctl list``.
For example::
@@ -53,7 +52,7 @@
__use__ = ('comment',)
will make the ``Comment`` entity available in your ``BlogDemo``
-application.
+cube.
Change the schema to add a relationship between ``BlogEntry`` and
``Comment`` and you are done. Since the comment cube defines the
@@ -67,15 +66,14 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~
Once you modified your data model, you need to synchronize the
-database with your model. For this purpose, `CubicWeb` provides
+database with your model. For this purpose, *CubicWeb* provides
a very useful command ``cubicweb-ctl shell blogdemo`` which
-launches an interactive migration Python shell. (see
-:ref:`cubicweb-ctl` for more details))
-As you modified a relation from the `BlogEntry` schema,
-run the following command:
+launches an interactive shell where you can enter migration
+commands. (see :ref:`cubicweb-ctl` for more details))
+As you added the cube named `comment`, you need to run:
+
::
- synchronize_rschema('BlogEntry')
-
-You can now start your application and add comments to each
-`BlogEntry`.
+ add_cube('comment')
+
+You can now start your instance and comment your blog entries.
--- a/doc/book/en/intro/tutorial/conclusion.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/tutorial/conclusion.rst Tue Aug 04 15:06:09 2009 +0200
@@ -3,15 +3,13 @@
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.
+We demonstrated how from a straight out of the box *CubicWeb* installation, you
+can build your web application based on a data model. 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.
+Many features are available to extend your application, for example: RSS channel
+integration (:ref:`XmlAndRss`), hooks (:ref:`hooks`), support of sources such as
+Google App Engine (:ref:`GoogleAppEngineSource`) and lots of others to discover
+through our book.
--- a/doc/book/en/intro/tutorial/create-cube.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/tutorial/create-cube.rst Tue Aug 04 15:06:09 2009 +0200
@@ -4,24 +4,24 @@
----------------
The packages ``cubicweb`` and ``cubicweb-dev`` installs a command line tool
-for `CubicWeb` called ``cubicweb-ctl``. This tool provides a wide range of
-commands described in details in :ref:`cubicweb-ctl`.
+for *CubicWeb* called ``cubicweb-ctl``. This tool provides a wide range of
+commands described in details in :ref:`cubicweb-ctl`.
-Once your `CubicWeb` development environment is set up, you can create a new
+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`.
+installation, ``/usr/share/cubicweb/cubes`` for debian packages installation)
+a directory named ``blog`` reflecting the structure described in :ref:`Concepts`.
.. _DefineDataModel:
Define your data model
----------------------
-The data model or schema is the core of your `CubicWeb` application.
+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``:
@@ -36,11 +36,11 @@
title = String(required=True, fulltextindexed=True, maxsize=256)
publish_date = Date(default='TODAY')
content = String(required=True, fulltextindexed=True)
- entry_of = SubjectRelation('Blog', cardinality='?*')
+ entry_of = SubjectRelation('Blog', cardinality='?*')
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.
+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
@@ -60,39 +60,39 @@
Create your instance
--------------------
-To use this cube as an application and create a new instance named ``blogdemo``, do::
-
+To use this cube as an instance and create a new instance named ``blogdemo``, do::
+
cubicweb-ctl create blog blogdemo
This command will create the corresponding database and initialize it.
-Welcome to your web application
+Welcome to your web instance
-------------------------------
-Start your application in debug mode with the following command: ::
+Start your instance 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
+You can now access your web instance to create blogs and post messages
by visiting the URL http://localhost:8080/.
-A login form will appear. By default, the application will not allow anonymous
-users to enter the application. To login, you need then use the admin account
+A login form will appear. By default, the instance will not allow anonymous
+users to enter the instance. To login, you need then use the admin account
you created at the time you initialized the database with ``cubicweb-ctl
create``.
.. image:: ../../images/login-form.png
-Once authenticated, you can start playing with your application
+Once authenticated, you can start playing with your instance
and create entities.
.. image:: ../../images/blog-demo-first-page.png
-Please notice that so far, the `CubicWeb` franework managed all aspects of
-the web application based on the schema provided at first.
+Please notice that so far, the *CubicWeb* framework managed all aspects of
+the web application based on the schema provided at the beginning.
Add entities
@@ -142,10 +142,10 @@
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``.
+``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
+new entity BlogEntry, hit ``Apply`` instead of ``Validate`` and the
combobox titled ``add relation`` would have showed up.
@@ -181,30 +181,30 @@
The view selection principle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-A view is defined by a Python class which includes:
-
- - an identifier (all objects in `CubicWeb` are entered in a registry
+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 result sets it can be applied to
A view has a set of methods complying
with the `View` class interface (`cubicweb.common.view`).
-`CubicWeb` provides a lot of standard views for the type `EntityView`;
+*CubicWeb* provides a lot of standard views for the type `EntityView`;
for a complete list, read the code in directory ``cubicweb/web/views/``.
A view is applied on a `result set` which contains a set of
-entities we are trying to display. `CubicWeb` uses a selector
-mechanism which computes for each available view a score:
+entities we are trying to display. *CubicWeb* uses a selector
+mechanism which computes for each available view a score:
the view with the highest score is then used to display the given `result set`.
-The standard library of selectors is in
+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.
+to find the best way to render the data.
For example, the view named ``primary`` is the one used to display
a single entity. We will now show you how to customize this view.
@@ -213,7 +213,7 @@
View customization
~~~~~~~~~~~~~~~~~~
-If you wish to modify the way a `BlogEntry` is rendered, you will have to
+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``.
@@ -222,7 +222,7 @@
To do so, please apply the following changes:
-.. code-block:: python
+.. sourcecode:: python
from cubicweb.web.views import baseviews
@@ -244,14 +244,14 @@
self.w(u'<h1>%s</h1>' % entity.title)
self.w(u'<p>published on %s</p>' % entity.publish_date.strftime('%Y-%m-%d'))
self.w(u'<p>%s</p>' % entity.content)
-
+
# display relations
siderelations = []
if self.main_related_section:
self.render_entity_relations(entity, siderelations)
.. note::
- When a view is modified, it is not required to restart the application
+ When a view is modified, it is not required to restart the instance
server. Save the Python file and reload the page in your web browser
to view the changes.
@@ -261,13 +261,14 @@
:alt: modified primary view
-The above source code defines a new primary view for ``BlogEntry``.
+The above source code defines a new primary view for ``BlogEntry``.
Since views are applied to result sets and result sets can be tables of
data, we have to recover the entity from its (row,col)-coordinates.
The view has a ``self.w()`` method that is used to output data, in our
example HTML output.
-You can find more details about views and selectors in :ref:`ViewDefinition`.
+.. note::
+ You can find more details about views and selectors in :ref:`ViewDefinition`.
--- a/doc/book/en/intro/tutorial/index.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/tutorial/index.rst Tue Aug 04 15:06:09 2009 +0200
@@ -5,17 +5,14 @@
Tutorial
========
-`CubicWeb` is a semantic web application framework that favors reuse and
+*CubicWeb* is a semantic web application framework that favors reuse and
object-oriented design.
A `cube` is a component that includes a model defining the data types and a set of
-views to display the data.
+views to display the data. A cube can be built by assembling other cubes.
-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.
+An `instance` is a specific installation of a cube and includes configuration
+files.
This tutorial will show how to create a `cube` and how to use it as an
@@ -24,7 +21,7 @@
.. toctree::
:maxdepth: 2
- blog-less-ten-minutes
+ blog-in-five-minutes
create-cube
components
maintemplate
--- a/doc/book/en/intro/tutorial/maintemplate.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/intro/tutorial/maintemplate.rst Tue Aug 04 15:06:09 2009 +0200
@@ -11,8 +11,8 @@
.. 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
+interesting template from the three you will look for,
+that is to say, the HTMLPageHeader, the HTMLPageFooter
and the TheMainTemplate.
@@ -24,30 +24,30 @@
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 now move the search box in the header and remove the login form from the
+header. We'll show how to move it to the left column of the user interface.
Let's say we do not want anymore the login menu to be in the header
First, to remove the login menu, we just need to comment out the display of the
login graphic component such as follows:
-.. code-block :: python
+.. sourcecode:: python
class MyBlogHTMLPageHeader(HTMLPageHeader):
-
+
def main_header(self, view):
"""build the top menu with authentification info and the rql box"""
self.w(u'<table id="header"><tr>\n')
@@ -89,9 +89,9 @@
````````````````
If you want to change the footer for example, look
-for HTMLPageFooter and override it in your views file as in: ::
+for HTMLPageFooter and override it in your views file as in:
-..code-block :: python
+.. sourcecode:: python
from cubicweb.web.views.basetemplates import HTMLPageFooter
@@ -115,10 +115,10 @@
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
+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
+be used based on TheMainTemplate called SimpleMainTemplate which does not have
a top section.
.. image:: ../../images/lax-book.06-simple-main-template.en.png
--- a/doc/book/en/makefile Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/makefile Tue Aug 04 15:06:09 2009 +0200
@@ -1,5 +1,5 @@
MKHTML=mkdoc
-MKHTMLOPTS=--doctype article --target html --stylesheet standard
+MKHTMLOPTS=--doctype article --target html --stylesheet standard
SRC=.
TXTFILES:= $(wildcard *.txt)
@@ -9,11 +9,13 @@
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
+#BUILDDIR = build
+BUILDDIR = /tmp/cwdoc
# Internal variables for sphinx
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+ALLSPHINXOPTS = -d ${BUILDDIR}/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
@@ -23,7 +25,7 @@
@echo " all to make standalone HTML files, developer manual and API doc"
@echo " apidoc to make API doc"
@echo " html to make standalone HTML files"
- @echo "--- "
+ @echo "--- "
@echo " pickle to make pickle files (usable by e.g. sphinx-web)"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@@ -33,59 +35,59 @@
clean:
rm -rf apidoc/
rm -f *.html
- -rm -rf build/*
+ -rm -rf ${BUILDDIR}/*
all: ${TARGET} apidoc html
%.html: %.txt
${MKHTML} ${MKHTMLOPTS} $<
-#apydoc:
+#apydoc:
# epydoc --html -o epydoc/ -n ../server/*.py ../core/*.py ../common/*.py ../server/*/*.py ../modpython/*/*.py ../common/*/*.py
apidoc:
epydoc --html -o apidoc -n "cubicweb" --exclude=setup --exclude=__pkginfo__ ../../../
# run sphinx ###
html:
- mkdir -p build/html build/doctrees
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html
+ mkdir -p ${BUILDDIR}/html ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) ${BUILDDIR}/html
@echo
- @echo "Build finished. The HTML pages are in build/html."
+ @echo "Build finished. The HTML pages are in ${BUILDDIR}/html."
pickle:
- mkdir -p build/pickle build/doctrees
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
+ mkdir -p ${BUILDDIR}/pickle ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) ${BUILDDIR}/pickle
@echo
@echo "Build finished; now you can process the pickle files or run"
- @echo " sphinx-web build/pickle"
+ @echo " sphinx-web ${BUILDDIR}/pickle"
@echo "to start the sphinx-web server."
web: pickle
htmlhelp:
- mkdir -p build/htmlhelp build/doctrees
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
+ mkdir -p ${BUILDDIR}/htmlhelp ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) ${BUILDDIR}/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in build/htmlhelp."
+ ".hhp project file in ${BUILDDIR}/htmlhelp."
latex:
- mkdir -p build/latex build/doctrees
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
+ mkdir -p ${BUILDDIR}/latex ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) ${BUILDDIR}/latex
@echo
- @echo "Build finished; the LaTeX files are in build/latex."
+ @echo "Build finished; the LaTeX files are in ${BUILDDIR}/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
- mkdir -p build/changes build/doctrees
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
+ mkdir -p ${BUILDDIR}/changes ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) ${BUILDDIR}/changes
@echo
- @echo "The overview file is in build/changes."
+ @echo "The overview file is in ${BUILDDIR}/changes."
linkcheck:
- mkdir -p build/linkcheck build/doctrees
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
+ mkdir -p ${BUILDDIR}/linkcheck ${BUILDDIR}/doctrees
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) ${BUILDDIR}/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
- "or in build/linkcheck/output.txt."
+ "or in ${BUILDDIR}/linkcheck/output.txt."
--- a/doc/book/en/toc.rst Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/book/en/toc.rst Tue Aug 04 15:06:09 2009 +0200
@@ -9,17 +9,17 @@
.. toctree::
:numbered:
- concepts/index
- cubes/index
- datamodel/index
- entityclasses/index
- devcore/index
- devweb/index
- devrepo/index
- testing/index
- migration/index
- webstdlib/index
+ intro/concepts/index
+ development/cubes/index
+ development/datamodel/index
+ development/entityclasses/index
+ development/devcore/index
+ development/devweb/index
+ development/devrepo/index
+ development/testing/index
+ development/migration/index
+ development/webstdlib/index
admin/index
- rql/index
+ annexes/rql/index
annexes/index
--- a/doc/tools/generate_modules.py Tue Aug 04 11:43:03 2009 +0200
+++ b/doc/tools/generate_modules.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,11 +8,15 @@
import sys
-EXCLUDE_DIRS = ('test', 'tests', 'examples', 'data', 'doc', '.hg', 'migration')
+EXCLUDE_DIRS = ('test', 'tests', 'examples', 'data', 'doc', 'dist',
+ '.hg', 'migration')
if __name__ == '__main__':
- from logilab.common.sphinxutils import generate_modules_file
-
- gen = generate_modules_file(sys.argv[1:])
- gen.set_docdir("cubicweb/doc/book/en")
- gen.make(['cubicweb', '/indexer', '/logilab', '/rql', '/yams'], EXCLUDE_DIRS)
+ from logilab.common.sphinxutils import ModuleGenerator
+ cw_gen = ModuleGenerator('cubicweb', '../..')
+ cw_gen.generate("../book/en/annexes/api_cubicweb.rst",
+ EXCLUDE_DIRS + ('cwdesklets', 'misc', 'skel', 'skeleton'))
+ for modname in ('indexer', 'logilab', 'rql', 'yams'):
+ cw_gen = ModuleGenerator(modname, '../../../' + modname)
+ cw_gen.generate("../book/en/annexes/api_%s.rst" % modname,
+ EXCLUDE_DIRS + ('tools',))
--- a/entities/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/entities/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,13 +9,12 @@
from warnings import warn
-from logilab.common.deprecation import deprecated_function, obsolete
+from logilab.common.deprecation import deprecated
from logilab.common.decorators import cached
from cubicweb import Unauthorized, typed_eid
from cubicweb.entity import Entity
from cubicweb.utils import dump_class
-from cubicweb.schema import FormatConstraint
from cubicweb.interfaces import IBreadCrumbs, IFeed
@@ -240,22 +239,22 @@
wdg = widget(cls.vreg, tschema, rschema, cls, 'object')
return wdg
- @obsolete('use EntityFieldsForm.subject_relation_vocabulary')
+ @deprecated('use EntityFieldsForm.subject_relation_vocabulary')
def subject_relation_vocabulary(self, rtype, limit):
form = self.vreg.select('forms', 'edition', self.req, entity=self)
return form.subject_relation_vocabulary(rtype, limit)
- @obsolete('use EntityFieldsForm.object_relation_vocabulary')
+ @deprecated('use EntityFieldsForm.object_relation_vocabulary')
def object_relation_vocabulary(self, rtype, limit):
form = self.vreg.select('forms', 'edition', self.req, entity=self)
return form.object_relation_vocabulary(rtype, limit)
- @obsolete('use AutomaticEntityForm.[e]relations_by_category')
+ @deprecated('use AutomaticEntityForm.[e]relations_by_category')
def relations_by_category(self, categories=None, permission=None):
from cubicweb.web.views.autoform import AutomaticEntityForm
return AutomaticEntityForm.erelations_by_category(self, categories, permission)
- @obsolete('use AutomaticEntityForm.[e]srelations_by_category')
+ @deprecated('use AutomaticEntityForm.[e]srelations_by_category')
def srelations_by_category(self, categories=None, permission=None):
from cubicweb.web.views.autoform import AutomaticEntityForm
return AutomaticEntityForm.esrelations_by_category(self, categories, permission)
--- a/entities/lib.py Tue Aug 04 11:43:03 2009 +0200
+++ b/entities/lib.py Tue Aug 04 15:06:09 2009 +0200
@@ -141,4 +141,7 @@
{'t': datetime.now(), 'x': self.eid}, 'x')
def valid(self, date):
- return date < self.timestamp
+ if date:
+ return date > self.timestamp
+ return False
+
--- a/entities/schemaobjs.py Tue Aug 04 11:43:03 2009 +0200
+++ b/entities/schemaobjs.py Tue Aug 04 15:06:09 2009 +0200
@@ -25,8 +25,6 @@
def dc_long_title(self):
stereotypes = []
_ = self.req._
- if self.meta:
- stereotypes.append(_('meta'))
if self.final:
stereotypes.append(_('final'))
if stereotypes:
@@ -48,8 +46,6 @@
def dc_long_title(self):
stereotypes = []
_ = self.req._
- if self.meta:
- stereotypes.append(_('meta'))
if self.symetric:
stereotypes.append(_('symetric'))
if self.inlined:
@@ -116,6 +112,18 @@
return self.relation_type[0].rest_path(), {}
return super(CWRelation, self).after_deletion_path()
+ @property
+ def rtype(self):
+ return self.relation_type[0]
+
+ @property
+ def stype(self):
+ return self.from_entity[0]
+
+ @property
+ def otype(self):
+ return self.to_entity[0]
+
class CWAttribute(CWRelation):
id = 'CWAttribute'
--- a/entities/test/data/schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/entities/test/data/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,8 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from yams.buildobjs import EntityType, String
+
class Company(EntityType):
name = String()
--- a/entity.py Tue Aug 04 11:43:03 2009 +0200
+++ b/entity.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,8 +12,8 @@
from logilab.common import interface
from logilab.common.compat import all
from logilab.common.decorators import cached
-from logilab.common.deprecation import obsolete
-from logilab.mtconverter import TransformData, TransformError, html_escape
+from logilab.common.deprecation import deprecated
+from logilab.mtconverter import TransformData, TransformError, xml_escape
from rql.utils import rqlvar_maker
@@ -23,13 +23,9 @@
from cubicweb.appobject import AppRsetObject
from cubicweb.schema import RQLVocabularyConstraint, RQLConstraint, bw_normalize_etype
-try:
- from cubicweb.common.uilib import printable_value, soup2xhtml
- from cubicweb.common.mixins import MI_REL_TRIGGERS
- from cubicweb.common.mttransforms import ENGINE
-except ImportError:
- # missing -common
- MI_REL_TRIGGERS = {}
+from cubicweb.common.uilib import printable_value, soup2xhtml
+from cubicweb.common.mixins import MI_REL_TRIGGERS
+from cubicweb.common.mttransforms import ENGINE
_marker = object()
@@ -405,7 +401,7 @@
path = etype.lower()
if mainattr != 'eid':
value = getattr(self, mainattr)
- if value is None:
+ if value is None or unicode(value) == u'':
mainattr = 'eid'
path += '/eid'
elif needcheck:
@@ -463,7 +459,7 @@
return u''
value = printable_value(self.req, attrtype, value, props, displaytime)
if format == 'text/html':
- value = html_escape(value)
+ value = xml_escape(value)
return value
def mtc_transform(self, data, format, target_format, encoding,
@@ -485,7 +481,7 @@
assert self.has_eid()
execute = self.req.execute
for rschema in self.e_schema.subject_relations():
- if rschema.meta or rschema.is_final():
+ if rschema.is_final() or rschema.meta:
continue
# skip already defined relations
if getattr(self, rschema.type):
@@ -651,7 +647,8 @@
if not self.is_saved():
return None
rql = "Any A WHERE X eid %%(x)s, X %s A" % name
- # XXX should we really use unsafe_execute here??
+ # XXX should we really use unsafe_execute here? I think so (syt),
+ # see #344874
execute = getattr(self.req, 'unsafe_execute', self.req.execute)
try:
rset = execute(rql, {'x': self.eid}, 'x')
@@ -666,6 +663,7 @@
self.critical("can't get value for attribute %s of entity with eid %s",
name, self.eid)
if self.e_schema.destination(name) == 'String':
+ # XXX (syt) imo emtpy string is better
self[name] = value = self.req._('unaccessible')
else:
self[name] = value = None
@@ -684,7 +682,10 @@
pass
assert self.has_eid()
rql = self.related_rql(rtype, role)
- rset = self.req.execute(rql, {'x': self.eid}, 'x')
+ # XXX should we really use unsafe_execute here? I think so (syt),
+ # see #344874
+ execute = getattr(self.req, 'unsafe_execute', self.req.execute)
+ rset = execute(rql, {'x': self.eid}, 'x')
self.set_related_cache(rtype, role, rset)
return self.related(rtype, role, limit, entities)
@@ -722,7 +723,7 @@
# generic vocabulary methods ##############################################
- @obsolete('see new form api')
+ @deprecated('see new form api')
def vocabulary(self, rtype, role='subject', limit=None):
"""vocabulary functions must return a list of couples
(label, eid) that will typically be used to fill the
@@ -790,7 +791,7 @@
def relation_cached(self, rtype, role):
"""return true if the given relation is already cached on the instance
"""
- return '%s_%s' % (rtype, role) in self._related_cache
+ return self._related_cache.get('%s_%s' % (rtype, role))
def related_cache(self, rtype, role, entities=True, limit=None):
"""return values for the given relation if it's cached on the instance,
--- a/etwist/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/etwist/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-""" CW - nevow/twisted client
+""" CW - nevow/twisted client
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
--- a/etwist/request.py Tue Aug 04 11:43:03 2009 +0200
+++ b/etwist/request.py Tue Aug 04 15:06:09 2009 +0200
@@ -38,7 +38,7 @@
self._headers = req.headers
def base_url(self):
- """return the root url of the application"""
+ """return the root url of the instance"""
return self._base_url
def http_method(self):
@@ -47,7 +47,7 @@
def relative_path(self, includeparams=True):
"""return the normalized path of the request (ie at least relative
- to the application's root, but some other normalization may be needed
+ to the instance's root, but some other normalization may be needed
so that the returned path may be used to compare to generated urls
:param includeparams:
--- a/etwist/server.py Tue Aug 04 11:43:03 2009 +0200
+++ b/etwist/server.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-"""twisted server for CubicWeb web applications
+"""twisted server for CubicWeb web instances
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -8,12 +8,15 @@
__docformat__ = "restructuredtext en"
import sys
+import os
import select
from time import mktime
from datetime import date, timedelta
from urlparse import urlsplit, urlunsplit
+import hotshot
from twisted.application import service, strports
+from twisted.scripts._twistd_unix import daemonize
from twisted.internet import reactor, task, threads
from twisted.internet.defer import maybeDeferred
from twisted.web2 import channel, http, server, iweb
@@ -135,6 +138,9 @@
if segments[0] == 'static':
# instance static directory
datadir = self.config.static_directory
+ elif segments[1] == 'fckeditor':
+ fckeditordir = self.config.ext_resources['FCKEDITOR_PATH']
+ return static.File(fckeditordir), segments[2:]
else:
# cube static data file
datadir = self.config.locate_resource(segments[1])
@@ -265,35 +271,6 @@
content = self.appli.need_login_content(req)
return http.Response(code, req.headers_out, content)
-
-# This part gets run when you run this file via: "twistd -noy demo.py"
-def main(appid, cfgname):
- """Starts an cubicweb twisted server for an application
-
- appid: application's identifier
- cfgname: name of the configuration to use (twisted or all-in-one)
- """
- from cubicweb.cwconfig import CubicWebConfiguration
- from cubicweb.etwist import twconfig # trigger configuration registration
- config = CubicWebConfiguration.config_for(appid, cfgname)
- # XXX why calling init_available_cubes here ?
- config.init_available_cubes()
- # create the site and application objects
- if '-n' in sys.argv: # debug mode
- cubicweb = CubicWebRootResource(config, debug=True)
- else:
- cubicweb = CubicWebRootResource(config)
- #toplevel = vhost.VHostURIRewrite(base_url, cubicweb)
- toplevel = cubicweb
- website = server.Site(toplevel)
- application = service.Application("cubicweb")
- # serve it via standard HTTP on port set in the configuration
- s = strports.service('tcp:%04d' % (config['port'] or 8080),
- channel.HTTPFactory(website))
- s.setServiceParent(application)
- return application
-
-
from twisted.python import failure
from twisted.internet import defer
from twisted.web2 import fileupload
@@ -375,3 +352,22 @@
ocount = sorted(ocount.items(), key=lambda x: x[1], reverse=True)[:20]
pprint(ocount)
print 'UNREACHABLE', gc.garbage
+
+def run(config, debug):
+ # create the site
+ root_resource = CubicWebRootResource(config, debug)
+ website = server.Site(root_resource)
+ # serve it via standard HTTP on port set in the configuration
+ port = config['port'] or 8080
+ reactor.listenTCP(port, channel.HTTPFactory(website))
+ baseurl = config['base-url'] or config.default_base_url()
+ print "-> Instance started on", baseurl
+ if not debug:
+ daemonize()
+ if config['pid-file']:
+ file(config['pid-file'], 'w').write(str(os.getpid()))
+ if config['profile']:
+ prof = hotshot.Profile(config['profile'])
+ prof.runcall(reactor.run)
+ else:
+ reactor.run()
--- a/etwist/twconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/etwist/twconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,9 +1,9 @@
"""twisted server configurations:
-* the "twisted" configuration to get a web application running in a standalone
+* the "twisted" configuration to get a web instance running in a standalone
twisted web server which talk to a repository server using Pyro
-* the "all-in-one" configuration to get a web application running in a twisted
+* the "all-in-one" configuration to get a web instance running in a twisted
web server integrating a repository server in the same process (only available
if the repository part of the software is installed
@@ -19,7 +19,7 @@
from cubicweb.web.webconfig import WebConfiguration, merge_options, Method
class TwistedConfiguration(WebConfiguration):
- """web application (in a twisted web server) client of a RQL server"""
+ """web instance (in a twisted web server) client of a RQL server"""
name = 'twisted'
options = merge_options((
@@ -81,7 +81,7 @@
from cubicweb.server.serverconfig import ServerConfiguration
class AllInOneConfiguration(TwistedConfiguration, ServerConfiguration):
- """repository and web application in the same twisted process"""
+ """repository and web instance in the same twisted process"""
name = 'all-in-one'
repo_method = 'inmemory'
options = merge_options(TwistedConfiguration.options
--- a/etwist/twctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/etwist/twctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,51 +8,19 @@
import sys
+from cubicweb import underline_title
from cubicweb.toolsutils import CommandHandler
-from cubicweb.web.webctl import WebCreateHandler
# trigger configuration registration
import cubicweb.etwist.twconfig # pylint: disable-msg=W0611
-
-class TWCreateHandler(WebCreateHandler):
- cfgname = 'twisted'
-
- def bootstrap(self, cubes, inputlevel=0):
- """bootstrap this configuration"""
- print '** twisted configuration'
- mainpyfile = self.config.server_file()
- mainpy = open(mainpyfile, 'w')
- mainpy.write('''
-from cubicweb.etwist import server
-application = server.main(%r, %r)
-''' % (self.config.appid, self.config.name))
- mainpy.close()
- print 'application\'s twisted file %s generated' % mainpyfile
- super(TWCreateHandler, self).bootstrap(cubes, inputlevel)
-
-
class TWStartHandler(CommandHandler):
cmdname = 'start'
cfgname = 'twisted'
def start_command(self, config, debug):
- command = ['%s `which twistd`' % sys.executable]
- for ctl_opt, server_opt in (('pid-file', 'pidfile'),
- ('uid', 'uid'),
- ('log-file', 'logfile',)):
- value = config[ctl_opt]
- if not value or (debug and ctl_opt == 'log-file'):
- continue
- command.append('--%s %s' % (server_opt, value))
- if debug:
- command.append('-n')
- if config['profile']:
- command.append('-p %s --savestats' % config['profile'])
- command.append('-oy')
- command.append(self.config.server_file())
- return ' '.join(command)
-
+ from cubicweb.etwist import server
+ server.run(config, debug)
class TWStopHandler(CommandHandler):
cmdname = 'stop'
@@ -62,16 +30,15 @@
try:
from cubicweb.server import serverctl
- class AllInOneCreateHandler(serverctl.RepositoryCreateHandler, TWCreateHandler):
- """configuration to get a web application running in a twisted web
- server integrating a repository server in the same process
+ class AllInOneCreateHandler(serverctl.RepositoryCreateHandler):
+ """configuration to get an instance running in a twisted web server
+ integrating a repository server in the same process
"""
cfgname = 'all-in-one'
def bootstrap(self, cubes, inputlevel=0):
"""bootstrap this configuration"""
serverctl.RepositoryCreateHandler.bootstrap(self, cubes, inputlevel)
- TWCreateHandler.bootstrap(self, cubes, inputlevel)
class AllInOneStartHandler(TWStartHandler):
cmdname = 'start'
--- a/ext/html4zope.py Tue Aug 04 11:43:03 2009 +0200
+++ b/ext/html4zope.py Tue Aug 04 15:06:09 2009 +0200
@@ -24,7 +24,7 @@
__docformat__ = 'reStructuredText'
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from docutils import nodes
from docutils.writers.html4css1 import Writer as CSS1Writer
@@ -154,7 +154,7 @@
error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
a_start, node['type'], node['level'], a_end,
self.encode(node['source']), line, backref_text)
- self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+ self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % xml_escape(error))
def depart_system_message(self, node):
pass
--- a/ext/rest.py Tue Aug 04 11:43:03 2009 +0200
+++ b/ext/rest.py Tue Aug 04 15:06:09 2009 +0200
@@ -29,8 +29,9 @@
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 logilab.mtconverter import ESC_UCAR_TABLE, ESC_CAR_TABLE, xml_escape
+from cubicweb import UnknownEid
from cubicweb.ext.html4zope import Writer
# We provide our own parser as an attempt to get rid of
@@ -68,8 +69,13 @@
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()
+ try:
+ refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+ except UnknownEid:
+ ref = '#'
+ rest += u' ' + context.req._('(UNEXISTANT EID)')
+ else:
+ ref = refedentity.absolute_url()
set_classes(options)
return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
**options)], []
@@ -207,8 +213,12 @@
req = context.req
if isinstance(data, unicode):
encoding = 'unicode'
+ # remove unprintable characters unauthorized in xml
+ data = data.translate(ESC_UCAR_TABLE)
else:
encoding = req.encoding
+ # remove unprintable characters unauthorized in xml
+ data = data.translate(ESC_CAR_TABLE)
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
@@ -232,5 +242,5 @@
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')
+ return xml_escape(req._('error while publishing ReST text')
+ '\n\n' + data)
--- a/goa/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -101,36 +101,36 @@
# activate entity caching on the server side
def set_entity_cache(self, entity):
- self._query_data.setdefault('_eid_cache', {})[entity.eid] = entity
+ self.transaction_data.setdefault('_eid_cache', {})[entity.eid] = entity
def entity_cache(self, eid):
- return self._query_data['_eid_cache'][eid]
+ return self.transaction_data['_eid_cache'][eid]
def drop_entity_cache(self, eid=None):
if eid is None:
- self._query_data['_eid_cache'] = {}
- elif '_eid_cache' in self._query_data:
- self._query_data['_eid_cache'].pop(eid, None)
+ self.transaction_data['_eid_cache'] = {}
+ elif '_eid_cache' in self.transaction_data:
+ self.transaction_data['_eid_cache'].pop(eid, None)
def datastore_get(self, key):
if isinstance(key, basestring):
key = Key(key)
try:
- gentity = self._query_data['_key_cache'][key]
+ gentity = self.transaction_data['_key_cache'][key]
#self.critical('cached %s', gentity)
except KeyError:
gentity = Get(key)
#self.critical('Get %s', gentity)
- self._query_data.setdefault('_key_cache', {})[key] = gentity
+ self.transaction_data.setdefault('_key_cache', {})[key] = gentity
return gentity
def clear_datastore_cache(self, key=None):
if key is None:
- self._query_data['_key_cache'] = {}
+ self.transaction_data['_key_cache'] = {}
else:
if isinstance(key, basestring):
key = Key(key)
- self._query_data['_key_cache'].pop(key, None)
+ self.transaction_data['_key_cache'].pop(key, None)
from cubicweb.server.session import Session
Session.set_entity_cache = set_entity_cache
--- a/goa/appobjects/components.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/appobjects/components.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import typed_eid
from cubicweb.selectors import one_line_rset, match_search_state, accept
@@ -45,7 +45,7 @@
content_type = 'image/png'
def call(self):
"""display global schema information"""
- skipmeta = not int(self.req.form.get('withmeta', 0))
+ skipmeta = int(self.req.form.get('skipmeta', 1))
if skipmeta:
url = self.build_url('data/schema.png')
else:
@@ -74,7 +74,7 @@
label = display_name(req, etype, 'plural')
view = self.vreg.select('views', 'list', req, req.etype_rset(etype))
url = view.url()
- etypelink = u' <a href="%s">%s</a>' % (html_escape(url), label)
+ etypelink = u' <a href="%s">%s</a>' % (xml_escape(url), label)
yield (label, etypelink, self.add_entity_link(eschema, req))
ManageView.entity_types = entity_types_no_count
--- a/goa/appobjects/dbmgmt.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/appobjects/dbmgmt.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,7 +12,7 @@
from pickle import loads, dumps
from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.common.view import StartupView
@@ -54,7 +54,7 @@
break
values.append('__session=%s' % cookie['__session'].value)
self.w(u"<p>pass this flag to the client: --cookie='%s'</p>"
- % html_escape('; '.join(values)))
+ % xml_escape('; '.join(values)))
@@ -148,7 +148,7 @@
% cpath)
self.w(u'<div>click <a href="%s?vid=contentclear">here</a> to '
'<b>delete all datastore content</b> so process can be '
- 'reinitialized</div>' % html_escape(self.req.base_url()))
+ 'reinitialized</div>' % xml_escape(self.req.base_url()))
Put(status)
@property
@@ -159,11 +159,11 @@
repo=self.config.repository())
def msg(self, msg):
- self.w(u'<div class="message">%s</div>' % html_escape(msg))
+ self.w(u'<div class="message">%s</div>' % xml_escape(msg))
def redirect(self, msg):
raise Redirect(self.req.build_url('', msg))
def continue_link(self):
- self.w(u'<a href="%s">continue</a><br/>' % html_escape(self.req.url()))
+ self.w(u'<a href="%s">continue</a><br/>' % xml_escape(self.req.url()))
class ContentClear(StartupView):
--- a/goa/dbinit.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/dbinit.py Tue Aug 04 15:06:09 2009 +0200
@@ -20,7 +20,7 @@
try:
group = Get(key)
except datastore_errors.EntityNotFoundError:
- raise Exception('can\'t find required group %s, is your application '
+ raise Exception('can\'t find required group %s, is your instance '
'correctly initialized (eg did you run the '
'initialization script) ?' % groupname)
_GROUP_CACHE[groupname] = group
@@ -86,14 +86,13 @@
def init_persistent_schema(ssession, schema):
execute = ssession.unsafe_execute
rql = ('INSERT CWEType X: X name %(name)s, X description %(descr)s,'
- 'X final FALSE, X meta %(meta)s')
+ 'X final FALSE')
eschema = schema.eschema('CWEType')
- execute(rql, {'name': u'CWEType', 'descr': unicode(eschema.description),
- 'meta': eschema.meta})
+ execute(rql, {'name': u'CWEType', 'descr': unicode(eschema.description)})
for eschema in schema.entities():
if eschema.is_final() or eschema == 'CWEType':
continue
- execute(rql, {'name': unicode(eschema), 'meta': eschema.meta,
+ execute(rql, {'name': unicode(eschema),
'descr': unicode(eschema.description)})
def insert_versions(ssession, config):
--- a/goa/dbmyams.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/dbmyams.py Tue Aug 04 15:06:09 2009 +0200
@@ -167,7 +167,7 @@
SCHEMAS_LIB_DIRECTORY = join(CW_SOFTWARE_ROOT, 'schemas')
def load_schema(config, schemaclasses=None, extrahook=None):
- """high level method to load all the schema for a lax application"""
+ """high level method to load all the schema for a lax instance"""
# IMPORTANT NOTE: dbmodel schemas must be imported **BEFORE**
# the loader is instantiated because this is where the dbmodels
# are registered in the yams schema
--- a/goa/gaesource.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/gaesource.py Tue Aug 04 15:06:09 2009 +0200
@@ -47,7 +47,7 @@
asession.user.clear_related_cache(rtype, 'object')
def _mark_modified(session, gaeentity):
- modified = session.query_data('modifiedentities', {}, setdefault=True)
+ modified = session.transaction_data.setdefault('modifiedentities', {})
modified[str(gaeentity.key())] = gaeentity
DatastorePutOp(session)
@@ -98,8 +98,8 @@
return 0
def _put_entities(self):
- pending = self.session.query_data('pendingeids', ())
- modified = self.session.query_data('modifiedentities', {})
+ pending = self.session.transaction_data.get('pendingeids', ())
+ modified = self.session.transaction_data.get('modifiedentities', {})
for eid, gaeentity in modified.iteritems():
assert not eid in pending
Put(gaeentity)
@@ -155,7 +155,7 @@
return rqlst
def set_schema(self, schema):
- """set the application'schema"""
+ """set the instance'schema"""
self.interpreter = RQLInterpreter(schema)
self.schema = schema
if 'CWUser' in schema and not self.repo.config['use-google-auth']:
@@ -263,7 +263,7 @@
Delete(key)
session.clear_datastore_cache(key)
session.drop_entity_cache(eid)
- session.query_data('modifiedentities', {}).pop(eid, None)
+ session.transaction_data.get('modifiedentities', {}).pop(eid, None)
def add_relation(self, session, subject, rtype, object):
"""add a relation to the source"""
@@ -275,7 +275,7 @@
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
gaesubj, gaeobj, cards = _rinfo(session, subject, rtype, object)
- pending = session.query_data('pendingeids', set(), setdefault=True)
+ pending = session.transaction_data.setdefault('pendingeids', set())
if not subject in pending:
_rdel(session, gaesubj, gaeobj.key(), 's_' + rtype, cards[0])
if not object in pending:
--- a/goa/goaconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/goaconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -17,7 +17,7 @@
from cubicweb.goa.dbmyams import load_schema
UNSUPPORTED_OPTIONS = set(('connections-pool-size',
- 'pyro-port', 'pyro-id', 'pyro-application-id',
+ 'pyro-port', 'pyro-id', 'pyro-instance-id',
'pyro-ns-host', 'pyro-ns-port', 'pyro-ns-group',
'https-url', 'host', 'pid-file', 'uid', 'base-url', 'log-file',
'smtp-host', 'smtp-port',
@@ -30,41 +30,41 @@
# * check auth-mode=http + fix doc (eg require use-google-auth = False)
class GAEConfiguration(ServerConfiguration, WebConfiguration):
- """repository and web application in the same twisted process"""
+ """repository and web instance in Google AppEngine environment"""
name = 'app'
repo_method = 'inmemory'
options = merge_options((
('included-cubes',
{'type' : 'csv',
'default': [],
- 'help': 'list of db model based cubes used by the application.',
+ 'help': 'list of db model based cubes used by the instance.',
'group': 'main', 'inputlevel': 1,
}),
('included-yams-cubes',
{'type' : 'csv',
'default': [],
- 'help': 'list of yams based cubes used by the application.',
+ 'help': 'list of yams based cubes used by the instance.',
'group': 'main', 'inputlevel': 1,
}),
('use-google-auth',
{'type' : 'yn',
'default': True,
- 'help': 'does this application rely on google authentication service or not.',
+ 'help': 'does this instance rely on google authentication service or not.',
'group': 'main', 'inputlevel': 1,
}),
('schema-type',
{'type' : 'choice', 'choices': ('yams', 'dbmodel'),
'default': 'yams',
- 'help': 'does this application is defining its schema using yams or db model.',
+ 'help': 'does this instance is defining its schema using yams or db model.',
'group': 'main', 'inputlevel': 1,
}),
# overriden options
('query-log-file',
{'type' : 'string',
'default': None,
- 'help': 'web application query log file: DON\'T SET A VALUE HERE WHEN '
- 'UPLOADING YOUR APPLICATION. This should only be used to analyse '
- 'queries issued by your application in the development environment.',
+ 'help': 'web instance query log file: DON\'T SET A VALUE HERE WHEN '
+ 'UPLOADING YOUR INSTANCE. This should only be used to analyse '
+ 'queries issued by your instance in the development environment.',
'group': 'main', 'inputlevel': 2,
}),
('anonymous-user',
@@ -86,7 +86,7 @@
cube_vobject_path = WebConfiguration.cube_vobject_path | ServerConfiguration.cube_vobject_path
# use file system schema
- bootstrap_schema = read_application_schema = False
+ bootstrap_schema = read_instance_schema = False
# schema is not persistent, don't load schema hooks (unavailable)
schema_hooks = False
# no user workflow for now
@@ -130,7 +130,7 @@
return self._cubes
def vc_config(self):
- """return CubicWeb's engine and application's cube versions number"""
+ """return CubicWeb's engine and instance's cube versions number"""
return {}
# overriden from cubicweb web configuration
--- a/goa/goactl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/goactl.py Tue Aug 04 15:06:09 2009 +0200
@@ -172,13 +172,13 @@
class NewGoogleAppCommand(Command):
- """Create a new google appengine application.
+ """Create a new google appengine instance.
- <application directory>
- the path to the appengine application directory
+ <instance directory>
+ the path to the appengine instance directory
"""
name = 'newgapp'
- arguments = '<application directory>'
+ arguments = '<instance directory>'
def run(self, args):
if len(args) != 1:
@@ -187,7 +187,7 @@
appldir = normpath(abspath(appldir))
appid = basename(appldir)
context = {'appname': appid}
- # goa application'skeleton
+ # goa instance'skeleton
copy_skeleton(join(CW_SOFTWARE_ROOT, 'goa', 'skel'),
appldir, context, askconfirm=True)
# cubicweb core dependancies
--- a/goa/goavreg.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/goavreg.py Tue Aug 04 15:06:09 2009 +0200
@@ -37,7 +37,7 @@
'cubicweb.goa.appobjects')
for cube in reversed(self.config.cubes()):
self.load_cube(cube)
- self.load_application(applroot)
+ self.load_instance(applroot)
def load_directory(self, directory, cube, skip=()):
for filename in listdir(directory):
@@ -49,7 +49,7 @@
cube in self.config['included-cubes'],
cube)
- def load_application(self, applroot):
+ def load_instance(self, applroot):
self._auto_load(applroot, self.config['schema-type'] == 'dbmodel')
def _import(self, modname):
--- a/goa/overrides/toolsutils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/overrides/toolsutils.py Tue Aug 04 15:06:09 2009 +0200
@@ -17,14 +17,14 @@
return result
def read_config(config_file):
- """read the application configuration from a file and return it as a
+ """read the instance configuration from a file and return it as a
dictionnary
:type config_file: str
:param config_file: path to the configuration file
:rtype: dict
- :return: a dictionary with specified values associated to option names
+ :return: a dictionary with specified values associated to option names
"""
config = current = {}
try:
--- a/goa/skel/loader.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/skel/loader.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,11 +12,11 @@
from cubicweb.goa.goaconfig import GAEConfiguration
from cubicweb.goa.dbinit import create_user, create_groups
- # compute application's root directory
+ # compute instance's root directory
APPLROOT = dirname(abspath(__file__))
# apply monkey patches first
goa.do_monkey_patch()
- # get application's configuration (will be loaded from app.conf file)
+ # get instance's configuration (will be loaded from app.conf file)
GAEConfiguration.ext_resources['JAVASCRIPTS'].append('DATADIR/goa.js')
config = GAEConfiguration('toto', APPLROOT)
# create default groups
--- a/goa/skel/main.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/skel/main.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-"""module defining the root handler for a lax application. You should not have
+"""module defining the root handler for a lax instance. You should not have
to change anything here.
:organization: Logilab
@@ -8,7 +8,7 @@
"""
__docformat__ = "restructuredtext en"
-# compute application's root directory
+# compute instance's root directory
from os.path import dirname, abspath
APPLROOT = dirname(abspath(__file__))
@@ -16,7 +16,7 @@
from cubicweb import goa
goa.do_monkey_patch()
-# get application's configuration (will be loaded from app.conf file)
+# get instance's configuration (will be loaded from app.conf file)
from cubicweb.goa.goaconfig import GAEConfiguration
GAEConfiguration.ext_resources['JAVASCRIPTS'].append('DATADIR/goa.js')
config = GAEConfiguration('toto', APPLROOT)
@@ -29,19 +29,19 @@
# before schema loading
import custom
-# load application'schema
+# load instance'schema
vreg.schema = config.load_schema()
# load dynamic objects
vreg.load(APPLROOT)
-# call the postinit so custom get a chance to do application specific stuff
+# call the postinit so custom get a chance to do instance specific stuff
custom.postinit(vreg)
from cubicweb.wsgi.handler import CubicWebWSGIApplication
application = CubicWebWSGIApplication(config, vreg=vreg)
-# main function so this handler module is cached
+# main function so this handler module is cached
def main():
from wsgiref.handlers import CGIHandler
CGIHandler().run(application)
--- a/goa/skel/schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/skel/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,6 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from cubicweb.schema import format_constraint
class Blog(EntityType):
title = String(maxsize=50, required=True)
@@ -14,8 +13,6 @@
class BlogEntry(EntityType):
title = String(maxsize=100, required=True)
publish_date = Date(default='TODAY')
- text_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- text = String(fulltextindexed=True)
+ text = RichString(fulltextindexed=True)
category = String(vocabulary=('important','business'))
entry_of = SubjectRelation('Blog', cardinality='?*')
--- a/goa/test/pytestconf.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/test/pytestconf.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,7 +6,7 @@
import cubicweb
# remove 'mx' modules imported by cubicweb
-for modname in sys.modules.keys():
+for modname in sys.modules.keys():
if modname.startswith('mx'):
sys.modules.pop(modname)
--- a/goa/test/unittest_views.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/test/unittest_views.py Tue Aug 04 15:06:09 2009 +0200
@@ -51,7 +51,7 @@
def test_django_index(self):
self.vreg.render('views', 'index', self.req, rset=None)
-for vid in ('primary', 'secondary', 'oneline', 'incontext', 'outofcontext', 'text'):
+for vid in ('primary', 'oneline', 'incontext', 'outofcontext', 'text'):
setattr(SomeViewsTC, 'test_%s'%vid, lambda self, vid=vid: self.blog.view(vid))
if __name__ == '__main__':
--- a/goa/tools/generate_schema_img.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/tools/generate_schema_img.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,6 +8,7 @@
import sys
from os.path import dirname, abspath, join
from yams import schema2dot
+from cubicweb.web.views.schema import SKIP_TYPES
APPLROOT = abspath(join(dirname(abspath(__file__)), '..'))
@@ -22,9 +23,8 @@
skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
path = join(APPLROOT, 'data', 'schema.png')
schema2dot.schema2dot(schema, path, #size=size,
- skiprels=skip_rels, skipmeta=True)
+ skiptypes=SKIP_TYPES)
print 'generated', path
path = join(APPLROOT, 'data', 'metaschema.png')
-schema2dot.schema2dot(schema, path, #size=size,
- skiprels=skip_rels, skipmeta=False)
+schema2dot.schema2dot(schema, path)
print 'generated', path
--- a/goa/tools/laxctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/goa/tools/laxctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -19,6 +19,8 @@
from logilab.common.clcommands import Command, register_commands, main_run
from cubicweb.common.uilib import remove_html_tags
+from cubicweb.web.views.schema import SKIP_TYPES
+
APPLROOT = osp.abspath(osp.join(osp.dirname(osp.abspath(__file__)), '..'))
@@ -57,19 +59,17 @@
assert not args, 'no argument expected'
from yams import schema2dot
schema = self.vreg.schema
- skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
path = osp.join(APPLROOT, 'data', 'schema.png')
schema2dot.schema2dot(schema, path, #size=size,
- skiprels=skip_rels, skipmeta=True)
+ skiptypes=SKIP_TYPES)
print 'generated', path
path = osp.join(APPLROOT, 'data', 'metaschema.png')
- schema2dot.schema2dot(schema, path, #size=size,
- skiprels=skip_rels, skipmeta=False)
+ schema2dot.schema2dot(schema, path)
print 'generated', path
class PopulateDataDirCommand(LaxCommand):
- """populate application's data directory according to used cubes"""
+ """populate instance's data directory according to used cubes"""
name = 'populatedata'
def _run(self, args):
@@ -118,7 +118,7 @@
class URLCommand(LaxCommand):
- """abstract class for commands doing stuff by accessing the web application
+ """abstract class for commands doing stuff by accessing the web instance
"""
min_args = max_args = 1
arguments = '<site url>'
--- a/hercule.py Tue Aug 04 11:43:03 2009 +0200
+++ b/hercule.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-"""RQL client for cubicweb, connecting to application using pyro
+"""RQL client for cubicweb, connecting to instance using pyro
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -79,7 +79,7 @@
'debug' : "Others",
})
- def __init__(self, application=None, user=None, password=None,
+ def __init__(self, instance=None, user=None, password=None,
host=None, debug=0):
CLIHelper.__init__(self, os.path.join(os.environ["HOME"], ".erqlhist"))
self.cnx = None
@@ -92,12 +92,12 @@
self.autocommit = False
self._last_result = None
self._previous_lines = []
- if application is not None:
- self.do_connect(application, user, password, host)
+ if instance is not None:
+ self.do_connect(instance, user, password, host)
self.do_debug(debug)
- def do_connect(self, application, user=None, password=None, host=None):
- """connect to an cubicweb application"""
+ def do_connect(self, instance, user=None, password=None, host=None):
+ """connect to an cubicweb instance"""
from cubicweb.dbapi import connect
if user is None:
user = raw_input('login: ')
@@ -106,17 +106,17 @@
password = getpass('password: ')
if self.cnx is not None:
self.cnx.close()
- self.cnx = connect(user=user, password=password, host=host,
- database=application)
+ self.cnx = connect(login=user, password=password, host=host,
+ database=instance)
self.schema = self.cnx.get_schema()
self.cursor = self.cnx.cursor()
# add entities types to the completion commands
self._completer.list = (self.commands.keys() +
self.schema.entities() + ['Any'])
- print _('You are now connected to %s') % application
+ print _('You are now connected to %s') % instance
- help_do_connect = ('connect', "connect <application> [<user> [<password> [<host>]]]",
+ help_do_connect = ('connect', "connect <instance> [<user> [<password> [<host>]]]",
_(do_connect.__doc__))
def do_debug(self, debug=1):
@@ -142,9 +142,9 @@
help_do_description = ('description', "description", _(do_description.__doc__))
def do_schema(self, name=None):
- """display information about the application schema """
+ """display information about the instance schema """
if self.cnx is None:
- print _('You are not connected to an application !')
+ print _('You are not connected to an instance !')
return
done = None
if name is None:
@@ -189,7 +189,7 @@
else, stores the query line and waits for the suite
"""
if self.cnx is None:
- print _('You are not connected to an application !')
+ print _('You are not connected to an instance !')
return
# append line to buffer
self._previous_lines.append(stripped_line)
@@ -232,11 +232,11 @@
class CubicWebClientCommand(Command):
"""A command line querier for CubicWeb, using the Relation Query Language.
- <application>
- identifier of the application to connect to
+ <instance>
+ identifier of the instance to connect to
"""
name = 'client'
- arguments = '<application>'
+ arguments = '<instance>'
options = CONNECT_OPTIONS + (
("verbose",
{'short': 'v', 'type' : 'int', 'metavar': '<level>',
--- a/i18n/en.po Tue Aug 04 11:43:03 2009 +0200
+++ b/i18n/en.po Tue Aug 04 15:06:09 2009 +0200
@@ -154,6 +154,15 @@
msgid "1?"
msgstr "1 0..1"
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+
+msgid "<no value>"
+msgstr ""
+
msgid "?*"
msgstr "0..1 0..n"
@@ -169,12 +178,18 @@
msgid "AND"
msgstr ""
+msgid "Add permissions"
+msgstr ""
+
msgid "Any"
msgstr ""
msgid "Application"
msgstr ""
+msgid "Attributes"
+msgstr ""
+
msgid "Bookmark"
msgstr "Bookmark"
@@ -284,6 +299,9 @@
msgid "Decimal_plural"
msgstr "Decimal numbers"
+msgid "Delete permissions"
+msgstr ""
+
msgid "Do you want to delete the following element(s) ?"
msgstr ""
@@ -395,6 +413,9 @@
msgid "RQLExpression_plural"
msgstr "RQL expressions"
+msgid "Read permissions"
+msgstr ""
+
msgid "Recipients:"
msgstr ""
@@ -408,6 +429,9 @@
msgid "Schema %s"
msgstr ""
+msgid "Schema of the data model"
+msgstr ""
+
msgid "Search for"
msgstr ""
@@ -449,6 +473,9 @@
msgid "The view %s could not be found"
msgstr ""
+msgid "There is no workflow defined for this entity."
+msgstr ""
+
#, python-format
msgid "This %s"
msgstr ""
@@ -526,6 +553,9 @@
msgid "Unable to find anything named \"%s\" in the schema !"
msgstr ""
+msgid "Update permissions"
+msgstr ""
+
msgid "Used by:"
msgstr ""
@@ -981,6 +1011,9 @@
msgid "bookmarks"
msgstr ""
+msgid "bookmarks are used to have user's specific internal links"
+msgstr ""
+
msgid "boxes"
msgstr ""
@@ -1111,9 +1144,15 @@
msgid "changes applied"
msgstr ""
+msgid "click here to see created entity"
+msgstr ""
+
msgid "click on the box to cancel the deletion"
msgstr ""
+msgid "click to edit this field"
+msgstr ""
+
msgid "comment"
msgstr ""
@@ -1132,12 +1171,6 @@
msgid "components_appliname_description"
msgstr "display the application title in the page's header"
-msgid "components_applmessages"
-msgstr "application messages"
-
-msgid "components_applmessages_description"
-msgstr "display the application messages"
-
msgid "components_breadcrumbs"
msgstr "breadcrumbs"
@@ -1419,6 +1452,18 @@
msgid "currently attached file: %s"
msgstr ""
+msgid "cwetype-schema-image"
+msgstr "schema"
+
+msgid "cwetype-schema-permissions"
+msgstr "permissions"
+
+msgid "cwetype-schema-text"
+msgstr "description"
+
+msgid "cwetype-workflow"
+msgstr "workflow"
+
msgid "data directory url"
msgstr ""
@@ -1538,9 +1583,6 @@
msgid "detach attached file %s"
msgstr ""
-msgid "detailed schema view"
-msgstr ""
-
msgid "display order of the action"
msgstr ""
@@ -1624,6 +1666,9 @@
msgid "entity edited"
msgstr ""
+msgid "entity linked"
+msgstr ""
+
msgid "entity type"
msgstr ""
@@ -1676,6 +1721,12 @@
msgid "facets_created_by-facet_description"
msgstr ""
+msgid "facets_cwfinal-facet"
+msgstr "\"final entity or relation type\" facet"
+
+msgid "facets_cwfinal-facet_description"
+msgstr ""
+
msgid "facets_etype-facet"
msgstr "\"entity type\" facet"
@@ -1820,9 +1871,6 @@
msgid "hide filter form"
msgstr ""
-msgid "hide meta-data"
-msgstr "hide meta entities and relations"
-
msgid "home"
msgstr ""
@@ -2211,9 +2259,6 @@
msgid "not selected"
msgstr ""
-msgid "not specified"
-msgstr ""
-
msgid "not the initial state for this entity"
msgstr ""
@@ -2484,6 +2529,12 @@
msgid "schema's permissions definitions"
msgstr ""
+msgid "schema-image"
+msgstr "schema"
+
+msgid "schema-text"
+msgstr "description"
+
msgid "search"
msgstr ""
@@ -2569,9 +2620,6 @@
msgid "show filter form"
msgstr ""
-msgid "show meta-data"
-msgstr "show the complete schema"
-
msgid "sioc"
msgstr ""
@@ -2685,6 +2733,9 @@
msgid "thursday"
msgstr ""
+msgid "timeline"
+msgstr ""
+
msgid "timestamp"
msgstr ""
@@ -2892,6 +2943,12 @@
msgid "view workflow"
msgstr ""
+msgid "view_index"
+msgstr "index"
+
+msgid "view_manage"
+msgstr "site management"
+
msgid "views"
msgstr ""
@@ -2957,14 +3014,26 @@
#~ msgid "add a Card"
#~ msgstr "add a card"
+#~ msgid "components_applmessages"
+#~ msgstr "application messages"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "display the application messages"
+
#~ msgid "content_format"
#~ msgstr "content format"
+#~ msgid "hide meta-data"
+#~ msgstr "hide meta entities and relations"
+
#~ msgid "planned_delivery"
#~ msgstr "planned delivery"
#~ msgid "remove this Card"
#~ msgstr "remove this card"
+#~ msgid "show meta-data"
+#~ msgstr "show the complete schema"
+
#~ msgid "wikiid"
#~ msgstr "wiki identifier"
--- a/i18n/entities.pot Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-msgid "__msg state changed"
-msgstr ""
-
-msgid "managers"
-msgstr ""
-
-msgid "users"
-msgstr ""
-
-msgid "guests"
-msgstr ""
-
-msgid "owners"
-msgstr ""
-
-msgid "read_perm"
-msgstr ""
-
-msgid "add_perm"
-msgstr ""
-
-msgid "update_perm"
-msgstr ""
-
-msgid "delete_perm"
-msgstr ""
--- a/i18n/es.po Tue Aug 04 11:43:03 2009 +0200
+++ b/i18n/es.po Tue Aug 04 15:06:09 2009 +0200
@@ -105,7 +105,7 @@
#, python-format
msgid "%d years"
-msgstr ""
+msgstr "%d años"
#, python-format
msgid "%s error report"
@@ -159,6 +159,14 @@
msgid "1?"
msgstr "1 0..1"
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+"<div>Este esquema del modelo de datos <em>no incluye</em> los meta-datos, pero "
+"se puede ver a un <a href=\"%s\">modelo completo con meta-datos</a>.</div>"
+
msgid "?*"
msgstr "0..1 0..n"
@@ -174,12 +182,18 @@
msgid "AND"
msgstr "Y"
+msgid "Add permissions"
+msgstr "Añadir autorizaciónes"
+
msgid "Any"
msgstr "Cualquiera"
msgid "Application"
msgstr "Aplicación"
+msgid "Attributes"
+msgstr "Atributos"
+
msgid "Bookmark"
msgstr "Favorito"
@@ -289,6 +303,9 @@
msgid "Decimal_plural"
msgstr "Decimales"
+msgid "Delete permissions"
+msgstr "Autorización de suprimir"
+
msgid "Do you want to delete the following element(s) ?"
msgstr "Desea suprimir el(los) elemento(s) siguiente(s)"
@@ -311,7 +328,7 @@
msgstr "Números flotantes"
msgid "From:"
-msgstr ""
+msgstr "De: "
msgid "Int"
msgstr "Número entero"
@@ -400,8 +417,11 @@
msgid "RQLExpression_plural"
msgstr "Expresiones RQL"
+msgid "Read permissions"
+msgstr "Autorización de leer"
+
msgid "Recipients:"
-msgstr ""
+msgstr "Destinatarios"
msgid "Relations"
msgstr "Relaciones"
@@ -413,6 +433,9 @@
msgid "Schema %s"
msgstr "Esquema %s"
+msgid "Schema of the data model"
+msgstr "Esquema del modelo de datos"
+
msgid "Search for"
msgstr "Buscar"
@@ -435,7 +458,7 @@
msgstr "Cadenas de caracteres"
msgid "Subject:"
-msgstr ""
+msgstr "Sujeto:"
msgid "Submit bug report"
msgstr "Enviar un reporte de error (bug)"
@@ -454,6 +477,9 @@
msgid "The view %s could not be found"
msgstr "La vista %s no ha podido ser encontrada"
+msgid "There is no workflow defined for this entity."
+msgstr "No hay workflow para este entidad"
+
#, python-format
msgid "This %s"
msgstr "Este %s"
@@ -531,6 +557,9 @@
msgid "Unable to find anything named \"%s\" in the schema !"
msgstr "No encontramos el nombre \"%s\" en el esquema"
+msgid "Update permissions"
+msgstr "Autorización de modificar"
+
msgid "Used by:"
msgstr "Utilizado por :"
@@ -683,7 +712,7 @@
msgstr ""
msgid "actions_managepermission"
-msgstr ""
+msgstr "Administración de autorizaciónes"
msgid "actions_managepermission_description"
msgstr ""
@@ -973,7 +1002,7 @@
msgstr "Atributo"
msgid "attributes with modified permissions:"
-msgstr ""
+msgstr "atributos con autorizaciónes modificadas:"
msgid "august"
msgstr "Agosto"
@@ -1008,6 +1037,9 @@
msgid "bookmarks"
msgstr "Favoritos"
+msgid "bookmarks are used to have user's specific internal links"
+msgstr "favoritos son usados para que un usuario recorde ligas"
+
msgid "boxes"
msgstr "Cajas"
@@ -1132,7 +1164,7 @@
msgstr "cardinalidad"
msgid "category"
-msgstr ""
+msgstr "categoria"
#, python-format
msgid "changed state of %(etype)s #%(eid)s (%(title)s)"
@@ -1141,6 +1173,9 @@
msgid "changes applied"
msgstr "Cambios realizados"
+msgid "click here to see created entity"
+msgstr "ver la entidad creada"
+
msgid "click on the box to cancel the deletion"
msgstr "Seleccione la zona de edición para cancelar la eliminación"
@@ -1162,12 +1197,6 @@
msgid "components_appliname_description"
msgstr "Muestra el título de la aplicación en el encabezado de la página"
-msgid "components_applmessages"
-msgstr "Mensajes de la aplicación"
-
-msgid "components_applmessages_description"
-msgstr "Muestra los mensajes de la aplicación"
-
msgid "components_breadcrumbs"
msgstr "Ruta de Navegación"
@@ -1474,7 +1503,19 @@
#, python-format
msgid "currently attached file: %s"
-msgstr ""
+msgstr "archivo adjunto: %s"
+
+msgid "cwetype-schema-image"
+msgstr "Esquema"
+
+msgid "cwetype-schema-permissions"
+msgstr "Autorizaciónes"
+
+msgid "cwetype-schema-text"
+msgstr "Modelo de datos"
+
+msgid "cwetype-workflow"
+msgstr "Workflow"
msgid "data directory url"
msgstr "Url del repertorio de datos"
@@ -1608,9 +1649,6 @@
msgid "detach attached file %s"
msgstr "Quitar archivo adjunto %s"
-msgid "detailed schema view"
-msgstr "Vista detallada del esquema"
-
msgid "display order of the action"
msgstr "Orden de aparición de la acción"
@@ -1685,16 +1723,19 @@
msgstr "Entidades eliminadas"
msgid "entity copied"
-msgstr ""
+msgstr "entidad copiada"
msgid "entity created"
-msgstr ""
+msgstr "entidad creada"
msgid "entity deleted"
msgstr "Entidad eliminada"
msgid "entity edited"
-msgstr ""
+msgstr "entidad modificada"
+
+msgid "entity linked"
+msgstr "entidad asociada"
msgid "entity type"
msgstr "Tipo de entidad"
@@ -1753,6 +1794,18 @@
msgid "facets_created_by-facet_description"
msgstr "faceta creado por"
+msgid "facets_cwfinal-facet"
+msgstr "faceta \"final\""
+
+msgid "facets_cwfinal-facet_description"
+msgstr "faceta para las entidades \"finales\""
+
+msgid "facets_cwmeta-facet"
+msgstr "faceta \"meta\""
+
+msgid "facets_cwmeta-facet_description"
+msgstr "faceta para las entidades \"meta\""
+
msgid "facets_etype-facet"
msgstr "faceta \"es de tipo\""
@@ -1809,7 +1862,7 @@
#, python-format
msgid "from %(date)s"
-msgstr ""
+msgstr "de %(date)s"
msgid "from_entity"
msgstr "De la entidad"
@@ -1836,7 +1889,7 @@
msgstr "Trazado de curbas estándares"
msgid "generic relation to link one entity to another"
-msgstr ""
+msgstr "relación generica para ligar entidades"
msgid "go back to the index page"
msgstr "Regresar a la página de inicio"
@@ -1897,9 +1950,6 @@
msgid "hide filter form"
msgstr "Esconder el filtro"
-msgid "hide meta-data"
-msgstr "Esconder los meta-datos"
-
msgid "home"
msgstr "Inicio"
@@ -2106,7 +2156,8 @@
msgid ""
"link a permission to the entity. This permission should be used in the "
"security definition of the entity's type to be useful."
-msgstr ""
+msgstr "relaciónar una autorización con la entidad. Este autorización debe "
+"ser usada en la definición de la entidad para ser utíl."
msgid ""
"link a property to the user which want this property customization. Unless "
@@ -2150,7 +2201,7 @@
msgstr "Clave de acesso"
msgid "login or email"
-msgstr ""
+msgstr "Clave de acesso o dirección de correo"
msgid "login_action"
msgstr "Ingresa tus datos"
@@ -2259,15 +2310,19 @@
msgid "navigation.combobox-limit"
msgstr ""
+# msgstr "Navegación: numero maximo de elementos en una caja de elección (combobox)"
msgid "navigation.page-size"
msgstr ""
+# msgstr "Navegación: numero maximo de elementos por pagina"
msgid "navigation.related-limit"
msgstr ""
+# msgstr "Navegación: numero maximo de elementos relacionados"
msgid "navigation.short-line-size"
msgstr ""
+#msgstr "Navegación: numero maximo de caracteres en una linéa corta"
msgid "navtop"
msgstr "Encabezado del contenido principal"
@@ -2282,7 +2337,7 @@
msgstr "no"
msgid "no associated permissions"
-msgstr ""
+msgstr "no autorización relacionada"
msgid "no possible transition"
msgstr "transición no posible"
@@ -2322,7 +2377,7 @@
msgstr "objeto"
msgid "object_plural:"
-msgstr ""
+msgstr "objetos:"
msgid "october"
msgstr "octubre"
@@ -2340,7 +2395,7 @@
msgstr "solo estan permitidas consultas de lectura"
msgid "open all"
-msgstr ""
+msgstr "abrir todos"
msgid "order"
msgstr "orden"
@@ -2349,10 +2404,10 @@
msgstr "orden"
msgid "owl"
-msgstr ""
+msgstr "owl"
msgid "owlabox"
-msgstr ""
+msgstr "owlabox"
msgid "owned_by"
msgstr "pertenece a"
@@ -2385,10 +2440,10 @@
msgstr "Permiso"
msgid "permissions for entities"
-msgstr ""
+msgstr "autorizaciónes para entidades"
msgid "permissions for relations"
-msgstr ""
+msgstr "autorizaciónes para relaciones"
msgid "permissions for this entity"
msgstr "Permisos para esta entidad"
@@ -2400,7 +2455,7 @@
msgstr "Seleccione los favoritos existentes"
msgid "pkey"
-msgstr ""
+msgstr "pkey"
msgid "please correct errors below"
msgstr "Favor de corregir errores"
@@ -2458,7 +2513,7 @@
msgstr "Definición"
msgid "relations"
-msgstr ""
+msgstr "relaciones"
msgid "relations deleted"
msgstr "Relaciones eliminadas"
@@ -2518,16 +2573,16 @@
msgstr "Eliminar esta transición"
msgid "require_group"
-msgstr "Requiere_grupo"
+msgstr "Requiere grupo"
msgid "require_group_object"
-msgstr "Objeto_grupo_requerido"
+msgstr "Requerido por grupo"
msgid "require_permission"
-msgstr ""
+msgstr "Requiere autorización"
msgid "require_permission_object"
-msgstr ""
+msgstr "Requerido por autorización"
msgid "required attribute"
msgstr "Atributo requerido"
@@ -2582,6 +2637,12 @@
msgid "schema's permissions definitions"
msgstr "definiciones de permisos del esquema"
+msgid "schema-image"
+msgstr "esquema imagen"
+
+msgid "schema-text"
+msgstr "esquema text"
+
msgid "search"
msgstr "buscar"
@@ -2601,7 +2662,7 @@
msgstr "Ver todos"
msgid "see_also"
-msgstr ""
+msgstr "Ver tambíen"
msgid "select"
msgstr "Seleccionar"
@@ -2610,7 +2671,7 @@
msgstr "seleccione un"
msgid "select a key first"
-msgstr ""
+msgstr "seleccione una clave"
msgid "select a relation"
msgstr "seleccione una relación"
@@ -2670,9 +2731,6 @@
msgid "show filter form"
msgstr "afficher le filtre"
-msgid "show meta-data"
-msgstr "mostrar meta-data"
-
msgid "sioc"
msgstr ""
@@ -2730,7 +2788,7 @@
msgstr "cardinalidad sujeto/objeto"
msgid "subject_plural:"
-msgstr ""
+msgstr "sujetos:"
msgid "sunday"
msgstr "domingo"
@@ -2787,6 +2845,9 @@
msgid "thursday"
msgstr "jueves"
+msgid "timeline"
+msgstr ""
+
msgid "timestamp"
msgstr "fecha"
@@ -2804,7 +2865,7 @@
#, python-format
msgid "to %(date)s"
-msgstr ""
+msgstr "a %(date)s"
msgid "to associate with"
msgstr "a asociar con"
@@ -2825,7 +2886,7 @@
msgstr "a hacer por"
msgid "toggle check boxes"
-msgstr ""
+msgstr "cambiar valor"
msgid "transition is not allowed"
msgstr "transition no permitida"
@@ -2894,7 +2955,7 @@
msgstr "propiedad desconocida"
msgid "up"
-msgstr ""
+msgstr "arriba"
msgid "upassword"
msgstr "clave de acceso"
@@ -3055,7 +3116,7 @@
msgstr "ha terminado la sesion"
msgid "you should probably delete that property"
-msgstr ""
+msgstr "deberia probablamente suprimir esta propriedad"
#~ msgid "%s constraint failed"
#~ msgstr "La contrainte %s n'est pas satisfaite"
@@ -3121,6 +3182,12 @@
#~ msgid "cancel edition"
#~ msgstr "annuler l'Èdition"
+#~ msgid "components_applmessages"
+#~ msgstr "Mensajes de la aplicación"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "Muestra los mensajes de la aplicación"
+
#~ msgid "components_rss_feed_url"
#~ msgstr "RSS FEED URL"
@@ -3140,6 +3207,9 @@
#~ "langue par dÈfaut (regarder le rÈpertoire i18n de l'application pour voir "
#~ "les langues disponibles)"
+#~ msgid "detailed schema view"
+#~ msgstr "Vista detallada del esquema"
+
#~ msgid "filter"
#~ msgstr "filtrer"
@@ -3149,6 +3219,9 @@
#~ msgid "header"
#~ msgstr "en-tÍte de page"
+#~ msgid "hide meta-data"
+#~ msgstr "Esconder los meta-datos"
+
#~ msgid "iCal"
#~ msgstr "iCal"
@@ -3190,6 +3263,9 @@
#~ msgid "see also"
#~ msgstr "voir aussi"
+#~ msgid "show meta-data"
+#~ msgstr "mostrar meta-data"
+
#~ msgid "status will change from %s to %s"
#~ msgstr "l'Ètat va passer de %s ‡ %s"
--- a/i18n/fr.po Tue Aug 04 11:43:03 2009 +0200
+++ b/i18n/fr.po Tue Aug 04 15:06:09 2009 +0200
@@ -159,6 +159,17 @@
msgid "1?"
msgstr "1 0..1"
+#, python-format
+msgid ""
+"<div>This schema of the data model <em>excludes</em> the meta-data, but you "
+"can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
+msgstr ""
+"<div>Ce schéma du modèle de données <em>exclue</em> les méta-données, mais "
+"vous pouvez afficher un <a href=\"%s\">schéma complet</a>.</div>"
+
+msgid "<no value>"
+msgstr "<non spécifié>"
+
msgid "?*"
msgstr "0..1 0..n"
@@ -174,12 +185,18 @@
msgid "AND"
msgstr "ET"
+msgid "Add permissions"
+msgstr "Permissions d'ajouter"
+
msgid "Any"
msgstr "N'importe"
msgid "Application"
msgstr "Application"
+msgid "Attributes"
+msgstr "Attributs"
+
msgid "Bookmark"
msgstr "Signet"
@@ -289,6 +306,9 @@
msgid "Decimal_plural"
msgstr "Nombres décimaux"
+msgid "Delete permissions"
+msgstr "Permissions de supprimer"
+
msgid "Do you want to delete the following element(s) ?"
msgstr "Voulez vous supprimer le(s) élément(s) suivant(s)"
@@ -400,6 +420,9 @@
msgid "RQLExpression_plural"
msgstr "Expressions RQL"
+msgid "Read permissions"
+msgstr "Permissions de lire"
+
msgid "Recipients:"
msgstr "Destinataires :"
@@ -413,6 +436,9 @@
msgid "Schema %s"
msgstr "Schéma %s"
+msgid "Schema of the data model"
+msgstr "Schéma du modèle de données"
+
msgid "Search for"
msgstr "Rechercher"
@@ -454,6 +480,9 @@
msgid "The view %s could not be found"
msgstr "La vue %s est introuvable"
+msgid "There is no workflow defined for this entity."
+msgstr "Il n'y a pas de workflow défini pour ce type d'entité"
+
#, python-format
msgid "This %s"
msgstr "Ce %s"
@@ -531,6 +560,9 @@
msgid "Unable to find anything named \"%s\" in the schema !"
msgstr "Rien de nommé \"%s\" dans le schéma"
+msgid "Update permissions"
+msgstr "Permissions de modifier"
+
msgid "Used by:"
msgstr "Utilisé par :"
@@ -604,6 +636,10 @@
"invalidate the cache (typically in hooks). Also, checkout the AppRsetObject."
"get_cache() method."
msgstr ""
+"une simple entité de cache, caractérisées par un nom et une date de "
+"validité. L'application est responsable de la mise à jour de la date quand "
+"il est nécessaire d'invalider le cache (typiquement dans les crochets). Voir "
+"aussi la méthode get_cache() sur la classe AppRsetObject."
msgid "about this site"
msgstr "à propos de ce site"
@@ -645,7 +681,7 @@
msgstr ""
msgid "actions_download_as_owl"
-msgstr ""
+msgstr "télécharger en owl"
msgid "actions_download_as_owl_description"
msgstr ""
@@ -681,7 +717,7 @@
msgstr ""
msgid "actions_managepermission"
-msgstr ""
+msgstr "gestion des permissions"
msgid "actions_managepermission_description"
msgstr ""
@@ -964,7 +1000,7 @@
#, python-format
msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)"
msgstr ""
-"L'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
+"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n"
"autre via la relation %(rtype)s"
msgid "attribute"
@@ -1006,6 +1042,10 @@
msgid "bookmarks"
msgstr "signets"
+msgid "bookmarks are used to have user's specific internal links"
+msgstr ""
+"les signets sont utilisés pour gérer des liens internes par utilisateur"
+
msgid "boxes"
msgstr "boîtes"
@@ -1139,8 +1179,14 @@
msgid "changes applied"
msgstr "changements appliqués"
+msgid "click here to see created entity"
+msgstr "cliquez ici pour voir l'entité créée"
+
msgid "click on the box to cancel the deletion"
-msgstr "cliquer dans la zone d'édition pour annuler la suppression"
+msgstr "cliquez dans la zone d'édition pour annuler la suppression"
+
+msgid "click to edit this field"
+msgstr "cliquez pour éditer ce champ"
msgid "comment"
msgstr "commentaire"
@@ -1160,12 +1206,6 @@
msgid "components_appliname_description"
msgstr "affiche le titre de l'application dans l'en-tête de page"
-msgid "components_applmessages"
-msgstr "messages applicatifs"
-
-msgid "components_applmessages_description"
-msgstr "affiche les messages applicatifs"
-
msgid "components_breadcrumbs"
msgstr "fil d'ariane"
@@ -1475,6 +1515,18 @@
msgid "currently attached file: %s"
msgstr "fichie actuellement attaché %s"
+msgid "cwetype-schema-image"
+msgstr "schéma"
+
+msgid "cwetype-schema-permissions"
+msgstr "permissions"
+
+msgid "cwetype-schema-text"
+msgstr "description"
+
+msgid "cwetype-workflow"
+msgstr "workflow"
+
msgid "data directory url"
msgstr "url du répertoire de données"
@@ -1606,9 +1658,6 @@
msgid "detach attached file %s"
msgstr "détacher le fichier existant %s"
-msgid "detailed schema view"
-msgstr "vue détaillée du schéma"
-
msgid "display order of the action"
msgstr "ordre d'affichage de l'action"
@@ -1694,6 +1743,9 @@
msgid "entity edited"
msgstr "entité éditée"
+msgid "entity linked"
+msgstr "entité liée"
+
msgid "entity type"
msgstr "type d'entité"
@@ -1750,6 +1802,12 @@
msgid "facets_created_by-facet_description"
msgstr ""
+msgid "facets_cwfinal-facet"
+msgstr "facette \"type d'entité ou de relation final\""
+
+msgid "facets_cwfinal-facet_description"
+msgstr ""
+
msgid "facets_etype-facet"
msgstr "facette \"est de type\""
@@ -1895,9 +1953,6 @@
msgid "hide filter form"
msgstr "cacher le filtre"
-msgid "hide meta-data"
-msgstr "cacher les entités et relations \"méta\""
-
msgid "home"
msgstr "maison"
@@ -2106,8 +2161,8 @@
"link a permission to the entity. This permission should be used in the "
"security definition of the entity's type to be useful."
msgstr ""
-"lie une permission à une entité. Cette permission doit généralement être utilisée "
-"dans la définition de sécurité du type d'entité pour être utile."
+"lie une permission à une entité. Cette permission doit généralement être "
+"utilisée dans la définition de sécurité du type d'entité pour être utile."
msgid ""
"link a property to the user which want this property customization. Unless "
@@ -2307,9 +2362,6 @@
msgid "not selected"
msgstr "non sélectionné"
-msgid "not specified"
-msgstr "non spécifié"
-
msgid "not the initial state for this entity"
msgstr "n'est pas l'état initial pour cette entité"
@@ -2590,6 +2642,12 @@
msgid "schema's permissions definitions"
msgstr "permissions définies dans le schéma"
+msgid "schema-image"
+msgstr "schéma"
+
+msgid "schema-text"
+msgstr "description"
+
msgid "search"
msgstr "rechercher"
@@ -2678,9 +2736,6 @@
msgid "show filter form"
msgstr "afficher le filtre"
-msgid "show meta-data"
-msgstr "afficher le schéma complet"
-
msgid "sioc"
msgstr "sioc"
@@ -2795,6 +2850,9 @@
msgid "thursday"
msgstr "jeudi"
+msgid "timeline"
+msgstr "échelle de temps"
+
msgid "timestamp"
msgstr "date"
@@ -3010,6 +3068,12 @@
msgid "view workflow"
msgstr "voir les états possibles"
+msgid "view_index"
+msgstr "accueil"
+
+msgid "view_manage"
+msgstr "gestion du site"
+
msgid "views"
msgstr "vues"
@@ -3133,6 +3197,12 @@
#~ msgid "close all"
#~ msgstr "tout fermer"
+#~ msgid "components_applmessages"
+#~ msgstr "messages applicatifs"
+
+#~ msgid "components_applmessages_description"
+#~ msgstr "affiche les messages applicatifs"
+
#~ msgid "components_rss_feed_url"
#~ msgstr "syndication rss"
@@ -3149,6 +3219,9 @@
#~ "langue par défaut (regarder le répertoire i18n de l'application pour voir "
#~ "les langues disponibles)"
+#~ msgid "detailed schema view"
+#~ msgstr "vue détaillée du schéma"
+
#~ msgid "filter"
#~ msgstr "filtrer"
@@ -3158,6 +3231,9 @@
#~ msgid "header"
#~ msgstr "en-tête de page"
+#~ msgid "hide meta-data"
+#~ msgstr "cacher les entités et relations \"méta\""
+
#~ msgid "iCal"
#~ msgstr "iCal"
@@ -3181,6 +3257,9 @@
#~ msgid "no associated epermissions"
#~ msgstr "aucune permission spécifique n'est définie"
+#~ msgid "not specified"
+#~ msgstr "non spécifié"
+
#~ msgid "owned by"
#~ msgstr "appartient à"
@@ -3193,6 +3272,9 @@
#~ msgid "see also"
#~ msgstr "voir aussi"
+#~ msgid "show meta-data"
+#~ msgstr "afficher le schéma complet"
+
#~ msgid "status will change from %s to %s"
#~ msgstr "l'état va passer de %s à %s"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/i18n/static-messages.pot Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,11 @@
+msgid "read_perm"
+msgstr ""
+
+msgid "add_perm"
+msgstr ""
+
+msgid "update_perm"
+msgstr ""
+
+msgid "delete_perm"
+msgstr ""
--- a/interfaces.py Tue Aug 04 11:43:03 2009 +0200
+++ b/interfaces.py Tue Aug 04 15:06:09 2009 +0200
@@ -197,6 +197,14 @@
"""interface for items that do have a begin date 'start' and an end date 'stop'
"""
+ @property
+ def start(self):
+ """return start date"""
+
+ @property
+ def stop(self):
+ """return stop state"""
+
class ICalendarViews(Interface):
"""calendar views interface"""
def matching_dates(self, begin, end):
--- a/md5crypt.py Tue Aug 04 11:43:03 2009 +0200
+++ b/md5crypt.py Tue Aug 04 15:06:09 2009 +0200
@@ -85,7 +85,7 @@
i = i >> 1
final = md5.md5(ctx).digest()
# The following is supposed to make
- # things run slower.
+ # things run slower.
# my question: WTF???
for i in xrange(1000):
ctx1 = ''
--- a/misc/cwdesklets/rqlsensor/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/misc/cwdesklets/rqlsensor/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -65,7 +65,7 @@
return self._v_cnx
except AttributeError:
appid, user, passwd = self._get_config("appid"), self._get_config("user"), self._get_config("passwd")
- cnx = connect(database=appid, user=user, password=passwd)
+ cnx = connect(database=appid, login=user, password=passwd)
self._v_cnx = cnx
return cnx
--- a/misc/cwzope/cwzope.py Tue Aug 04 11:43:03 2009 +0200
+++ b/misc/cwzope/cwzope.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,7 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from AccessControl import getSecurityManager
+from AccessControl import getSecurityManager
from cubicweb.dbapi import connect, Connection, Cursor
from cubicweb.common.utils import ResultSet, ResultSetIterator, ResultSetRow, Entity
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.3.5_Any.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,8 @@
+# some entities have been added before schema entities, fix the 'is' and
+# 'is_instance_of' relations
+for rtype in ('is', 'is_instance_of'):
+ sql('INSERT INTO %s_relation '
+ 'SELECT X.eid, ET.cw_eid FROM entities as X, cw_CWEType as ET '
+ 'WHERE X.type=ET.cw_name AND NOT EXISTS('
+ ' SELECT 1 from is_relation '
+ ' WHERE eid_from=X.eid AND eid_to=ET.cw_eid)' % rtype)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.4.0_Any.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,2 @@
+drop_attribute('CWEType', 'meta')
+drop_attribute('CWRType', 'meta')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.4.0_common.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,6 @@
+from os.path import join
+from cubicweb.toolsutils import create_dir
+
+option_renamed('pyro-application-id', 'pyro-instance-id')
+
+create_dir(join(config.appdatahome, 'backup'))
--- a/misc/migration/bootstrapmigration_repository.py Tue Aug 04 11:43:03 2009 +0200
+++ b/misc/migration/bootstrapmigration_repository.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,49 +8,71 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+applcubicwebversion, cubicwebversion = versions_map['cubicweb']
+
+if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
+ from cubicweb import RepositoryError
+ from cubicweb.server.hooks import uniquecstrcheck_before_modification
+ session.set_shared_data('do-not-insert-cwuri', True)
+ repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
+ repo.hm.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+ add_relation_type('cwuri')
+ base_url = session.base_url()
+ # use an internal session since some entity might forbid modifications to admin
+ isession = repo.internal_session()
+ for eid, in rql('Any X', ask_confirm=False):
+ try:
+ isession.execute('SET X cwuri %(u)s WHERE X eid %(x)s',
+ {'x': eid, 'u': base_url + u'eid/%s' % eid})
+ except RepositoryError:
+ print 'unable to set cwuri for', eid, session.describe(eid)
+ isession.commit()
+ repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
+ repo.hm.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+ session.set_shared_data('do-not-insert-cwuri', False)
+
if applcubicwebversion < (3, 2, 2) and cubicwebversion >= (3, 2, 1):
- from base64 import b64encode
- for table in ('entities', 'deleted_entities'):
- for eid, extid in sql('SELECT eid, extid FROM %s WHERE extid is NOT NULL'
- % table, ask_confirm=False):
- sql('UPDATE %s SET extid=%%(extid)s WHERE eid=%%(eid)s' % table,
- {'extid': b64encode(extid), 'eid': eid}, ask_confirm=False)
- checkpoint()
+ from base64 import b64encode
+ for table in ('entities', 'deleted_entities'):
+ for eid, extid in sql('SELECT eid, extid FROM %s WHERE extid is NOT NULL'
+ % table, ask_confirm=False):
+ sql('UPDATE %s SET extid=%%(extid)s WHERE eid=%%(eid)s' % table,
+ {'extid': b64encode(extid), 'eid': eid}, ask_confirm=False)
+ checkpoint()
if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0):
- add_cube('card', update_database=False)
+ add_cube('card', update_database=False)
if applcubicwebversion < (2, 47, 0) and cubicwebversion >= (2, 47, 0):
- from cubicweb.server import schemaserial
- schemaserial.HAS_FULLTEXT_CONTAINER = False
- session.set_shared_data('do-not-insert-is_instance_of', True)
- add_attribute('CWRType', 'fulltext_container')
- schemaserial.HAS_FULLTEXT_CONTAINER = True
+ from cubicweb.server import schemaserial
+ schemaserial.HAS_FULLTEXT_CONTAINER = False
+ session.set_shared_data('do-not-insert-is_instance_of', True)
+ add_attribute('CWRType', 'fulltext_container')
+ schemaserial.HAS_FULLTEXT_CONTAINER = True
if applcubicwebversion < (2, 50, 0) and cubicwebversion >= (2, 50, 0):
- session.set_shared_data('do-not-insert-is_instance_of', True)
- add_relation_type('is_instance_of')
- # fill the relation using an efficient sql query instead of using rql
- sql('INSERT INTO is_instance_of_relation '
- ' SELECT * from is_relation')
- checkpoint()
- session.set_shared_data('do-not-insert-is_instance_of', False)
+ session.set_shared_data('do-not-insert-is_instance_of', True)
+ add_relation_type('is_instance_of')
+ # fill the relation using an efficient sql query instead of using rql
+ sql('INSERT INTO is_instance_of_relation '
+ ' SELECT * from is_relation')
+ checkpoint()
+ session.set_shared_data('do-not-insert-is_instance_of', False)
if applcubicwebversion < (2, 42, 0) and cubicwebversion >= (2, 42, 0):
- sql('ALTER TABLE entities ADD COLUMN mtime TIMESTAMP')
- sql('UPDATE entities SET mtime=CURRENT_TIMESTAMP')
- sql('CREATE INDEX entities_mtime_idx ON entities(mtime)')
- sql('''CREATE TABLE deleted_entities (
+ sql('ALTER TABLE entities ADD COLUMN mtime TIMESTAMP')
+ sql('UPDATE entities SET mtime=CURRENT_TIMESTAMP')
+ sql('CREATE INDEX entities_mtime_idx ON entities(mtime)')
+ sql('''CREATE TABLE deleted_entities (
eid INTEGER PRIMARY KEY NOT NULL,
type VARCHAR(64) NOT NULL,
source VARCHAR(64) NOT NULL,
dtime TIMESTAMP NOT NULL,
extid VARCHAR(256)
)''')
- sql('CREATE INDEX deleted_entities_type_idx ON deleted_entities(type)')
- sql('CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime)')
- sql('CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid)')
- checkpoint()
-
+ sql('CREATE INDEX deleted_entities_type_idx ON deleted_entities(type)')
+ sql('CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime)')
+ sql('CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid)')
+ checkpoint()
--- a/misc/migration/postcreate.py Tue Aug 04 11:43:03 2009 +0200
+++ b/misc/migration/postcreate.py Tue Aug 04 15:06:09 2009 +0200
@@ -43,4 +43,4 @@
eid = add_entity('CWPermission', name=PERM_USE_TEMPLATE_FORMAT,
label=_('use template languages'))
rql('SET X require_group G WHERE G name "managers", X eid %(x)s',
- {'x': eid}, 'x')
+ {'x': eid}, 'x')
--- a/rset.py Tue Aug 04 11:43:03 2009 +0200
+++ b/rset.py Tue Aug 04 15:06:09 2009 +0200
@@ -347,7 +347,7 @@
raise NotAnEntity(etype)
return self._build_entity(row, col)
- def _build_entity(self, row, col, _localcache=None):
+ def _build_entity(self, row, col):
"""internal method to get a single entity, returns a
partially initialized Entity instance.
@@ -370,14 +370,7 @@
# XXX should we consider updating a cached entity with possible
# new attributes found in this resultset ?
try:
- if hasattr(req, 'is_super_session'):
- # this is a Session object which is not caching entities, so we
- # have to use a local cache to avoid recursion pb
- if _localcache is None:
- _localcache = {}
- return _localcache[eid]
- else:
- return req.entity_cache(eid)
+ return req.entity_cache(eid)
except KeyError:
pass
# build entity instance
@@ -385,8 +378,6 @@
entity = self.vreg.etype_class(etype)(req, self, row, col)
entity.set_eid(eid)
# cache entity
- if _localcache is not None:
- _localcache[eid] = entity
req.set_entity_cache(entity)
eschema = entity.e_schema
# try to complete the entity if there are some additional columns
@@ -420,7 +411,7 @@
rrset = ResultSet([], rql % (attr, entity.eid))
req.decorate_rset(rrset)
else:
- rrset = self._build_entity(row, i, _localcache).as_rset()
+ rrset = self._build_entity(row, i).as_rset()
entity.set_related_cache(attr, x, rrset)
return entity
@@ -538,10 +529,12 @@
for i, term in enumerate(rqlst.selection):
if i == index:
continue
- try:
- # XXX rewritten const
- var = term.variable
- except AttributeError:
+ # XXX rewritten const
+ # use iget_nodes for (hack) case where we have things like MAX(V)
+ for vref in term.iget_nodes(nodes.VariableRef):
+ var = vref.variable
+ break
+ else:
continue
#varname = var.name
for ref in var.references():
--- a/rtags.py Tue Aug 04 11:43:03 2009 +0200
+++ b/rtags.py Tue Aug 04 15:06:09 2009 +0200
@@ -101,6 +101,7 @@
'%r is not an allowed tag (should be in %s)' % (
tag, self._allowed_values)
self._tagdefs[(rtype, tagged, stype, otype)] = tag
+ return tag
# rtag runtime api ########################################################
@@ -123,15 +124,19 @@
class RelationTagsSet(RelationTags):
- """This class associates a set of tags to each key."""
+ """This class associates a set of tags to each key.
+ """
+ tag_container_cls = set
def tag_relation(self, key, tag):
stype, rtype, otype, tagged = [str(k) for k in key]
- rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set())
+ rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype),
+ self.tag_container_cls())
rtags.add(tag)
+ return rtags
def get(self, stype, rtype, otype, tagged):
- rtags = set()
+ rtags = self.tag_container_cls()
for key in self._get_keys(stype, rtype, otype, tagged):
try:
rtags.update(self._tagdefs[key])
@@ -140,6 +145,31 @@
return rtags
+class RelationTagsDict(RelationTagsSet):
+ """This class associates a set of tags to each key."""
+ tag_container_cls = dict
+
+ def tag_relation(self, key, tag):
+ stype, rtype, otype, tagged = [str(k) for k in key]
+ try:
+ rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+ rtags.update(tag)
+ return rtags
+ except KeyError:
+ self._tagdefs[(rtype, tagged, stype, otype)] = tag
+ return tag
+
+ def setdefault(self, key, tagkey, tagvalue):
+ stype, rtype, otype, tagged = [str(k) for k in key]
+ try:
+ rtags = self._tagdefs[(rtype, tagged, stype, otype)]
+ rtags.setdefault(tagkey, tagvalue)
+ return rtags
+ except KeyError:
+ self._tagdefs[(rtype, tagged, stype, otype)] = {tagkey: tagvalue}
+ return self._tagdefs[(rtype, tagged, stype, otype)]
+
+
class RelationTagsBool(RelationTags):
_allowed_values = frozenset((True, False))
--- a/schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,19 +6,23 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
import re
+from os.path import join
from logging import getLogger
from warnings import warn
from logilab.common.decorators import cached, clear_cache, monkeypatch
+from logilab.common.deprecation import deprecated
from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema
-from yams.constraints import BaseConstraint, StaticVocabularyConstraint
-from yams.reader import (CONSTRAINTS, RelationFileReader, PyFileReader,
- SchemaLoader)
+from yams.constraints import (BaseConstraint, StaticVocabularyConstraint,
+ FormatConstraint)
+from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
+ obsolete as yobsolete, cleanup_sys_modules)
from rql import parse, nodes, RQLSyntaxError, TypeResolverException
@@ -30,11 +34,24 @@
schema.use_py_datetime()
nodes.use_py_datetime()
-_ = unicode
+PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
+VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
+
+# set of meta-relations available for every entity types
+META_RTYPES = set((
+ 'owned_by', 'created_by', 'is', 'is_instance_of', 'identity',
+ 'eid', 'creation_date', 'modification_date', 'has_text', 'cwuri',
+ ))
-BASEGROUPS = ('managers', 'users', 'guests', 'owners')
+# set of entity and relation types used to build the schema
+SCHEMA_TYPES = set((
+ 'CWEType', 'CWRType', 'CWAttribute', 'CWRelation',
+ 'CWConstraint', 'CWConstraintType', 'RQLExpression',
+ 'relation_type', 'from_entity', 'to_entity',
+ 'constrained_by', 'cstrtype',
+ ))
-LOGGER = getLogger('cubicweb.schemaloader')
+_LOGGER = getLogger('cubicweb.schemaloader')
# schema entities created from serialized schema have an eid rproperty
ybo.ETYPE_PROPERTIES += ('eid',)
@@ -49,65 +66,6 @@
etype = ETYPE_NAME_MAP[etype]
return etype
-# monkey path yams.builder.RelationDefinition to support a new wildcard type '@'
-# corresponding to system entity (ie meta but not schema)
-def _actual_types(self, schema, etype):
- # two bits of error checking & reporting :
- if type(etype) not in (str, list, tuple):
- raise RuntimeError, ('Entity types must not be instances but strings or'
- ' list/tuples thereof. Ex. (bad, good) : '
- 'SubjectRelation(Foo), SubjectRelation("Foo"). '
- 'Hence, %r is not acceptable.' % etype)
- # real work :
- if etype == '**':
- return self._pow_etypes(schema)
- if isinstance(etype, (tuple, list)):
- return etype
- if '*' in etype or '@' in etype:
- assert len(etype) in (1, 2)
- etypes = ()
- if '*' in etype:
- etypes += tuple(self._wildcard_etypes(schema))
- if '@' in etype:
- etypes += tuple(system_etypes(schema))
- return etypes
- return (etype,)
-ybo.RelationDefinition._actual_types = _actual_types
-
-
-## cubicweb provides a RichString class for convenience
-class RichString(ybo.String):
- """Convenience RichString attribute type
- The following declaration::
-
- class Card(EntityType):
- content = RichString(fulltextindexed=True, default_format='text/rest')
-
- is equivalent to::
-
- class Card(EntityType):
- content_format = String(meta=True, internationalizable=True,
- default='text/rest', constraints=[format_constraint])
- content = String(fulltextindexed=True)
- """
- def __init__(self, default_format='text/plain', format_constraints=None, **kwargs):
- self.default_format = default_format
- self.format_constraints = format_constraints or [format_constraint]
- super(RichString, self).__init__(**kwargs)
-
-PyFileReader.context['RichString'] = RichString
-
-## need to monkeypatch yams' _add_relation function to handle RichString
-yams_add_relation = ybo._add_relation
-@monkeypatch(ybo)
-def _add_relation(relations, rdef, name=None, insertidx=None):
- if isinstance(rdef, RichString):
- format_attrdef = ybo.String(meta=True, internationalizable=True,
- default=rdef.default_format, maxsize=50,
- constraints=rdef.format_constraints)
- yams_add_relation(relations, format_attrdef, name+'_format', insertidx)
- yams_add_relation(relations, rdef, name, insertidx)
-
def display_name(req, key, form=''):
"""return a internationalized string for the key (schema entity or relation
name) in a given form
@@ -120,7 +78,7 @@
# ensure unicode
# added .lower() in case no translation are available
return unicode(req._(key)).lower()
-__builtins__['display_name'] = display_name
+__builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
def ERSchema_display_name(self, req, form=''):
"""return a internationalized string for the entity/relation type name in
@@ -242,7 +200,7 @@
"""return system entity types only: skip final, schema and application entities
"""
for eschema in schema.entities():
- if eschema.is_final() or eschema.schema_entity() or not eschema.meta:
+ if eschema.is_final() or eschema.schema_entity():
continue
yield eschema.type
@@ -282,6 +240,15 @@
continue
yield rschema, attrschema
+ def main_attribute(self):
+ """convenience method that returns the *main* (i.e. the first non meta)
+ attribute defined in the entity schema
+ """
+ for rschema, _ in self.attribute_definitions():
+ if not (rschema in META_RTYPES
+ or self.is_metadata(rschema)):
+ return rschema
+
def add_subject_relation(self, rschema):
"""register the relation schema as possible subject relation"""
super(CubicWebEntitySchema, self).add_subject_relation(rschema)
@@ -289,10 +256,11 @@
def del_subject_relation(self, rtype):
super(CubicWebEntitySchema, self).del_subject_relation(rtype)
- self._update_has_text(False)
+ self._update_has_text(True)
- def _update_has_text(self, need_has_text=None):
+ def _update_has_text(self, deletion=False):
may_need_has_text, has_has_text = False, False
+ need_has_text = None
for rschema in self.subject_relations():
if rschema.is_final():
if rschema == 'has_text':
@@ -310,10 +278,9 @@
may_need_has_text = True
else:
need_has_text = False
- break
if need_has_text is None:
need_has_text = may_need_has_text
- if need_has_text and not has_has_text:
+ if need_has_text and not has_has_text and not deletion:
rdef = ybo.RelationDefinition(self.type, 'has_text', 'String')
self.schema.add_relation_def(rdef)
elif not need_has_text and has_has_text:
@@ -321,7 +288,7 @@
def schema_entity(self):
"""return True if this entity type is used to build the schema"""
- return self.type in self.schema.schema_entity_types()
+ return self.type in SCHEMA_TYPES
def check_perm(self, session, action, eid=None):
# NB: session may be a server session or a request object
@@ -345,6 +312,7 @@
"""rql expression factory"""
return ERQLExpression(expression, mainvars, eid)
+
class CubicWebRelationSchema(RelationSchema):
RelationSchema._RPROPERTIES['eid'] = None
_perms_checked = False
@@ -358,6 +326,9 @@
eid = getattr(rdef, 'eid', None)
self.eid = eid
+ @property
+ def meta(self):
+ return self.type in META_RTYPES
def update(self, subjschema, objschema, rdef):
super(CubicWebRelationSchema, self).update(subjschema, objschema, rdef)
@@ -396,8 +367,8 @@
(target == 'object' and card[1])
def schema_relation(self):
- return self.type in ('relation_type', 'from_entity', 'to_entity',
- 'constrained_by', 'cstrtype')
+ """return True if this relation type is used to build the schema"""
+ return self.type in SCHEMA_TYPES
def physical_mode(self):
"""return an appropriate mode for physical storage of this relation type:
@@ -434,7 +405,7 @@
:type name: str
- :ivar name: name of the schema, usually the application identifier
+ :ivar name: name of the schema, usually the instance identifier
:type base: str
:ivar base: path of the directory where the schema is defined
@@ -447,24 +418,16 @@
self._eid_index = {}
super(CubicWebSchema, self).__init__(*args, **kwargs)
ybo.register_base_types(self)
- rschema = self.add_relation_type(ybo.RelationType('eid', meta=True))
+ rschema = self.add_relation_type(ybo.RelationType('eid'))
rschema.final = True
rschema.set_default_groups()
- rschema = self.add_relation_type(ybo.RelationType('has_text', meta=True))
+ rschema = self.add_relation_type(ybo.RelationType('has_text'))
rschema.final = True
rschema.set_default_groups()
- rschema = self.add_relation_type(ybo.RelationType('identity', meta=True))
+ rschema = self.add_relation_type(ybo.RelationType('identity'))
rschema.final = False
rschema.set_default_groups()
- def schema_entity_types(self):
- """return the list of entity types used to build the schema"""
- return frozenset(('CWEType', 'CWRType', 'CWAttribute', 'CWRelation',
- 'CWConstraint', 'CWConstraintType', 'RQLExpression',
- # XXX those are not really "schema" entity types
- # but we usually don't want them as @* targets
- 'CWProperty', 'CWPermission', 'State', 'Transition'))
-
def add_entity_type(self, edef):
edef.name = edef.name.encode()
edef.name = bw_normalize_etype(edef.name)
@@ -636,8 +599,8 @@
raise RQLSyntaxError(expression)
for mainvar in mainvars.split(','):
if len(self.rqlst.defined_vars[mainvar].references()) <= 2:
- LOGGER.warn('You did not use the %s variable in your RQL expression %s',
- mainvar, self)
+ _LOGGER.warn('You did not use the %s variable in your RQL '
+ 'expression %s', mainvar, self)
def __str__(self):
return self.full_rql
@@ -787,7 +750,7 @@
return self._check(session, x=eid)
return self._check(session)
-PyFileReader.context['ERQLExpression'] = ERQLExpression
+PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
class RRQLExpression(RQLExpression):
def __init__(self, expression, mainvars=None, eid=None):
@@ -831,9 +794,10 @@
kwargs['o'] = toeid
return self._check(session, **kwargs)
-PyFileReader.context['RRQLExpression'] = RRQLExpression
+PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
# workflow extensions #########################################################
+from yams.buildobjs import _add_relation as yams_add_relation
class workflowable_definition(ybo.metadefinition):
"""extends default EntityType's metaclass to add workflow relations
@@ -863,23 +827,6 @@
# schema loading ##############################################################
-class CubicWebRelationFileReader(RelationFileReader):
- """cubicweb specific relation file reader, handling additional RQL
- constraints on a relation definition
- """
-
- def handle_constraint(self, rdef, constraint_text):
- """arbitrary constraint is an rql expression for cubicweb"""
- if not rdef.constraints:
- rdef.constraints = []
- rdef.constraints.append(RQLVocabularyConstraint(constraint_text))
-
- def process_properties(self, rdef, relation_def):
- if 'inline' in relation_def:
- rdef.inlined = True
- RelationFileReader.process_properties(self, rdef, relation_def)
-
-
CONSTRAINTS['RQLConstraint'] = RQLConstraint
CONSTRAINTS['RQLUniqueConstraint'] = RQLUniqueConstraint
CONSTRAINTS['RQLVocabularyConstraint'] = RQLVocabularyConstraint
@@ -891,8 +838,6 @@
the persistent schema
"""
schemacls = CubicWebSchema
- SchemaLoader.file_handlers.update({'.rel' : CubicWebRelationFileReader,
- })
def load(self, config, path=(), **kwargs):
"""return a Schema instance from the schema definition read
@@ -904,9 +849,9 @@
def _load_definition_files(self, cubes=None):
# bootstraping, ignore cubes
- for filepath in self.include_schema_files('bootstrap'):
- self.info('loading %s', filepath)
- self.handle_file(filepath)
+ filepath = join(self.lib_directory, 'bootstrap.py')
+ self.info('loading %s', filepath)
+ self.handle_file(filepath)
def unhandled_file(self, filepath):
"""called when a file without handler associated has been found"""
@@ -915,7 +860,7 @@
class CubicWebSchemaLoader(BootstrapSchemaLoader):
"""cubicweb specific schema loader, automatically adding metadata to the
- application's schema
+ instance's schema
"""
def load(self, config, **kwargs):
@@ -924,16 +869,20 @@
"""
self.info('loading %s schemas', ', '.join(config.cubes()))
if config.apphome:
- path = reversed([config.apphome] + config.cubes_path())
+ path = tuple(reversed([config.apphome] + config.cubes_path()))
else:
- path = reversed(config.cubes_path())
- return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
+ path = tuple(reversed(config.cubes_path()))
+ try:
+ return super(CubicWebSchemaLoader, self).load(config, path=path, **kwargs)
+ finally:
+ # we've to cleanup modules imported from cubicweb.schemas as well
+ cleanup_sys_modules([self.lib_directory])
def _load_definition_files(self, cubes):
- for filepath in (self.include_schema_files('bootstrap')
- + self.include_schema_files('base')
- + self.include_schema_files('workflow')
- + self.include_schema_files('Bookmark')):
+ for filepath in (join(self.lib_directory, 'bootstrap.py'),
+ join(self.lib_directory, 'base.py'),
+ join(self.lib_directory, 'workflow.py'),
+ join(self.lib_directory, 'Bookmark.py')):
self.info('loading %s', filepath)
self.handle_file(filepath)
for cube in cubes:
@@ -942,49 +891,22 @@
self.handle_file(filepath)
+set_log_methods(CubicWebSchemaLoader, getLogger('cubicweb.schemaloader'))
+set_log_methods(BootstrapSchemaLoader, getLogger('cubicweb.bootstrapschemaloader'))
+set_log_methods(RQLExpression, getLogger('cubicweb.schema'))
+
# _() is just there to add messages to the catalog, don't care about actual
# translation
PERM_USE_TEMPLATE_FORMAT = _('use_template_format')
-
-class FormatConstraint(StaticVocabularyConstraint):
- need_perm_formats = [_('text/cubicweb-page-template')]
-
- regular_formats = (_('text/rest'),
- _('text/html'),
- _('text/plain'),
- )
- def __init__(self):
- pass
-
- def serialize(self):
- """called to make persistent valuable data of a constraint"""
- return None
+NEED_PERM_FORMATS = [_('text/cubicweb-page-template')]
- @classmethod
- def deserialize(cls, value):
- """called to restore serialized data of a constraint. Should return
- a `cls` instance
- """
- return cls()
-
- def vocabulary(self, entity=None, req=None):
- if req is None and entity is not None:
- req = entity.req
- if req is not None and req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
- return self.regular_formats + tuple(self.need_perm_formats)
- return self.regular_formats
-
- def __str__(self):
- return 'value in (%s)' % u', '.join(repr(unicode(word)) for word in self.vocabulary())
-
-
-format_constraint = FormatConstraint()
-CONSTRAINTS['FormatConstraint'] = FormatConstraint
-PyFileReader.context['format_constraint'] = format_constraint
-
-set_log_methods(CubicWebSchemaLoader, getLogger('cubicweb.schemaloader'))
-set_log_methods(BootstrapSchemaLoader, getLogger('cubicweb.bootstrapschemaloader'))
-set_log_methods(RQLExpression, getLogger('cubicweb.schema'))
+@monkeypatch(FormatConstraint)
+def vocabulary(self, entity=None, req=None):
+ if req is None and entity is not None:
+ req = entity.req
+ if req is not None and req.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
+ return self.regular_formats + tuple(NEED_PERM_FORMATS)
+ return self.regular_formats
# XXX monkey patch PyFileReader.import_erschema until bw_normalize_etype is
# necessary
--- a/schemas/Bookmark.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schemas/Bookmark.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,14 +1,26 @@
-"""
+"""the Bookmark entity type for internal links
: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
"""
+__docformat__ = "restructuredtext en"
+_ = unicode
-class Bookmark(MetaUserEntityType):
- """define an entity type, used to build the application schema"""
- title = String(required=True, maxsize=128)
+from yams.buildobjs import EntityType, RelationType, SubjectRelation, String
+from cubicweb.schema import RRQLExpression
+
+class Bookmark(EntityType):
+ """bookmarks are used to have user's specific internal links"""
+ permissions = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': ('managers', 'users',),
+ 'delete': ('managers', 'owners',),
+ 'update': ('managers', 'owners',),
+ }
+
+ title = String(required=True, maxsize=128, internationalizable=True)
path = String(maxsize=512, required=True,
description=_("relative url of the bookmarked page"))
@@ -16,7 +28,7 @@
description=_("users using this bookmark"))
-class bookmarked_by(MetaUserRelationType):
+class bookmarked_by(RelationType):
permissions = {'read': ('managers', 'users', 'guests',),
# test user in users group to avoid granting permission to anonymous user
'add': ('managers', RRQLExpression('O identity U, U in_group G, G name "users"')),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/schemas/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,24 @@
+# permissions for "meta" entity type (readable by anyone, can only be
+# added/deleted by managers)
+META_ETYPE_PERMS = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': ('managers',),
+ 'delete': ('managers',),
+ 'update': ('managers', 'owners',),
+ }
+
+# permissions for "meta" relation type (readable by anyone, can only be
+# added/deleted by managers)
+META_RTYPE_PERMS = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': ('managers',),
+ 'delete': ('managers',),
+ }
+
+# permissions for relation type that should only set by hooks using unsafe
+# execute, readable by anyone
+HOOKS_RTYPE_PERMS = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': (),
+ 'delete': (),
+ }
--- a/schemas/base.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schemas/base.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,11 +6,16 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
+from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
+ String, Boolean, Datetime, Password)
+from cubicweb.schema import (RQLConstraint, WorkflowableEntityType,
+ ERQLExpression, RRQLExpression)
+from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
class CWUser(WorkflowableEntityType):
"""define a CubicWeb user"""
- meta = True # XXX backported from old times, shouldn't be there anymore
permissions = {
'read': ('managers', 'users', ERQLExpression('X identity U')),
'add': ('managers',),
@@ -35,7 +40,7 @@
description=_('groups grant permissions to the user'))
-class EmailAddress(MetaEntityType):
+class EmailAddress(EntityType):
"""an electronic mail address associated to a short alias"""
permissions = {
'read': ('managers', 'users', 'guests',), # XXX if P use_email X, U has_read_permission P
@@ -81,11 +86,11 @@
'delete': ('managers', RRQLExpression('U has_update_permission S'),),
}
-class in_group(MetaRelationType):
+class in_group(RelationType):
"""core relation indicating a user's groups"""
- meta = False
+ permissions = META_RTYPE_PERMS
-class owned_by(MetaRelationType):
+class owned_by(RelationType):
"""core relation indicating owners of an entity. This relation
implicitly put the owner into the owners group for the entity
"""
@@ -97,10 +102,10 @@
# 0..n cardinality for entities created by internal session (no attached user)
# and to support later deletion of a user which has created some entities
cardinality = '**'
- subject = '**'
+ subject = '*'
object = 'CWUser'
-class created_by(MetaRelationType):
+class created_by(RelationType):
"""core relation indicating the original creator of an entity"""
permissions = {
'read': ('managers', 'users', 'guests'),
@@ -110,22 +115,28 @@
# 0..1 cardinality for entities created by internal session (no attached user)
# and to support later deletion of a user which has created some entities
cardinality = '?*'
- subject = '**'
+ subject = '*'
object = 'CWUser'
-class creation_date(MetaAttributeRelationType):
+class creation_date(RelationType):
"""creation time of an entity"""
cardinality = '11'
- subject = '**'
+ subject = '*'
object = 'Datetime'
-class modification_date(MetaAttributeRelationType):
+class modification_date(RelationType):
"""latest modification time of an entity"""
cardinality = '11'
- subject = '**'
+ subject = '*'
object = 'Datetime'
+class cwuri(RelationType):
+ """internal entity uri"""
+ cardinality = '11'
+ subject = '*'
+ object = 'String'
+
class CWProperty(EntityType):
"""used for cubicweb configuration. Once a property has been created you
@@ -137,7 +148,6 @@
'update': ('managers', 'owners',),
'delete': ('managers', 'owners',),
}
- meta = True
# key is a reserved word for mysql
pkey = String(required=True, internationalizable=True, maxsize=256,
description=_('defines what\'s the property is applied for. '
@@ -152,7 +162,7 @@
' a global property'))
-class for_user(MetaRelationType):
+class for_user(RelationType):
"""link a property to the user which want this property customization. Unless
you're a site manager, this relation will be handled automatically.
"""
@@ -164,9 +174,11 @@
inlined = True
-class CWPermission(MetaEntityType):
+class CWPermission(EntityType):
"""entity type that may be used to construct some advanced security configuration
"""
+ permissions = META_ETYPE_PERMS
+
name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
description=_('name or identifier of the permission'))
label = String(required=True, internationalizable=True, maxsize=100,
@@ -186,7 +198,7 @@
'delete': ('managers',),
}
-class require_group(MetaRelationType):
+class require_group(RelationType):
"""used to grant a permission to a group"""
permissions = {
'read': ('managers', 'users', 'guests'),
@@ -199,8 +211,31 @@
"""generic relation to link one entity to another"""
symetric = True
+class ExternalUri(EntityType):
+ """a URI representing an object in external data store"""
+ uri = String(required=True, unique=True, maxsize=256,
+ description=_('the URI of the object'))
-class CWCache(MetaEntityType):
+class same_as(RelationType):
+ """generic relation to specify that an external entity represent the same
+ object as a local one:
+ http://www.w3.org/TR/owl-ref/#sameAs-def
+
+ NOTE: You'll have to explicitly declare which entity types can have a
+ same_as relation
+ """
+ permissions = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': ('managers', 'users'),
+ 'delete': ('managers', 'owners'),
+ }
+ cardinality = '*1'
+ symetric = True
+ # NOTE: the 'object = ExternalUri' declaration will still be mandatory
+ # in the cube's schema.
+ object = 'ExternalUri'
+
+class CWCache(EntityType):
"""a simple cache entity characterized by a name and
a validity date.
@@ -212,7 +247,7 @@
permissions = {
'read': ('managers', 'users', 'guests'),
'add': ('managers',),
- 'update': ('managers', 'users',),
+ 'update': ('managers', 'users',), # XXX
'delete': ('managers',),
}
--- a/schemas/bootstrap.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schemas/bootstrap.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,36 +1,38 @@
-"""core CubicWeb schema necessary for bootstrapping the actual application's schema
+"""core CubicWeb schema necessary for bootstrapping the actual instance's schema
: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
"""
+__docformat__ = "restructuredtext en"
+_ = unicode
-from cubicweb.schema import format_constraint
-
+from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
+ ObjectRelation, RichString, String, Boolean, Int)
+from cubicweb.schema import RQLConstraint
+from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
# not restricted since as "is" is handled as other relations, guests need
# access to this
-class CWEType(MetaEntityType):
- """define an entity type, used to build the application schema"""
+class CWEType(EntityType):
+ """define an entity type, used to build the instance schema"""
+ permissions = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
description = RichString(internationalizable=True,
description=_('semantic description of this entity type'))
- meta = Boolean(description=_('is it an application entity type or not ?'))
# necessary to filter using RQL
final = Boolean(description=_('automatic'))
-class CWRType(MetaEntityType):
- """define a relation type, used to build the application schema"""
+class CWRType(EntityType):
+ """define a relation type, used to build the instance schema"""
+ permissions = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/plain', constraints=[format_constraint])
- description = String(internationalizable=True,
- description=_('semantic description of this relation type'))
- meta = Boolean(description=_('is it an application relation type or not ?'))
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this relation type'))
symetric = Boolean(description=_('is this relation equivalent in both direction ?'))
inlined = Boolean(description=_('is this relation physically inlined? you should know what you\'re doing if you are changing this!'))
fulltext_container = String(description=_('if full text content of subject/object entity '
@@ -40,12 +42,13 @@
final = Boolean(description=_('automatic'))
-class CWAttribute(MetaEntityType):
+class CWAttribute(EntityType):
"""define a final relation: link a final relation type from a non final
entity to a final entity type.
- used to build the application schema
+ used to build the instance schema
"""
+ permissions = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final TRUE')],
composite='object')
@@ -67,10 +70,8 @@
internationalizable = Boolean(description=_('is this attribute\'s value translatable'))
defaultval = String(maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/plain', constraints=[format_constraint])
- description = String(internationalizable=True,
- description=_('semantic description of this attribute'))
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this attribute'))
CARDINALITY_VOCAB = [_('?*'), _('1*'), _('+*'), _('**'),
@@ -78,12 +79,13 @@
_('?1'), _('11'), _('+1'), _('*1'),
_('??'), _('1?'), _('+?'), _('*?')]
-class CWRelation(MetaEntityType):
+class CWRelation(EntityType):
"""define a non final relation: link a non final relation type from a non
final entity to a non final entity type.
- used to build the application schema
+ used to build the instance schema
"""
+ permissions = META_ETYPE_PERMS
relation_type = SubjectRelation('CWRType', cardinality='1*',
constraints=[RQLConstraint('O final FALSE')],
composite='object')
@@ -107,15 +109,14 @@
vocabulary=('', _('subject'), _('object')),
maxsize=8, default=None)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/plain', constraints=[format_constraint])
- description = String(internationalizable=True,
- description=_('semantic description of this relation'))
+ description = RichString(internationalizable=True,
+ description=_('semantic description of this relation'))
# not restricted since it has to be read when checking allowed transitions
-class RQLExpression(MetaEntityType):
+class RQLExpression(EntityType):
"""define a rql expression used to define permissions"""
+ permissions = META_ETYPE_PERMS
exprtype = String(required=True, vocabulary=['ERQLExpression', 'RRQLExpression'])
mainvars = String(maxsize=8,
description=_('name of the main variables which should be '
@@ -140,21 +141,24 @@
description=_('rql expression allowing to update entities of this type'))
-class CWConstraint(MetaEntityType):
+class CWConstraint(EntityType):
"""define a schema constraint"""
+ permissions = META_ETYPE_PERMS
cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
value = String(description=_('depends on the constraint type'))
-class CWConstraintType(MetaEntityType):
+class CWConstraintType(EntityType):
"""define a schema constraint type"""
+ permissions = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
# not restricted since it has to be read when checking allowed transitions
-class CWGroup(MetaEntityType):
+class CWGroup(EntityType):
"""define a CubicWeb users group"""
+ permissions = META_ETYPE_PERMS
name = String(required=True, indexed=True, internationalizable=True,
unique=True, maxsize=64)
@@ -169,40 +173,55 @@
-class relation_type(MetaRelationType):
+class relation_type(RelationType):
"""link a relation definition to its relation type"""
- inlined = True
-class from_entity(MetaRelationType):
- """link a relation definition to its subject entity type"""
+ permissions = META_RTYPE_PERMS
inlined = True
-class to_entity(MetaRelationType):
- """link a relation definition to its object entity type"""
- inlined = True
-class constrained_by(MetaRelationType):
- """constraints applying on this relation"""
-class cstrtype(MetaRelationType):
- """constraint factory"""
+class from_entity(RelationType):
+ """link a relation definition to its subject entity type"""
+ permissions = META_RTYPE_PERMS
inlined = True
-class read_permission(MetaRelationType):
+class to_entity(RelationType):
+ """link a relation definition to its object entity type"""
+ permissions = META_RTYPE_PERMS
+ inlined = True
+
+class constrained_by(RelationType):
+ """constraints applying on this relation"""
+ permissions = META_RTYPE_PERMS
+
+class cstrtype(RelationType):
+ """constraint factory"""
+ permissions = META_RTYPE_PERMS
+ inlined = True
+
+class read_permission(RelationType):
"""core relation giving to a group the permission to read an entity or
relation type
"""
-class add_permission(MetaRelationType):
+ permissions = META_RTYPE_PERMS
+
+class add_permission(RelationType):
"""core relation giving to a group the permission to add an entity or
relation type
"""
-class delete_permission(MetaRelationType):
+ permissions = META_RTYPE_PERMS
+
+class delete_permission(RelationType):
"""core relation giving to a group the permission to delete an entity or
relation type
"""
-class update_permission(MetaRelationType):
+ permissions = META_RTYPE_PERMS
+
+class update_permission(RelationType):
"""core relation giving to a group the permission to update an entity type
"""
+ permissions = META_RTYPE_PERMS
-class is_(MetaRelationType):
+class is_(RelationType):
"""core relation indicating the type of an entity
"""
name = 'is'
@@ -214,10 +233,10 @@
'delete': (),
}
cardinality = '1*'
- subject = '**'
+ subject = '*'
object = 'CWEType'
-class is_instance_of(MetaRelationType):
+class is_instance_of(RelationType):
"""core relation indicating the types (including specialized types)
of an entity
"""
@@ -229,10 +248,10 @@
'delete': (),
}
cardinality = '+*'
- subject = '**'
+ subject = '*'
object = 'CWEType'
-class specializes(MetaRelationType):
+class specializes(RelationType):
name = 'specializes'
permissions = {
'read': ('managers', 'users', 'guests'),
--- a/schemas/workflow.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schemas/workflow.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,11 +5,20 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+__docformat__ = "restructuredtext en"
+_ = unicode
-class State(MetaEntityType):
+from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
+ ObjectRelation, RichString, String)
+from cubicweb.schema import RQLConstraint
+from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS, HOOKS_RTYPE_PERMS
+
+class State(EntityType):
"""used to associate simple states to an entity type and/or to define
workflows
"""
+ permissions = META_ETYPE_PERMS
+
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
description = RichString(fulltextindexed=True, default_format='text/rest',
@@ -28,15 +37,15 @@
description=_('initial state for entities of this type'))
-class Transition(MetaEntityType):
+class Transition(EntityType):
"""use to define a transition from one or multiple states to a destination
states in workflow's definitions.
"""
+ permissions = META_ETYPE_PERMS
+
name = String(required=True, indexed=True, internationalizable=True,
maxsize=256)
- description_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True,
+ description = RichString(fulltextindexed=True,
description=_('semantic description of this transition'))
condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
description=_('a RQL expression which should return some results, '
@@ -51,25 +60,28 @@
transition_of = SubjectRelation('CWEType', cardinality='+*',
description=_('entity types which may use this transition'),
constraints=[RQLConstraint('O final FALSE')])
- destination_state = SubjectRelation('State', cardinality='?*',
+ destination_state = SubjectRelation('State', cardinality='1*',
constraints=[RQLConstraint('S transition_of ET, O state_of ET')],
description=_('destination state for this transition'))
-class TrInfo(MetaEntityType):
+class TrInfo(EntityType):
+ permissions = META_ETYPE_PERMS
+
from_state = SubjectRelation('State', cardinality='?*')
to_state = SubjectRelation('State', cardinality='1*')
- comment_format = String(meta=True, internationalizable=True, maxsize=50,
- default='text/rest', constraints=[format_constraint])
- comment = String(fulltextindexed=True)
+ comment = RichString(fulltextindexed=True)
# get actor and date time using owned_by and creation_date
-class from_state(MetaRelationType):
+class from_state(RelationType):
+ permissions = HOOKS_RTYPE_PERMS
inlined = True
-class to_state(MetaRelationType):
+class to_state(RelationType):
+ permissions = HOOKS_RTYPE_PERMS
inlined = True
-class wf_info_for(MetaRelationType):
+
+class wf_info_for(RelationType):
"""link a transition information to its object"""
permissions = {
'read': ('managers', 'users', 'guests',),# RRQLExpression('U has_read_permission O')),
@@ -80,30 +92,39 @@
composite = 'object'
fulltext_container = composite
-class state_of(MetaRelationType):
+class state_of(RelationType):
"""link a state to one or more entity type"""
-class transition_of(MetaRelationType):
+ permissions = META_RTYPE_PERMS
+class transition_of(RelationType):
"""link a transition to one or more entity type"""
+ permissions = META_RTYPE_PERMS
-class initial_state(MetaRelationType):
+class initial_state(RelationType):
"""indicate which state should be used by default when an entity using
states is created
"""
+ permissions = META_RTYPE_PERMS
inlined = True
-class destination_state(MetaRelationType):
+class destination_state(RelationType):
"""destination state of a transition"""
+ permissions = META_RTYPE_PERMS
inlined = True
-class allowed_transition(MetaRelationType):
+class allowed_transition(RelationType):
"""allowed transition from this state"""
+ permissions = META_RTYPE_PERMS
-class in_state(UserRelationType):
+class in_state(RelationType):
"""indicate the current state of an entity"""
- meta = True
# not inlined intentionnaly since when using ldap sources, user'state
# has to be stored outside the CWUser table
-
+ inlined = False
# add/delete perms given to managers/users, after what most of the job
# is done by workflow enforcment
+ permissions = {
+ 'read': ('managers', 'users', 'guests',),
+ 'add': ('managers', 'users',), # XXX has_update_perm
+ 'delete': ('managers', 'users',),
+ }
--- a/schemaviewer.py Tue Aug 04 11:43:03 2009 +0200
+++ b/schemaviewer.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,11 +6,11 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.common.ureports import Section, Title, Table, Link, Span, Text
from yams.schema2dot import CARD_MAP
-_ = unicode
I18NSTRINGS = [_('read'), _('add'), _('delete'), _('update'), _('order')]
class SchemaViewer(object):
@@ -38,8 +38,7 @@
klass='acl')
- def visit_schema(self, schema, display_relations=0,
- skiprels=(), skipmeta=True):
+ def visit_schema(self, schema, display_relations=0, skiptypes=()):
"""get a layout for a whole schema"""
title = Title(self.req._('Schema %s') % schema.name,
klass='titleUnderline')
@@ -48,21 +47,15 @@
klass='titleUnderline'),))
layout.append(esection)
eschemas = [eschema for eschema in schema.entities()
- if not eschema.is_final()]
- if skipmeta:
- eschemas = [eschema for eschema in eschemas
- if not eschema.meta]
+ if not (eschema.is_final() or eschema in skiptypes)]
for eschema in sorted(eschemas):
- esection.append(self.visit_entityschema(eschema, skiprels))
+ esection.append(self.visit_entityschema(eschema, skiptypes))
if display_relations:
title = Title(self.req._('Relations'), klass='titleUnderline')
rsection = Section(children=(title,))
layout.append(rsection)
relations = [rschema for rschema in schema.relations()
- if not (rschema.is_final() or rschema.type in skiprels)]
- if skipmeta:
- relations = [rschema for rschema in relations
- if not rschema.meta]
+ if not (rschema.is_final() or rschema.type in skiptypes)]
keys = [(rschema.type, rschema) for rschema in relations]
for key, rschema in sorted(keys):
relstr = self.visit_relationschema(rschema)
@@ -107,17 +100,13 @@
def stereotype(self, name):
return Span((' <<%s>>' % name,), klass='stereotype')
- def visit_entityschema(self, eschema, skiprels=()):
+ def visit_entityschema(self, eschema, skiptypes=()):
"""get a layout for an entity schema"""
etype = eschema.type
layout = Section(children=' ', klass='clear')
layout.append(Link(etype,' ' , id=etype)) # anchor
title = Link(self.eschema_link_url(eschema), etype)
- if eschema.meta:
- stereotype = self.stereotype('meta')
- boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req), stereotype), klass='title')]
- else:
- boxchild = [Section(children=(title, ' (%s)'%eschema.display_name(self.req)), klass='title')]
+ boxchild = [Section(children=(title, ' (%s)'% eschema.display_name(self.req)), klass='title')]
table = Table(cols=4, rheaders=1,
children=self._entity_attributes_data(eschema))
boxchild.append(Section(children=(table,), klass='body'))
@@ -129,7 +118,7 @@
rels = []
first = True
for rschema, targetschemas, x in eschema.relation_definitions():
- if rschema.type in skiprels:
+ if rschema.type in skiptypes:
continue
if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
continue
--- a/selectors.py Tue Aug 04 11:43:03 2009 +0200
+++ b/selectors.py Tue Aug 04 15:06:09 2009 +0200
@@ -46,7 +46,7 @@
from warnings import warn
from logilab.common.compat import all
-from logilab.common.deprecation import deprecated_function
+from logilab.common.deprecation import deprecated
from logilab.common.interface import implements as implements_iface
from yams import BASE_TYPES
@@ -79,7 +79,7 @@
ret = selector(cls, *args, **kwargs)
if TRACED_OIDS == 'all' or oid in TRACED_OIDS:
#SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls)
- print 'selector %s returned %s for %s' % (selname, ret, vobj)
+ print '%s -> %s for %s' % (selname, ret, vobj)
return ret
traced.__name__ = selector.__name__
return traced
@@ -482,8 +482,7 @@
def __call__(self, cls, req, *args, **kwargs):
score = 0
for param in self.expected:
- val = req.form.get(param)
- if not val:
+ if not param in req.form:
return 0
score += 1
return len(self.expected)
@@ -617,10 +616,18 @@
@lltrace
def __call__(self, cls, req, *args, **kwargs):
try:
- etype = req.form['etype']
+ etype = kwargs['etype']
except KeyError:
try:
- etype = kwargs['etype']
+ etype = req.form['etype']
+ except KeyError:
+ return 0
+ else:
+ # only check this is a known type if etype comes from req.form,
+ # else we want the error to propagate
+ try:
+ etype = cls.vreg.case_insensitive_etypes[etype.lower()]
+ req.form['etype'] = etype
except KeyError:
return 0
return self.score_class(cls.vreg.etype_class(etype), req)
@@ -969,86 +976,86 @@
# XXX DEPRECATED ##############################################################
-yes_selector = deprecated_function(yes)
-norset_selector = deprecated_function(none_rset)
-rset_selector = deprecated_function(any_rset)
-anyrset_selector = deprecated_function(nonempty_rset)
-emptyrset_selector = deprecated_function(empty_rset)
-onelinerset_selector = deprecated_function(one_line_rset)
-twolinerset_selector = deprecated_function(two_lines_rset)
-twocolrset_selector = deprecated_function(two_cols_rset)
-largerset_selector = deprecated_function(paginated_rset)
-sortedrset_selector = deprecated_function(sorted_rset)
-oneetyperset_selector = deprecated_function(one_etype_rset)
-multitype_selector = deprecated_function(two_etypes_rset)
-anonymous_selector = deprecated_function(anonymous_user)
-not_anonymous_selector = deprecated_function(authenticated_user)
-primaryview_selector = deprecated_function(primary_view)
-contextprop_selector = deprecated_function(match_context_prop)
+yes_selector = deprecated()(yes)
+norset_selector = deprecated()(none_rset)
+rset_selector = deprecated()(any_rset)
+anyrset_selector = deprecated()(nonempty_rset)
+emptyrset_selector = deprecated()(empty_rset)
+onelinerset_selector = deprecated()(one_line_rset)
+twolinerset_selector = deprecated()(two_lines_rset)
+twocolrset_selector = deprecated()(two_cols_rset)
+largerset_selector = deprecated()(paginated_rset)
+sortedrset_selector = deprecated()(sorted_rset)
+oneetyperset_selector = deprecated()(one_etype_rset)
+multitype_selector = deprecated()(two_etypes_rset)
+anonymous_selector = deprecated()(anonymous_user)
+not_anonymous_selector = deprecated()(authenticated_user)
+primaryview_selector = deprecated()(primary_view)
+contextprop_selector = deprecated()(match_context_prop)
+@deprecated('use non_final_entity instead of %s')
def nfentity_selector(cls, req, rset=None, row=None, col=0, **kwargs):
return non_final_entity()(cls, req, rset, row, col)
-nfentity_selector = deprecated_function(nfentity_selector)
+@deprecated('use implements instead of %s')
def implement_interface(cls, req, rset=None, row=None, col=0, **kwargs):
return implements(*cls.accepts_interfaces)(cls, req, rset, row, col)
-_interface_selector = deprecated_function(implement_interface)
-interface_selector = deprecated_function(implement_interface)
-implement_interface = deprecated_function(implement_interface, 'use implements')
+_interface_selector = deprecated()(implement_interface)
+interface_selector = deprecated()(implement_interface)
+@deprecated('use specified_etype_implements instead of %s')
def accept_etype(cls, req, *args, **kwargs):
"""check etype presence in request form *and* accepts conformance"""
return specified_etype_implements(*cls.accepts)(cls, req, *args)
-etype_form_selector = deprecated_function(accept_etype)
-accept_etype = deprecated_function(accept_etype, 'use specified_etype_implements')
+etype_form_selector = accept_etype
+@deprecated('use match_search_state instead of %s')
def searchstate_selector(cls, req, rset=None, row=None, col=0, **kwargs):
return match_search_state(cls.search_states)(cls, req, rset, row, col)
-searchstate_selector = deprecated_function(searchstate_selector)
+@deprecated('use match_user_groups instead of %s')
def match_user_group(cls, req, rset=None, row=None, col=0, **kwargs):
return match_user_groups(*cls.require_groups)(cls, req, rset, row, col, **kwargs)
-in_group_selector = deprecated_function(match_user_group)
-match_user_group = deprecated_function(match_user_group)
+in_group_selector = match_user_group
+@deprecated('use relation_possible instead of %s')
def has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
return relation_possible(cls.rtype, role(cls), cls.etype,
getattr(cls, 'require_permission', 'read'))(cls, req, rset, row, col, **kwargs)
-has_relation = deprecated_function(has_relation)
+@deprecated('use relation_possible instead of %s')
def one_has_relation(cls, req, rset=None, row=None, col=0, **kwargs):
return relation_possible(cls.rtype, role(cls), cls.etype,
getattr(cls, 'require_permission', 'read',
once_is_enough=True))(cls, req, rset, row, col, **kwargs)
-one_has_relation = deprecated_function(one_has_relation, 'use relation_possible selector')
+@deprecated('use implements instead of %s')
def accept_rset(cls, req, rset=None, row=None, col=0, **kwargs):
"""simply delegate to cls.accept_rset method"""
return implements(*cls.accepts)(cls, req, rset, row=row, col=col)
-accept_rset_selector = deprecated_function(accept_rset)
-accept_rset = deprecated_function(accept_rset, 'use implements selector')
+accept_rset_selector = accept_rset
accept = chainall(non_final_entity(), accept_rset, name='accept')
-accept_selector = deprecated_function(accept)
-accept = deprecated_function(accept, 'use implements selector')
+accept = deprecated('use implements selector')(accept)
+accept_selector = deprecated()(accept)
-accept_one = deprecated_function(chainall(one_line_rset, accept,
+accept_one = deprecated()(chainall(one_line_rset, accept,
name='accept_one'))
-accept_one_selector = deprecated_function(accept_one)
+accept_one_selector = deprecated()(accept_one)
def _rql_condition(cls, req, rset=None, row=None, col=0, **kwargs):
if cls.condition:
return rql_condition(cls.condition)(cls, req, rset, row, col)
return 1
-_rqlcondition_selector = deprecated_function(_rql_condition)
+_rqlcondition_selector = deprecated()(_rql_condition)
-rqlcondition_selector = deprecated_function(chainall(non_final_entity(), one_line_rset, _rql_condition,
+rqlcondition_selector = deprecated()(chainall(non_final_entity(), one_line_rset, _rql_condition,
name='rql_condition'))
+@deprecated('use but_etype instead of %s')
def but_etype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
return but_etype(cls.etype)(cls, req, rset, row, col)
-but_etype_selector = deprecated_function(but_etype_selector)
@lltrace
def etype_rtype_selector(cls, req, rset=None, row=None, col=0, **kwargs):
@@ -1063,24 +1070,25 @@
if not (rschema.has_perm(req, perm) or rschema.has_local_role(perm)):
return 0
return 1
-etype_rtype_selector = deprecated_function(etype_rtype_selector)
+etype_rtype_selector = deprecated()(etype_rtype_selector)
-#req_form_params_selector = deprecated_function(match_form_params) # form_params
-#kwargs_selector = deprecated_function(match_kwargs) # expected_kwargs
+#req_form_params_selector = deprecated()(match_form_params) # form_params
+#kwargs_selector = deprecated()(match_kwargs) # expected_kwargs
# compound selectors ##########################################################
searchstate_accept = chainall(nonempty_rset(), accept,
name='searchstate_accept')
-searchstate_accept_selector = deprecated_function(searchstate_accept)
+searchstate_accept_selector = deprecated()(searchstate_accept)
searchstate_accept_one = chainall(one_line_rset, accept, _rql_condition,
name='searchstate_accept_one')
-searchstate_accept_one_selector = deprecated_function(searchstate_accept_one)
+searchstate_accept_one_selector = deprecated()(searchstate_accept_one)
-searchstate_accept = deprecated_function(searchstate_accept)
-searchstate_accept_one = deprecated_function(searchstate_accept_one)
+searchstate_accept = deprecated()(searchstate_accept)
+searchstate_accept_one = deprecated()(searchstate_accept_one)
+# end of deprecation section ##################################################
def unbind_method(selector):
def new_selector(registered):
--- a/server/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -14,9 +14,78 @@
from os.path import join, exists
from logilab.common.modutils import LazyObject
+from logilab.common.textutils import splitstrip
-# server debugging flag
-DEBUG = False
+# server-side debugging #########################################################
+
+# server debugging flags. They may be combined using binary operators.
+DBG_NONE = 0 # no debug information
+DBG_RQL = 1 # rql execution information
+DBG_SQL = 2 # executed sql
+DBG_REPO = 4 # repository events
+DBG_MORE = 8 # repository events
+
+# current debug mode
+DEBUG = 0
+
+def set_debug(debugmode):
+ """change the repository debugging mode"""
+ global DEBUG
+ if not debugmode:
+ DEBUG = 0
+ return
+ if isinstance(debugmode, basestring):
+ for mode in splitstrip(debugmode, sep='|'):
+ DEBUG |= globals()[mode]
+ else:
+ DEBUG |= debugmode
+
+
+class debugged(object):
+ """repository debugging context manager / decorator
+
+ Can be used either as a context manager:
+
+ >>> with debugged(server.DBG_RQL | server.DBG_REPO):
+ ... # some code in which you want to debug repository activity,
+ ... # seing information about RQL being executed an repository events.
+
+ or as a function decorator:
+
+ >>> @debugged(server.DBG_RQL | server.DBG_REPO)
+ ... def some_function():
+ ... # some code in which you want to debug repository activity,
+ ... # seing information about RQL being executed an repository events
+
+ debug mode will be reseted at its original value when leaving the "with"
+ block or the decorated function
+ """
+ def __init__(self, debugmode):
+ self.debugmode = debugmode
+ self._clevel = None
+
+ def __enter__(self):
+ """enter with block"""
+ self._clevel = DEBUG
+ set_debug(self.debugmode)
+
+ def __exit__(self, exctype, exc, traceback):
+ """leave with block"""
+ set_debug(self._clevel)
+ return traceback is None
+
+ def __call__(self, func):
+ """decorate function"""
+ def wrapped(*args, **kwargs):
+ _clevel = DEBUG
+ set_debug(self.debugmode)
+ try:
+ return func(*args, **kwargs)
+ finally:
+ set_debug(self._clevel)
+ return wrapped
+
+# database initialization ######################################################
def init_repository(config, interactive=True, drop=False, vreg=None):
"""initialise a repository database by creating tables add filling them
@@ -24,16 +93,16 @@
a initial user)
"""
from glob import glob
- from cubicweb.schema import BASEGROUPS
+ from yams import BASE_GROUPS
from cubicweb.dbapi import in_memory_cnx
from cubicweb.server.repository import Repository
from cubicweb.server.utils import manager_userpasswd
from cubicweb.server.sqlutils import sqlexec, sqlschema, sqldropschema
# configuration to avoid db schema loading and user'state checking
# on connection
- read_application_schema = config.read_application_schema
+ read_instance_schema = config.read_instance_schema
bootstrap_schema = config.bootstrap_schema
- config.read_application_schema = False
+ config.read_instance_schema = False
config.creating = True
config.bootstrap_schema = True
config.consider_user_state = False
@@ -45,19 +114,19 @@
assert len(repo.sources) == 1, repo.sources
schema = repo.schema
sourcescfg = config.sources()
- print 'creating necessary tables into the system source'
+ _title = '-> creating tables '
+ print _title,
source = sourcescfg['system']
driver = source['db-driver']
sqlcnx = repo.system_source.get_connection()
sqlcursor = sqlcnx.cursor()
- def execute(sql, args=None):
- repo.system_source.doexec(sqlcursor, sql, args)
+ execute = sqlcursor.execute
if drop:
dropsql = sqldropschema(schema, driver)
try:
sqlexec(dropsql, execute)
except Exception, ex:
- print 'drop failed, skipped (%s)' % ex
+ print '-> drop failed, skipped (%s).' % ex
sqlcnx.rollback()
# schema entities and relations tables
# can't skip entities table even if system source doesn't support them,
@@ -69,14 +138,14 @@
schemasql = sqlschema(schema, driver)
#skip_entities=[str(e) for e in schema.entities()
# if not repo.system_source.support_entity(str(e))])
- sqlexec(schemasql, execute)
+ sqlexec(schemasql, execute, pbtitle=_title)
# install additional driver specific sql files
for fpath in glob(join(config.schemas_lib_dir(), '*.sql.%s' % driver)):
- print 'install', fpath
+ print '-> installing', fpath
sqlexec(open(fpath).read(), execute, False, delimiter=';;')
for directory in config.cubes_path():
for fpath in glob(join(directory, 'schema', '*.sql.%s' % driver)):
- print 'install', fpath
+ print '-> installing', fpath
sqlexec(open(fpath).read(), execute, False, delimiter=';;')
sqlcursor.close()
sqlcnx.commit()
@@ -91,15 +160,13 @@
login, pwd = manager_userpasswd(msg=msg, confirm=True)
else:
login, pwd = unicode(source['db-user']), source['db-password']
- print 'inserting default user and groups'
- needisfix = []
- for group in BASEGROUPS:
- rset = session.execute('INSERT CWGroup X: X name %(name)s',
- {'name': unicode(group)})
- needisfix.append( (rset.rows[0][0], rset.description[0][0]) )
- rset = session.execute('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s',
- {'login': login, 'pwd': pwd})
- needisfix.append( (rset.rows[0][0], rset.description[0][0]) )
+ print '-> inserting default user and default groups.'
+ # sort for eid predicatability as expected in some server tests
+ for group in sorted(BASE_GROUPS):
+ session.execute('INSERT CWGroup X: X name %(name)s',
+ {'name': unicode(group)})
+ session.execute('INSERT CWUser X: X login %(login)s, X upassword %(pwd)s',
+ {'login': login, 'pwd': pwd})
session.execute('SET U in_group G WHERE G name "managers"')
session.commit()
# reloging using the admin user
@@ -109,20 +176,22 @@
handler = config.migration_handler(schema, interactive=False,
cnx=cnx, repo=repo)
initialize_schema(config, schema, handler)
- # admin user and groups have been added before schema entities, fix the 'is'
- # relation
- for eid, etype in needisfix:
- handler.session.unsafe_execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
- {'x': eid, 'name': etype}, 'x')
- handler.session.unsafe_execute('SET X is_instance_of E WHERE X eid %(x)s, E name %(name)s',
- {'x': eid, 'name': etype}, 'x')
# insert versions
handler.cmd_add_entity('CWProperty', pkey=u'system.version.cubicweb',
value=unicode(config.cubicweb_version()))
for cube in config.cubes():
- handler.cmd_add_entity('CWProperty',
+ handler.cmd_add_entity('CWProperty',
pkey=u'system.version.%s' % cube.lower(),
value=unicode(config.cube_version(cube)))
+ # some entities have been added before schema entities, fix the 'is' and
+ # 'is_instance_of' relations
+ for rtype in ('is', 'is_instance_of'):
+ handler.sqlexec(
+ 'INSERT INTO %s_relation '
+ 'SELECT X.eid, ET.cw_eid FROM entities as X, cw_CWEType as ET '
+ 'WHERE X.type=ET.cw_name AND NOT EXISTS('
+ ' SELECT 1 from is_relation '
+ ' WHERE eid_from=X.eid AND eid_to=ET.cw_eid)' % rtype)
# yoo !
cnx.commit()
config.enabled_sources = None
@@ -137,11 +206,11 @@
session.close()
# restore initial configuration
config.creating = False
- config.read_application_schema = read_application_schema
+ config.read_instance_schema = read_instance_schema
config.bootstrap_schema = bootstrap_schema
config.consider_user_state = True
config.set_language = True
- print 'application %s initialized' % config.appid
+ print '-> database for instance %s initialized.' % config.appid
def initialize_schema(config, schema, mhandler, event='create'):
@@ -153,7 +222,7 @@
# execute cubes pre<event> script if any
for path in reversed(paths):
mhandler.exec_event_script('pre%s' % event, path)
- # enter application'schema into the database
+ # enter instance'schema into the database
serialize_schema(mhandler.rqlcursor, schema)
# execute cubicweb's post<event> script
mhandler.exec_event_script('post%s' % event)
@@ -161,20 +230,6 @@
for path in reversed(paths):
mhandler.exec_event_script('post%s' % event, path)
-def set_debug(debugmode):
- global DEBUG
- DEBUG = debugmode
-
-def debugged(func):
- """decorator to activate debug mode"""
- def wrapped(*args, **kwargs):
- global DEBUG
- DEBUG = True
- try:
- return func(*args, **kwargs)
- finally:
- DEBUG = False
- return wrapped
# sqlite'stored procedures have to be registered at connexion opening time
SQL_CONNECT_HOOKS = {}
--- a/server/checkintegrity.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/checkintegrity.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,6 +13,7 @@
from logilab.common.shellutils import ProgressBar
+from cubicweb.schema import PURE_VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
def has_eid(sqlcursor, eid, eids):
@@ -71,6 +72,14 @@
uniquecstrcheck_before_modification)
from cubicweb.server.repository import FTIndexEntityOp
repo = session.repo
+ cursor = session.pool['system']
+ if not repo.system_source.indexer.has_fti_table(cursor):
+ from indexer import get_indexer
+ print 'no text index table'
+ indexer = get_indexer(repo.system_source.dbdriver)
+ # XXX indexer.init_fti(cursor) once index 0.7 is out
+ indexer.init_extensions(cursor)
+ cursor.execute(indexer.sql_init_fti())
repo.hm.unregister_hook(setmtime_before_update_entity,
'before_update_entity', '')
repo.hm.unregister_hook(uniquecstrcheck_before_modification,
@@ -188,9 +197,7 @@
"""check all relations registered in the repo system table"""
print 'Checking relations'
for rschema in schema.relations():
- if rschema.is_final():
- continue
- if rschema == 'identity':
+ if rschema.is_final() or rschema in PURE_VIRTUAL_RTYPES:
continue
if rschema.inlined:
for subjtype in rschema.subjects():
@@ -274,7 +281,7 @@
def check(repo, cnx, checks, reindex, fix):
- """check integrity of application's repository,
+ """check integrity of instance's repository,
using given user and password to locally connect to the repository
(no running cubicweb server needed)
"""
--- a/server/hookhelper.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/hookhelper.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,9 +7,6 @@
"""
__docformat__ = "restructuredtext en"
-from smtplib import SMTP
-from threading import Lock
-
from cubicweb import RepositoryError
from cubicweb.server.pool import SingleLastOperation
@@ -20,9 +17,7 @@
def entity_attr(session, eid, attr):
"""return an arbitrary attribute of the entity with the given eid"""
- rset = session.execute('Any N WHERE X eid %%(x)s, X %s N' % attr,
- {'x': eid}, 'x')
- return rset[0][0]
+ return getattr(session.entity_from_eid(eid), attr)
def rproperty(session, rtype, eidfrom, eidto, rprop):
rschema = session.repo.schema[rtype]
@@ -47,8 +42,6 @@
# mail related ################################################################
-SMTP_LOCK = Lock()
-
class SendMailOp(SingleLastOperation):
def __init__(self, session, msg=None, recipients=None, **kwargs):
# may not specify msg yet, as
@@ -70,26 +63,7 @@
self.repo.threaded_task(self.sendmails)
def sendmails(self):
- server, port = self.config['smtp-host'], self.config['smtp-port']
- SMTP_LOCK.acquire()
- try:
- try:
- smtp = SMTP(server, port)
- except Exception, ex:
- self.exception("can't connect to smtp server %s:%s (%s)",
- server, port, ex)
- return
- heloaddr = '%s <%s>' % (self.config['sender-name'],
- self.config['sender-addr'])
- for msg, recipients in self.to_send:
- try:
- smtp.sendmail(heloaddr, recipients, msg.as_string())
- except Exception, ex:
- self.exception("error sending mail to %s (%s)",
- recipients, ex)
- smtp.close()
- finally:
- SMTP_LOCK.release()
+ self.config.sendmails(self.to_send)
# state related ###############################################################
@@ -100,7 +74,10 @@
relation hooks, the relation may has been deleted at this point, so
we have handle that
"""
- for eidfrom, rtype, eidto in reversed(session.query_data('pendingrelations', ())):
+ if eid in session.transaction_data.get('neweids', ()):
+ return
+ pending = session.transaction_data.get('pendingrelations', ())
+ for eidfrom, rtype, eidto in reversed(pending):
if rtype == 'in_state' and eidfrom == eid:
rset = session.execute('Any S,N WHERE S eid %(x)s, S name N',
{'x': eidto}, 'x')
--- a/server/hooks.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/hooks.py Tue Aug 04 15:06:09 2009 +0200
@@ -17,65 +17,89 @@
get_user_sessions, rproperty)
from cubicweb.server.repository import FTIndexEntityOp
-def relation_deleted(session, eidfrom, rtype, eidto):
- session.add_query_data('pendingrelations', (eidfrom, rtype, eidto))
+# special relations that don't have to be checked for integrity, usually
+# because they are handled internally by hooks (so we trust ourselves)
+DONT_CHECK_RTYPES_ON_ADD = set(('owned_by', 'created_by',
+ 'is', 'is_instance_of',
+ 'wf_info_for', 'from_state', 'to_state'))
+DONT_CHECK_RTYPES_ON_DEL = set(('is', 'is_instance_of',
+ 'wf_info_for', 'from_state', 'to_state'))
-# base meta-data handling #####################################################
+def relation_deleted(session, eidfrom, rtype, eidto):
+ session.transaction_data.setdefault('pendingrelations', []).append(
+ (eidfrom, rtype, eidto))
+
+def eschema_type_eid(session, etype):
+ """get eid of the CWEType entity for the given yams type"""
+ eschema = session.repo.schema.eschema(etype)
+ # eschema.eid is None if schema has been readen from the filesystem, not
+ # from the database (eg during tests)
+ if eschema.eid is None:
+ eschema.eid = session.unsafe_execute(
+ 'Any X WHERE X is CWEType, X name %(name)s', {'name': etype})[0][0]
+ return eschema.eid
+
+
+# base meta-data handling ######################################################
def setctime_before_add_entity(session, entity):
"""before create a new entity -> set creation and modification date
this is a conveniency hook, you shouldn't have to disable it
"""
- if not 'creation_date' in entity:
- entity['creation_date'] = datetime.now()
- if not 'modification_date' in entity:
- entity['modification_date'] = datetime.now()
+ timestamp = datetime.now()
+ entity.setdefault('creation_date', timestamp)
+ entity.setdefault('modification_date', timestamp)
+ if not session.get_shared_data('do-not-insert-cwuri'):
+ entity.setdefault('cwuri', u'%seid/%s' % (session.base_url(), entity.eid))
+
def setmtime_before_update_entity(session, entity):
"""update an entity -> set modification date"""
- if not 'modification_date' in entity:
- entity['modification_date'] = datetime.now()
+ entity.setdefault('modification_date', datetime.now())
+
class SetCreatorOp(PreCommitOperation):
def precommit_event(self):
- if self.eid in self.session.query_data('pendingeids', ()):
+ session = self.session
+ if self.entity.eid in session.transaction_data.get('pendingeids', ()):
# entity have been created and deleted in the same transaction
return
- ueid = self.session.user.eid
- execute = self.session.unsafe_execute
- if not execute('Any X WHERE X created_by U, X eid %(x)s',
- {'x': self.eid}, 'x'):
- execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s',
- {'x': self.eid, 'u': ueid}, 'x')
+ if not self.entity.created_by:
+ session.add_relation(self.entity.eid, 'created_by', session.user.eid)
+
def setowner_after_add_entity(session, entity):
"""create a new entity -> set owner and creator metadata"""
asession = session.actual_session()
if not asession.is_internal_session:
- session.unsafe_execute('SET X owned_by U WHERE X eid %(x)s, U eid %(u)s',
- {'x': entity.eid, 'u': asession.user.eid}, 'x')
- SetCreatorOp(asession, eid=entity.eid)
+ session.add_relation(entity.eid, 'owned_by', asession.user.eid)
+ SetCreatorOp(asession, entity=entity)
+
def setis_after_add_entity(session, entity):
"""create a new entity -> set is relation"""
if hasattr(entity, '_cw_recreating'):
return
- session.unsafe_execute('SET X is E WHERE X eid %(x)s, E name %(name)s',
- {'x': entity.eid, 'name': entity.id}, 'x')
+ try:
+ session.add_relation(entity.eid, 'is',
+ eschema_type_eid(session, entity.id))
+ except IndexError:
+ # during schema serialization, skip
+ return
# XXX < 2.50 bw compat
if not session.get_shared_data('do-not-insert-is_instance_of'):
- basetypes = entity.e_schema.ancestors() + [entity.e_schema]
- session.unsafe_execute('SET X is_instance_of E WHERE X eid %%(x)s, E name IN (%s)' %
- ','.join("'%s'" % str(etype) for etype in basetypes),
- {'x': entity.eid}, 'x')
+ for etype in entity.e_schema.ancestors() + [entity.e_schema]:
+ session.add_relation(entity.eid, 'is_instance_of',
+ eschema_type_eid(session, etype))
+
def setowner_after_add_user(session, entity):
"""when a user has been created, add owned_by relation on itself"""
- session.unsafe_execute('SET X owned_by X WHERE X eid %(x)s',
- {'x': entity.eid}, 'x')
+ session.add_relation(entity.eid, 'owned_by', entity.eid)
+
def fti_update_after_add_relation(session, eidfrom, rtype, eidto):
"""sync fulltext index when relevant relation is added. Reindexing the
@@ -84,16 +108,19 @@
"""
ftcontainer = session.repo.schema.rschema(rtype).fulltext_container
if ftcontainer == 'subject':
- FTIndexEntityOp(session, entity=session.entity(eidto))
+ FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
elif ftcontainer == 'object':
- FTIndexEntityOp(session, entity=session.entity(eidfrom))
+ FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
+
+
def fti_update_after_delete_relation(session, eidfrom, rtype, eidto):
"""sync fulltext index when relevant relation is deleted. Reindexing both
entities is necessary.
"""
if session.repo.schema.rschema(rtype).fulltext_container:
- FTIndexEntityOp(session, entity=session.entity(eidto))
- FTIndexEntityOp(session, entity=session.entity(eidfrom))
+ FTIndexEntityOp(session, entity=session.entity_from_eid(eidto))
+ FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom))
+
class SyncOwnersOp(PreCommitOperation):
@@ -103,12 +130,13 @@
{'c': self.compositeeid, 'x': self.composedeid},
('c', 'x'))
+
def sync_owner_after_add_composite_relation(session, eidfrom, rtype, eidto):
"""when adding composite relation, the composed should have the same owners
has the composite
"""
if rtype == 'wf_info_for':
- # skip this special composite relation
+ # skip this special composite relation # XXX (syt) why?
return
composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
if composite == 'subject':
@@ -116,6 +144,7 @@
elif composite == 'object':
SyncOwnersOp(session, compositeeid=eidto, composedeid=eidfrom)
+
def _register_metadata_hooks(hm):
"""register meta-data related hooks on the hooks manager"""
hm.register_hook(setctime_before_add_entity, 'before_add_entity', '')
@@ -129,6 +158,7 @@
if 'CWUser' in hm.schema:
hm.register_hook(setowner_after_add_user, 'after_add_entity', 'CWUser')
+
# core hooks ##################################################################
class DelayedDeleteOp(PreCommitOperation):
@@ -138,12 +168,15 @@
def precommit_event(self):
session = self.session
- if not self.eid in session.query_data('pendingeids', ()):
+ # don't do anything if the entity is being created or deleted
+ if not (self.eid in session.transaction_data.get('pendingeids', ()) or
+ self.eid in session.transaction_data.get('neweids', ())):
etype = session.describe(self.eid)[0]
session.unsafe_execute('DELETE %s X WHERE X eid %%(x)s, NOT %s'
% (etype, self.relation),
{'x': self.eid}, 'x')
+
def handle_composite_before_del_relation(session, eidfrom, rtype, eidto):
"""delete the object of composite relation"""
composite = rproperty(session, rtype, eidfrom, eidto, 'composite')
@@ -152,6 +185,7 @@
elif composite == 'object':
DelayedDeleteOp(session, eid=eidfrom, relation='X %s Y' % rtype)
+
def before_del_group(session, eid):
"""check that we don't remove the owners group"""
check_internal_entity(session, eid, ('owners',))
@@ -166,7 +200,7 @@
eidfrom, rtype, eidto = self.rdef
# first check related entities have not been deleted in the same
# transaction
- pending = self.session.query_data('pendingeids', ())
+ pending = self.session.transaction_data.get('pendingeids', ())
if eidfrom in pending:
return
if eidto in pending:
@@ -181,6 +215,7 @@
def commit_event(self):
pass
+
def cstrcheck_after_add_relation(session, eidfrom, rtype, eidto):
"""check the relation satisfy its constraints
@@ -206,9 +241,6 @@
raise ValidationError(entity.eid, {attr: msg % val})
-
-
-
class CheckRequiredRelationOperation(LateOperation):
"""checking relation cardinality has to be done after commit in
case the relation is being replaced
@@ -217,14 +249,14 @@
def precommit_event(self):
# recheck pending eids
- if self.eid in self.session.query_data('pendingeids', ()):
+ if self.eid in self.session.transaction_data.get('pendingeids', ()):
return
if self.session.unsafe_execute(*self._rql()).rowcount < 1:
etype = self.session.describe(self.eid)[0]
- msg = self.session._('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
- raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype,
- 'etype': etype,
- 'eid': self.eid}})
+ _ = self.session._
+ msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)')
+ msg %= {'rtype': _(self.rtype), 'etype': _(etype), 'eid': self.eid}
+ raise ValidationError(self.eid, {self.rtype: msg})
def commit_event(self):
pass
@@ -232,16 +264,19 @@
def _rql(self):
raise NotImplementedError()
+
class CheckSRelationOp(CheckRequiredRelationOperation):
"""check required subject relation"""
def _rql(self):
return 'Any O WHERE S eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
class CheckORelationOp(CheckRequiredRelationOperation):
"""check required object relation"""
def _rql(self):
return 'Any S WHERE O eid %%(x)s, S %s O' % self.rtype, {'x': self.eid}, 'x'
+
def checkrel_if_necessary(session, opcls, rtype, eid):
"""check an equivalent operation has not already been added"""
for op in session.pending_operations:
@@ -250,12 +285,13 @@
else:
opcls(session, rtype=rtype, eid=eid)
+
def cardinalitycheck_after_add_entity(session, entity):
"""check cardinalities are satisfied"""
eid = entity.eid
for rschema, targetschemas, x in entity.e_schema.relation_definitions():
# skip automatically handled relations
- if rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'):
+ if rschema.type in DONT_CHECK_RTYPES_ON_ADD:
continue
if x == 'subject':
subjtype = entity.e_schema
@@ -271,10 +307,13 @@
if card[cardindex] in '1+':
checkrel_if_necessary(session, opcls, rschema.type, eid)
+
def cardinalitycheck_before_del_relation(session, eidfrom, rtype, eidto):
"""check cardinalities are satisfied"""
+ if rtype in DONT_CHECK_RTYPES_ON_DEL:
+ return
card = rproperty(session, rtype, eidfrom, eidto, 'cardinality')
- pendingeids = session.query_data('pendingeids', ())
+ pendingeids = session.transaction_data.get('pendingeids', ())
if card[0] in '1+' and not eidfrom in pendingeids:
checkrel_if_necessary(session, CheckSRelationOp, rtype, eidfrom)
if card[1] in '1+' and not eidto in pendingeids:
@@ -309,6 +348,7 @@
Operation.__init__(self, session, *args, **kwargs)
self.group = result[0][0]
+
class DeleteGroupOp(GroupOperation):
"""synchronize user when a in_group relation has been deleted"""
def commit_event(self):
@@ -320,6 +360,7 @@
self.error('user %s not in group %s', self.cnxuser, self.group)
return
+
def after_del_in_group(session, fromeid, rtype, toeid):
"""modify user permission, need to update users"""
for session_ in get_user_sessions(session.repo, fromeid):
@@ -337,6 +378,7 @@
return
groups.add(self.group)
+
def after_add_in_group(session, fromeid, rtype, toeid):
"""modify user permission, need to update users"""
for session_ in get_user_sessions(session.repo, fromeid):
@@ -356,11 +398,13 @@
except BadConnectionId:
pass # already closed
+
def after_del_user(session, eid):
"""modify user permission, need to update users"""
for session_ in get_user_sessions(session.repo, eid):
DelUserOp(session, session_.id)
+
def _register_usergroup_hooks(hm):
"""register user/group related hooks on the hooks manager"""
hm.register_hook(after_del_user, 'after_delete_entity', 'CWUser')
@@ -378,7 +422,7 @@
etype = session.describe(fromeid)[0]
if not (session.is_super_session or 'managers' in session.user.groups):
if not state is None:
- entity = session.entity(fromeid)
+ entity = session.entity_from_eid(fromeid)
# we should find at least one transition going to this state
try:
iter(state.transitions(entity, toeid)).next()
@@ -419,19 +463,20 @@
def precommit_event(self):
session = self.session
entity = self.entity
- rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s',
- {'name': str(entity.e_schema)})
# if there is an initial state and the entity's state is not set,
# use the initial state as a default state
- pendingeids = session.query_data('pendingeids', ())
- if rset and not entity.eid in pendingeids and not entity.in_state:
- session.unsafe_execute('SET X in_state S WHERE X eid %(x)s, S eid %(s)s',
- {'x' : entity.eid, 's' : rset[0][0]}, 'x')
+ pendingeids = session.transaction_data.get('pendingeids', ())
+ if not entity.eid in pendingeids and not entity.in_state:
+ rset = session.execute('Any S WHERE ET initial_state S, ET name %(name)s',
+ {'name': entity.id})
+ if rset:
+ session.add_relation(entity.eid, 'in_state', rset[0][0])
def set_initial_state_after_add(session, entity):
SetInitialStateOp(session, entity=entity)
+
def _register_wf_hooks(hm):
"""register workflow related hooks on the hooks manager"""
if 'in_state' in hm.schema:
@@ -456,6 +501,7 @@
except KeyError:
self.error('%s has no associated value', self.key)
+
class ChangeCWPropertyOp(Operation):
"""a user's custom properties has been added/changed"""
@@ -463,6 +509,7 @@
"""the observed connections pool has been commited"""
self.epropdict[self.key] = self.value
+
class AddCWPropertyOp(Operation):
"""a user's custom properties has been added/changed"""
@@ -473,6 +520,7 @@
self.repo.vreg.eprop_values[eprop.pkey] = eprop.value
# if for_user is set, update is handled by a ChangeCWPropertyOp operation
+
def after_add_eproperty(session, entity):
key, value = entity.pkey, entity.value
try:
@@ -482,11 +530,11 @@
except ValueError, ex:
raise ValidationError(entity.eid, {'value': session._(str(ex))})
if not session.user.matching_groups('managers'):
- session.unsafe_execute('SET P for_user U WHERE P eid %(x)s,U eid %(u)s',
- {'x': entity.eid, 'u': session.user.eid}, 'x')
+ session.add_relation(entity.eid, 'for_user', session.user.eid)
else:
AddCWPropertyOp(session, eprop=entity)
+
def after_update_eproperty(session, entity):
key, value = entity.pkey, entity.value
try:
@@ -504,8 +552,9 @@
ChangeCWPropertyOp(session, epropdict=session.vreg.eprop_values,
key=key, value=value)
+
def before_del_eproperty(session, eid):
- for eidfrom, rtype, eidto in session.query_data('pendingrelations', ()):
+ for eidfrom, rtype, eidto in session.transaction_data.get('pendingrelations', ()):
if rtype == 'for_user' and eidfrom == eid:
# if for_user was set, delete has already been handled
break
@@ -514,6 +563,7 @@
{'x': eid}, 'x')[0][0]
DelCWPropertyOp(session, epropdict=session.vreg.eprop_values, key=key)
+
def after_add_for_user(session, fromeid, rtype, toeid):
if not session.describe(fromeid)[0] == 'CWProperty':
return
@@ -526,6 +576,7 @@
ChangeCWPropertyOp(session, epropdict=session_.user.properties,
key=key, value=value)
+
def before_del_for_user(session, fromeid, rtype, toeid):
key = session.execute('Any K WHERE P eid %(x)s, P pkey K',
{'x': fromeid}, 'x')[0][0]
@@ -533,6 +584,7 @@
for session_ in get_user_sessions(session.repo, toeid):
DelCWPropertyOp(session, epropdict=session_.user.properties, key=key)
+
def _register_eproperty_hooks(hm):
"""register workflow related hooks on the hooks manager"""
hm.register_hook(after_add_eproperty, 'after_add_entity', 'CWProperty')
--- a/server/hooksmanager.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/hooksmanager.py Tue Aug 04 15:06:09 2009 +0200
@@ -34,7 +34,8 @@
'before_delete_entity', 'after_delete_entity')
RELATIONS_HOOKS = ('before_add_relation', 'after_add_relation' ,
'before_delete_relation','after_delete_relation')
-SYSTEM_HOOKS = ('server_startup', 'server_shutdown',
+SYSTEM_HOOKS = ('server_backup', 'server_restore',
+ 'server_startup', 'server_shutdown',
'session_open', 'session_close')
ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS)
@@ -65,10 +66,11 @@
def register_hook(self, function, event, etype=''):
"""register a function to call when <event> occurs
- <etype> is an entity/relation type or an empty string.
- If etype is the empty string, the function will be called at each
- event, else the function will be called only when event occurs on an
- entity/relation of the given type.
+ <etype> is an entity/relation type or an empty string.
+
+ If etype is the empty string, the function will be called at each event,
+ else the function will be called only when event occurs on an entity or
+ relation of the given type.
"""
assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS)
assert (not event in SYSTEM_HOOKS or not etype), (event, etype)
@@ -82,19 +84,28 @@
self.error('can\'t register hook %s on %s (%s)',
event, etype or 'any', function.func_name)
- def unregister_hook(self, function, event, etype=''):
- """register a function to call when <event> occurs
-
- <etype> is an entity/relation type or an empty string.
- If etype is the empty string, the function will be called at each
- event, else the function will be called only when event occurs on an
- entity/relation of the given type.
+ def unregister_hook(self, function_or_cls, event=None, etype=''):
+ """unregister a function to call when <event> occurs, or a Hook subclass.
+ In the later case, event/type information are extracted from the given
+ class.
"""
- assert event in ALL_HOOKS, event
- etype = etype or ''
- self.info('unregister hook %s on %s (%s)', event, etype,
- function.func_name)
- self._hooks[event][etype].remove(function)
+ if isinstance(function_or_cls, type) and issubclass(function_or_cls, Hook):
+ for event, ertype in function_or_cls.register_to():
+ for hook in self._hooks[event][ertype]:
+ if getattr(hook, 'im_self', None).__class__ is function_or_cls:
+ self._hooks[event][ertype].remove(hook)
+ self.info('unregister hook %s on %s (%s)', event, etype,
+ function_or_cls.__name__)
+ break
+ else:
+ self.warning("can't unregister hook %s on %s (%s), not found",
+ event, etype, function_or_cls.__name__)
+ else:
+ assert event in ALL_HOOKS, event
+ etype = etype or ''
+ self.info('unregister hook %s on %s (%s)', event, etype,
+ function_or_cls.func_name)
+ self._hooks[event][etype].remove(function_or_cls)
def call_hooks(self, __event, __type='', *args, **kwargs):
"""call hook matching event and optional type"""
@@ -216,7 +227,14 @@
cls.warning('%s hook has been disabled', cls)
return
done = set()
+ assert isinstance(cls.events, (tuple, list)), \
+ '%s: events is expected to be a tuple, not %s' % (
+ cls, type(cls.events))
for event in cls.events:
+ if event in SYSTEM_HOOKS:
+ assert not cls.accepts or cls.accepts == ('Any',), \
+ '%s doesnt make sense on %s' % (cls.accepts, event)
+ cls.accepts = ('Any',)
for ertype in cls.accepts:
if (event, ertype) in done:
continue
@@ -244,7 +262,7 @@
raise NotImplementedError
class SystemHook(Hook):
- accepts = ('',)
+ accepts = ()
from logging import getLogger
from cubicweb import set_log_methods
--- a/server/migractions.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/migractions.py Tue Aug 04 15:06:09 2009 +0200
@@ -22,21 +22,21 @@
from os.path import join, exists
from datetime import datetime
-from logilab.common.deprecation import deprecated_function, obsolete
-from logilab.common.decorators import cached
+from logilab.common.deprecation import deprecated
+from logilab.common.decorators import cached, clear_cache
from logilab.common.adbh import get_adv_func_helper
from yams.constraints import SizeConstraint
from yams.schema2sql import eschema2sql, rschema2sql
from cubicweb import AuthenticationError, ETYPE_NAME_MAP
-from cubicweb.schema import CubicWebRelationSchema
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CubicWebRelationSchema
from cubicweb.dbapi import get_repository, repo_connect
from cubicweb.common.migration import MigrationHelper, yes
try:
- from cubicweb.server import schemaserial as ss
- from cubicweb.server.utils import manager_userpasswd
+ from cubicweb.server import SOURCE_TYPES, schemaserial as ss
+ from cubicweb.server.utils import manager_userpasswd, ask_source_config
from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
except ImportError: # LAX
pass
@@ -64,83 +64,22 @@
self.fs_schema = schema
self._synchronized = set()
+ # overriden from base MigrationHelper ######################################
+
@cached
def repo_connect(self):
self.repo = get_repository(method='inmemory', config=self.config)
return self.repo
+ def cube_upgraded(self, cube, version):
+ self.cmd_set_property('system.version.%s' % cube.lower(),
+ unicode(version))
+ self.commit()
+
def shutdown(self):
if self.repo is not None:
self.repo.shutdown()
- def rewrite_vcconfiguration(self):
- """write current installed versions (of cubicweb software
- and of each used cube) into the database
- """
- self.cmd_set_property('system.version.cubicweb', self.config.cubicweb_version())
- for pkg in self.config.cubes():
- pkgversion = self.config.cube_version(pkg)
- self.cmd_set_property('system.version.%s' % pkg.lower(), pkgversion)
- self.commit()
-
- def backup_database(self, backupfile=None, askconfirm=True):
- config = self.config
- source = config.sources()['system']
- helper = get_adv_func_helper(source['db-driver'])
- date = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
- app = config.appid
- backupfile = backupfile or join(config.backup_dir(),
- '%s-%s.dump' % (app, date))
- if exists(backupfile):
- if not self.confirm('a backup already exists for %s, overwrite it?' % app):
- return
- elif askconfirm and not self.confirm('backup %s database?' % app):
- return
- cmd = helper.backup_command(source['db-name'], source.get('db-host'),
- source.get('db-user'), backupfile,
- keepownership=False)
- while True:
- print cmd
- if os.system(cmd):
- print 'error while backuping the base'
- answer = self.confirm('continue anyway?',
- shell=False, abort=False, retry=True)
- if not answer:
- raise SystemExit(1)
- if answer == 1: # 1: continue, 2: retry
- break
- else:
- from cubicweb.toolsutils import restrict_perms_to_user
- print 'database backup:', backupfile
- restrict_perms_to_user(backupfile, self.info)
- break
-
- def restore_database(self, backupfile, drop=True):
- config = self.config
- source = config.sources()['system']
- helper = get_adv_func_helper(source['db-driver'])
- app = config.appid
- if not exists(backupfile):
- raise Exception("backup file %s doesn't exist" % backupfile)
- if self.confirm('restore %s database from %s ?' % (app, backupfile)):
- for cmd in helper.restore_commands(source['db-name'], source.get('db-host'),
- source.get('db-user'), backupfile,
- source['db-encoding'],
- keepownership=False, drop=drop):
- while True:
- print cmd
- if os.system(cmd):
- print 'error while restoring the base'
- answer = self.confirm('continue anyway?',
- shell=False, abort=False, retry=True)
- if not answer:
- raise SystemExit(1)
- if answer == 1: # 1: continue, 2: retry
- break
- else:
- break
- print 'database restored'
-
def migrate(self, vcconf, toupgrade, options):
if not options.fs_only:
if options.backup_db is None:
@@ -154,12 +93,42 @@
in interactive mode, display the migration script path, ask for
confirmation and execute it if confirmed
"""
- if migrscript.endswith('.sql'):
- if self.execscript_confirm(migrscript):
- sqlexec(open(migrscript).read(), self.session.system_sql)
+ try:
+ if migrscript.endswith('.sql'):
+ if self.execscript_confirm(migrscript):
+ sqlexec(open(migrscript).read(), self.session.system_sql)
+ else:
+ return super(ServerMigrationHelper, self).process_script(
+ migrscript, funcname, *args, **kwargs)
+ self.commit()
+ except:
+ self.rollback()
+ raise
+
+ # server specific migration methods ########################################
+
+ def backup_database(self, backupfile=None, askconfirm=True):
+ config = self.config
+ repo = self.repo_connect()
+ timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
+ for source in repo.sources:
+ source.backup(self.confirm, backupfile, timestamp,
+ askconfirm=askconfirm)
+ repo.hm.call_hooks('server_backup', repo=repo, timestamp=timestamp)
+
+ def restore_database(self, backupfile, drop=True, systemonly=True,
+ askconfirm=True):
+ config = self.config
+ repo = self.repo_connect()
+ if systemonly:
+ repo.system_source.restore(self.confirm, backupfile=backupfile,
+ drop=drop, askconfirm=askconfirm)
else:
- return super(ServerMigrationHelper, self).process_script(
- migrscript, funcname, *args, **kwargs)
+ # in that case, backup file is expected to be a time stamp
+ for source in repo.sources:
+ source.backup(self.confirm, timestamp=backupfile, drop=drop,
+ askconfirm=askconfirm)
+ repo.hm.call_hooks('server_restore', repo=repo, timestamp=backupfile)
@property
def cnx(self):
@@ -190,6 +159,7 @@
except (KeyboardInterrupt, EOFError):
print 'aborting...'
sys.exit(0)
+ self.session.keep_pool_mode('transaction')
return self._cnx
@property
@@ -229,9 +199,9 @@
'fsschema': self.fs_schema,
'session' : self.session,
'repo' : self.repo,
- 'synchronize_schema': deprecated_function(self.cmd_sync_schema_props_perms),
- 'synchronize_eschema': deprecated_function(self.cmd_sync_schema_props_perms),
- 'synchronize_rschema': deprecated_function(self.cmd_sync_schema_props_perms),
+ 'synchronize_schema': deprecated()(self.cmd_sync_schema_props_perms),
+ 'synchronize_eschema': deprecated()(self.cmd_sync_schema_props_perms),
+ 'synchronize_rschema': deprecated()(self.cmd_sync_schema_props_perms),
})
return context
@@ -271,7 +241,7 @@
def _synchronize_permissions(self, ertype):
"""permission synchronization for an entity or relation type"""
- if ertype in ('eid', 'has_text', 'identity'):
+ if ertype in VIRTUAL_RTYPES:
return
newrschema = self.fs_schema[ertype]
teid = self.repo.schema[ertype].eid
@@ -473,15 +443,22 @@
def cmd_add_cubes(self, cubes, update_database=True):
"""update_database is telling if the database schema should be updated
or if only the relevant eproperty should be inserted (for the case where
- a cube has been extracted from an existing application, so the
+ a cube has been extracted from an existing instance, so the
cube schema is already in there)
"""
newcubes = super(ServerMigrationHelper, self).cmd_add_cubes(cubes)
if not newcubes:
return
- for pack in newcubes:
- self.cmd_set_property('system.version.'+pack,
- self.config.cube_version(pack))
+ for cube in newcubes:
+ self.cmd_set_property('system.version.'+cube,
+ self.config.cube_version(cube))
+ if cube in SOURCE_TYPES:
+ # don't use config.sources() in case some sources have been
+ # disabled for migration
+ sourcescfg = self.config.read_sources_file()
+ sourcescfg[cube] = ask_source_config(cube)
+ self.config.write_sources_file(sourcescfg)
+ clear_cache(self.config, 'read_sources_file')
if not update_database:
self.commit()
return
@@ -515,8 +492,9 @@
self.exec_event_script('postcreate', self.config.cube_dir(pack))
self.commit()
- def cmd_remove_cube(self, cube):
- removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(cube)
+ def cmd_remove_cube(self, cube, removedeps=False):
+ removedcubes = super(ServerMigrationHelper, self).cmd_remove_cube(
+ cube, removedeps)
if not removedcubes:
return
fsschema = self.fs_schema
@@ -609,7 +587,7 @@
# register entity's attributes
for rschema, attrschema in eschema.attribute_definitions():
# ignore those meta relations, they will be automatically added
- if rschema.type in ('eid', 'creation_date', 'modification_date'):
+ if rschema.type in META_RTYPES:
continue
if not rschema.type in applschema:
# need to add the relation type and to commit to get it
@@ -625,12 +603,12 @@
for rschema in eschema.subject_relations():
# attribute relation have already been processed and
# 'owned_by'/'created_by' will be automatically added
- if rschema.final or rschema.type in ('owned_by', 'created_by', 'is', 'is_instance_of'):
+ if rschema.final or rschema.type in META_RTYPES:
continue
rtypeadded = rschema.type in applschema
for targetschema in rschema.objects(etype):
# ignore relations where the targeted type is not in the
- # current application schema
+ # current instance schema
targettype = targetschema.type
if not targettype in applschema and targettype != etype:
continue
@@ -650,7 +628,7 @@
rtypeadded = rschema.type in applschema or rschema.type in added
for targetschema in rschema.subjects(etype):
# ignore relations where the targeted type is not in the
- # current application schema
+ # current instance schema
targettype = targetschema.type
# don't check targettype != etype since in this case the
# relation has already been added as a subject relation
@@ -688,7 +666,8 @@
`newname` is a string giving the name of the renamed entity type
"""
self.rqlexec('SET ET name %(newname)s WHERE ET is CWEType, ET name %(oldname)s',
- {'newname' : unicode(newname), 'oldname' : oldname})
+ {'newname' : unicode(newname), 'oldname' : oldname},
+ ask_confirm=False)
if commit:
self.commit()
@@ -859,7 +838,7 @@
if commit:
self.commit()
- @obsolete('use sync_schema_props_perms(ertype, syncprops=False)')
+ @deprecated('use sync_schema_props_perms(ertype, syncprops=False)')
def cmd_synchronize_permissions(self, ertype, commit=True):
self.cmd_sync_schema_props_perms(ertype, syncprops=False, commit=commit)
--- a/server/msplanner.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/msplanner.py Tue Aug 04 15:06:09 2009 +0200
@@ -158,6 +158,7 @@
# XXX move functions below to rql ##############################################
def is_ancestor(n1, n2):
+ """return True if n2 is a parent scope of n1"""
p = n1.parent
while p is not None:
if p is n2:
@@ -171,17 +172,14 @@
newnode.append(part)
return newnode
-def same_scope(var):
- """return true if the variable is always used in the same scope"""
- try:
- return var.stinfo['samescope']
- except KeyError:
- for rel in var.stinfo['relations']:
- if not rel.scope is var.scope:
- var.stinfo['samescope'] = False
- return False
- var.stinfo['samescope'] = True
- return True
+def used_in_outer_scope(var, scope):
+ """return true if the variable is used in an outer scope of the given scope
+ """
+ for rel in var.stinfo['relations']:
+ rscope = rel.scope
+ if not rscope is scope and is_ancestor(scope, rscope):
+ return True
+ return False
################################################################################
@@ -354,6 +352,8 @@
if source is self.system_source:
for const in vconsts:
self._set_source_for_term(source, const)
+ elif not self._sourcesterms:
+ self._set_source_for_term(source, const)
elif source in self._sourcesterms:
source_scopes = frozenset(t.scope for t in self._sourcesterms[source])
for const in vconsts:
@@ -361,7 +361,7 @@
self._set_source_for_term(source, const)
# if system source is used, add every rewritten constant
# to its supported terms even when associated entity
- # doesn't actually comes from it so we get a changes
+ # doesn't actually come from it so we get a changes
# that allequals will return True as expected when
# computing needsplit
# check const is used in a relation restriction
@@ -553,9 +553,24 @@
# NOTE: < 2 since may be 0 on queries such as Any X WHERE X eid 2
if len(self._sourcesterms) < 2:
self.needsplit = False
+ # if this is not the system source but we have only constant terms
+ # and no relation (other than eid), apply query on the system source
+ #
+ # testing for rqlst with nothing in vargraph nor defined_vars is the
+ # simplest way the check the condition explained below
+ if not self.system_source in self._sourcesterms and \
+ not self.rqlst.vargraph and not self.rqlst.defined_vars:
+ self._sourcesterms = {self.system_source: {}}
elif not self.needsplit:
if not allequals(self._sourcesterms.itervalues()):
- self.needsplit = True
+ for source, terms in self._sourcesterms.iteritems():
+ if source is self.system_source:
+ continue
+ if any(x for x in terms if not isinstance(x, Constant)):
+ self.needsplit = True
+ return
+ self._sourcesterms = {self.system_source: {}}
+ self.needsplit = False
else:
sample = self._sourcesterms.itervalues().next()
if len(sample) > 1:
@@ -1216,6 +1231,7 @@
self.terms = terms
self.solindices = solindices
self.final = final
+ self._pending_vrefs = []
# terms which appear in unsupported branches
needsel |= self.extneedsel
self.needsel = needsel
@@ -1227,6 +1243,7 @@
self.mayneedvar, self.hasvar = {}, {}
self.use_only_defined = False
self.scopes = {rqlst: newroot}
+ self.current_scope = rqlst
if rqlst.where:
rqlst = self._rqlst_accept(rqlst, rqlst.where, newroot, terms,
newroot.set_where)
@@ -1368,9 +1385,14 @@
else:
raise UnsupportedBranch()
rschema = self.schema.rschema(node.r_type)
+ self._pending_vrefs = []
try:
res = self.visit_default(node, newroot, terms)[0]
- except Exception, ex:
+ except:
+ # when a relation isn't supported, we should dereference potentially
+ # introduced variable refs
+ for vref in self._pending_vrefs:
+ vref.unregister_reference()
raise
ored = node.ored()
if rschema.is_final() or rschema.inlined:
@@ -1397,7 +1419,7 @@
return False
if var.name in self.extneedsel or var.stinfo['selected']:
return False
- if not same_scope(var):
+ if not var in terms or used_in_outer_scope(var, self.current_scope):
return False
if any(v for v, _ in var.stinfo['attrvars'] if not v in terms):
return False
@@ -1433,7 +1455,9 @@
# set scope so we can insert types restriction properly
newvar = newroot.get_variable(node.name)
newvar.stinfo['scope'] = self.scopes.get(node.variable.scope, newroot)
- return VariableRef(newvar), node
+ vref = VariableRef(newvar)
+ self._pending_vrefs.append(vref)
+ return vref, node
def visit_constant(self, node, newroot, terms):
return copy_node(newroot, node), node
--- a/server/pool.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/pool.py Tue Aug 04 15:06:09 2009 +0200
@@ -68,11 +68,11 @@
# internals ###############################################################
- def pool_set(self, session):
+ def pool_set(self):
"""pool is being set"""
self.check_connections()
- def pool_reset(self, session):
+ def pool_reset(self):
"""pool is being reseted"""
for source, cnx in self.source_cnxs.values():
source.pool_reset(cnx)
--- a/server/querier.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/querier.py Tue Aug 04 15:06:09 2009 +0200
@@ -24,6 +24,8 @@
from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
from cubicweb.server.ssplanner import add_types_restriction
+READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
+
def empty_rset(session, rql, args, rqlst=None):
"""build an empty result set object"""
return ResultSet([], rql, args, rqlst=rqlst)
@@ -67,7 +69,7 @@
if rqlst.where is not None:
for rel in rqlst.where.iget_nodes(Relation):
# XXX has_text may have specific perm ?
- if rel.r_type in ('is', 'is_instance_of', 'has_text', 'identity', 'eid'):
+ if rel.r_type in READ_ONLY_RTYPES:
continue
if not schema.rschema(rel.r_type).has_access(user, 'read'):
raise Unauthorized('read', rel.r_type)
@@ -189,8 +191,6 @@
return rqlst to actually execute
"""
- #if server.DEBUG:
- # print '------- preprocessing', union.as_string('utf8')
noinvariant = set()
if security and not self.session.is_super_session:
self._insert_security(union, noinvariant)
@@ -298,7 +298,7 @@
localchecks = {}
if rqlst.where is not None:
varkwargs = var_kwargs(rqlst.where, self.args)
- neweids = self.session.query_data('neweids', ())
+ neweids = self.session.transaction_data.get('neweids', ())
else:
varkwargs = None
restricted_vars = set()
@@ -373,7 +373,7 @@
for relation in rqlst.main_relations:
lhs, rhs = relation.get_variable_parts()
rtype = relation.r_type
- if rtype in ('eid', 'has_text', 'is', 'is_instance_of', 'identity'):
+ if rtype in READ_ONLY_RTYPES:
raise QueryError("can't assign to %s" % rtype)
try:
edef = to_build[str(lhs)]
@@ -519,7 +519,7 @@
def __init__(self, repo, schema):
# system info helper
self._repo = repo
- # application schema
+ # instance schema
self.set_schema(schema)
def set_schema(self, schema):
@@ -586,9 +586,10 @@
always use substitute arguments in queries (eg avoid query such as
'Any X WHERE X eid 123'!)
"""
- if server.DEBUG:
- print '*'*80
- print rql
+ if server.DEBUG & (server.DBG_RQL | server.DBG_SQL):
+ if server.DEBUG & (server.DBG_MORE | server.DBG_SQL):
+ print '*'*80
+ print 'querier input', rql, args
# parse the query and binds variables
if eid_key is not None:
if not isinstance(eid_key, (tuple, list)):
--- a/server/repository.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/repository.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,7 @@
repository mainly:
* brings these classes all together to provide a single access
- point to a cubicweb application.
+ point to a cubicweb instance.
* handles session management
* provides method for pyro registration, to call if pyro is enabled
@@ -34,8 +34,8 @@
ExecutionError, typed_eid,
CW_MIGRATION_MAP)
from cubicweb.cwvreg import CubicWebRegistry
-from cubicweb.schema import CubicWebSchema
-
+from cubicweb.schema import VIRTUAL_RTYPES, CubicWebSchema
+from cubicweb import server
from cubicweb.server.utils import RepoThread, LoopTask
from cubicweb.server.pool import ConnectionsPool, LateOperation, SingleLastOperation
from cubicweb.server.session import Session, InternalSession
@@ -60,13 +60,19 @@
"""the observed connections pool has been rollbacked,
remove inserted eid from repository type/source cache
"""
- self.repo.clear_caches(self.session.query_data('pendingeids', ()))
+ try:
+ self.repo.clear_caches(self.session.transaction_data['pendingeids'])
+ except KeyError:
+ pass
def rollback_event(self):
"""the observed connections pool has been rollbacked,
remove inserted eid from repository type/source cache
"""
- self.repo.clear_caches(self.session.query_data('neweids', ()))
+ try:
+ self.repo.clear_caches(self.session.transaction_data['neweids'])
+ except KeyError:
+ pass
class FTIndexEntityOp(LateOperation):
@@ -80,7 +86,7 @@
def precommit_event(self):
session = self.session
entity = self.entity
- if entity.eid in session.query_data('pendingeids', ()):
+ if entity.eid in session.transaction_data.get('pendingeids', ()):
return # entity added and deleted in the same transaction
session.repo.system_source.fti_unindex_entity(session, entity.eid)
for container in entity.fti_containers():
@@ -109,7 +115,6 @@
# the web interface but may occurs during test or dbapi connection (though
# not expected for this). So: don't do it, we pretend to ensure repository
# consistency.
- # XXX should probably not use unsafe_execute!
if card[0] in '1?':
rschema = session.repo.schema.rschema(rtype)
if not rschema.inlined:
@@ -173,12 +178,12 @@
# open some connections pools
self._available_pools = Queue.Queue()
self._available_pools.put_nowait(ConnectionsPool(self.sources))
- if config.read_application_schema:
- # normal start: load the application schema from the database
+ if config.read_instance_schema:
+ # normal start: load the instance schema from the database
self.fill_schema()
elif config.bootstrap_schema:
# usually during repository creation
- self.warning("set fs application'schema as bootstrap schema")
+ self.warning("set fs instance'schema as bootstrap schema")
config.bootstrap_cubes()
self.set_bootstrap_schema(self.config.load_schema())
# need to load the Any and CWUser entity types
@@ -191,7 +196,7 @@
'cubicweb.entities.authobjs')
else:
# test start: use the file system schema (quicker)
- self.warning("set fs application'schema")
+ self.warning("set fs instance'schema")
config.bootstrap_cubes()
self.set_schema(self.config.load_schema())
if not config.creating:
@@ -203,11 +208,6 @@
# initialized)
for source in self.sources:
source.init()
- # call application level initialisation hooks
- self.hm.call_hooks('server_startup', repo=self)
- # register a task to cleanup expired session
- self.looping_task(self.config['session-time']/3.,
- self.clean_sessions)
else:
# call init_creating so for instance native source can configurate
# tsearch according to postgres version
@@ -216,9 +216,18 @@
# close initialization pool and reopen fresh ones for proper
# initialization now that we know cubes
self._get_pool().close(True)
+ # list of available pools (we can't iterated on Queue instance)
+ self.pools = []
for i in xrange(config['connections-pool-size']):
- self._available_pools.put_nowait(ConnectionsPool(self.sources))
+ self.pools.append(ConnectionsPool(self.sources))
+ self._available_pools.put_nowait(self.pools[-1])
self._shutting_down = False
+ if not (config.creating or config.repairing):
+ # call instance level initialisation hooks
+ self.hm.call_hooks('server_startup', repo=self)
+ # register a task to cleanup expired session
+ self.looping_task(self.config['session-time']/3.,
+ self.clean_sessions)
# internals ###############################################################
@@ -240,9 +249,9 @@
self.vreg.set_schema(schema)
self.hm.set_schema(schema)
self.hm.register_system_hooks(self.config)
- # application specific hooks
- if self.config.application_hooks:
- self.info('loading application hooks')
+ # instance specific hooks
+ if self.config.instance_hooks:
+ self.info('loading instance hooks')
self.hm.register_hooks(self.config.load_hooks(self.vreg))
def fill_schema(self):
@@ -290,13 +299,13 @@
config.usergroup_hooks = False
config.schema_hooks = False
config.notification_hooks = False
- config.application_hooks = False
+ config.instance_hooks = False
self.set_schema(schema, resetvreg=False)
config.core_hooks = True
config.usergroup_hooks = True
config.schema_hooks = True
config.notification_hooks = True
- config.application_hooks = True
+ config.instance_hooks = True
def start_looping_tasks(self):
assert isinstance(self._looping_tasks, list), 'already started'
@@ -336,7 +345,6 @@
'connections pools size)')
def _free_pool(self, pool):
- pool.rollback()
self._available_pools.put_nowait(pool)
def pinfo(self):
@@ -386,10 +394,23 @@
except ZeroDivisionError:
pass
+ def _login_from_email(self, login):
+ session = self.internal_session()
+ try:
+ rset = session.execute('Any L WHERE U login L, U primary_email M, '
+ 'M address %(login)s', {'login': login})
+ if rset.rowcount == 1:
+ login = rset[0][0]
+ finally:
+ session.close()
+ return login
+
def authenticate_user(self, session, login, password):
"""validate login / password, raise AuthenticationError on failure
return associated CWUser instance on success
"""
+ if self.vreg.config['allow-email-login'] and '@' in login:
+ login = self._login_from_email(login)
for source in self.sources:
if source.support_entity('CWUser'):
try:
@@ -399,11 +420,11 @@
continue
else:
raise AuthenticationError('authentication failed with all sources')
- euser = self._build_user(session, eid)
+ cwuser = self._build_user(session, eid)
if self.config.consider_user_state and \
- not euser.state in euser.AUTHENTICABLE_STATES:
+ not cwuser.state in cwuser.AUTHENTICABLE_STATES:
raise AuthenticationError('user is not in authenticable state')
- return euser
+ return cwuser
def _build_user(self, session, eid):
"""return a CWUser entity for user with the given eid"""
@@ -411,18 +432,18 @@
rql = cls.fetch_rql(session.user, ['X eid %(x)s'])
rset = session.execute(rql, {'x': eid}, 'x')
assert len(rset) == 1, rset
- euser = rset.get_entity(0, 0)
+ cwuser = rset.get_entity(0, 0)
# pylint: disable-msg=W0104
- # prefetch / cache euser's groups and properties. This is especially
+ # prefetch / cache cwuser's groups and properties. This is especially
# useful for internal sessions to avoid security insertions
- euser.groups
- euser.properties
- return euser
+ cwuser.groups
+ cwuser.properties
+ return cwuser
# public (dbapi) interface ################################################
def get_schema(self):
- """return the application schema. This is a public method, not
+ """return the instance schema. This is a public method, not
requiring a session id
"""
try:
@@ -433,17 +454,18 @@
self.schema.__hashmode__ = None
def get_cubes(self):
- """return the list of cubes used by this application. This is a
+ """return the list of cubes used by this instance. This is a
public method, not requiring a session id.
"""
- versions = self.get_versions(not self.config.creating)
+ versions = self.get_versions(not (self.config.creating
+ or self.config.repairing))
cubes = list(versions)
cubes.remove('cubicweb')
return cubes
@cached
def get_versions(self, checkversions=False):
- """return the a dictionary containing cubes used by this application
+ """return the a dictionary containing cubes used by this instance
as key with their version as value, including cubicweb version. This is a
public method, not requiring a session id.
"""
@@ -466,7 +488,7 @@
else:
fsversion = self.config.cubicweb_version()
if version < fsversion:
- msg = ('application has %s version %s but %s '
+ msg = ('instance has %s version %s but %s '
'is installed. Run "cubicweb-ctl upgrade".')
raise ExecutionError(msg % (cube, version, fsversion))
finally:
@@ -658,13 +680,22 @@
custom properties)
"""
session = self._get_session(sessionid, setpool=False)
- if props:
- # update session properties
- for prop, value in props.items():
- session.change_property(prop, value)
+ if props is not None:
+ self.set_session_props(sessionid, props)
user = session.user
return user.eid, user.login, user.groups, user.properties
+ def set_session_props(self, sessionid, props):
+ """this method should be used by client to:
+ * check session id validity
+ * update user information on each user's request (i.e. groups and
+ custom properties)
+ """
+ session = self._get_session(sessionid, setpool=False)
+ # update session properties
+ for prop, value in props.items():
+ session.change_property(prop, value)
+
# public (inter-repository) interface #####################################
def entities_modified_since(self, etypes, mtime):
@@ -864,7 +895,8 @@
self.system_source.add_info(session, entity, source, extid)
if complete:
entity.complete(entity.e_schema.indexable_attributes())
- session.add_query_data('neweids', entity.eid)
+ new = session.transaction_data.setdefault('neweids', set())
+ new.add(entity.eid)
# now we can update the full text index
if self.do_fti:
FTIndexEntityOp(session, entity=entity)
@@ -881,7 +913,7 @@
* setup cache update operation
"""
self.system_source.fti_unindex_entity(session, eid)
- pending = session.query_data('pendingeids', set(), setdefault=True)
+ pending = session.transaction_data.setdefault('pendingeids', set())
pending.add(eid)
CleanupEidTypeCacheOp(session)
@@ -900,9 +932,10 @@
"""
rql = []
eschema = self.schema.eschema(etype)
+ pendingrtypes = session.transaction_data.get('pendingrtypes', ())
for rschema, targetschemas, x in eschema.relation_definitions():
rtype = rschema.type
- if rtype == 'identity':
+ if rtype in VIRTUAL_RTYPES or rtype in pendingrtypes:
continue
var = '%s%s' % (rtype.upper(), x.upper())
if x == 'subject':
@@ -918,7 +951,7 @@
def index_entity(self, session, entity):
"""full text index a modified entity"""
- alreadydone = session.query_data('indexedeids', set(), setdefault=True)
+ alreadydone = session.transaction_data.setdefault('indexedeids', set())
if entity.eid in alreadydone:
self.info('skipping reindexation of %s, already done', entity.eid)
return
@@ -955,6 +988,8 @@
source = self.locate_etype_source(etype)
# attribute an eid to the entity before calling hooks
entity.set_eid(self.system_source.create_eid(session))
+ if server.DEBUG & server.DBG_REPO:
+ print 'ADD entity', etype, entity.eid, dict(entity)
entity._is_saved = False # entity has an eid but is not yet saved
relations = []
# if inlined relations are specified, fill entity's related cache to
@@ -962,11 +997,10 @@
for attr in entity.keys():
rschema = eschema.subject_relation(attr)
if not rschema.is_final(): # inlined relation
- entity.set_related_cache(attr, 'subject',
- entity.req.eid_rset(entity[attr]))
relations.append((attr, entity[attr]))
if source.should_call_hooks:
self.hm.call_hooks('before_add_entity', etype, session, entity)
+ entity.edited_attributes = entity.keys()
entity.set_defaults()
entity.check(creation=True)
source.add_entity(session, entity)
@@ -977,7 +1011,21 @@
extid = None
self.add_info(session, entity, source, extid, complete=False)
entity._is_saved = True # entity has an eid and is saved
- #print 'added', entity#, entity.items()
+ # prefill entity relation caches
+ session.set_entity_cache(entity)
+ for rschema in eschema.subject_relations():
+ rtype = str(rschema)
+ if rtype in VIRTUAL_RTYPES:
+ continue
+ if rschema.is_final():
+ entity.setdefault(rtype, None)
+ else:
+ entity.set_related_cache(rtype, 'subject', session.empty_rset())
+ for rschema in eschema.object_relations():
+ rtype = str(rschema)
+ if rtype in VIRTUAL_RTYPES:
+ continue
+ entity.set_related_cache(rtype, 'object', session.empty_rset())
# trigger after_add_entity after after_add_relation
if source.should_call_hooks:
self.hm.call_hooks('after_add_entity', etype, session, entity)
@@ -985,6 +1033,7 @@
for attr, value in relations:
self.hm.call_hooks('before_add_relation', attr, session,
entity.eid, attr, value)
+ session.update_rel_cache_add(entity.eid, attr, value)
self.hm.call_hooks('after_add_relation', attr, session,
entity.eid, attr, value)
return entity.eid
@@ -993,10 +1042,12 @@
"""replace an entity in the repository
the type and the eid of an entity must not be changed
"""
- #print 'update', entity
+ etype = str(entity.e_schema)
+ if server.DEBUG & server.DBG_REPO:
+ print 'UPDATE entity', etype, entity.eid, dict(entity)
entity.check()
- etype = str(entity.e_schema)
eschema = entity.e_schema
+ session.set_entity_cache(entity)
only_inline_rels, need_fti_update = True, False
relations = []
for attr in entity.keys():
@@ -1012,10 +1063,12 @@
previous_value = entity.related(attr)
if previous_value:
previous_value = previous_value[0][0] # got a result set
- self.hm.call_hooks('before_delete_relation', attr, session,
- entity.eid, attr, previous_value)
- entity.set_related_cache(attr, 'subject',
- entity.req.eid_rset(entity[attr]))
+ if previous_value == entity[attr]:
+ previous_value = None
+ else:
+ self.hm.call_hooks('before_delete_relation', attr,
+ session, entity.eid, attr,
+ previous_value)
relations.append((attr, entity[attr], previous_value))
source = self.source_from_eid(entity.eid, session)
if source.should_call_hooks:
@@ -1037,19 +1090,31 @@
entity)
if source.should_call_hooks:
for attr, value, prevvalue in relations:
+ # if the relation is already cached, update existant cache
+ relcache = entity.relation_cached(attr, 'subject')
if prevvalue:
self.hm.call_hooks('after_delete_relation', attr, session,
entity.eid, attr, prevvalue)
+ if relcache is not None:
+ session.update_rel_cache_del(entity.eid, attr, prevvalue)
del_existing_rel_if_needed(session, entity.eid, attr, value)
+ if relcache is not None:
+ session.update_rel_cache_add(entity.eid, attr, value)
+ else:
+ entity.set_related_cache(attr, 'subject',
+ session.eid_rset(value))
self.hm.call_hooks('after_add_relation', attr, session,
entity.eid, attr, value)
def glob_delete_entity(self, session, eid):
"""delete an entity and all related entities from the repository"""
- #print 'deleting', eid
# call delete_info before hooks
self._prepare_delete_info(session, eid)
etype, uri, extid = self.type_and_source_from_eid(eid, session)
+ if server.DEBUG & server.DBG_REPO:
+ print 'DELETE entity', etype, eid
+ if eid == 937:
+ server.DEBUG |= (server.DBG_SQL | server.DBG_RQL | server.DBG_MORE)
source = self.sources_by_uri[uri]
if source.should_call_hooks:
self.hm.call_hooks('before_delete_entity', etype, session, eid)
@@ -1061,32 +1126,32 @@
def glob_add_relation(self, session, subject, rtype, object):
"""add a relation to the repository"""
- assert subject is not None
- assert rtype
- assert object is not None
+ if server.DEBUG & server.DBG_REPO:
+ print 'ADD relation', subject, rtype, object
source = self.locate_relation_source(session, subject, rtype, object)
- #print 'adding', subject, rtype, object, 'to', source
if source.should_call_hooks:
del_existing_rel_if_needed(session, subject, rtype, object)
self.hm.call_hooks('before_add_relation', rtype, session,
subject, rtype, object)
source.add_relation(session, subject, rtype, object)
+ rschema = self.schema.rschema(rtype)
+ session.update_rel_cache_add(subject, rtype, object, rschema.symetric)
if source.should_call_hooks:
self.hm.call_hooks('after_add_relation', rtype, session,
subject, rtype, object)
def glob_delete_relation(self, session, subject, rtype, object):
"""delete a relation from the repository"""
- assert subject is not None
- assert rtype
- assert object is not None
+ if server.DEBUG & server.DBG_REPO:
+ print 'DELETE relation', subject, rtype, object
source = self.locate_relation_source(session, subject, rtype, object)
- #print 'delete rel', subject, rtype, object
if source.should_call_hooks:
self.hm.call_hooks('before_delete_relation', rtype, session,
subject, rtype, object)
source.delete_relation(session, subject, rtype, object)
- if self.schema.rschema(rtype).symetric:
+ rschema = self.schema.rschema(rtype)
+ session.update_rel_cache_del(subject, rtype, object, rschema.symetric)
+ if rschema.symetric:
# on symetric relation, we can't now in which sense it's
# stored so try to delete both
source.delete_relation(session, object, rtype, subject)
--- a/server/rqlannotation.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/rqlannotation.py Tue Aug 04 15:06:09 2009 +0200
@@ -22,21 +22,7 @@
has_text_query = False
need_distinct = rqlst.distinct
for rel in rqlst.iget_nodes(Relation):
- if rel.neged(strict=True):
- if rel.is_types_restriction():
- need_distinct = True
- else:
- rschema = getrschema(rel.r_type)
- if not rschema.is_final():
- if rschema.inlined:
- try:
- var = rel.children[1].children[0].variable
- except AttributeError:
- pass # rewritten variable
- else:
- if not var.stinfo['constnode']:
- need_distinct = True
- elif getrschema(rel.r_type).symetric:
+ if getrschema(rel.r_type).symetric and not rel.neged(strict=True):
for vref in rel.iget_nodes(VariableRef):
stinfo = vref.variable.stinfo
if not stinfo['constnode'] and stinfo['selected']:
--- a/server/schemahooks.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/schemahooks.py Tue Aug 04 15:06:09 2009 +0200
@@ -16,17 +16,31 @@
from yams.buildobjs import EntityType, RelationType, RelationDefinition
from yams.schema2sql import eschema2sql, rschema2sql, type_from_constraints
+
from cubicweb import ValidationError, RepositoryError
+from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, CONSTRAINTS
from cubicweb.server import schemaserial as ss
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.pool import Operation, SingleLastOperation, PreCommitOperation
from cubicweb.server.hookhelper import (entity_attr, entity_name,
- check_internal_entity)
+ check_internal_entity)
+
+
+TYPE_CONVERTER = { # XXX
+ 'Boolean': bool,
+ 'Int': int,
+ 'Float': float,
+ 'Password': str,
+ 'String': unicode,
+ 'Date' : unicode,
+ 'Datetime' : unicode,
+ 'Time' : unicode,
+ }
# core entity and relation types which can't be removed
CORE_ETYPES = list(BASE_TYPES) + ['CWEType', 'CWRType', 'CWUser', 'CWGroup',
'CWConstraint', 'CWAttribute', 'CWRelation']
-CORE_RTYPES = ['eid', 'creation_date', 'modification_date',
+CORE_RTYPES = ['eid', 'creation_date', 'modification_date', 'cwuri',
'login', 'upassword', 'name',
'is', 'instanceof', 'owned_by', 'created_by', 'in_group',
'relation_type', 'from_entity', 'to_entity',
@@ -37,8 +51,8 @@
def get_constraints(session, entity):
constraints = []
- for cstreid in session.query_data(entity.eid, ()):
- cstrent = session.entity(cstreid)
+ for cstreid in session.transaction_data.get(entity.eid, ()):
+ cstrent = session.entity_from_eid(cstreid)
cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value)
cstr.eid = cstreid
constraints.append(cstr)
@@ -50,7 +64,7 @@
column = SQL_PREFIX + rtype
try:
session.system_sql(str('ALTER TABLE %s ADD COLUMN %s integer'
- % (table, column)))
+ % (table, column)), rollback_on_failure=False)
session.info('added column %s to table %s', column, table)
except:
# silent exception here, if this error has not been raised because the
@@ -62,32 +76,53 @@
# is done by the dbhelper)
session.pool.source('system').create_index(session, table, column)
session.info('added index on %s(%s)', table, column)
- session.add_query_data('createdattrs', '%s.%s' % (etype, rtype))
+ session.transaction_data.setdefault('createdattrs', []).append(
+ '%s.%s' % (etype, rtype))
+
+
+# operations for low-level database alteration ################################
+
+class DropTable(PreCommitOperation):
+ """actually remove a database from the instance's schema"""
+ table = None # make pylint happy
+ def precommit_event(self):
+ dropped = self.session.transaction_data.setdefault('droppedtables',
+ set())
+ if self.table in dropped:
+ return # already processed
+ dropped.add(self.table)
+ self.session.system_sql('DROP TABLE %s' % self.table)
+ self.info('dropped table %s', self.table)
-class SchemaOperation(Operation):
- """base class for schema operations"""
- def __init__(self, session, kobj=None, **kwargs):
- self.schema = session.repo.schema
- self.kobj = kobj
- # once Operation.__init__ has been called, event may be triggered, so
- # do this last !
- Operation.__init__(self, session, **kwargs)
- # every schema operation is triggering a schema update
- UpdateSchemaOp(session)
+class DropRelationTable(DropTable):
+ def __init__(self, session, rtype):
+ super(DropRelationTable, self).__init__(
+ session, table='%s_relation' % rtype)
+ session.transaction_data.setdefault('pendingrtypes', set()).add(rtype)
+
-class EarlySchemaOperation(SchemaOperation):
- def insert_index(self):
- """schema operation which are inserted at the begining of the queue
- (typically to add/remove entity or relation types)
- """
- i = -1
- for i, op in enumerate(self.session.pending_operations):
- if not isinstance(op, EarlySchemaOperation):
- return i
- return i + 1
+class DropColumn(PreCommitOperation):
+ """actually remove the attribut's column from entity table in the system
+ database
+ """
+ table = column = None # make pylint happy
+ def precommit_event(self):
+ session, table, column = self.session, self.table, self.column
+ # drop index if any
+ session.pool.source('system').drop_index(session, table, column)
+ try:
+ session.system_sql('ALTER TABLE %s DROP COLUMN %s'
+ % (table, column), rollback_on_failure=False)
+ self.info('dropped column %s from table %s', column, table)
+ except Exception, ex:
+ # not supported by sqlite for instance
+ self.error('error while altering table %s: %s', table, ex)
-class UpdateSchemaOp(SingleLastOperation):
+
+# base operations for in-memory schema synchronization ########################
+
+class MemSchemaNotifyChanges(SingleLastOperation):
"""the update schema operation:
special operation which should be called once and after all other schema
@@ -103,433 +138,51 @@
self.repo.set_schema(self.repo.schema)
-class DropTableOp(PreCommitOperation):
- """actually remove a database from the application's schema"""
- table = None # make pylint happy
- def precommit_event(self):
- dropped = self.session.query_data('droppedtables',
- default=set(), setdefault=True)
- if self.table in dropped:
- return # already processed
- dropped.add(self.table)
- self.session.system_sql('DROP TABLE %s' % self.table)
- self.info('dropped table %s', self.table)
-
-class DropColumnOp(PreCommitOperation):
- """actually remove the attribut's column from entity table in the system
- database
- """
- table = column = None # make pylint happy
- def precommit_event(self):
- session, table, column = self.session, self.table, self.column
- # drop index if any
- session.pool.source('system').drop_index(session, table, column)
- try:
- session.system_sql('ALTER TABLE %s DROP COLUMN %s'
- % (table, column))
- self.info('dropped column %s from table %s', column, table)
- except Exception, ex:
- # not supported by sqlite for instance
- self.error('error while altering table %s: %s', table, ex)
-
-
-# deletion ####################################################################
-
-class DeleteCWETypeOp(SchemaOperation):
- """actually remove the entity type from the application's schema"""
- def commit_event(self):
- try:
- # del_entity_type also removes entity's relations
- self.schema.del_entity_type(self.kobj)
- except KeyError:
- # s/o entity type have already been deleted
- pass
-
-def before_del_eetype(session, eid):
- """before deleting a CWEType entity:
- * check that we don't remove a core entity type
- * cascade to delete related CWAttribute and CWRelation entities
- * instantiate an operation to delete the entity type on commit
- """
- # final entities can't be deleted, don't care about that
- name = check_internal_entity(session, eid, CORE_ETYPES)
- # delete every entities of this type
- session.unsafe_execute('DELETE %s X' % name)
- DropTableOp(session, table=SQL_PREFIX + name)
- DeleteCWETypeOp(session, name)
-
-def after_del_eetype(session, eid):
- # workflow cleanup
- session.execute('DELETE State X WHERE NOT X state_of Y')
- session.execute('DELETE Transition X WHERE NOT X transition_of Y')
-
-
-class DeleteCWRTypeOp(SchemaOperation):
- """actually remove the relation type from the application's schema"""
- def commit_event(self):
- try:
- self.schema.del_relation_type(self.kobj)
- except KeyError:
- # s/o entity type have already been deleted
- pass
-
-def before_del_ertype(session, eid):
- """before deleting a CWRType entity:
- * check that we don't remove a core relation type
- * cascade to delete related CWAttribute and CWRelation entities
- * instantiate an operation to delete the relation type on commit
- """
- name = check_internal_entity(session, eid, CORE_RTYPES)
- # delete relation definitions using this relation type
- session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
- {'x': eid})
- session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
- {'x': eid})
- DeleteCWRTypeOp(session, name)
-
-
-class DelRelationDefOp(SchemaOperation):
- """actually remove the relation definition from the application's schema"""
- def commit_event(self):
- subjtype, rtype, objtype = self.kobj
- try:
- self.schema.del_relation_def(subjtype, rtype, objtype)
- except KeyError:
- # relation type may have been already deleted
- pass
+class MemSchemaOperation(Operation):
+ """base class for schema operations"""
+ def __init__(self, session, kobj=None, **kwargs):
+ self.schema = session.schema
+ self.kobj = kobj
+ # once Operation.__init__ has been called, event may be triggered, so
+ # do this last !
+ Operation.__init__(self, session, **kwargs)
+ # every schema operation is triggering a schema update
+ MemSchemaNotifyChanges(session)
-def after_del_relation_type(session, rdefeid, rtype, rteid):
- """before deleting a CWAttribute or CWRelation entity:
- * if this is a final or inlined relation definition, instantiate an
- operation to drop necessary column, else if this is the last instance
- of a non final relation, instantiate an operation to drop necessary
- table
- * instantiate an operation to delete the relation definition on commit
- * delete the associated relation type when necessary
- """
- subjschema, rschema, objschema = session.repo.schema.schema_by_eid(rdefeid)
- pendings = session.query_data('pendingeids', ())
- # first delete existing relation if necessary
- if rschema.is_final():
- rdeftype = 'CWAttribute'
- else:
- rdeftype = 'CWRelation'
- if not (subjschema.eid in pendings or objschema.eid in pendings):
- session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
- % (rschema, subjschema, objschema))
- execute = session.unsafe_execute
- rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
- 'R eid %%(x)s' % rdeftype, {'x': rteid})
- lastrel = rset[0][0] == 0
- # we have to update physical schema systematically for final and inlined
- # relations, but only if it's the last instance for this relation type
- # for other relations
-
- if (rschema.is_final() or rschema.inlined):
- rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
- 'R eid %%(x)s, X from_entity E, E name %%(name)s'
- % rdeftype, {'x': rteid, 'name': str(subjschema)})
- if rset[0][0] == 0 and not subjschema.eid in pendings:
- DropColumnOp(session, table=SQL_PREFIX + subjschema.type,
- column=SQL_PREFIX + rschema.type)
- elif lastrel:
- DropTableOp(session, table='%s_relation' % rschema.type)
- # if this is the last instance, drop associated relation type
- if lastrel and not rteid in pendings:
- execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
- DelRelationDefOp(session, (subjschema, rschema, objschema))
-
-
-# addition ####################################################################
-
-class AddCWETypeOp(EarlySchemaOperation):
- """actually add the entity type to the application's schema"""
- eid = None # make pylint happy
- def commit_event(self):
- eschema = self.schema.add_entity_type(self.kobj)
- eschema.eid = self.eid
-
-def before_add_eetype(session, entity):
- """before adding a CWEType entity:
- * check that we are not using an existing entity type,
- """
- name = entity['name']
- schema = session.repo.schema
- if name in schema and schema[name].eid is not None:
- raise RepositoryError('an entity type %s already exists' % name)
-
-def after_add_eetype(session, entity):
- """after adding a CWEType entity:
- * create the necessary table
- * set creation_date and modification_date by creating the necessary
- CWAttribute entities
- * add owned_by relation by creating the necessary CWRelation entity
- * register an operation to add the entity type to the application's
- schema on commit
- """
- if entity.get('final'):
- return
- schema = session.repo.schema
- name = entity['name']
- etype = EntityType(name=name, description=entity.get('description'),
- meta=entity.get('meta')) # don't care about final
- # fake we add it to the schema now to get a correctly initialized schema
- # but remove it before doing anything more dangerous...
- schema = session.repo.schema
- eschema = schema.add_entity_type(etype)
- eschema.set_default_groups()
- # generate table sql and rql to add metadata
- tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
- prefix=SQL_PREFIX)
- relrqls = []
- for rtype in ('is', 'is_instance_of', 'creation_date', 'modification_date',
- 'created_by', 'owned_by'):
- rschema = schema[rtype]
- sampletype = rschema.subjects()[0]
- desttype = rschema.objects()[0]
- props = rschema.rproperties(sampletype, desttype)
- relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
- # now remove it !
- schema.del_entity_type(name)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- session.system_sql(sql)
- # register operation to modify the schema on commit
- # this have to be done before adding other relations definitions
- # or permission settings
- AddCWETypeOp(session, etype, eid=entity.eid)
- # add meta creation_date, modification_date and owned_by relations
- for rql, kwargs in relrqls:
- session.execute(rql, kwargs)
+ def prepare_constraints(self, subjtype, rtype, objtype):
+ constraints = rtype.rproperty(subjtype, objtype, 'constraints')
+ self.constraints = list(constraints)
+ rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
-class AddCWRTypeOp(EarlySchemaOperation):
- """actually add the relation type to the application's schema"""
- eid = None # make pylint happy
- def commit_event(self):
- rschema = self.schema.add_relation_type(self.kobj)
- rschema.set_default_groups()
- rschema.eid = self.eid
-
-def before_add_ertype(session, entity):
- """before adding a CWRType entity:
- * check that we are not using an existing relation type,
- * register an operation to add the relation type to the application's
- schema on commit
-
- We don't know yeat this point if a table is necessary
- """
- name = entity['name']
- if name in session.repo.schema.relations():
- raise RepositoryError('a relation type %s already exists' % name)
-
-def after_add_ertype(session, entity):
- """after a CWRType entity has been added:
- * register an operation to add the relation type to the application's
- schema on commit
- We don't know yeat this point if a table is necessary
- """
- AddCWRTypeOp(session, RelationType(name=entity['name'],
- description=entity.get('description'),
- meta=entity.get('meta', False),
- inlined=entity.get('inlined', False),
- symetric=entity.get('symetric', False)),
- eid=entity.eid)
-
-
-class AddErdefOp(EarlySchemaOperation):
- """actually add the attribute relation definition to the application's
- schema
- """
- def commit_event(self):
- self.schema.add_relation_def(self.kobj)
-
-TYPE_CONVERTER = {
- 'Boolean': bool,
- 'Int': int,
- 'Float': float,
- 'Password': str,
- 'String': unicode,
- 'Date' : unicode,
- 'Datetime' : unicode,
- 'Time' : unicode,
- }
+class MemSchemaEarlyOperation(MemSchemaOperation):
+ def insert_index(self):
+ """schema operation which are inserted at the begining of the queue
+ (typically to add/remove entity or relation types)
+ """
+ i = -1
+ for i, op in enumerate(self.session.pending_operations):
+ if not isinstance(op, MemSchemaEarlyOperation):
+ return i
+ return i + 1
-class AddCWAttributePreCommitOp(PreCommitOperation):
- """an attribute relation (CWAttribute) has been added:
- * add the necessary column
- * set default on this column if any and possible
- * register an operation to add the relation definition to the
- application's schema on commit
-
- constraints are handled by specific hooks
- """
- entity = None # make pylint happy
- def precommit_event(self):
- session = self.session
- entity = self.entity
- fromentity = entity.from_entity[0]
- relationtype = entity.relation_type[0]
- session.execute('SET X ordernum Y+1 WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, X ordernum >= %(order)s, NOT X eid %(x)s',
- {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0})
- subj, rtype = str(fromentity.name), str(relationtype.name)
- obj = str(entity.to_entity[0].name)
- # at this point default is a string or None, but we need a correctly
- # typed value
- default = entity.defaultval
- if default is not None:
- default = TYPE_CONVERTER[obj](default)
- constraints = get_constraints(session, entity)
- rdef = RelationDefinition(subj, rtype, obj,
- cardinality=entity.cardinality,
- order=entity.ordernum,
- description=entity.description,
- default=default,
- indexed=entity.indexed,
- fulltextindexed=entity.fulltextindexed,
- internationalizable=entity.internationalizable,
- constraints=constraints,
- eid=entity.eid)
- sysource = session.pool.source('system')
- attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
- constraints)
- # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
- # add a new column with UNIQUE, it should be added after the ALTER TABLE
- # using ADD INDEX
- if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
- extra_unique_index = True
- attrtype = attrtype.replace(' UNIQUE', '')
+class MemSchemaPermissionOperation(MemSchemaOperation):
+ """base class to synchronize schema permission definitions"""
+ def __init__(self, session, perm, etype_eid):
+ self.perm = perm
+ try:
+ self.name = entity_name(session, etype_eid)
+ except IndexError:
+ self.error('changing permission of a no more existant type #%s',
+ etype_eid)
else:
- extra_unique_index = False
- # added some str() wrapping query since some backend (eg psycopg) don't
- # allow unicode queries
- table = SQL_PREFIX + subj
- column = SQL_PREFIX + rtype
- try:
- session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
- % (table, column, attrtype)))
- self.info('added column %s to table %s', table, column)
- except Exception, ex:
- # the column probably already exists. this occurs when
- # the entity's type has just been added or if the column
- # has not been previously dropped
- self.error('error while altering table %s: %s', table, ex)
- if extra_unique_index or entity.indexed:
- try:
- sysource.create_index(session, table, column,
- unique=extra_unique_index)
- except Exception, ex:
- self.error('error while creating index for %s.%s: %s',
- table, column, ex)
- AddErdefOp(session, rdef)
-
-def after_add_efrdef(session, entity):
- AddCWAttributePreCommitOp(session, entity=entity)
+ Operation.__init__(self, session)
-class AddCWRelationPreCommitOp(PreCommitOperation):
- """an actual relation has been added:
- * if this is an inlined relation, add the necessary column
- else if it's the first instance of this relation type, add the
- necessary table and set default permissions
- * register an operation to add the relation definition to the
- application's schema on commit
+# operations for high-level source database alteration ########################
- constraints are handled by specific hooks
- """
- entity = None # make pylint happy
- def precommit_event(self):
- session = self.session
- entity = self.entity
- fromentity = entity.from_entity[0]
- relationtype = entity.relation_type[0]
- session.execute('SET X ordernum Y+1 WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, X ordernum >= %(order)s, NOT X eid %(x)s',
- {'x': entity.eid, 'se': fromentity.eid, 'order': entity.ordernum or 0})
- subj, rtype = str(fromentity.name), str(relationtype.name)
- obj = str(entity.to_entity[0].name)
- card = entity.get('cardinality')
- rdef = RelationDefinition(subj, rtype, obj,
- cardinality=card,
- order=entity.ordernum,
- composite=entity.composite,
- description=entity.description,
- constraints=get_constraints(session, entity),
- eid=entity.eid)
- schema = session.repo.schema
- rschema = schema.rschema(rtype)
- # this have to be done before permissions setting
- AddErdefOp(session, rdef)
- if rschema.inlined:
- # need to add a column if the relation is inlined and if this is the
- # first occurence of "Subject relation Something" whatever Something
- # and if it has not been added during other event of the same
- # transaction
- key = '%s.%s' % (subj, rtype)
- try:
- alreadythere = bool(rschema.objects(subj))
- except KeyError:
- alreadythere = False
- if not (alreadythere or
- key in session.query_data('createdattrs', ())):
- add_inline_relation_column(session, subj, rtype)
- else:
- # need to create the relation if no relation definition in the
- # schema and if it has not been added during other event of the same
- # transaction
- if not (rschema.subjects() or
- rtype in session.query_data('createdtables', ())):
- try:
- rschema = schema[rtype]
- tablesql = rschema2sql(rschema)
- except KeyError:
- # fake we add it to the schema now to get a correctly
- # initialized schema but remove it before doing anything
- # more dangerous...
- rschema = schema.add_relation_type(rdef)
- tablesql = rschema2sql(rschema)
- schema.del_relation_type(rtype)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- self.session.system_sql(sql)
- session.add_query_data('createdtables', rtype)
-
-def after_add_enfrdef(session, entity):
- AddCWRelationPreCommitOp(session, entity=entity)
-
-
-# update ######################################################################
-
-def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
- errors = {}
- # don't use getattr(entity, attr), we would get the modified value if any
- for attr in ro_attrs:
- origval = entity_attr(session, entity.eid, attr)
- if entity.get(attr, origval) != origval:
- errors[attr] = session._("can't change the %s attribute") % \
- display_name(session, attr)
- if errors:
- raise ValidationError(entity.eid, errors)
-
-def before_update_eetype(session, entity):
- """check name change, handle final"""
- check_valid_changes(session, entity, ro_attrs=('final',))
- # don't use getattr(entity, attr), we would get the modified value if any
- oldname = entity_attr(session, entity.eid, 'name')
- newname = entity.get('name', oldname)
- if newname.lower() != oldname.lower():
- eschema = session.repo.schema[oldname]
- UpdateEntityTypeName(session, eschema=eschema,
- oldname=oldname, newname=newname)
-
-def before_update_ertype(session, entity):
- """check name change, handle final"""
- check_valid_changes(session, entity)
-
-
-class UpdateEntityTypeName(SchemaOperation):
+class SourceDbCWETypeRename(PreCommitOperation):
"""this operation updates physical storage accordingly"""
oldname = newname = None # make pylint happy
@@ -544,11 +197,211 @@
sqlexec('UPDATE deleted_entities SET type=%s WHERE type=%s',
(self.newname, self.oldname))
- def commit_event(self):
- self.session.repo.schema.rename_entity_type(self.oldname, self.newname)
+
+class SourceDbCWRTypeUpdate(PreCommitOperation):
+ """actually update some properties of a relation definition"""
+ rschema = values = entity = None # make pylint happy
+
+ def precommit_event(self):
+ session = self.session
+ rschema = self.rschema
+ if rschema.is_final() or not 'inlined' in self.values:
+ return # nothing to do
+ inlined = self.values['inlined']
+ entity = self.entity
+ # check in-lining is necessary / possible
+ if not entity.inlined_changed(inlined):
+ return # nothing to do
+ # inlined changed, make necessary physical changes!
+ sqlexec = self.session.system_sql
+ rtype = rschema.type
+ eidcolumn = SQL_PREFIX + 'eid'
+ if not inlined:
+ # need to create the relation if it has not been already done by
+ # another event of the same transaction
+ if not rschema.type in session.transaction_data.get('createdtables', ()):
+ tablesql = rschema2sql(rschema)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ sqlexec(sql)
+ session.transaction_data.setdefault('createdtables', []).append(
+ rschema.type)
+ # copy existant data
+ column = SQL_PREFIX + rtype
+ for etype in rschema.subjects():
+ table = SQL_PREFIX + str(etype)
+ sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
+ % (rtype, eidcolumn, column, table, column))
+ # drop existant columns
+ for etype in rschema.subjects():
+ DropColumn(session, table=SQL_PREFIX + str(etype),
+ column=SQL_PREFIX + rtype)
+ else:
+ for etype in rschema.subjects():
+ try:
+ add_inline_relation_column(session, str(etype), rtype)
+ except Exception, ex:
+ # the column probably already exists. this occurs when the
+ # entity's type has just been added or if the column has not
+ # been previously dropped
+ self.error('error while altering table %s: %s', etype, ex)
+ # copy existant data.
+ # XXX don't use, it's not supported by sqlite (at least at when i tried it)
+ #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
+ # 'FROM %(rtype)s_relation '
+ # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
+ # % locals())
+ table = SQL_PREFIX + str(etype)
+ cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
+ '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
+ '%(rtype)s_relation.eid_from' % locals())
+ args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
+ if args:
+ column = SQL_PREFIX + rtype
+ cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
+ % (table, column, eidcolumn), args)
+ # drop existant table
+ DropRelationTable(session, rtype)
-class UpdateRelationDefOp(SchemaOperation):
+class SourceDbCWAttributeAdd(PreCommitOperation):
+ """an attribute relation (CWAttribute) has been added:
+ * add the necessary column
+ * set default on this column if any and possible
+ * register an operation to add the relation definition to the
+ instance's schema on commit
+
+ constraints are handled by specific hooks
+ """
+ entity = None # make pylint happy
+
+ def init_rdef(self, **kwargs):
+ entity = self.entity
+ fromentity = entity.stype
+ self.session.execute('SET X ordernum Y+1 '
+ 'WHERE X from_entity SE, SE eid %(se)s, X ordernum Y, '
+ 'X ordernum >= %(order)s, NOT X eid %(x)s',
+ {'x': entity.eid, 'se': fromentity.eid,
+ 'order': entity.ordernum or 0})
+ subj = str(fromentity.name)
+ rtype = entity.rtype.name
+ obj = str(entity.otype.name)
+ constraints = get_constraints(self.session, entity)
+ rdef = RelationDefinition(subj, rtype, obj,
+ description=entity.description,
+ cardinality=entity.cardinality,
+ constraints=constraints,
+ order=entity.ordernum,
+ eid=entity.eid,
+ **kwargs)
+ MemSchemaRDefAdd(self.session, rdef)
+ return rdef
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ # entity.defaultval is a string or None, but we need a correctly typed
+ # value
+ default = entity.defaultval
+ if default is not None:
+ default = TYPE_CONVERTER[entity.otype.name](default)
+ rdef = self.init_rdef(default=default,
+ indexed=entity.indexed,
+ fulltextindexed=entity.fulltextindexed,
+ internationalizable=entity.internationalizable)
+ sysource = session.pool.source('system')
+ attrtype = type_from_constraints(sysource.dbhelper, rdef.object,
+ rdef.constraints)
+ # XXX should be moved somehow into lgc.adbh: sqlite doesn't support to
+ # add a new column with UNIQUE, it should be added after the ALTER TABLE
+ # using ADD INDEX
+ if sysource.dbdriver == 'sqlite' and 'UNIQUE' in attrtype:
+ extra_unique_index = True
+ attrtype = attrtype.replace(' UNIQUE', '')
+ else:
+ extra_unique_index = False
+ # added some str() wrapping query since some backend (eg psycopg) don't
+ # allow unicode queries
+ table = SQL_PREFIX + rdef.subject
+ column = SQL_PREFIX + rdef.name
+ try:
+ session.system_sql(str('ALTER TABLE %s ADD COLUMN %s %s'
+ % (table, column, attrtype)),
+ rollback_on_failure=False)
+ self.info('added column %s to table %s', table, column)
+ except Exception, ex:
+ # the column probably already exists. this occurs when
+ # the entity's type has just been added or if the column
+ # has not been previously dropped
+ self.error('error while altering table %s: %s', table, ex)
+ if extra_unique_index or entity.indexed:
+ try:
+ sysource.create_index(session, table, column,
+ unique=extra_unique_index)
+ except Exception, ex:
+ self.error('error while creating index for %s.%s: %s',
+ table, column, ex)
+
+
+class SourceDbCWRelationAdd(SourceDbCWAttributeAdd):
+ """an actual relation has been added:
+ * if this is an inlined relation, add the necessary column
+ else if it's the first instance of this relation type, add the
+ necessary table and set default permissions
+ * register an operation to add the relation definition to the
+ instance's schema on commit
+
+ constraints are handled by specific hooks
+ """
+ entity = None # make pylint happy
+
+ def precommit_event(self):
+ session = self.session
+ entity = self.entity
+ rdef = self.init_rdef(composite=entity.composite)
+ schema = session.schema
+ rtype = rdef.name
+ rschema = session.schema.rschema(rtype)
+ # this have to be done before permissions setting
+ if rschema.inlined:
+ # need to add a column if the relation is inlined and if this is the
+ # first occurence of "Subject relation Something" whatever Something
+ # and if it has not been added during other event of the same
+ # transaction
+ key = '%s.%s' % (rdef.subject, rtype)
+ try:
+ alreadythere = bool(rschema.objects(rdef.subject))
+ except KeyError:
+ alreadythere = False
+ if not (alreadythere or
+ key in session.transaction_data.get('createdattrs', ())):
+ add_inline_relation_column(session, rdef.subject, rtype)
+ else:
+ # need to create the relation if no relation definition in the
+ # schema and if it has not been added during other event of the same
+ # transaction
+ if not (rschema.subjects() or
+ rtype in session.transaction_data.get('createdtables', ())):
+ try:
+ rschema = session.schema.rschema(rtype)
+ tablesql = rschema2sql(rschema)
+ except KeyError:
+ # fake we add it to the schema now to get a correctly
+ # initialized schema but remove it before doing anything
+ # more dangerous...
+ rschema = session.schema.add_relation_type(rdef)
+ tablesql = rschema2sql(rschema)
+ session.schema.del_relation_type(rtype)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ session.system_sql(sql)
+ session.transaction_data.setdefault('createdtables', []).append(
+ rtype)
+
+
+class SourceDbRDefUpdate(PreCommitOperation):
"""actually update some properties of a relation definition"""
rschema = values = None # make pylint happy
@@ -572,155 +425,43 @@
constraints = self.rschema.rproperty(etype, atype, 'constraints')
coltype = type_from_constraints(adbh, atype, constraints,
creating=False)
+ # XXX check self.values['cardinality'][0] actually changed?
sql = adbh.sql_set_null_allowed(table, column, coltype,
self.values['cardinality'][0] != '1')
self.session.system_sql(sql)
- def commit_event(self):
- # structure should be clean, not need to remove entity's relations
- # at this point
- self.rschema._rproperties[self.kobj].update(self.values)
-
-def after_update_erdef(session, entity):
- desttype = entity.to_entity[0].name
- rschema = session.repo.schema[entity.relation_type[0].name]
- newvalues = {}
- for prop in rschema.rproperty_defs(desttype):
- if prop == 'constraints':
- continue
- if prop == 'order':
- prop = 'ordernum'
- if prop in entity:
- newvalues[prop] = entity[prop]
- if newvalues:
- subjtype = entity.from_entity[0].name
- UpdateRelationDefOp(session, (subjtype, desttype),
- rschema=rschema, values=newvalues)
-
-
-class UpdateRtypeOp(SchemaOperation):
- """actually update some properties of a relation definition"""
- rschema = values = entity = None # make pylint happy
-
- def precommit_event(self):
- session = self.session
- rschema = self.rschema
- if rschema.is_final() or not 'inlined' in self.values:
- return # nothing to do
- inlined = self.values['inlined']
- entity = self.entity
- if not entity.inlined_changed(inlined): # check in-lining is necessary/possible
- return # nothing to do
- # inlined changed, make necessary physical changes!
- sqlexec = self.session.system_sql
- rtype = rschema.type
- eidcolumn = SQL_PREFIX + 'eid'
- if not inlined:
- # need to create the relation if it has not been already done by another
- # event of the same transaction
- if not rschema.type in session.query_data('createdtables', ()):
- tablesql = rschema2sql(rschema)
- # create the necessary table
- for sql in tablesql.split(';'):
- if sql.strip():
- sqlexec(sql)
- session.add_query_data('createdtables', rschema.type)
- # copy existant data
- column = SQL_PREFIX + rtype
- for etype in rschema.subjects():
- table = SQL_PREFIX + str(etype)
- sqlexec('INSERT INTO %s_relation SELECT %s, %s FROM %s WHERE NOT %s IS NULL'
- % (rtype, eidcolumn, column, table, column))
- # drop existant columns
- for etype in rschema.subjects():
- DropColumnOp(session, table=SQL_PREFIX + str(etype),
- column=SQL_PREFIX + rtype)
- else:
- for etype in rschema.subjects():
- try:
- add_inline_relation_column(session, str(etype), rtype)
- except Exception, ex:
- # the column probably already exists. this occurs when
- # the entity's type has just been added or if the column
- # has not been previously dropped
- self.error('error while altering table %s: %s', etype, ex)
- # copy existant data.
- # XXX don't use, it's not supported by sqlite (at least at when i tried it)
- #sqlexec('UPDATE %(etype)s SET %(rtype)s=eid_to '
- # 'FROM %(rtype)s_relation '
- # 'WHERE %(etype)s.eid=%(rtype)s_relation.eid_from'
- # % locals())
- table = SQL_PREFIX + str(etype)
- cursor = sqlexec('SELECT eid_from, eid_to FROM %(table)s, '
- '%(rtype)s_relation WHERE %(table)s.%(eidcolumn)s='
- '%(rtype)s_relation.eid_from' % locals())
- args = [{'val': eid_to, 'x': eid} for eid, eid_to in cursor.fetchall()]
- if args:
- column = SQL_PREFIX + rtype
- cursor.executemany('UPDATE %s SET %s=%%(val)s WHERE %s=%%(x)s'
- % (table, column, eidcolumn), args)
- # drop existant table
- DropTableOp(session, table='%s_relation' % rtype)
-
- def commit_event(self):
- # structure should be clean, not need to remove entity's relations
- # at this point
- self.rschema.__dict__.update(self.values)
-
-def after_update_ertype(session, entity):
- rschema = session.repo.schema.rschema(entity.name)
- newvalues = {}
- for prop in ('meta', 'symetric', 'inlined'):
- if prop in entity:
- newvalues[prop] = entity[prop]
- if newvalues:
- UpdateRtypeOp(session, entity=entity, rschema=rschema, values=newvalues)
-
-# constraints synchronization #################################################
-
-from cubicweb.schema import CONSTRAINTS
-
-class ConstraintOp(SchemaOperation):
+class SourceDbCWConstraintAdd(PreCommitOperation):
"""actually update constraint of a relation definition"""
entity = None # make pylint happy
-
- def prepare_constraints(self, rtype, subjtype, objtype):
- constraints = rtype.rproperty(subjtype, objtype, 'constraints')
- self.constraints = list(constraints)
- rtype.set_rproperty(subjtype, objtype, 'constraints', self.constraints)
- return self.constraints
+ cancelled = False
def precommit_event(self):
rdef = self.entity.reverse_constrained_by[0]
session = self.session
- # when the relation is added in the same transaction, the constraint object
- # is created by AddEN?FRDefPreCommitOp, there is nothing to do here
- if rdef.eid in session.query_data('neweids', ()):
- self.cancelled = True
+ # when the relation is added in the same transaction, the constraint
+ # object is created by the operation adding the attribute or relation,
+ # so there is nothing to do here
+ if rdef.eid in session.transaction_data.get('neweids', ()):
return
- self.cancelled = False
- schema = session.repo.schema
- subjtype, rtype, objtype = schema.schema_by_eid(rdef.eid)
- self.prepare_constraints(rtype, subjtype, objtype)
+ subjtype, rtype, objtype = session.schema.schema_by_eid(rdef.eid)
cstrtype = self.entity.type
- self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
- self._cstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
- self._cstr.eid = self.entity.eid
+ cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+ prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
table = SQL_PREFIX + str(subjtype)
column = SQL_PREFIX + str(rtype)
# alter the physical schema on size constraint changes
- if self._cstr.type() == 'SizeConstraint' and (
- self.cstr is None or self.cstr.max != self._cstr.max):
+ if prevcstr.type() == 'SizeConstraint' and (
+ cstr is None or cstr.max != prevcstr.max):
adbh = self.session.pool.source('system').dbhelper
card = rtype.rproperty(subjtype, objtype, 'cardinality')
- coltype = type_from_constraints(adbh, objtype, [self._cstr],
+ coltype = type_from_constraints(adbh, objtype, [prevcstr],
creating=False)
sql = adbh.sql_change_col_type(table, column, coltype, card != '1')
try:
- session.system_sql(sql)
+ session.system_sql(sql, rollback_on_failure=False)
self.info('altered column %s of table %s: now VARCHAR(%s)',
- column, table, self._cstr.max)
+ column, table, prevcstr.max)
except Exception, ex:
# not supported by sqlite for instance
self.error('error while altering table %s: %s', table, ex)
@@ -728,28 +469,12 @@
session.pool.source('system').create_index(
self.session, table, column, unique=True)
- def commit_event(self):
- if self.cancelled:
- return
- # in-place modification
- if not self.cstr is None:
- self.constraints.remove(self.cstr)
- self.constraints.append(self._cstr)
-
-def after_add_econstraint(session, entity):
- ConstraintOp(session, entity=entity)
-
-def after_update_econstraint(session, entity):
- ConstraintOp(session, entity=entity)
-
-
-class DelConstraintOp(ConstraintOp):
+class SourceDbCWConstraintDel(PreCommitOperation):
"""actually remove a constraint of a relation definition"""
rtype = subjtype = objtype = None # make pylint happy
def precommit_event(self):
- self.prepare_constraints(self.rtype, self.subjtype, self.objtype)
cstrtype = self.cstr.type()
table = SQL_PREFIX + str(self.subjtype)
column = SQL_PREFIX + str(self.rtype)
@@ -757,7 +482,8 @@
if cstrtype == 'SizeConstraint':
try:
self.session.system_sql('ALTER TABLE %s ALTER COLUMN %s TYPE TEXT'
- % (table, column))
+ % (table, column),
+ rollback_on_failure=False)
self.info('altered column %s of table %s: now TEXT',
column, table)
except Exception, ex:
@@ -767,48 +493,143 @@
self.session.pool.source('system').drop_index(
self.session, table, column, unique=True)
+
+# operations for in-memory schema synchronization #############################
+
+class MemSchemaCWETypeAdd(MemSchemaEarlyOperation):
+ """actually add the entity type to the instance's schema"""
+ eid = None # make pylint happy
+ def commit_event(self):
+ self.schema.add_entity_type(self.kobj)
+
+
+class MemSchemaCWETypeRename(MemSchemaOperation):
+ """this operation updates physical storage accordingly"""
+ oldname = newname = None # make pylint happy
+
+ def commit_event(self):
+ self.session.schema.rename_entity_type(self.oldname, self.newname)
+
+
+class MemSchemaCWETypeDel(MemSchemaOperation):
+ """actually remove the entity type from the instance's schema"""
+ def commit_event(self):
+ try:
+ # del_entity_type also removes entity's relations
+ self.schema.del_entity_type(self.kobj)
+ except KeyError:
+ # s/o entity type have already been deleted
+ pass
+
+
+class MemSchemaCWRTypeAdd(MemSchemaEarlyOperation):
+ """actually add the relation type to the instance's schema"""
+ eid = None # make pylint happy
+ def commit_event(self):
+ rschema = self.schema.add_relation_type(self.kobj)
+ rschema.set_default_groups()
+
+
+class MemSchemaCWRTypeUpdate(MemSchemaOperation):
+ """actually update some properties of a relation definition"""
+ rschema = values = None # make pylint happy
+
+ def commit_event(self):
+ # structure should be clean, not need to remove entity's relations
+ # at this point
+ self.rschema.__dict__.update(self.values)
+
+
+class MemSchemaCWRTypeDel(MemSchemaOperation):
+ """actually remove the relation type from the instance's schema"""
+ def commit_event(self):
+ try:
+ self.schema.del_relation_type(self.kobj)
+ except KeyError:
+ # s/o entity type have already been deleted
+ pass
+
+
+class MemSchemaRDefAdd(MemSchemaEarlyOperation):
+ """actually add the attribute relation definition to the instance's
+ schema
+ """
+ def commit_event(self):
+ self.schema.add_relation_def(self.kobj)
+
+
+class MemSchemaRDefUpdate(MemSchemaOperation):
+ """actually update some properties of a relation definition"""
+ rschema = values = None # make pylint happy
+
+ def commit_event(self):
+ # structure should be clean, not need to remove entity's relations
+ # at this point
+ self.rschema._rproperties[self.kobj].update(self.values)
+
+
+class MemSchemaRDefDel(MemSchemaOperation):
+ """actually remove the relation definition from the instance's schema"""
+ def commit_event(self):
+ subjtype, rtype, objtype = self.kobj
+ try:
+ self.schema.del_relation_def(subjtype, rtype, objtype)
+ except KeyError:
+ # relation type may have been already deleted
+ pass
+
+
+class MemSchemaCWConstraintAdd(MemSchemaOperation):
+ """actually update constraint of a relation definition
+
+ has to be called before SourceDbCWConstraintAdd
+ """
+ cancelled = False
+
+ def precommit_event(self):
+ rdef = self.entity.reverse_constrained_by[0]
+ # when the relation is added in the same transaction, the constraint
+ # object is created by the operation adding the attribute or relation,
+ # so there is nothing to do here
+ if rdef.eid in self.session.transaction_data.get('neweids', ()):
+ self.cancelled = True
+ return
+ subjtype, rtype, objtype = self.session.schema.schema_by_eid(rdef.eid)
+ self.prepare_constraints(subjtype, rtype, objtype)
+ cstrtype = self.entity.type
+ self.cstr = rtype.constraint_by_type(subjtype, objtype, cstrtype)
+ self.prevcstr = CONSTRAINTS[cstrtype].deserialize(self.entity.value)
+ self.prevcstr.eid = self.entity.eid
+
+ def commit_event(self):
+ if self.cancelled:
+ return
+ # in-place modification
+ if not self.cstr is None:
+ self.constraints.remove(self.cstr)
+ self.constraints.append(self.prevcstr)
+
+
+class MemSchemaCWConstraintDel(MemSchemaOperation):
+ """actually remove a constraint of a relation definition
+
+ has to be called before SourceDbCWConstraintDel
+ """
+ rtype = subjtype = objtype = None # make pylint happy
+ def precommit_event(self):
+ self.prepare_constraints(self.subjtype, self.rtype, self.objtype)
+
def commit_event(self):
self.constraints.remove(self.cstr)
-def before_delete_constrained_by(session, fromeid, rtype, toeid):
- if not fromeid in session.query_data('pendingeids', ()):
- schema = session.repo.schema
- entity = session.eid_rset(toeid).get_entity(0, 0)
- subjtype, rtype, objtype = schema.schema_by_eid(fromeid)
- try:
- cstr = rtype.constraint_by_type(subjtype, objtype, entity.cstrtype[0].name)
- DelConstraintOp(session, subjtype=subjtype, rtype=rtype, objtype=objtype,
- cstr=cstr)
- except IndexError:
- session.critical('constraint type no more accessible')
-
-
-def after_add_constrained_by(session, fromeid, rtype, toeid):
- if fromeid in session.query_data('neweids', ()):
- session.add_query_data(fromeid, toeid)
-
-
-# schema permissions synchronization ##########################################
-
-class PermissionOp(Operation):
- """base class to synchronize schema permission definitions"""
- def __init__(self, session, perm, etype_eid):
- self.perm = perm
- try:
- self.name = entity_name(session, etype_eid)
- except IndexError:
- self.error('changing permission of a no more existant type #%s',
- etype_eid)
- else:
- Operation.__init__(self, session)
-
-class AddGroupPermissionOp(PermissionOp):
+class MemSchemaPermissionCWGroupAdd(MemSchemaPermissionOperation):
"""synchronize schema when a *_permission relation has been added on a group
"""
def __init__(self, session, perm, etype_eid, group_eid):
self.group = entity_name(session, group_eid)
- PermissionOp.__init__(self, session, perm, etype_eid)
+ super(MemSchemaPermissionCWGroupAdd, self).__init__(
+ session, perm, etype_eid)
def commit_event(self):
"""the observed connections pool has been commited"""
@@ -827,40 +648,11 @@
groups.append(self.group)
erschema.set_groups(self.perm, groups)
-class AddRQLExpressionPermissionOp(PermissionOp):
- """synchronize schema when a *_permission relation has been added on a rql
- expression
+
+class MemSchemaPermissionCWGroupDel(MemSchemaPermissionCWGroupAdd):
+ """synchronize schema when a *_permission relation has been deleted from a
+ group
"""
- def __init__(self, session, perm, etype_eid, expression):
- self.expr = expression
- PermissionOp.__init__(self, session, perm, etype_eid)
-
- def commit_event(self):
- """the observed connections pool has been commited"""
- try:
- erschema = self.schema[self.name]
- except KeyError:
- # duh, schema not found, log error and skip operation
- self.error('no schema for %s', self.name)
- return
- exprs = list(erschema.get_rqlexprs(self.perm))
- exprs.append(erschema.rql_expression(self.expr))
- erschema.set_rqlexprs(self.perm, exprs)
-
-def after_add_permission(session, subject, rtype, object):
- """added entity/relation *_permission, need to update schema"""
- perm = rtype.split('_', 1)[0]
- if session.describe(object)[0] == 'CWGroup':
- AddGroupPermissionOp(session, perm, subject, object)
- else: # RQLExpression
- expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
- {'x': object}, 'x')[0][0]
- AddRQLExpressionPermissionOp(session, perm, subject, expr)
-
-
-
-class DelGroupPermissionOp(AddGroupPermissionOp):
- """synchronize schema when a *_permission relation has been deleted from a group"""
def commit_event(self):
"""the observed connections pool has been commited"""
@@ -879,8 +671,32 @@
self.perm, erschema.type, self.group)
-class DelRQLExpressionPermissionOp(AddRQLExpressionPermissionOp):
- """synchronize schema when a *_permission relation has been deleted from an rql expression"""
+class MemSchemaPermissionRQLExpressionAdd(MemSchemaPermissionOperation):
+ """synchronize schema when a *_permission relation has been added on a rql
+ expression
+ """
+ def __init__(self, session, perm, etype_eid, expression):
+ self.expr = expression
+ super(MemSchemaPermissionRQLExpressionAdd, self).__init__(
+ session, perm, etype_eid)
+
+ def commit_event(self):
+ """the observed connections pool has been commited"""
+ try:
+ erschema = self.schema[self.name]
+ except KeyError:
+ # duh, schema not found, log error and skip operation
+ self.error('no schema for %s', self.name)
+ return
+ exprs = list(erschema.get_rqlexprs(self.perm))
+ exprs.append(erschema.rql_expression(self.expr))
+ erschema.set_rqlexprs(self.perm, exprs)
+
+
+class MemSchemaPermissionRQLExpressionDel(MemSchemaPermissionRQLExpressionAdd):
+ """synchronize schema when a *_permission relation has been deleted from an
+ rql expression
+ """
def commit_event(self):
"""the observed connections pool has been commited"""
@@ -902,27 +718,304 @@
erschema.set_rqlexprs(self.perm, rqlexprs)
+# deletion hooks ###############################################################
+
+def before_del_eetype(session, eid):
+ """before deleting a CWEType entity:
+ * check that we don't remove a core entity type
+ * cascade to delete related CWAttribute and CWRelation entities
+ * instantiate an operation to delete the entity type on commit
+ """
+ # final entities can't be deleted, don't care about that
+ name = check_internal_entity(session, eid, CORE_ETYPES)
+ # delete every entities of this type
+ session.unsafe_execute('DELETE %s X' % name)
+ DropTable(session, table=SQL_PREFIX + name)
+ MemSchemaCWETypeDel(session, name)
+
+
+def after_del_eetype(session, eid):
+ # workflow cleanup
+ session.execute('DELETE State X WHERE NOT X state_of Y')
+ session.execute('DELETE Transition X WHERE NOT X transition_of Y')
+
+
+def before_del_ertype(session, eid):
+ """before deleting a CWRType entity:
+ * check that we don't remove a core relation type
+ * cascade to delete related CWAttribute and CWRelation entities
+ * instantiate an operation to delete the relation type on commit
+ """
+ name = check_internal_entity(session, eid, CORE_RTYPES)
+ # delete relation definitions using this relation type
+ session.execute('DELETE CWAttribute X WHERE X relation_type Y, Y eid %(x)s',
+ {'x': eid})
+ session.execute('DELETE CWRelation X WHERE X relation_type Y, Y eid %(x)s',
+ {'x': eid})
+ MemSchemaCWRTypeDel(session, name)
+
+
+def after_del_relation_type(session, rdefeid, rtype, rteid):
+ """before deleting a CWAttribute or CWRelation entity:
+ * if this is a final or inlined relation definition, instantiate an
+ operation to drop necessary column, else if this is the last instance
+ of a non final relation, instantiate an operation to drop necessary
+ table
+ * instantiate an operation to delete the relation definition on commit
+ * delete the associated relation type when necessary
+ """
+ subjschema, rschema, objschema = session.schema.schema_by_eid(rdefeid)
+ pendings = session.transaction_data.get('pendingeids', ())
+ # first delete existing relation if necessary
+ if rschema.is_final():
+ rdeftype = 'CWAttribute'
+ else:
+ rdeftype = 'CWRelation'
+ if not (subjschema.eid in pendings or objschema.eid in pendings):
+ session.execute('DELETE X %s Y WHERE X is %s, Y is %s'
+ % (rschema, subjschema, objschema))
+ execute = session.unsafe_execute
+ rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R,'
+ 'R eid %%(x)s' % rdeftype, {'x': rteid})
+ lastrel = rset[0][0] == 0
+ # we have to update physical schema systematically for final and inlined
+ # relations, but only if it's the last instance for this relation type
+ # for other relations
+
+ if (rschema.is_final() or rschema.inlined):
+ rset = execute('Any COUNT(X) WHERE X is %s, X relation_type R, '
+ 'R eid %%(x)s, X from_entity E, E name %%(name)s'
+ % rdeftype, {'x': rteid, 'name': str(subjschema)})
+ if rset[0][0] == 0 and not subjschema.eid in pendings:
+ ptypes = session.transaction_data.setdefault('pendingrtypes', set())
+ ptypes.add(rschema.type)
+ DropColumn(session, table=SQL_PREFIX + subjschema.type,
+ column=SQL_PREFIX + rschema.type)
+ elif lastrel:
+ DropRelationTable(session, rschema.type)
+ # if this is the last instance, drop associated relation type
+ if lastrel and not rteid in pendings:
+ execute('DELETE CWRType X WHERE X eid %(x)s', {'x': rteid}, 'x')
+ MemSchemaRDefDel(session, (subjschema, rschema, objschema))
+
+
+# addition hooks ###############################################################
+
+def before_add_eetype(session, entity):
+ """before adding a CWEType entity:
+ * check that we are not using an existing entity type,
+ """
+ name = entity['name']
+ schema = session.schema
+ if name in schema and schema[name].eid is not None:
+ raise RepositoryError('an entity type %s already exists' % name)
+
+def after_add_eetype(session, entity):
+ """after adding a CWEType entity:
+ * create the necessary table
+ * set creation_date and modification_date by creating the necessary
+ CWAttribute entities
+ * add owned_by relation by creating the necessary CWRelation entity
+ * register an operation to add the entity type to the instance's
+ schema on commit
+ """
+ if entity.get('final'):
+ return
+ schema = session.schema
+ name = entity['name']
+ etype = EntityType(name=name, description=entity.get('description'),
+ meta=entity.get('meta')) # don't care about final
+ # fake we add it to the schema now to get a correctly initialized schema
+ # but remove it before doing anything more dangerous...
+ schema = session.schema
+ eschema = schema.add_entity_type(etype)
+ eschema.set_default_groups()
+ # generate table sql and rql to add metadata
+ tablesql = eschema2sql(session.pool.source('system').dbhelper, eschema,
+ prefix=SQL_PREFIX)
+ relrqls = []
+ for rtype in (META_RTYPES - VIRTUAL_RTYPES):
+ rschema = schema[rtype]
+ sampletype = rschema.subjects()[0]
+ desttype = rschema.objects()[0]
+ props = rschema.rproperties(sampletype, desttype)
+ relrqls += list(ss.rdef2rql(rschema, name, desttype, props))
+ # now remove it !
+ schema.del_entity_type(name)
+ # create the necessary table
+ for sql in tablesql.split(';'):
+ if sql.strip():
+ session.system_sql(sql)
+ # register operation to modify the schema on commit
+ # this have to be done before adding other relations definitions
+ # or permission settings
+ etype.eid = entity.eid
+ MemSchemaCWETypeAdd(session, etype)
+ # add meta relations
+ for rql, kwargs in relrqls:
+ session.execute(rql, kwargs)
+
+
+def before_add_ertype(session, entity):
+ """before adding a CWRType entity:
+ * check that we are not using an existing relation type,
+ * register an operation to add the relation type to the instance's
+ schema on commit
+
+ We don't know yeat this point if a table is necessary
+ """
+ name = entity['name']
+ if name in session.schema.relations():
+ raise RepositoryError('a relation type %s already exists' % name)
+
+
+def after_add_ertype(session, entity):
+ """after a CWRType entity has been added:
+ * register an operation to add the relation type to the instance's
+ schema on commit
+ We don't know yeat this point if a table is necessary
+ """
+ rtype = RelationType(name=entity['name'],
+ description=entity.get('description'),
+ meta=entity.get('meta', False),
+ inlined=entity.get('inlined', False),
+ symetric=entity.get('symetric', False))
+ rtype.eid = entity.eid
+ MemSchemaCWRTypeAdd(session, rtype)
+
+
+def after_add_efrdef(session, entity):
+ SourceDbCWAttributeAdd(session, entity=entity)
+
+def after_add_enfrdef(session, entity):
+ SourceDbCWRelationAdd(session, entity=entity)
+
+
+# update hooks #################################################################
+
+def check_valid_changes(session, entity, ro_attrs=('name', 'final')):
+ errors = {}
+ # don't use getattr(entity, attr), we would get the modified value if any
+ for attr in ro_attrs:
+ origval = entity_attr(session, entity.eid, attr)
+ if entity.get(attr, origval) != origval:
+ errors[attr] = session._("can't change the %s attribute") % \
+ display_name(session, attr)
+ if errors:
+ raise ValidationError(entity.eid, errors)
+
+def before_update_eetype(session, entity):
+ """check name change, handle final"""
+ check_valid_changes(session, entity, ro_attrs=('final',))
+ # don't use getattr(entity, attr), we would get the modified value if any
+ oldname = entity_attr(session, entity.eid, 'name')
+ newname = entity.get('name', oldname)
+ if newname.lower() != oldname.lower():
+ SourceDbCWETypeRename(session, oldname=oldname, newname=newname)
+ MemSchemaCWETypeRename(session, oldname=oldname, newname=newname)
+
+def before_update_ertype(session, entity):
+ """check name change, handle final"""
+ check_valid_changes(session, entity)
+
+
+def after_update_erdef(session, entity):
+ desttype = entity.otype.name
+ rschema = session.schema[entity.rtype.name]
+ newvalues = {}
+ for prop in rschema.rproperty_defs(desttype):
+ if prop == 'constraints':
+ continue
+ if prop == 'order':
+ prop = 'ordernum'
+ if prop in entity:
+ newvalues[prop] = entity[prop]
+ if newvalues:
+ subjtype = entity.stype.name
+ MemSchemaRDefUpdate(session, kobj=(subjtype, desttype),
+ rschema=rschema, values=newvalues)
+ SourceDbRDefUpdate(session, kobj=(subjtype, desttype),
+ rschema=rschema, values=newvalues)
+
+def after_update_ertype(session, entity):
+ rschema = session.schema.rschema(entity.name)
+ newvalues = {}
+ for prop in ('meta', 'symetric', 'inlined'):
+ if prop in entity:
+ newvalues[prop] = entity[prop]
+ if newvalues:
+ MemSchemaCWRTypeUpdate(session, rschema=rschema, values=newvalues)
+ SourceDbCWRTypeUpdate(session, rschema=rschema, values=newvalues,
+ entity=entity)
+
+# constraints synchronization hooks ############################################
+
+def after_add_econstraint(session, entity):
+ MemSchemaCWConstraintAdd(session, entity=entity)
+ SourceDbCWConstraintAdd(session, entity=entity)
+
+
+def after_update_econstraint(session, entity):
+ MemSchemaCWConstraintAdd(session, entity=entity)
+ SourceDbCWConstraintAdd(session, entity=entity)
+
+
+def before_delete_constrained_by(session, fromeid, rtype, toeid):
+ if not fromeid in session.transaction_data.get('pendingeids', ()):
+ schema = session.schema
+ entity = session.eid_rset(toeid).get_entity(0, 0)
+ subjtype, rtype, objtype = schema.schema_by_eid(fromeid)
+ try:
+ cstr = rtype.constraint_by_type(subjtype, objtype,
+ entity.cstrtype[0].name)
+ except IndexError:
+ session.critical('constraint type no more accessible')
+ else:
+ SourceDbCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
+ objtype=objtype, cstr=cstr)
+ MemSchemaCWConstraintDel(session, subjtype=subjtype, rtype=rtype,
+ objtype=objtype, cstr=cstr)
+
+
+def after_add_constrained_by(session, fromeid, rtype, toeid):
+ if fromeid in session.transaction_data.get('neweids', ()):
+ session.transaction_data.setdefault(fromeid, []).append(toeid)
+
+
+# permissions synchronization hooks ############################################
+
+def after_add_permission(session, subject, rtype, object):
+ """added entity/relation *_permission, need to update schema"""
+ perm = rtype.split('_', 1)[0]
+ if session.describe(object)[0] == 'CWGroup':
+ MemSchemaPermissionCWGroupAdd(session, perm, subject, object)
+ else: # RQLExpression
+ expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
+ {'x': object}, 'x')[0][0]
+ MemSchemaPermissionRQLExpressionAdd(session, perm, subject, expr)
+
+
def before_del_permission(session, subject, rtype, object):
"""delete entity/relation *_permission, need to update schema
skip the operation if the related type is being deleted
"""
- if subject in session.query_data('pendingeids', ()):
+ if subject in session.transaction_data.get('pendingeids', ()):
return
perm = rtype.split('_', 1)[0]
if session.describe(object)[0] == 'CWGroup':
- DelGroupPermissionOp(session, perm, subject, object)
+ MemSchemaPermissionCWGroupDel(session, perm, subject, object)
else: # RQLExpression
expr = session.execute('Any EXPR WHERE X eid %(x)s, X expression EXPR',
{'x': object}, 'x')[0][0]
- DelRQLExpressionPermissionOp(session, perm, subject, expr)
+ MemSchemaPermissionRQLExpressionDel(session, perm, subject, expr)
def rebuild_infered_relations(session, subject, rtype, object):
# registering a schema operation will trigger a call to
# repo.set_schema() on commit which will in turn rebuild
# infered relation definitions
- UpdateSchemaOp(session)
+ MemSchemaNotifyChanges(session)
def _register_schema_hooks(hm):
--- a/server/schemaserial.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/schemaserial.py Tue Aug 04 15:06:09 2009 +0200
@@ -14,7 +14,7 @@
from yams import schema as schemamod, buildobjs as ybo
-from cubicweb.schema import CONSTRAINTS, ETYPE_NAME_MAP
+from cubicweb.schema import CONSTRAINTS, ETYPE_NAME_MAP, VIRTUAL_RTYPES
from cubicweb.server import sqlutils
def group_mapping(cursor, interactive=True):
@@ -109,15 +109,15 @@
ETYPE_NAME_MAP[etype])
print sql
sqlcu.execute(sql)
- # other table renaming done once schema has been readen
+ # other table renaming done once schema has been read
# print 'reading schema from the database...'
index = {}
permsdict = deserialize_ertype_permissions(session)
schema.reading_from_database = True
- for eid, etype, desc, meta in session.execute('Any X, N, D, M WHERE '
- 'X is CWEType, X name N, '
- 'X description D, X meta M',
- build_descr=False):
+ for eid, etype, desc in session.execute('Any X, N, D WHERE '
+ 'X is CWEType, X name N, '
+ 'X description D',
+ build_descr=False):
# base types are already in the schema, skip them
if etype in schemamod.BASE_TYPES:
# just set the eid
@@ -136,7 +136,7 @@
{'x': etype, 'n': netype})
# XXX should be donne as well on sqlite based sources
if not etype in OLD_SCHEMA_TYPES and \
- (getattr(dbhelper, 'case_sensitive', False)
+ (getattr(dbhelper, 'case_sensitive', False)
or etype.lower() != netype.lower()):
session.system_sql('ALTER TABLE %s%s RENAME TO %s%s' % (
sqlutils.SQL_PREFIX, etype, sqlutils.SQL_PREFIX, netype))
@@ -152,7 +152,7 @@
repo.clear_caches(tocleanup)
session.commit(False)
etype = netype
- etype = ybo.EntityType(name=etype, description=desc, meta=meta, eid=eid)
+ etype = ybo.EntityType(name=etype, description=desc, eid=eid)
eschema = schema.add_entity_type(etype)
index[eid] = eschema
set_perms(eschema, permsdict.get(eid, {}))
@@ -167,9 +167,9 @@
seschema = schema.eschema(stype)
eschema._specialized_type = stype
seschema._specialized_by.append(etype)
- for eid, rtype, desc, meta, sym, il in session.execute(
- 'Any X,N,D,M,S,I WHERE X is CWRType, X name N, X description D, '
- 'X meta M, X symetric S, X inlined I', build_descr=False):
+ for eid, rtype, desc, sym, il in session.execute(
+ 'Any X,N,D,S,I WHERE X is CWRType, X name N, X description D, '
+ 'X symetric S, X inlined I', build_descr=False):
try:
# bw compat: fulltext_container added in 2.47
ft_container = session.execute('Any FTC WHERE X eid %(x)s, X fulltext_container FTC',
@@ -177,7 +177,7 @@
except:
ft_container = None
session.rollback(False)
- rtype = ybo.RelationType(name=rtype, description=desc, meta=bool(meta),
+ rtype = ybo.RelationType(name=rtype, description=desc,
symetric=bool(sym), inlined=bool(il),
fulltext_container=ft_container, eid=eid)
rschema = schema.add_relation_type(rtype)
@@ -277,23 +277,24 @@
"""synchronize schema and permissions in the database according to
current schema
"""
- print 'serializing the schema, this may take some time'
+ _title = '-> storing the schema in the database '
+ print _title,
eschemas = schema.entities()
aller = eschemas + schema.relations()
if not verbose:
pb_size = len(aller) + len(CONSTRAINTS) + len([x for x in eschemas if x.specializes()])
- pb = ProgressBar(pb_size)
+ pb = ProgressBar(pb_size, title=_title)
+ rql = 'INSERT CWConstraintType X: X name %(ct)s'
for cstrtype in CONSTRAINTS:
- rql = 'INSERT CWConstraintType X: X name "%s"' % cstrtype
if verbose:
print rql
- cursor.execute(rql)
+ cursor.execute(rql, {'ct': unicode(cstrtype)})
if not verbose:
pb.update()
groupmap = group_mapping(cursor, interactive=False)
for ertype in aller:
# skip eid and has_text relations
- if ertype in ('eid', 'identity', 'has_text',):
+ if ertype in VIRTUAL_RTYPES:
pb.update()
continue
for rql, kwargs in erschema2rql(schema[ertype]):
@@ -326,7 +327,6 @@
raise Exception("can't decode %s [was %s]" % (erschema.description, e))
return {
'name': type_,
- 'meta': erschema.meta,
'final': erschema.is_final(),
'description': desc,
}
@@ -549,12 +549,12 @@
relations, values = frdef_relations_values(rschema, objtype, props)
values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
yield 'SET %s WHERE %s, %s, X is CWAttribute' % (','.join(relations),
- _LOCATE_RDEF_RQL0,
- _LOCATE_RDEF_RQL1), values
+ _LOCATE_RDEF_RQL0,
+ _LOCATE_RDEF_RQL1), values
def updatenfrdef2rql(rschema, subjtype, objtype, props):
relations, values = nfrdef_relations_values(rschema, objtype, props)
values.update({'se': subjtype, 'rt': str(rschema), 'oe': objtype})
yield 'SET %s WHERE %s, %s, X is CWRelation' % (','.join(relations),
- _LOCATE_RDEF_RQL0,
- _LOCATE_RDEF_RQL1), values
+ _LOCATE_RDEF_RQL0,
+ _LOCATE_RDEF_RQL1), values
--- a/server/securityhooks.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/securityhooks.py Tue Aug 04 15:06:09 2009 +0200
@@ -18,7 +18,11 @@
# ._default_set is only there on entity creation to indicate unspecified
# attributes which has been set to a default value defined in the schema
defaults = getattr(entity, '_default_set', ())
- for attr in entity.keys():
+ try:
+ editedattrs = entity.edited_attributes
+ except AttributeError:
+ editedattrs = entity.keys()
+ for attr in editedattrs:
if attr in defaults:
continue
rschema = eschema.subject_relation(attr)
--- a/server/serverconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/serverconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,12 +10,66 @@
import os
from os.path import join, exists
-from logilab.common.configuration import Method
+from logilab.common.configuration import REQUIRED, Method, Configuration, \
+ ini_format_section
from logilab.common.decorators import wproperty, cached, clear_cache
from cubicweb import CW_SOFTWARE_ROOT, RegistryNotFound
-from cubicweb.toolsutils import env_path, read_config
+from cubicweb.toolsutils import env_path, read_config, restrict_perms_to_user
from cubicweb.cwconfig import CubicWebConfiguration, merge_options
+from cubicweb.server import SOURCE_TYPES
+
+
+USER_OPTIONS = (
+ ('login', {'type' : 'string',
+ 'default': REQUIRED,
+ 'help': "cubicweb manager account's login "
+ '(this user will be created)',
+ 'inputlevel': 0,
+ }),
+ ('password', {'type' : 'password',
+ 'help': "cubicweb manager account's password",
+ 'inputlevel': 0,
+ }),
+ )
+
+def generate_sources_file(sourcesfile, sourcescfg, keys=None):
+ """serialize repository'sources configuration into a INI like file
+
+ the `keys` parameter may be used to sort sections
+ """
+ if keys is None:
+ keys = sourcescfg.keys()
+ else:
+ for key in sourcescfg:
+ if not key in keys:
+ keys.append(key)
+ stream = open(sourcesfile, 'w')
+ for uri in keys:
+ sconfig = sourcescfg[uri]
+ if isinstance(sconfig, dict):
+ # get a Configuration object
+ if uri == 'admin':
+ options = USER_OPTIONS
+ else:
+ options = SOURCE_TYPES[sconfig['adapter']].options
+ _sconfig = Configuration(options=options)
+ for attr, val in sconfig.items():
+ if attr == 'uri':
+ continue
+ if attr == 'adapter':
+ _sconfig.adapter = val
+ else:
+ _sconfig.set_option(attr, val)
+ sconfig = _sconfig
+ optsbysect = list(sconfig.options_by_section())
+ assert len(optsbysect) == 1, 'all options for a source should be in the same group'
+ ini_format_section(stream, uri, optsbysect[0][1])
+ if hasattr(sconfig, 'adapter'):
+ print >> stream
+ print >> stream, '# adapter for this source (YOU SHOULD NOT CHANGE THIS)'
+ print >> stream, 'adapter=%s' % sconfig.adapter
+ print >> stream
class ServerConfiguration(CubicWebConfiguration):
@@ -117,7 +171,7 @@
'help': 'Pyro server port. If not set, it will be choosen randomly',
'group': 'pyro-server', 'inputlevel': 2,
}),
- ('pyro-id', # XXX reuse pyro-application-id
+ ('pyro-id', # XXX reuse pyro-instance-id
{'type' : 'string',
'default': None,
'help': 'identifier of the repository in the pyro name server',
@@ -126,7 +180,7 @@
) + CubicWebConfiguration.options)
# read the schema from the database
- read_application_schema = True
+ read_instance_schema = True
bootstrap_schema = True
# check user's state at login time
@@ -139,7 +193,7 @@
schema_hooks = True
notification_hooks = True
security_hooks = True
- application_hooks = True
+ instance_hooks = True
# should some hooks be deactivated during [pre|post]create script execution
free_wheel = False
@@ -154,21 +208,16 @@
@classmethod
def schemas_lib_dir(cls):
- """application schema directory"""
+ """instance schema directory"""
return env_path('CW_SCHEMA_LIB', cls.SCHEMAS_LIB_DIR, 'schemas')
- @classmethod
- def backup_dir(cls):
- """backup directory where a stored db backups before migration"""
- return env_path('CW_BACKUP', cls.BACKUP_DIR, 'run time')
-
def bootstrap_cubes(self):
- from logilab.common.textutils import get_csv
+ from logilab.common.textutils import splitstrip
for line in file(join(self.apphome, 'bootstrap_cubes')):
line = line.strip()
if not line or line.startswith('#'):
continue
- self.init_cubes(self.expand_cubes(get_csv(line)))
+ self.init_cubes(self.expand_cubes(splitstrip(line)))
break
else:
# no cubes
@@ -188,39 +237,33 @@
# restricted user, this user usually don't have access to the sources
# configuration file (#16102)
@cached
+ def read_sources_file(self):
+ return read_config(self.sources_file())
+
def sources(self):
"""return a dictionnaries containing sources definitions indexed by
sources'uri
"""
- allsources = read_config(self.sources_file())
+ allsources = self.read_sources_file()
if self._enabled_sources is None:
return allsources
return dict((uri, config) for uri, config in allsources.items()
if uri in self._enabled_sources or uri == 'admin')
+ def write_sources_file(self, sourcescfg):
+ sourcesfile = self.sources_file()
+ if exists(sourcesfile):
+ import shutil
+ shutil.copy(sourcesfile, sourcesfile + '.bak')
+ generate_sources_file(sourcesfile, sourcescfg, ['admin', 'system'])
+ restrict_perms_to_user(sourcesfile)
+
def pyro_enabled(self):
"""pyro is always enabled in standalone repository configuration"""
return True
def load_hooks(self, vreg):
hooks = {}
- for path in reversed([self.apphome] + self.cubes_path()):
- hooksfile = join(path, 'application_hooks.py')
- if exists(hooksfile):
- self.warning('application_hooks.py is deprecated, use dynamic '
- 'objects to register hooks (%s)', hooksfile)
- context = {}
- # Use execfile rather than `load_module_from_name` because
- # the latter gets fooled by the `sys.modules` cache when
- # loading different configurations one after the other
- # (another fix would have been to do :
- # sys.modules.pop('applications_hooks')
- # or to modify load_module_from_name so that it provides
- # a use_cache optional parameter
- execfile(hooksfile, context, context)
- for event, hooksdef in context['HOOKS'].items():
- for ertype, hookcbs in hooksdef.items():
- hooks.setdefault(event, {}).setdefault(ertype, []).extend(hookcbs)
try:
apphookdefs = vreg.registry_objects('hooks')
except RegistryNotFound:
@@ -277,9 +320,11 @@
clear_cache(self, 'sources')
def migration_handler(self, schema=None, interactive=True,
- cnx=None, repo=None, connect=True):
+ cnx=None, repo=None, connect=True, verbosity=None):
"""return a migration handler instance"""
from cubicweb.server.migractions import ServerMigrationHelper
+ if verbosity is None:
+ verbosity = getattr(self, 'verbosity', 0)
return ServerMigrationHelper(self, schema, interactive=interactive,
cnx=cnx, repo=repo, connect=connect,
- verbosity=getattr(self, 'verbosity', 0))
+ verbosity=verbosity)
--- a/server/serverctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/serverctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,19 +5,20 @@
: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"
+__docformat__ = 'restructuredtext en'
import sys
import os
-from logilab.common.configuration import REQUIRED, Configuration, ini_format_section
+from logilab.common.configuration import Configuration
from logilab.common.clcommands import register_commands, cmd_run, pop_arg
+from logilab.common.shellutils import ASK
-from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
-from cubicweb.toolsutils import (Command, CommandHandler, confirm,
- restrict_perms_to_user)
-from cubicweb.server.serverconfig import ServerConfiguration
-
+from cubicweb import AuthenticationError, ExecutionError, ConfigurationError, underline_title
+from cubicweb.toolsutils import Command, CommandHandler
+from cubicweb.server import SOURCE_TYPES
+from cubicweb.server.utils import ask_source_config
+from cubicweb.server.serverconfig import USER_OPTIONS, ServerConfiguration
# utility functions ###########################################################
@@ -31,7 +32,7 @@
if dbname is None:
dbname = source['db-name']
driver = source['db-driver']
- print '**** connecting to %s database %s@%s' % (driver, dbname, dbhost),
+ print '-> connecting to %s database %s@%s' % (driver, dbname, dbhost or 'localhost'),
if not verbose or (not special_privs and source.get('db-user')):
user = source['db-user']
print 'as', user
@@ -47,7 +48,7 @@
print special_privs
print
default_user = source.get('db-user', os.environ.get('USER', ''))
- user = raw_input('user (%r by default): ' % default_user)
+ user = raw_input('Connect as user ? [%r]: ' % default_user)
user = user or default_user
if user == source.get('db-user') and source.get('db-password'):
password = source['db-password']
@@ -58,10 +59,10 @@
def system_source_cnx(source, dbms_system_base=False,
special_privs='CREATE/DROP DATABASE', verbose=True):
- """shortcut to get a connextion to the application system database
+ """shortcut to get a connextion to the instance system database
defined in the given config. If <dbms_system_base> is True,
connect to the dbms system database instead (for task such as
- create/drop the application database)
+ create/drop the instance database)
"""
if dbms_system_base:
from logilab.common.adbh import get_adv_func_helper
@@ -73,7 +74,9 @@
"""return a connection on the RDMS system table (to create/drop a user
or a database
"""
+ import logilab.common as lgp
from logilab.common.adbh import get_adv_func_helper
+ lgp.USE_MX_DATETIME = False
special_privs = ''
driver = source['db-driver']
helper = get_adv_func_helper(driver)
@@ -92,41 +95,6 @@
pass
return cnx
-def generate_sources_file(sourcesfile, sourcescfg, keys=None):
- """serialize repository'sources configuration into a INI like file
-
- the `keys` parameter may be used to sort sections
- """
- from cubicweb.server.sources import SOURCE_TYPES
- if keys is None:
- keys = sourcescfg.keys()
- else:
- for key in sourcescfg:
- if not key in keys:
- keys.append(key)
- stream = open(sourcesfile, 'w')
- for uri in keys:
- sconfig = sourcescfg[uri]
- if isinstance(sconfig, dict):
- # get a Configuration object
- _sconfig = Configuration(options=SOURCE_TYPES[sconfig['adapter']].options)
- for attr, val in sconfig.items():
- if attr == 'uri':
- continue
- if attr == 'adapter':
- _sconfig.adapter = val
- else:
- _sconfig.set_option(attr, val)
- sconfig = _sconfig
- optsbysect = list(sconfig.options_by_section())
- assert len(optsbysect) == 1, 'all options for a source should be in the same group'
- ini_format_section(stream, uri, optsbysect[0][1])
- if hasattr(sconfig, 'adapter'):
- print >> stream
- print >> stream, '# adapter for this source (YOU SHOULD NOT CHANGE THIS)'
- print >> stream, 'adapter=%s' % sconfig.adapter
- print >> stream
-
def repo_cnx(config):
"""return a in-memory repository and a db api connection it"""
from cubicweb.dbapi import in_memory_cnx
@@ -140,7 +108,7 @@
try:
return in_memory_cnx(config, login, pwd)
except AuthenticationError:
- print 'wrong user/password'
+ print '-> Error: wrong user/password.'
# reset cubes else we'll have an assertion error on next retry
config._cubes = None
login, pwd = manager_userpasswd()
@@ -152,65 +120,62 @@
cfgname = 'repository'
def bootstrap(self, cubes, inputlevel=0):
- """create an application by copying files from the given cube and by
+ """create an instance by copying files from the given cube and by
asking information necessary to build required configuration files
"""
- from cubicweb.server.sources import SOURCE_TYPES
config = self.config
- print 'application\'s repository configuration'
- print '-' * 72
+ print underline_title('Configuring the repository')
config.input_config('email', inputlevel)
if config.pyro_enabled():
config.input_config('pyro-server', inputlevel)
- print
- print 'repository sources configuration'
- print '-' * 72
+ print '\n'+underline_title('Configuring the sources')
sourcesfile = config.sources_file()
sconfig = Configuration(options=SOURCE_TYPES['native'].options)
sconfig.adapter = 'native'
sconfig.input_config(inputlevel=inputlevel)
sourcescfg = {'system': sconfig}
- while raw_input('enter another source [y/N]: ').strip().lower() == 'y':
- sourcetype = raw_input('source type (%s): ' % ', '.join(SOURCE_TYPES.keys()))
- sconfig = Configuration(options=SOURCE_TYPES[sourcetype].options)
- sconfig.adapter = sourcetype
- sourceuri = raw_input('source uri: ').strip()
- assert not sourceuri in sourcescfg
- sconfig.input_config(inputlevel=inputlevel)
- sourcescfg[sourceuri] = sconfig
- # module names look like cubes.mycube.themodule
- sourcecube = SOURCE_TYPES[sourcetype].module.split('.', 2)[1]
- # if the source adapter is coming from an external component, ensure
- # it's specified in used cubes
- if sourcecube != 'cubicweb' and not sourcecube in cubes:
- cubes.append(sourcecube)
+ for cube in cubes:
+ # if a source is named as the cube containing it, we need the
+ # source to use the cube, so add it.
+ if cube in SOURCE_TYPES:
+ sourcescfg[cube] = ask_source_config(cube, inputlevel)
+ print
+ while ASK.confirm('Enter another source ?', default_is_yes=False):
+ available = sorted(stype for stype in SOURCE_TYPES
+ if not stype in cubes)
+ while True:
+ sourcetype = raw_input('source type (%s): ' % ', '.join(available))
+ if sourcetype in available:
+ break
+ print '-> unknown source type, use one of the available types.'
+ while True:
+ sourceuri = raw_input('source uri: ').strip()
+ if sourceuri != 'admin' and sourceuri not in sourcescfg:
+ break
+ print '-> uri already used, choose another one.'
+ sourcescfg[sourceuri] = ask_source_config(sourcetype)
+ sourcemodule = SOURCE_TYPES[sourcetype].module
+ if not sourcemodule.startswith('cubicweb.'):
+ # module names look like cubes.mycube.themodule
+ sourcecube = SOURCE_TYPES[sourcetype].module.split('.', 2)[1]
+ # if the source adapter is coming from an external component,
+ # ensure it's specified in used cubes
+ if not sourcecube in cubes:
+ cubes.append(sourcecube)
sconfig = Configuration(options=USER_OPTIONS)
sconfig.input_config(inputlevel=inputlevel)
sourcescfg['admin'] = sconfig
- generate_sources_file(sourcesfile, sourcescfg, ['admin', 'system'])
- restrict_perms_to_user(sourcesfile)
+ config.write_sources_file(sourcescfg)
# remember selected cubes for later initialization of the database
config.write_bootstrap_cubes_file(cubes)
def postcreate(self):
- if confirm('do you want to create repository\'s system database?'):
+ if ASK.confirm('Run db-create to create the system database ?'):
verbosity = (self.config.mode == 'installed') and 'y' or 'n'
cmd_run('db-create', self.config.appid, '--verbose=%s' % verbosity)
else:
- print 'nevermind, you can do it later using the db-create command'
-
-USER_OPTIONS = (
- ('login', {'type' : 'string',
- 'default': REQUIRED,
- 'help': "cubicweb manager account's login "
- '(this user will be created)',
- 'inputlevel': 0,
- }),
- ('password', {'type' : 'password',
- 'help': "cubicweb manager account's password",
- 'inputlevel': 0,
- }),
- )
+ print ('-> nevermind, you can do it later with '
+ '"cubicweb-ctl db-create %s".' % self.config.appid)
class RepositoryDeleteHandler(CommandHandler):
@@ -218,23 +183,23 @@
cfgname = 'repository'
def cleanup(self):
- """remove application's configuration and database"""
+ """remove instance's configuration and database"""
from logilab.common.adbh import get_adv_func_helper
source = self.config.sources()['system']
dbname = source['db-name']
helper = get_adv_func_helper(source['db-driver'])
- if confirm('delete database %s ?' % dbname):
+ if ASK.confirm('Delete database %s ?' % dbname):
user = source['db-user'] or None
cnx = _db_sys_cnx(source, 'DROP DATABASE', user=user)
cursor = cnx.cursor()
try:
cursor.execute('DROP DATABASE %s' % dbname)
- print 'database %s dropped' % dbname
+ print '-> database %s dropped.' % dbname
# XXX should check we are not connected as user
if user and helper.users_support and \
- confirm('delete user %s ?' % user, default_is_yes=False):
+ ASK.confirm('Delete user %s ?' % user, default_is_yes=False):
cursor.execute('DROP USER %s' % user)
- print 'user %s dropped' % user
+ print '-> user %s dropped.' % user
cnx.commit()
except:
cnx.rollback()
@@ -267,26 +232,26 @@
# repository specific commands ################################################
-class CreateApplicationDBCommand(Command):
- """Create the system database of an application (run after 'create').
+class CreateInstanceDBCommand(Command):
+ """Create the system database of an instance (run after 'create').
You will be prompted for a login / password to use to connect to
the system database. The given user should have almost all rights
on the database (ie a super user on the dbms allowed to create
database, users, languages...).
- <application>
- the identifier of the application to initialize.
+ <instance>
+ the identifier of the instance to initialize.
"""
name = 'db-create'
- arguments = '<application>'
+ arguments = '<instance>'
options = (
- ("create-db",
- {'short': 'c', 'type': "yn", 'metavar': '<y or n>',
+ ('create-db',
+ {'short': 'c', 'type': 'yn', 'metavar': '<y or n>',
'default': True,
'help': 'create the database (yes by default)'}),
- ("verbose",
+ ('verbose',
{'short': 'v', 'type' : 'yn', 'metavar': '<verbose>',
'default': 'n',
'help': 'verbose mode: will ask all possible configuration questions',
@@ -298,13 +263,14 @@
from logilab.common.adbh import get_adv_func_helper
from indexer import get_indexer
verbose = self.get('verbose')
- appid = pop_arg(args, msg="No application specified !")
+ appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
create_db = self.config.create_db
source = config.sources()['system']
driver = source['db-driver']
helper = get_adv_func_helper(driver)
if create_db:
+ print '\n'+underline_title('Creating the system database')
# connect on the dbms system base to create our base
dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER', verbose=verbose)
cursor = dbcnx.cursor()
@@ -312,12 +278,12 @@
if helper.users_support:
user = source['db-user']
if not helper.user_exists(cursor, user) and \
- confirm('create db user %s ?' % user, default_is_yes=False):
+ ASK.confirm('Create db user %s ?' % user, default_is_yes=False):
helper.create_user(source['db-user'], source['db-password'])
- print 'user %s created' % user
+ print '-> user %s created.' % user
dbname = source['db-name']
if dbname in helper.list_databases(cursor):
- if confirm('DB %s already exists -- do you want to drop it ?' % dbname):
+ if ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname):
cursor.execute('DROP DATABASE %s' % dbname)
else:
return
@@ -328,7 +294,7 @@
helper.create_database(cursor, dbname,
encoding=source['db-encoding'])
dbcnx.commit()
- print 'database %s created' % source['db-name']
+ print '-> database %s created.' % source['db-name']
except:
dbcnx.rollback()
raise
@@ -343,29 +309,30 @@
helper.create_language(cursor, extlang)
cursor.close()
cnx.commit()
- print 'database for application %s created and necessary extensions installed' % appid
+ print '-> database for instance %s created and necessary extensions installed.' % appid
print
- if confirm('do you want to initialize the system database?'):
+ if ASK.confirm('Run db-init to initialize the system database ?'):
cmd_run('db-init', config.appid)
else:
- print 'nevermind, you can do it later using the db-init command'
+ print ('-> nevermind, you can do it later with '
+ '"cubicweb-ctl db-init %s".' % self.config.appid)
-class InitApplicationCommand(Command):
- """Initialize the system database of an application (run after 'db-create').
+class InitInstanceCommand(Command):
+ """Initialize the system database of an instance (run after 'db-create').
You will be prompted for a login / password to use to connect to
the system database. The given user should have the create tables,
and grant permissions.
- <application>
- the identifier of the application to initialize.
+ <instance>
+ the identifier of the instance to initialize.
"""
name = 'db-init'
- arguments = '<application>'
+ arguments = '<instance>'
options = (
- ("drop",
+ ('drop',
{'short': 'd', 'action': 'store_true',
'default': False,
'help': 'insert drop statements to remove previously existant \
@@ -373,26 +340,27 @@
)
def run(self, args):
+ print '\n'+underline_title('Initializing the system database')
from cubicweb.server import init_repository
- appid = pop_arg(args, msg="No application specified !")
+ appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
init_repository(config, drop=self.config.drop)
-class GrantUserOnApplicationCommand(Command):
+class GrantUserOnInstanceCommand(Command):
"""Grant a database user on a repository system database.
- <application>
- the identifier of the application
+ <instance>
+ the identifier of the instance
<user>
the database's user requiring grant access
"""
name = 'db-grant-user'
- arguments = '<application> <user>'
+ arguments = '<instance> <user>'
options = (
- ("set-owner",
- {'short': 'o', 'type' : "yn", 'metavar' : '<yes or no>',
+ ('set-owner',
+ {'short': 'o', 'type' : 'yn', 'metavar' : '<yes or no>',
'default' : False,
'help': 'Set the user as tables owner if yes (no by default).'}
),
@@ -400,8 +368,8 @@
def run(self, args):
"""run the command with its specific arguments"""
from cubicweb.server.sqlutils import sqlexec, sqlgrants
- appid = pop_arg(args, 1, msg="No application specified !")
- user = pop_arg(args, msg="No user specified !")
+ appid = pop_arg(args, 1, msg='No instance specified !')
+ user = pop_arg(args, msg='No user specified !')
config = ServerConfiguration.config_for(appid)
source = config.sources()['system']
set_owner = self.config.set_owner
@@ -415,31 +383,31 @@
cnx.rollback()
import traceback
traceback.print_exc()
- print 'An error occured:', ex
+ print '-> an error occured:', ex
else:
cnx.commit()
- print 'grants given to %s on application %s' % (appid, user)
+ print '-> rights granted to %s on instance %s.' % (appid, user)
class ResetAdminPasswordCommand(Command):
"""Reset the administrator password.
- <application>
- the identifier of the application
+ <instance>
+ the identifier of the instance
"""
name = 'reset-admin-pwd'
- arguments = '<application>'
+ arguments = '<instance>'
def run(self, args):
"""run the command with its specific arguments"""
from cubicweb.server.sqlutils import sqlexec, SQL_PREFIX
from cubicweb.server.utils import crypt_password, manager_userpasswd
- appid = pop_arg(args, 1, msg="No application specified !")
+ appid = pop_arg(args, 1, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
- sourcescfg = config.sources()
+ sourcescfg = config.read_sources_file()
try:
adminlogin = sourcescfg['admin']['login']
except KeyError:
- print 'could not get cubicweb administrator login'
+ print '-> Error: could not get cubicweb administrator login.'
sys.exit(1)
cnx = source_cnx(sourcescfg['system'])
cursor = cnx.cursor()
@@ -454,39 +422,37 @@
sconfig['login'] = adminlogin
sconfig['password'] = passwd
sourcescfg['admin'] = sconfig
- sourcesfile = config.sources_file()
- generate_sources_file(sourcesfile, sourcescfg)
- restrict_perms_to_user(sourcesfile)
+ config.write_sources_file(sourcescfg)
except Exception, ex:
cnx.rollback()
import traceback
traceback.print_exc()
- print 'An error occured:', ex
+ print '-> an error occured:', ex
else:
cnx.commit()
- print 'password reset, sources file regenerated'
+ print '-> password reset, sources file regenerated.'
class StartRepositoryCommand(Command):
- """Start an CubicWeb RQL server for a given application.
+ """Start an CubicWeb RQL server for a given instance.
The server will be accessible through pyro
- <application>
- the identifier of the application to initialize.
+ <instance>
+ the identifier of the instance to initialize.
"""
name = 'start-repository'
- arguments = '<application>'
+ arguments = '<instance>'
options = (
- ("debug",
+ ('debug',
{'short': 'D', 'action' : 'store_true',
'help': 'start server in debug mode.'}),
)
def run(self, args):
from cubicweb.server.server import RepositoryServer
- appid = pop_arg(args, msg="No application specified !")
+ appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
debug = self.config.debug
# create the server
@@ -508,6 +474,7 @@
def _remote_dump(host, appid, output, sudo=False):
+ # XXX generate unique/portable file name
dmpcmd = 'cubicweb-ctl db-dump -o /tmp/%s.dump %s' % (appid, appid)
if sudo:
dmpcmd = 'sudo %s' % (dmpcmd)
@@ -525,21 +492,24 @@
raise ExecutionError('Error while retrieving the dump')
rmcmd = 'ssh -t %s "rm -f /tmp/%s.dump"' % (host, appid)
print rmcmd
- if os.system(rmcmd) and not confirm(
- 'an error occured while deleting remote dump. Continue anyway?'):
+ if os.system(rmcmd) and not ASK.confirm(
+ 'An error occured while deleting remote dump. Continue anyway?'):
raise ExecutionError('Error while deleting remote dump')
def _local_dump(appid, output):
config = ServerConfiguration.config_for(appid)
# schema=1 to avoid unnecessary schema loading
- mih = config.migration_handler(connect=False, schema=1)
+ mih = config.migration_handler(connect=False, schema=1, verbosity=1)
mih.backup_database(output, askconfirm=False)
+ mih.shutdown()
-def _local_restore(appid, backupfile, drop):
+def _local_restore(appid, backupfile, drop, systemonly=True):
config = ServerConfiguration.config_for(appid)
+ config.verbosity = 1 # else we won't be asked for confirmation on problems
+ config.repairing = 1 # don't check versions
# schema=1 to avoid unnecessary schema loading
- mih = config.migration_handler(connect=False, schema=1)
- mih.restore_database(backupfile, drop)
+ mih = config.migration_handler(connect=False, schema=1, verbosity=1)
+ mih.restore_database(backupfile, drop, systemonly, askconfirm=False)
repo = mih.repo_connect()
# version of the database
dbversions = repo.get_versions()
@@ -549,7 +519,7 @@
return
# version of installed software
eversion = dbversions['cubicweb']
- status = application_status(config, eversion, dbversions)
+ status = instance_status(config, eversion, dbversions)
# * database version > installed software
if status == 'needsoftupgrade':
print "database is using some earlier version than installed software!"
@@ -567,7 +537,7 @@
# ok!
-def application_status(config, cubicwebapplversion, vcconf):
+def instance_status(config, cubicwebapplversion, vcconf):
cubicwebversion = config.cubicweb_version()
if cubicwebapplversion > cubicwebversion:
return 'needsoftupgrade'
@@ -577,12 +547,12 @@
try:
softversion = config.cube_version(cube)
except ConfigurationError:
- print "no cube version information for %s, is the cube installed?" % cube
+ print '-> Error: no cube version information for %s, please check that the cube is installed.' % cube
continue
try:
applversion = vcconf[cube]
except KeyError:
- print "no cube version information for %s in version configuration" % cube
+ print '-> Error: no cube version information for %s in version configuration.' % cube
continue
if softversion == applversion:
continue
@@ -594,18 +564,18 @@
class DBDumpCommand(Command):
- """Backup the system database of an application.
+ """Backup the system database of an instance.
- <application>
- the identifier of the application to backup
+ <instance>
+ the identifier of the instance to backup
format [[user@]host:]appname
"""
name = 'db-dump'
- arguments = '<application>'
+ arguments = '<instance>'
options = (
- ("output",
- {'short': 'o', 'type' : "string", 'metavar' : '<file>',
+ ('output',
+ {'short': 'o', 'type' : 'string', 'metavar' : '<file>',
'default' : None,
'help': 'Specify the backup file where the backup will be stored.'}
),
@@ -617,7 +587,7 @@
)
def run(self, args):
- appid = pop_arg(args, 1, msg="No application specified !")
+ appid = pop_arg(args, 1, msg='No instance specified !')
if ':' in appid:
host, appid = appid.split(':')
_remote_dump(host, appid, self.config.output, self.config.sudo)
@@ -626,50 +596,58 @@
class DBRestoreCommand(Command):
- """Restore the system database of an application.
+ """Restore the system database of an instance.
- <application>
- the identifier of the application to restore
+ <instance>
+ the identifier of the instance to restore
"""
name = 'db-restore'
- arguments = '<application> <backupfile>'
+ arguments = '<instance> <backupfile>'
options = (
- ("no-drop",
- {'short': 'n', 'action' : 'store_true',
- 'default' : False,
+ ('no-drop',
+ {'short': 'n', 'action' : 'store_true', 'default' : False,
'help': 'for some reason the database doesn\'t exist and so '
'should not be dropped.'}
),
+ ('restore-all',
+ {'short': 'r', 'action' : 'store_true', 'default' : False,
+ 'help': 'restore everything, eg not only the system source database '
+ 'but also data for all sources supporting backup/restore and custom '
+ 'instance data. In that case, <backupfile> is expected to be the '
+ 'timestamp of the backup to restore, not a file'}
+ ),
)
def run(self, args):
- appid = pop_arg(args, 1, msg="No application specified !")
- backupfile = pop_arg(args, msg="No backup file specified !")
- _local_restore(appid, backupfile, not self.config.no_drop)
+ appid = pop_arg(args, 1, msg='No instance specified !')
+ backupfile = pop_arg(args, msg='No backup file or timestamp specified !')
+ _local_restore(appid, backupfile,
+ drop=not self.config.no_drop,
+ systemonly=not self.config.restore_all)
class DBCopyCommand(Command):
- """Copy the system database of an application (backup and restore).
+ """Copy the system database of an instance (backup and restore).
- <src-application>
- the identifier of the application to backup
+ <src-instance>
+ the identifier of the instance to backup
format [[user@]host:]appname
- <dest-application>
- the identifier of the application to restore
+ <dest-instance>
+ the identifier of the instance to restore
"""
name = 'db-copy'
- arguments = '<src-application> <dest-application>'
+ arguments = '<src-instance> <dest-instance>'
options = (
- ("no-drop",
+ ('no-drop',
{'short': 'n', 'action' : 'store_true',
'default' : False,
'help': 'For some reason the database doesn\'t exist and so '
'should not be dropped.'}
),
- ("keep-dump",
+ ('keep-dump',
{'short': 'k', 'action' : 'store_true',
'default' : False,
'help': 'Specify that the dump file should not be automatically removed.'}
@@ -683,9 +661,9 @@
def run(self, args):
import tempfile
- srcappid = pop_arg(args, 1, msg="No source application specified !")
- destappid = pop_arg(args, msg="No destination application specified !")
- output = tempfile.mktemp()
+ srcappid = pop_arg(args, 1, msg='No source instance specified !')
+ destappid = pop_arg(args, msg='No destination instance specified !')
+ _, output = tempfile.mkstemp()
if ':' in srcappid:
host, srcappid = srcappid.split(':')
_remote_dump(host, srcappid, output, self.config.sudo)
@@ -693,66 +671,72 @@
_local_dump(srcappid, output)
_local_restore(destappid, output, not self.config.no_drop)
if self.config.keep_dump:
- print 'you can get the dump file at', output
+ print '-> you can get the dump file at', output
else:
os.remove(output)
class CheckRepositoryCommand(Command):
- """Check integrity of the system database of an application.
+ """Check integrity of the system database of an instance.
- <application>
- the identifier of the application to check
+ <instance>
+ the identifier of the instance to check
"""
name = 'db-check'
- arguments = '<application>'
+ arguments = '<instance>'
options = (
- ("checks",
- {'short': 'c', 'type' : "csv", 'metavar' : '<check list>',
+ ('checks',
+ {'short': 'c', 'type' : 'csv', 'metavar' : '<check list>',
'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
'help': 'Comma separated list of check to run. By default run all \
checks, i.e. entities, relations, text_index and metadata.'}
),
- ("autofix",
- {'short': 'a', 'type' : "yn", 'metavar' : '<yes or no>',
+ ('autofix',
+ {'short': 'a', 'type' : 'yn', 'metavar' : '<yes or no>',
'default' : False,
'help': 'Automatically correct integrity problems if this option \
is set to "y" or "yes", else only display them'}
),
- ("reindex",
- {'short': 'r', 'type' : "yn", 'metavar' : '<yes or no>',
+ ('reindex',
+ {'short': 'r', 'type' : 'yn', 'metavar' : '<yes or no>',
'default' : False,
'help': 're-indexes the database for full text search if this \
option is set to "y" or "yes" (may be long for large database).'}
),
+ ('force',
+ {'short': 'f', 'action' : 'store_true',
+ 'default' : False,
+ 'help': 'don\'t check instance is up to date.'}
+ ),
)
def run(self, args):
from cubicweb.server.checkintegrity import check
- appid = pop_arg(args, 1, msg="No application specified !")
+ appid = pop_arg(args, 1, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
+ config.repairing = self.config.force
repo, cnx = repo_cnx(config)
check(repo, cnx,
self.config.checks, self.config.reindex, self.config.autofix)
class RebuildFTICommand(Command):
- """Rebuild the full-text index of the system database of an application.
+ """Rebuild the full-text index of the system database of an instance.
- <application>
- the identifier of the application to rebuild
+ <instance>
+ the identifier of the instance to rebuild
"""
name = 'db-rebuild-fti'
- arguments = '<application>'
+ arguments = '<instance>'
options = ()
def run(self, args):
from cubicweb.server.checkintegrity import reindex_entities
- appid = pop_arg(args, 1, msg="No application specified !")
+ appid = pop_arg(args, 1, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
repo, cnx = repo_cnx(config)
session = repo._get_session(cnx.sessionid, setpool=True)
@@ -760,28 +744,28 @@
cnx.commit()
-class SynchronizeApplicationSchemaCommand(Command):
+class SynchronizeInstanceSchemaCommand(Command):
"""Synchronize persistent schema with cube schema.
Will synchronize common stuff between the cube schema and the
actual persistent schema, but will not add/remove any entity or relation.
- <application>
- the identifier of the application to synchronize.
+ <instance>
+ the identifier of the instance to synchronize.
"""
name = 'schema-sync'
- arguments = '<application>'
+ arguments = '<instance>'
def run(self, args):
- appid = pop_arg(args, msg="No application specified !")
+ appid = pop_arg(args, msg='No instance specified !')
config = ServerConfiguration.config_for(appid)
mih = config.migration_handler()
mih.cmd_synchronize_schema()
-register_commands( (CreateApplicationDBCommand,
- InitApplicationCommand,
- GrantUserOnApplicationCommand,
+register_commands( (CreateInstanceDBCommand,
+ InitInstanceCommand,
+ GrantUserOnInstanceCommand,
ResetAdminPasswordCommand,
StartRepositoryCommand,
DBDumpCommand,
@@ -789,5 +773,5 @@
DBCopyCommand,
CheckRepositoryCommand,
RebuildFTICommand,
- SynchronizeApplicationSchemaCommand,
+ SynchronizeInstanceSchemaCommand,
) )
--- a/server/session.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/session.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,6 +11,7 @@
import threading
from time import time
+from logilab.common.deprecation import deprecated
from rql.nodes import VariableRef, Function, ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
@@ -39,8 +40,6 @@
description.append(term.get_type(solution, args))
return description
-from rql import stmts
-assert hasattr(stmts.Union, 'get_variable_variables'), "You need RQL > 0.18.3"
class Session(RequestSessionMixIn):
"""tie session id, user, connections pool and other session data all
@@ -58,6 +57,7 @@
self.timestamp = self.creation
self.is_internal_session = False
self.is_super_session = False
+ self.default_mode = 'read'
# short cut to querier .execute method
self._execute = repo.querier.execute
# shared data, used to communicate extra information between the client
@@ -65,54 +65,93 @@
self.data = {}
# i18n initialization
self.set_language(cnxprops.lang)
+ # internals
self._threaddata = threading.local()
self._threads_in_transaction = set()
self._closed = False
- def get_mode(self):
- return getattr(self._threaddata, 'mode', 'read')
- def set_mode(self, value):
- self._threaddata.mode = value
- # transaction mode (read/write), resetted to read on commit / rollback
- mode = property(get_mode, set_mode)
+ def __str__(self):
+ return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
+ self.id, id(self))
+
+ @property
+ def schema(self):
+ return self.repo.schema
- def get_commit_state(self):
- return getattr(self._threaddata, 'commit_state', None)
- def set_commit_state(self, value):
- self._threaddata.commit_state = value
- commit_state = property(get_commit_state, set_commit_state)
+ def add_relation(self, fromeid, rtype, toeid):
+ if self.is_super_session:
+ self.repo.glob_add_relation(self, fromeid, rtype, toeid)
+ return
+ self.is_super_session = True
+ try:
+ self.repo.glob_add_relation(self, fromeid, rtype, toeid)
+ finally:
+ self.is_super_session = False
- # set according to transaction mode for each query
- @property
- def pool(self):
- return getattr(self._threaddata, 'pool', None)
+ def update_rel_cache_add(self, subject, rtype, object, symetric=False):
+ self._update_entity_rel_cache_add(subject, rtype, 'subject', object)
+ if symetric:
+ self._update_entity_rel_cache_add(object, rtype, 'subject', subject)
+ else:
+ self._update_entity_rel_cache_add(object, rtype, 'object', subject)
- # pending transaction operations
- @property
- def pending_operations(self):
+ def update_rel_cache_del(self, subject, rtype, object, symetric=False):
+ self._update_entity_rel_cache_del(subject, rtype, 'subject', object)
+ if symetric:
+ self._update_entity_rel_cache_del(object, rtype, 'object', object)
+ else:
+ self._update_entity_rel_cache_del(object, rtype, 'object', subject)
+
+ def _rel_cache(self, eid, rtype, role):
try:
- return self._threaddata.pending_operations
- except AttributeError:
- self._threaddata.pending_operations = []
- return self._threaddata.pending_operations
+ entity = self.entity_cache(eid)
+ except KeyError:
+ return
+ return entity.relation_cached(rtype, role)
+
+ def _update_entity_rel_cache_add(self, eid, rtype, role, targeteid):
+ rcache = self._rel_cache(eid, rtype, role)
+ if rcache is not None:
+ rset, entities = rcache
+ rset.rows.append([targeteid])
+ if isinstance(rset.description, list): # else description not set
+ rset.description.append([self.describe(targeteid)[0]])
+ rset.rowcount += 1
+ targetentity = self.entity_from_eid(targeteid)
+ entities.append(targetentity)
- # rql rewriter
- @property
- def rql_rewriter(self):
- try:
- return self._threaddata._rewriter
- except AttributeError:
- self._threaddata._rewriter = RQLRewriter(self.repo.querier, self)
- return self._threaddata._rewriter
+ def _update_entity_rel_cache_del(self, eid, rtype, role, targeteid):
+ rcache = self._rel_cache(eid, rtype, role)
+ if rcache is not None:
+ rset, entities = rcache
+ for idx, row in enumerate(rset.rows):
+ if row[0] == targeteid:
+ break
+ else:
+ raise Exception('cache inconsistency for %s %s %s %s' %
+ (eid, rtype, role, targeteid))
+ del rset.rows[idx]
+ if isinstance(rset.description, list): # else description not set
+ del rset.description[idx]
+ del entities[idx]
+ rset.rowcount -= 1
- # transaction queries data
- @property
- def _query_data(self):
- try:
- return self._threaddata._query_data
- except AttributeError:
- self._threaddata._query_data = {}
- return self._threaddata._query_data
+ # resource accessors ######################################################
+
+ def actual_session(self):
+ """return the original parent session if any, else self"""
+ return self
+
+ def etype_class(self, etype):
+ """return an entity class for the given entity type"""
+ return self.vreg.etype_class(etype)
+
+ def system_sql(self, sql, args=None, rollback_on_failure=True):
+ """return a sql cursor on the system database"""
+ if not sql.split(None, 1)[0].upper() == 'SELECT':
+ self.mode = 'write'
+ return self.pool.source('system').doexec(self, sql, args,
+ rollback=rollback_on_failure)
def set_language(self, language):
"""i18n configuration for translation"""
@@ -132,63 +171,84 @@
assert prop == 'lang' # this is the only one changeable property for now
self.set_language(value)
- def __str__(self):
- return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
- self.id, id(self))
+ # connection management ###################################################
+
+ def keep_pool_mode(self, mode):
+ """set pool_mode, e.g. how the session will keep its pool:
- def etype_class(self, etype):
- """return an entity class for the given entity type"""
- return self.vreg.etype_class(etype)
+ * if mode == 'write', the pool is freed after each ready query, but kept
+ until the transaction's end (eg commit or rollback) when a write query
+ is detected (eg INSERT/SET/DELETE queries)
+
+ * if mode == 'transaction', the pool is only freed after the
+ transaction's end
- def entity(self, eid):
- """return a result set for the given eid"""
- return self.eid_rset(eid).get_entity(0, 0)
+ notice that a repository has a limited set of pools, and a session has to
+ wait for a free pool to run any rql query (unless it already has a pool
+ set).
+ """
+ assert mode in ('transaction', 'write')
+ if mode == 'transaction':
+ self.default_mode = 'transaction'
+ else: # mode == 'write'
+ self.default_mode = 'read'
- def _touch(self):
- """update latest session usage timestamp and reset mode to read
- """
- self.timestamp = time()
- self.local_perm_cache.clear()
- self._threaddata.mode = 'read'
+ def get_mode(self):
+ return getattr(self._threaddata, 'mode', self.default_mode)
+ def set_mode(self, value):
+ self._threaddata.mode = value
+ mode = property(get_mode, set_mode,
+ doc='transaction mode (read/write/transaction), resetted to'
+ ' default_mode on commit / rollback')
+
+ def get_commit_state(self):
+ return getattr(self._threaddata, 'commit_state', None)
+ def set_commit_state(self, value):
+ self._threaddata.commit_state = value
+ commit_state = property(get_commit_state, set_commit_state)
+
+ @property
+ def pool(self):
+ """connections pool, set according to transaction mode for each query"""
+ return getattr(self._threaddata, 'pool', None)
def set_pool(self):
"""the session need a pool to execute some queries"""
if self._closed:
raise Exception('try to set pool on a closed session')
if self.pool is None:
- self._threaddata.pool = self.repo._get_pool()
+ # get pool first to avoid race-condition
+ self._threaddata.pool = pool = self.repo._get_pool()
try:
- self._threaddata.pool.pool_set(self)
+ pool.pool_set()
except:
- self.repo._free_pool(self.pool)
self._threaddata.pool = None
+ self.repo._free_pool(pool)
raise
self._threads_in_transaction.add(threading.currentThread())
return self._threaddata.pool
- def reset_pool(self):
- """the session has no longer using its pool, at least for some time
- """
+ def reset_pool(self, ignoremode=False):
+ """the session is no longer using its pool, at least for some time"""
# pool may be none if no operation has been done since last commit
# or rollback
- if self.pool is not None and self.mode == 'read':
+ if self.pool is not None and (ignoremode or self.mode == 'read'):
# even in read mode, we must release the current transaction
- self._threads_in_transaction.remove(threading.currentThread())
- self.repo._free_pool(self.pool)
- self.pool.pool_reset(self)
+ pool = self.pool
+ try:
+ self._threads_in_transaction.remove(threading.currentThread())
+ except KeyError:
+ pass
+ pool.pool_reset()
self._threaddata.pool = None
+ # free pool once everything is done to avoid race-condition
+ self.repo._free_pool(pool)
- def system_sql(self, sql, args=None):
- """return a sql cursor on the system database"""
- if not sql.split(None, 1)[0].upper() == 'SELECT':
- self.mode = 'write'
- cursor = self.pool['system']
- self.pool.source('system').doexec(cursor, sql, args)
- return cursor
-
- def actual_session(self):
- """return the original parent session if any, else self"""
- return self
+ def _touch(self):
+ """update latest session usage timestamp and reset mode to read"""
+ self.timestamp = time()
+ self.local_perm_cache.clear()
+ self._threaddata.mode = self.default_mode
# shared data handling ###################################################
@@ -202,22 +262,47 @@
def set_shared_data(self, key, value, querydata=False):
"""set value associated to `key` in session data"""
if querydata:
- self.set_query_data(key, value)
+ self.transaction_data[key] = value
else:
self.data[key] = value
# request interface #######################################################
def set_entity_cache(self, entity):
- # no entity cache in the server, too high risk of inconsistency
- # between pre/post hooks
- pass
+ # XXX session level caching may be a pb with multiple repository
+ # instances, but 1. this is probably not the only one :$ and 2. it
+ # may be an acceptable risk. Anyway we could activate it or not
+ # according to a configuration option
+ try:
+ self.transaction_data['ecache'].setdefault(entity.eid, entity)
+ except KeyError:
+ self.transaction_data['ecache'] = ecache = {}
+ ecache[entity.eid] = entity
def entity_cache(self, eid):
- raise KeyError(eid)
+ try:
+ return self.transaction_data['ecache'][eid]
+ except:
+ raise
+
+ def cached_entities(self):
+ return self.transaction_data.get('ecache', {}).values()
+
+ def drop_entity_cache(self, eid=None):
+ if eid is None:
+ self.transaction_data.pop('ecache', None)
+ else:
+ del self.transaction_data['ecache'][eid]
def base_url(self):
- return self.repo.config['base-url'] or u''
+ url = self.repo.config['base-url']
+ if not url:
+ try:
+ url = self.repo.config.default_base_url()
+ except AttributeError: # default_base_url() might not be available
+ self.warning('missing base-url definition in server config')
+ url = u''
+ return url
def from_controller(self):
"""return the id (string) of the controller issuing the request (no
@@ -257,7 +342,7 @@
self.set_pool()
return csession
- def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=False,
+ def unsafe_execute(self, rql, kwargs=None, eid_key=None, build_descr=True,
propagate=False):
"""like .execute but with security checking disabled (this method is
internal to the server, it's not part of the db-api)
@@ -288,8 +373,9 @@
"""commit the current session's transaction"""
if self.pool is None:
assert not self.pending_operations
- self._query_data.clear()
+ self.transaction_data.clear()
self._touch()
+ self.debug('commit session %s done (no db activity)', self.id)
return
if self.commit_state:
return
@@ -321,16 +407,17 @@
self._touch()
self.commit_state = None
self.pending_operations[:] = []
- self._query_data.clear()
+ self.transaction_data.clear()
if reset_pool:
- self.reset_pool()
+ self.reset_pool(ignoremode=True)
def rollback(self, reset_pool=True):
"""rollback the current session's transaction"""
if self.pool is None:
assert not self.pending_operations
- self._query_data.clear()
+ self.transaction_data.clear()
self._touch()
+ self.debug('rollback session %s done (no db activity)', self.id)
return
try:
while self.pending_operations:
@@ -341,12 +428,13 @@
self.critical('rollback error', exc_info=sys.exc_info())
continue
self.pool.rollback()
+ self.debug('rollback for session %s done', self.id)
finally:
self._touch()
self.pending_operations[:] = []
- self._query_data.clear()
+ self.transaction_data.clear()
if reset_pool:
- self.reset_pool()
+ self.reset_pool(ignoremode=True)
def close(self):
"""do not close pool on session close, since they are shared now"""
@@ -371,20 +459,22 @@
# transaction data/operations management ##################################
- def add_query_data(self, key, value):
- self._query_data.setdefault(key, []).append(value)
-
- def set_query_data(self, key, value):
- self._query_data[key] = value
+ @property
+ def transaction_data(self):
+ try:
+ return self._threaddata.transaction_data
+ except AttributeError:
+ self._threaddata.transaction_data = {}
+ return self._threaddata.transaction_data
- def query_data(self, key, default=None, setdefault=False, pop=False):
- if setdefault:
- assert not pop
- return self._query_data.setdefault(key, default)
- if pop:
- return self._query_data.pop(key, default)
- else:
- return self._query_data.get(key, default)
+ @property
+ def pending_operations(self):
+ try:
+ return self._threaddata.pending_operations
+ except AttributeError:
+ self._threaddata.pending_operations = []
+ return self._threaddata.pending_operations
+
def add_operation(self, operation, index=None):
"""add an observer"""
@@ -396,6 +486,14 @@
# querier helpers #########################################################
+ @property
+ def rql_rewriter(self):
+ try:
+ return self._threaddata._rewriter
+ except AttributeError:
+ self._threaddata._rewriter = RQLRewriter(self.repo.querier, self)
+ return self._threaddata._rewriter
+
def build_description(self, rqlst, args, result):
"""build a description for a given result"""
if len(rqlst.children) == 1 and len(rqlst.children[0].solutions) == 1:
@@ -457,6 +555,21 @@
description.append(tuple(row_descr))
return description
+ @deprecated('use direct access to session.transaction_data')
+ def query_data(self, key, default=None, setdefault=False, pop=False):
+ if setdefault:
+ assert not pop
+ return self.transaction_data.setdefault(key, default)
+ if pop:
+ return self.transaction_data.pop(key, default)
+ else:
+ return self.transaction_data.get(key, default)
+
+ @deprecated('use entity_from_eid(eid, etype=None)')
+ def entity(self, eid):
+ """return a result set for the given eid"""
+ return self.eid_rset(eid).get_entity(0, 0)
+
class ChildSession(Session):
"""child (or internal) session are used to hijack the security system
@@ -502,8 +615,8 @@
def pending_operations(self):
return self.parent_session.pending_operations
@property
- def _query_data(self):
- return self.parent_session._query_data
+ def transaction_data(self):
+ return self.parent_session.transaction_data
def set_pool(self):
"""the session need a pool to execute some queries"""
--- a/server/sources/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,13 +7,37 @@
"""
__docformat__ = "restructuredtext en"
+from os.path import join, splitext
from datetime import datetime, timedelta
from logging import getLogger
-from cubicweb import set_log_methods
+from cubicweb import set_log_methods, server
+from cubicweb.schema import VIRTUAL_RTYPES
from cubicweb.server.sqlutils import SQL_PREFIX
+def dbg_st_search(uri, union, varmap, args, cachekey=None, prefix='rql for'):
+ if server.DEBUG & server.DBG_RQL:
+ print '%s %s source: %s' % (prefix, uri, union.as_string())
+ if varmap:
+ print ' using varmap', varmap
+ if server.DEBUG & server.DBG_MORE:
+ print ' args', args
+ print ' cache key', cachekey
+ print ' solutions', ','.join(str(s.solutions)
+ for s in union.children)
+ # return true so it can be used as assertion (and so be killed by python -O)
+ return True
+
+def dbg_results(results):
+ if server.DEBUG & server.DBG_RQL:
+ if len(results) > 10:
+ print ' -->', results[:10], '...', len(results)
+ else:
+ print ' -->', results
+ # return true so it can be used as assertion (and so be killed by python -O)
+ return True
+
class TimedCache(dict):
def __init__(self, ttlm, ttls=0):
# time to live in minutes
@@ -53,7 +77,7 @@
uri = None
# a reference to the system information helper
repo = None
- # a reference to the application'schema (may differs from the source'schema)
+ # a reference to the instance'schema (may differs from the source'schema)
schema = None
def __init__(self, repo, appschema, source_config, *args, **kwargs):
@@ -71,6 +95,42 @@
"""method called by the repository once ready to handle request"""
pass
+ def backup_file(self, backupfile=None, timestamp=None):
+ """return a unique file name for a source's dump
+
+ either backupfile or timestamp (used to generated a backup file name if
+ needed) should be specified.
+ """
+ if backupfile is None:
+ config = self.repo.config
+ return join(config.appdatahome, 'backup',
+ '%s-%s-%s.dump' % (config.appid, timestamp, self.uri))
+ # backup file is the system database backup file, add uri to it if not
+ # already there
+ base, ext = splitext(backupfile)
+ if not base.endswith('-%s' % self.uri):
+ return '%s-%s%s' % (base, self.uri, ext)
+ return backupfile
+
+ def backup(self, confirm, backupfile=None, timestamp=None,
+ askconfirm=False):
+ """method called to create a backup of source's data"""
+ pass
+
+ def restore(self, confirm, backupfile=None, timestamp=None, drop=True,
+ askconfirm=False):
+ """method called to restore a backup of source's data"""
+ pass
+
+ def close_pool_connections(self):
+ for pool in self.repo.pools:
+ pool._cursors.pop(self.uri, None)
+ pool.source_cnxs[self.uri][1].close()
+
+ def open_pool_connections(self):
+ for pool in self.repo.pools:
+ pool.source_cnxs[self.uri] = (self, self.get_connection())
+
def reset_caches(self):
"""method called during test to reset potential source caches"""
pass
@@ -95,7 +155,7 @@
return cmp(self.uri, other.uri)
def set_schema(self, schema):
- """set the application'schema"""
+ """set the instance'schema"""
self.schema = schema
def support_entity(self, etype, write=False):
@@ -163,7 +223,7 @@
# delete relations referencing one of those eids
eidcolum = SQL_PREFIX + 'eid'
for rschema in self.schema.relations():
- if rschema.is_final() or rschema.type == 'identity':
+ if rschema.is_final() or rschema.type in VIRTUAL_RTYPES:
continue
if rschema.inlined:
column = SQL_PREFIX + rschema.type
@@ -253,8 +313,7 @@
.executemany().
"""
res = self.syntax_tree_search(session, union, args, varmap=varmap)
- session.pool.source('system')._manual_insert(res, table, session)
-
+ session.pool.source('system').manual_insert(res, table, session)
# system source don't have to implement the two methods below
--- a/server/sources/extlite.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/extlite.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,21 +8,13 @@
__docformat__ = "restructuredtext en"
-import time
-import threading
from os.path import join, exists
from cubicweb import server
-from cubicweb.server.sqlutils import SQL_PREFIX, sqlexec, SQLAdapterMixIn
-from cubicweb.server.sources import AbstractSource, native
-from cubicweb.server.sources.rql2sql import SQLGenerator
-
-def timeout_acquire(lock, timeout):
- while not lock.acquire(False):
- time.sleep(0.2)
- timeout -= 0.2
- if timeout <= 0:
- raise RuntimeError("svn source is busy, can't acquire connection lock")
+from cubicweb.server.sqlutils import (SQL_PREFIX, SQLAdapterMixIn, sqlexec,
+ sql_source_backup, sql_source_restore)
+from cubicweb.server.sources import native, rql2sql
+from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
class ConnectionWrapper(object):
def __init__(self, source=None):
@@ -30,28 +22,42 @@
self._cnx = None
@property
- def cnx(self):
+ def logged_user(self):
if self._cnx is None:
- timeout_acquire(self.source._cnxlock, 5)
self._cnx = self.source._sqlcnx
- return self._cnx
+ return self._cnx.logged_user
+
+ def cursor(self):
+ if self._cnx is None:
+ self._cnx = self.source._sqlcnx
+ if server.DEBUG & server.DBG_SQL:
+ print 'sql cnx OPEN', self._cnx
+ return self._cnx.cursor()
def commit(self):
if self._cnx is not None:
+ if server.DEBUG & server.DBG_SQL:
+ print 'sql cnx COMMIT', self._cnx
self._cnx.commit()
def rollback(self):
if self._cnx is not None:
+ if server.DEBUG & server.DBG_SQL:
+ print 'sql cnx ROLLBACK', self._cnx
self._cnx.rollback()
- def cursor(self):
- return self.cnx.cursor()
+ def close(self):
+ if self._cnx is not None:
+ if server.DEBUG & server.DBG_SQL:
+ print 'sql cnx CLOSE', self._cnx
+ self._cnx.close()
+ self._cnx = None
class SQLiteAbstractSource(AbstractSource):
"""an abstract class for external sources using a sqlite database helper
"""
- sqlgen_class = SQLGenerator
+ sqlgen_class = rql2sql.SQLGenerator
@classmethod
def set_nonsystem_types(cls):
# those entities are only in this source, we don't want them in the
@@ -87,11 +93,19 @@
self._need_full_import = self._need_sql_create
AbstractSource.__init__(self, repo, appschema, source_config,
*args, **kwargs)
- # sql database can only be accessed by one connection at a time, and a
- # connection can only be used by the thread which created it so:
- # * create the connection when needed
- # * use a lock to be sure only one connection is used
- self._cnxlock = threading.Lock()
+
+ def backup(self, confirm, backupfile=None, timestamp=None, askconfirm=False):
+ """method called to create a backup of source's data"""
+ backupfile = self.backup_file(backupfile, timestamp)
+ sql_source_backup(self, self.sqladapter, confirm, backupfile,
+ askconfirm)
+
+ def restore(self, confirm, backupfile=None, timestamp=None, drop=True,
+ askconfirm=False):
+ """method called to restore a backup of source's data"""
+ backupfile = self.backup_file(backupfile, timestamp)
+ sql_source_restore(self, self.sqladapter, confirm, backupfile, drop,
+ askconfirm)
@property
def _sqlcnx(self):
@@ -163,15 +177,12 @@
attached session: release the connection lock if the connection wrapper
has a connection set
"""
- if cnx._cnx is not None:
- try:
- cnx._cnx.close()
- cnx._cnx = None
- finally:
- self._cnxlock.release()
+ # reset _cnx to ensure next thread using cnx will get a new
+ # connection
+ cnx.close()
- def syntax_tree_search(self, session, union,
- args=None, cachekey=None, varmap=None, debug=0):
+ def syntax_tree_search(self, session, union, args=None, cachekey=None,
+ varmap=None):
"""return result from this source for a rql query (actually from a rql
syntax tree and a solution dictionary mapping each used variable to a
possible type). If cachekey is given, the query necessary to fetch the
@@ -179,14 +190,12 @@
"""
if self._need_sql_create:
return []
+ assert dbg_st_search(self.uri, union, varmap, args, cachekey)
sql, query_args = self.rqlsqlgen.generate(union, args)
- if server.DEBUG:
- print self.uri, 'SOURCE RQL', union.as_string()
- print 'GENERATED SQL', sql
args = self.sqladapter.merge_args(args, query_args)
- cursor = session.pool[self.uri]
- cursor.execute(sql, args)
- return self.sqladapter.process_result(cursor)
+ results = self.sqladapter.process_result(self.doexec(session, sql, args))
+ assert dbg_results(results)
+ return results
def local_add_entity(self, session, entity):
"""insert the entity in the local database.
@@ -195,10 +204,9 @@
don't want to simply do this, so let raise NotImplementedError and the
source implementor may use this method if necessary
"""
- cu = session.pool[self.uri]
attrs = self.sqladapter.preprocess_entity(entity)
sql = self.sqladapter.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
- cu.execute(sql, attrs)
+ self.doexec(session, sql, attrs)
def add_entity(self, session, entity):
"""add a new entity to the source"""
@@ -211,12 +219,11 @@
source don't want to simply do this, so let raise NotImplementedError
and the source implementor may use this method if necessary
"""
- cu = session.pool[self.uri]
if attrs is None:
attrs = self.sqladapter.preprocess_entity(entity)
sql = self.sqladapter.sqlgen.update(SQL_PREFIX + str(entity.e_schema),
attrs, [SQL_PREFIX + 'eid'])
- cu.execute(sql, attrs)
+ self.doexec(session, sql, attrs)
def update_entity(self, session, entity):
"""update an entity in the source"""
@@ -229,16 +236,30 @@
source. Main usage is to delete repository content when a Repository
entity is deleted.
"""
- sqlcursor = session.pool[self.uri]
attrs = {SQL_PREFIX + 'eid': eid}
sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + etype, attrs)
- sqlcursor.execute(sql, attrs)
+ self.doexec(session, sql, attrs)
+
+ def local_add_relation(self, session, subject, rtype, object):
+ """add a relation to the source
+
+ This is not provided as add_relation implementation since usually
+ source don't want to simply do this, so let raise NotImplementedError
+ and the source implementor may use this method if necessary
+ """
+ attrs = {'eid_from': subject, 'eid_to': object}
+ sql = self.sqladapter.sqlgen.insert('%s_relation' % rtype, attrs)
+ self.doexec(session, sql, attrs)
+
+ def add_relation(self, session, subject, rtype, object):
+ """add a relation to the source"""
+ raise NotImplementedError()
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
rschema = self.schema.rschema(rtype)
if rschema.inlined:
- if subject in session.query_data('pendingeids', ()):
+ if subject in session.transaction_data.get('pendingeids', ()):
return
table = SQL_PREFIX + session.describe(subject)[0]
column = SQL_PREFIX + rtype
@@ -247,5 +268,25 @@
else:
attrs = {'eid_from': subject, 'eid_to': object}
sql = self.sqladapter.sqlgen.delete('%s_relation' % rtype, attrs)
- sqlcursor = session.pool[self.uri]
- sqlcursor.execute(sql, attrs)
+ self.doexec(session, sql, attrs)
+
+ def doexec(self, session, query, args=None):
+ """Execute a query.
+ it's a function just so that it shows up in profiling
+ """
+ if server.DEBUG:
+ print 'exec', query, args
+ cursor = session.pool[self.uri]
+ try:
+ # str(query) to avoid error if it's an unicode string
+ cursor.execute(str(query), args)
+ except Exception, ex:
+ self.critical("sql: %r\n args: %s\ndbms message: %r",
+ query, args, ex.args[0])
+ try:
+ session.pool.connection(self.uri).rollback()
+ self.critical('transaction has been rollbacked')
+ except:
+ pass
+ raise
+ return cursor
--- a/server/sources/ldapuser.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/ldapuser.py Tue Aug 04 15:06:09 2009 +0200
@@ -23,7 +23,7 @@
from base64 import b64decode
-from logilab.common.textutils import get_csv
+from logilab.common.textutils import splitstrip
from rql.nodes import Relation, VariableRef, Constant, Function
import ldap
@@ -131,10 +131,10 @@
self.host = source_config['host']
self.user_base_dn = source_config['user-base-dn']
self.user_base_scope = globals()[source_config['user-scope']]
- self.user_classes = get_csv(source_config['user-classes'])
+ self.user_classes = splitstrip(source_config['user-classes'])
self.user_login_attr = source_config['user-login-attr']
- self.user_default_groups = get_csv(source_config['user-default-group'])
- self.user_attrs = dict(v.split(':', 1) for v in get_csv(source_config['user-attrs-map']))
+ self.user_default_groups = splitstrip(source_config['user-default-group'])
+ self.user_attrs = dict(v.split(':', 1) for v in splitstrip(source_config['user-attrs-map']))
self.user_rev_attrs = {'eid': 'dn'}
for ldapattr, cwattr in self.user_attrs.items():
self.user_rev_attrs[cwattr] = ldapattr
--- a/server/sources/native.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/native.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,7 +5,7 @@
from which it comes from) are stored in a varchar column encoded as a base64
string. This is because it should actually be Bytes but we want an index on
it for fast querying.
-
+
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -25,12 +25,14 @@
from cubicweb import UnknownEid, AuthenticationError, Binary, server
from cubicweb.server.utils import crypt_password
-from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
+from cubicweb.server.sqlutils import (SQL_PREFIX, SQLAdapterMixIn,
+ sql_source_backup, sql_source_restore)
from cubicweb.server.rqlannotation import set_qdata
-from cubicweb.server.sources import AbstractSource
+from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
from cubicweb.server.sources.rql2sql import SQLGenerator
+ATTR_MAP = {}
NONSYSTEM_ETYPES = set()
NONSYSTEM_RELATIONS = set()
@@ -42,7 +44,7 @@
"""Execute a query.
it's a function just so that it shows up in profiling
"""
- if server.DEBUG:
+ if server.DEBUG & server.DBG_SQL:
print 'exec', query, args
try:
self.cu.execute(str(query), args)
@@ -57,6 +59,7 @@
def fetchone(self):
return self.cu.fetchone()
+
def make_schema(selected, solution, table, typemap):
"""return a sql schema to store RQL query result"""
sql = []
@@ -73,6 +76,7 @@
sql.append('%s %s' % (name, typemap['Int']))
return ','.join(sql), varmap
+
def _modified_sql(table, etypes):
# XXX protect against sql injection
if len(etypes) > 1:
@@ -90,6 +94,7 @@
class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
"""adapter for source using the native cubicweb schema (see below)
"""
+ sqlgen_class = SQLGenerator
# need default value on class since migration doesn't call init method
has_deleted_entitites_table = True
@@ -110,6 +115,12 @@
'help': 'database host',
'group': 'native-source', 'inputlevel': 1,
}),
+ ('db-port',
+ {'type' : 'string',
+ 'default': '',
+ 'help': 'database port',
+ 'group': 'native-source', 'inputlevel': 1,
+ }),
('db-name',
{'type' : 'string',
'default': REQUIRED,
@@ -141,8 +152,8 @@
AbstractSource.__init__(self, repo, appschema, source_config,
*args, **kwargs)
# sql generator
- self._rql_sqlgen = SQLGenerator(appschema, self.dbhelper,
- self.encoding)
+ self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper,
+ self.encoding, ATTR_MAP.copy())
# full text index helper
self.indexer = get_indexer(self.dbdriver, self.encoding)
# advanced functionality helper
@@ -154,6 +165,23 @@
self._cache = Cache(repo.config['rql-cache-size'])
self._temp_table_data = {}
self._eid_creation_lock = Lock()
+ # XXX no_sqlite_wrap trick since we've a sqlite locking pb when
+ # running unittest_multisources with the wrapping below
+ if self.dbdriver == 'sqlite' and \
+ not getattr(repo.config, 'no_sqlite_wrap', False):
+ from cubicweb.server.sources.extlite import ConnectionWrapper
+ self.get_connection = lambda: ConnectionWrapper(self)
+ self.check_connection = lambda cnx: cnx
+ def pool_reset(cnx):
+ cnx.close()
+ self.pool_reset = pool_reset
+
+ @property
+ def _sqlcnx(self):
+ # XXX: sqlite connections can only be used in the same thread, so
+ # create a new one each time necessary. If it appears to be time
+ # consuming, find another way
+ return SQLAdapterMixIn.get_connection(self)
def reset_caches(self):
"""method called during test to reset potential source caches"""
@@ -161,33 +189,51 @@
def clear_eid_cache(self, eid, etype):
"""clear potential caches for the given eid"""
- self._cache.pop('%s X WHERE X eid %s' % (etype, eid), None)
+ self._cache.pop('Any X WHERE X eid %s, X is %s' % (eid, etype), None)
self._cache.pop('Any X WHERE X eid %s' % eid, None)
+ self._cache.pop('Any %s' % eid, None)
def sqlexec(self, session, sql, args=None):
"""execute the query and return its result"""
- cursor = session.pool[self.uri]
- self.doexec(cursor, sql, args)
- return self.process_result(cursor)
+ return self.process_result(self.doexec(session, sql, args))
def init_creating(self):
+ pool = self.repo._get_pool()
+ pool.pool_set()
# check full text index availibility
- pool = self.repo._get_pool()
if not self.indexer.has_fti_table(pool['system']):
self.error('no text index table')
self.indexer = None
+ pool.pool_reset()
self.repo._free_pool(pool)
+ def backup(self, confirm, backupfile=None, timestamp=None,
+ askconfirm=False):
+ """method called to create a backup of source's data"""
+ backupfile = self.backup_file(backupfile, timestamp)
+ sql_source_backup(self, self, confirm, backupfile, askconfirm)
+
+ def restore(self, confirm, backupfile=None, timestamp=None, drop=True,
+ askconfirm=False):
+ """method called to restore a backup of source's data"""
+ backupfile = self.backup_file(backupfile, timestamp)
+ sql_source_restore(self, self, confirm, backupfile, drop, askconfirm)
+
def init(self):
self.init_creating()
pool = self.repo._get_pool()
+ pool.pool_set()
# XXX cubicweb < 2.42 compat
if 'deleted_entities' in self.dbhelper.list_tables(pool['system']):
self.has_deleted_entitites_table = True
else:
self.has_deleted_entitites_table = False
+ pool.pool_reset()
self.repo._free_pool(pool)
+ def map_attribute(self, etype, attr, cb):
+ self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb
+
# ISource interface #######################################################
def compile_rql(self, rql):
@@ -199,7 +245,7 @@
return rqlst
def set_schema(self, schema):
- """set the application'schema"""
+ """set the instance'schema"""
self._cache = Cache(self.repo.config['rql-cache-size'])
self.cache_hit, self.cache_miss, self.no_cache = 0, 0, 0
self.schema = schema
@@ -260,13 +306,7 @@
necessary to fetch the results (but not the results themselves)
may be cached using this key.
"""
- if server.DEBUG:
- print 'RQL FOR NATIVE SOURCE', self.uri, cachekey
- if varmap:
- print 'USING VARMAP', varmap
- print union.as_string()
- if args: print 'ARGS', args
- print 'SOLUTIONS', ','.join(str(s.solutions) for s in union.children)
+ assert dbg_st_search(self.uri, union, varmap, args, cachekey)
# remember number of actually selected term (sql generation may append some)
if cachekey is None:
self.no_cache += 1
@@ -282,21 +322,18 @@
sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
self._cache[cachekey] = sql, query_args
args = self.merge_args(args, query_args)
- cursor = session.pool[self.uri]
assert isinstance(sql, basestring), repr(sql)
try:
- self.doexec(cursor, sql, args)
+ cursor = self.doexec(session, sql, args)
except (self.dbapi_module.OperationalError,
self.dbapi_module.InterfaceError):
# FIXME: better detection of deconnection pb
self.info("request failed '%s' ... retry with a new cursor", sql)
session.pool.reconnect(self)
- cursor = session.pool[self.uri]
- self.doexec(cursor, sql, args)
- res = self.process_result(cursor)
- if server.DEBUG:
- print '------>', res
- return res
+ cursor = self.doexec(session, sql, args)
+ results = self.process_result(cursor)
+ assert dbg_results(results)
+ return results
def flying_insert(self, table, session, union, args=None, varmap=None):
"""similar as .syntax_tree_search, but inserts data in the
@@ -304,66 +341,39 @@
source whose the given cursor come from). If not possible,
inserts all data by calling .executemany().
"""
- if self.uri == 'system':
- if server.DEBUG:
- print 'FLYING RQL FOR SOURCE', self.uri
- if varmap:
- print 'USING VARMAP', varmap
- print union.as_string()
- print 'SOLUTIONS', ','.join(str(s.solutions) for s in union.children)
- # generate sql queries if we are able to do so
- sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
- query = 'INSERT INTO %s %s' % (table, sql.encode(self.encoding))
- self.doexec(session.pool[self.uri], query,
- self.merge_args(args, query_args))
-# XXX commented until it's proved to be necessary
-# # XXX probably inefficient
-# tempdata = self._temp_table_data.setdefault(table, set())
-# cursor = session.pool[self.uri]
-# cursor.execute('select * from %s' % table)
-# for row in cursor.fetchall():
-# print 'data', row
-# tempdata.add(tuple(row))
- else:
- super(NativeSQLSource, self).flying_insert(table, session, union,
- args, varmap)
+ assert dbg_st_search(
+ self.uri, union, varmap, args,
+ prefix='ON THE FLY temp data insertion into %s from' % table)
+ # generate sql queries if we are able to do so
+ sql, query_args = self._rql_sqlgen.generate(union, args, varmap)
+ query = 'INSERT INTO %s %s' % (table, sql.encode(self.encoding))
+ self.doexec(session, query, self.merge_args(args, query_args))
- def _manual_insert(self, results, table, session):
+ def manual_insert(self, results, table, session):
"""insert given result into a temporary table on the system source"""
- #print 'manual insert', table, results
+ if server.DEBUG & server.DBG_RQL:
+ print ' manual insertion of', res, 'into', table
if not results:
return
- #cursor.execute('select * from %s'%table)
- #assert len(cursor.fetchall())== 0
- encoding = self.encoding
- # added chr to be sqlite compatible
query_args = ['%%(%s)s' % i for i in xrange(len(results[0]))]
query = 'INSERT INTO %s VALUES(%s)' % (table, ','.join(query_args))
kwargs_list = []
-# tempdata = self._temp_table_data.setdefault(table, set())
for row in results:
kwargs = {}
row = tuple(row)
-# XXX commented until it's proved to be necessary
-# if row in tempdata:
-# continue
-# tempdata.add(row)
for index, cell in enumerate(row):
- if type(cell) is unicode:
- cell = cell.encode(encoding)
- elif isinstance(cell, Binary):
+ if isinstance(cell, Binary):
cell = self.binary(cell.getvalue())
kwargs[str(index)] = cell
kwargs_list.append(kwargs)
- self.doexecmany(session.pool[self.uri], query, kwargs_list)
+ self.doexecmany(session, query, kwargs_list)
def clean_temp_data(self, session, temptables):
"""remove temporary data, usually associated to temporary tables"""
if temptables:
- cursor = session.pool[self.uri]
for table in temptables:
try:
- self.doexec(cursor,'DROP TABLE %s' % table)
+ self.doexec(session,'DROP TABLE %s' % table)
except:
pass
try:
@@ -375,25 +385,25 @@
"""add a new entity to the source"""
attrs = self.preprocess_entity(entity)
sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs)
- self.doexec(session.pool[self.uri], sql, attrs)
+ self.doexec(session, sql, attrs)
def update_entity(self, session, entity):
"""replace an entity in the source"""
attrs = self.preprocess_entity(entity)
sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, [SQL_PREFIX + 'eid'])
- self.doexec(session.pool[self.uri], sql, attrs)
+ self.doexec(session, sql, attrs)
def delete_entity(self, session, etype, eid):
"""delete an entity from the source"""
attrs = {SQL_PREFIX + 'eid': eid}
sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs)
- self.doexec(session.pool[self.uri], sql, attrs)
+ self.doexec(session, sql, attrs)
def add_relation(self, session, subject, rtype, object):
"""add a relation to the source"""
attrs = {'eid_from': subject, 'eid_to': object}
sql = self.sqlgen.insert('%s_relation' % rtype, attrs)
- self.doexec(session.pool[self.uri], sql, attrs)
+ self.doexec(session, sql, attrs)
def delete_relation(self, session, subject, rtype, object):
"""delete a relation from the source"""
@@ -407,39 +417,51 @@
else:
attrs = {'eid_from': subject, 'eid_to': object}
sql = self.sqlgen.delete('%s_relation' % rtype, attrs)
- self.doexec(session.pool[self.uri], sql, attrs)
+ self.doexec(session, sql, attrs)
- def doexec(self, cursor, query, args=None):
+ def doexec(self, session, query, args=None, rollback=True):
"""Execute a query.
it's a function just so that it shows up in profiling
"""
- #t1 = time()
- if server.DEBUG:
- print 'exec', query, args
- #import sys
- #sys.stdout.flush()
- # str(query) to avoid error if it's an unicode string
+ cursor = session.pool[self.uri]
+ if server.DEBUG & server.DBG_SQL:
+ cnx = session.pool.connection(self.uri)
+ # getattr to get the actual connection if cnx is a ConnectionWrapper
+ # instance
+ print 'exec', query, args, getattr(cnx, '_cnx', cnx)
try:
+ # str(query) to avoid error if it's an unicode string
cursor.execute(str(query), args)
except Exception, ex:
self.critical("sql: %r\n args: %s\ndbms message: %r",
query, args, ex.args[0])
+ if rollback:
+ try:
+ session.pool.connection(self.uri).rollback()
+ self.critical('transaction has been rollbacked')
+ except:
+ pass
raise
+ return cursor
- def doexecmany(self, cursor, query, args):
+ def doexecmany(self, session, query, args):
"""Execute a query.
it's a function just so that it shows up in profiling
"""
- #t1 = time()
- if server.DEBUG:
+ if server.DEBUG & server.DBG_SQL:
print 'execmany', query, 'with', len(args), 'arguments'
- #import sys
- #sys.stdout.flush()
- # str(query) to avoid error if it's an unicode string
+ cursor = session.pool[self.uri]
try:
+ # str(query) to avoid error if it's an unicode string
cursor.executemany(str(query), args)
- except:
- self.critical("sql many: %r\n args: %s", query, args)
+ except Exception, ex:
+ self.critical("sql many: %r\n args: %s\ndbms message: %r",
+ query, args, ex.args[0])
+ try:
+ session.pool.connection(self.uri).rollback()
+ self.critical('transaction has been rollbacked')
+ except:
+ pass
raise
# short cut to method requiring advanced db helper usage ##################
@@ -495,14 +517,13 @@
# running with an ldap source, and table will be deleted manually any way
# on commit
sql = self.dbhelper.sql_temporary_table(table, schema, False)
- self.doexec(session.pool[self.uri], sql)
+ self.doexec(session, sql)
def create_eid(self, session):
self._eid_creation_lock.acquire()
try:
- cursor = session.pool[self.uri]
for sql in self.dbhelper.sqls_increment_sequence('entities_id_seq'):
- self.doexec(cursor, sql)
+ cursor = self.doexec(session, sql)
return cursor.fetchone()[0]
finally:
self._eid_creation_lock.release()
--- a/server/sources/pyrorql.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/pyrorql.py Tue Aug 04 15:06:09 2009 +0200
@@ -23,7 +23,8 @@
from cubicweb import dbapi, server
from cubicweb import BadConnectionId, UnknownEid, ConnectionError
from cubicweb.cwconfig import register_persistent_options
-from cubicweb.server.sources import AbstractSource, ConnectionWrapper, TimedCache
+from cubicweb.server.sources import (AbstractSource, ConnectionWrapper,
+ TimedCache, dbg_st_search, dbg_results)
class ReplaceByInOperator(Exception):
def __init__(self, eids):
@@ -164,7 +165,12 @@
"""
self.info('synchronizing pyro source %s', self.uri)
cnx = self.get_connection()
- extrepo = cnx._repo
+ try:
+ extrepo = cnx._repo
+ except AttributeError:
+ # fake connection wrapper returned when we can't connect to the
+ # external source (hence we've no chance to synchronize...)
+ return
etypes = self.support_entities.keys()
if mtime is None:
mtime = self.last_update_time()
@@ -212,7 +218,7 @@
nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group']
#cnxprops = ConnectionProperties(cnxtype=self.config['cnx-type'])
return dbapi.connect(database=self.config['pyro-ns-id'],
- user=self.config['cubicweb-user'],
+ login=self.config['cubicweb-user'],
password=self.config['cubicweb-password'],
host=nshost, port=nsport, group=nsgroup,
setvreg=False) #cnxprops=cnxprops)
@@ -248,13 +254,14 @@
def syntax_tree_search(self, session, union, args=None, cachekey=None,
varmap=None):
- #assert not varmap, (varmap, union)
+ assert dbg_st_search(self.uri, union, varmap, args, cachekey)
rqlkey = union.as_string(kwargs=args)
try:
results = self._query_cache[rqlkey]
except KeyError:
results = self._syntax_tree_search(session, union, args)
self._query_cache[rqlkey] = results
+ assert dbg_results(results)
return results
def _syntax_tree_search(self, session, union, args):
@@ -265,11 +272,6 @@
"""
if not args is None:
args = args.copy()
- if server.DEBUG:
- print 'RQL FOR PYRO SOURCE', self.uri
- print union.as_string()
- if args: print 'ARGS', args
- print 'SOLUTIONS', ','.join(str(s.solutions) for s in union.children)
# get cached cursor anyway
cu = session.pool[self.uri]
if cu is None:
@@ -281,10 +283,10 @@
rql, cachekey = RQL2RQL(self).generate(session, union, args)
except UnknownEid, ex:
if server.DEBUG:
- print 'unknown eid', ex, 'no results'
+ print ' unknown eid', ex, 'no results'
return []
- if server.DEBUG:
- print 'TRANSLATED RQL', rql
+ if server.DEBUG & server.DBG_RQL:
+ print ' translated rql', rql
try:
rset = cu.execute(rql, args, cachekey)
except Exception, ex:
@@ -320,11 +322,6 @@
results = rows
else:
results = []
- if server.DEBUG:
- if len(results)>10:
- print '--------------->', results[:10], '...', len(results)
- else:
- print '--------------->', results
return results
def _entity_relations_and_kwargs(self, session, entity):
--- a/server/sources/rql2sql.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sources/rql2sql.py Tue Aug 04 15:06:09 2009 +0200
@@ -303,7 +303,7 @@
protected by a lock
"""
- def __init__(self, schema, dbms_helper, dbencoding='UTF-8'):
+ def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None):
self.schema = schema
self.dbms_helper = dbms_helper
self.dbencoding = dbencoding
@@ -313,6 +313,9 @@
if not self.dbms_helper.union_parentheses_support:
self.union_sql = self.noparen_union_sql
self._lock = threading.Lock()
+ if attrmap is None:
+ attrmap = {}
+ self.attr_map = attrmap
def generate(self, union, args=None, varmap=None):
"""return SQL queries and a variable dictionnary from a RQL syntax tree
@@ -490,10 +493,10 @@
sql.insert(1, 'FROM (SELECT 1) AS _T')
sqls.append('\n'.join(sql))
if select.need_intersect:
- if distinct or not self.dbms_helper.intersect_all_support:
- return '\nINTERSECT\n'.join(sqls)
- else:
- return '\nINTERSECT ALL\n'.join(sqls)
+ #if distinct or not self.dbms_helper.intersect_all_support:
+ return '\nINTERSECT\n'.join(sqls)
+ #else:
+ # return '\nINTERSECT ALL\n'.join(sqls)
elif distinct:
return '\nUNION\n'.join(sqls)
else:
@@ -661,13 +664,27 @@
lhsvar, _, rhsvar, rhsconst = relation_info(relation)
# we are sure here to have a lhsvar
assert lhsvar is not None
- lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
if isinstance(relation.parent, Not):
self._state.done.add(relation.parent)
- sql = "%s IS NULL" % lhssql
if rhsvar is not None and not rhsvar._q_invariant:
- sql = '(%s OR %s!=%s)' % (sql, lhssql, rhsvar.accept(self))
+ # if the lhs variable is only linked to this relation, this mean we
+ # only want the relation to NOT exists
+ self._state.push_scope()
+ lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
+ rhssql = rhsvar.accept(self)
+ restrictions, tables = self._state.pop_scope()
+ restrictions.append('%s=%s' % (lhssql, rhssql))
+ if not tables:
+ sql = 'NOT EXISTS(SELECT 1 WHERE %s)' % (
+ ' AND '.join(restrictions))
+ else:
+ sql = 'NOT EXISTS(SELECT 1 FROM %s WHERE %s)' % (
+ ', '.join(tables), ' AND '.join(restrictions))
+ else:
+ lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
+ sql = '%s IS NULL' % self._inlined_var_sql(lhsvar, relation.r_type)
return sql
+ lhssql = self._inlined_var_sql(lhsvar, relation.r_type)
if rhsconst is not None:
return '%s=%s' % (lhssql, rhsconst.accept(self))
if isinstance(rhsvar, Variable) and not rhsvar.name in self._varmap:
@@ -827,6 +844,9 @@
contextrels[var.name] = attrvars[var.name]
except KeyError:
attrvars[var.name] = relation
+ if var.name in self._varmap:
+ # ensure table is added
+ self._var_info(var.variable)
if not contextrels:
relation.children[1].accept(self, contextrels)
return ''
@@ -836,27 +856,30 @@
relation.r_type)
return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels))
- def _visit_attribute_relation(self, relation):
+ def _visit_attribute_relation(self, rel):
"""generate SQL for an attribute relation"""
- lhs, rhs = relation.get_parts()
+ lhs, rhs = rel.get_parts()
rhssql = rhs.accept(self)
table = self._var_table(lhs.variable)
if table is None:
- assert relation.r_type == 'eid'
+ assert rel.r_type == 'eid'
lhssql = lhs.accept(self)
else:
try:
- lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)]
+ lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)]
except KeyError:
- if relation.r_type == 'eid':
+ mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type)
+ if mapkey in self.attr_map:
+ lhssql = self.attr_map[mapkey](self, lhs.variable, rel)
+ elif rel.r_type == 'eid':
lhssql = lhs.variable._q_sql
else:
- lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type)
+ lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type)
try:
- if relation._q_needcast == 'TODAY':
+ if rel._q_needcast == 'TODAY':
sql = 'DATE(%s)%s' % (lhssql, rhssql)
# XXX which cast function should be used
- #elif relation._q_needcast == 'NOW':
+ #elif rel._q_needcast == 'NOW':
# sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql)
else:
sql = '%s%s' % (lhssql, rhssql)
@@ -867,15 +890,15 @@
else:
return sql
- def _visit_has_text_relation(self, relation):
+ def _visit_has_text_relation(self, rel):
"""generate SQL for a has_text relation"""
- lhs, rhs = relation.get_parts()
+ lhs, rhs = rel.get_parts()
const = rhs.children[0]
- alias = self._fti_table(relation)
+ alias = self._fti_table(rel)
jointo = lhs.accept(self)
restriction = ''
lhsvar = lhs.variable
- me_is_principal = lhsvar.stinfo.get('principal') is relation
+ me_is_principal = lhsvar.stinfo.get('principal') is rel
if me_is_principal:
if not lhsvar.stinfo['typerels']:
# the variable is using the fti table, no join needed
@@ -891,8 +914,8 @@
else:
etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes'])
restriction = " AND %s.type IN (%s)" % (ealias, etypes)
- if isinstance(relation.parent, Not):
- self._state.done.add(relation.parent)
+ if isinstance(rel.parent, Not):
+ self._state.done.add(rel.parent)
not_ = True
else:
not_ = False
@@ -1100,6 +1123,9 @@
if isinstance(linkedvar, ColumnAlias):
raise BadRQLQuery('variable %s should be selected by the subquery'
% variable.name)
+ mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type)
+ if mapkey in self.attr_map:
+ return self.attr_map[mapkey](self, linkedvar, rel)
try:
sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)]
except KeyError:
--- a/server/sqlutils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/sqlutils.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,6 +7,8 @@
"""
__docformat__ = "restructuredtext en"
+import os
+from os.path import exists
from warnings import warn
from datetime import datetime, date, timedelta
@@ -21,6 +23,8 @@
from cubicweb import Binary, ConfigurationError
from cubicweb.utils import todate, todatetime
from cubicweb.common.uilib import remove_html_tags
+from cubicweb.toolsutils import restrict_perms_to_user
+from cubicweb.schema import PURE_VIRTUAL_RTYPES
from cubicweb.server import SQL_CONNECT_HOOKS
from cubicweb.server.utils import crypt_password
@@ -29,7 +33,7 @@
SQL_PREFIX = 'cw_'
-def sqlexec(sqlstmts, cursor_or_execute, withpb=True, delimiter=';'):
+def sqlexec(sqlstmts, cursor_or_execute, withpb=True, pbtitle='', delimiter=';'):
"""execute sql statements ignoring DROP/ CREATE GROUP or USER statements
error. If a cnx is given, commit at each statement
"""
@@ -39,7 +43,7 @@
execute = cursor_or_execute
sqlstmts = sqlstmts.split(delimiter)
if withpb:
- pb = ProgressBar(len(sqlstmts))
+ pb = ProgressBar(len(sqlstmts), title=pbtitle)
for sql in sqlstmts:
sql = sql.strip()
if withpb:
@@ -74,7 +78,7 @@
def sqlschema(schema, driver, text_index=True,
user=None, set_owner=False,
- skip_relations=('has_text', 'identity'), skip_entities=()):
+ skip_relations=PURE_VIRTUAL_RTYPES, skip_entities=()):
"""return the system sql schema, according to the given parameters"""
from yams.schema2sql import schema2sql
from cubicweb.server.sources import native
@@ -99,7 +103,7 @@
def sqldropschema(schema, driver, text_index=True,
- skip_relations=('has_text', 'identity'), skip_entities=()):
+ skip_relations=PURE_VIRTUAL_RTYPES, skip_entities=()):
"""return the sql to drop the schema, according to the given parameters"""
from yams.schema2sql import dropschema2sql
from cubicweb.server.sources import native
@@ -116,6 +120,39 @@
skip_relations=skip_relations))
return '\n'.join(output)
+
+def sql_source_backup(source, sqladapter, confirm, backupfile,
+ askconfirm=False):
+ if exists(backupfile):
+ if not confirm('Backup file %s exists, overwrite it?' % backupfile):
+ return
+ elif askconfirm and not confirm('Backup %s database?'
+ % source.repo.config.appid):
+ print '-> no backup done.'
+ return
+ # should close opened connection before backuping
+ source.close_pool_connections()
+ try:
+ sqladapter.backup_to_file(backupfile, confirm)
+ finally:
+ source.open_pool_connections()
+
+def sql_source_restore(source, sqladapter, confirm, backupfile, drop=True,
+ askconfirm=False):
+ if not exists(backupfile):
+ raise Exception("backup file %s doesn't exist" % backupfile)
+ app = source.repo.config.appid
+ if askconfirm and not confirm('Restore %s %s database from %s ?'
+ % (app, source.uri, backupfile)):
+ return
+ # should close opened connection before restoring
+ source.close_pool_connections()
+ try:
+ sqladapter.restore_from_file(backupfile, confirm, drop=drop)
+ finally:
+ source.open_pool_connections()
+
+
try:
from mx.DateTime import DateTimeType, DateTimeDeltaType
except ImportError:
@@ -159,6 +196,46 @@
#self.dbapi_module.type_code_test(cnx.cursor())
return cnx
+ def backup_to_file(self, backupfile, confirm):
+ cmd = self.dbhelper.backup_command(self.dbname, self.dbhost,
+ self.dbuser, backupfile,
+ keepownership=False)
+ backupdir = os.path.dirname(backupfile)
+ if not os.path.exists(backupdir):
+ if confirm('%s does not exist. Create it?' % backupdir,
+ abort=False, shell=False):
+ os.mkdir(backupdir)
+ else:
+ print '-> failed to backup instance'
+ return
+ if os.system(cmd):
+ print '-> error trying to backup with command', cmd
+ if not confirm('Continue anyway?', default_is_yes=False):
+ raise SystemExit(1)
+ else:
+ print '-> backup file', backupfile
+ restrict_perms_to_user(backupfile, self.info)
+
+ def restore_from_file(self, backupfile, confirm, drop=True):
+ for cmd in self.dbhelper.restore_commands(self.dbname, self.dbhost,
+ self.dbuser, backupfile,
+ self.encoding,
+ keepownership=False,
+ drop=drop):
+ while True:
+ print cmd
+ if os.system(cmd):
+ print '-> error while restoring the base'
+ answer = confirm('Continue anyway?',
+ shell=False, abort=False, retry=True)
+ if not answer:
+ raise SystemExit(1)
+ if answer == 1: # 1: continue, 2: retry
+ break
+ else:
+ break
+ print '-> database restored.'
+
def merge_args(self, args, query_args):
if args is not None:
args = dict(args)
--- a/server/ssplanner.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/ssplanner.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,6 +13,7 @@
from rql.nodes import Constant
from cubicweb import QueryError, typed_eid
+from cubicweb.schema import VIRTUAL_RTYPES
def add_types_restriction(schema, rqlst, newroot=None, solutions=None):
if newroot is None:
@@ -196,7 +197,7 @@
relations, attrrelations = [], []
getrschema = self.schema.rschema
for relation in rqlst.main_relations:
- if relation.r_type in ('eid', 'has_text', 'identity'):
+ if relation.r_type in VIRTUAL_RTYPES:
raise QueryError('can not assign to %r relation'
% relation.r_type)
lhs, rhs = relation.get_variable_parts()
@@ -440,7 +441,7 @@
session = self.plan.session
delete = session.repo.glob_delete_entity
# register pending eids first to avoid multiple deletion
- pending = session.query_data('pendingeids', set(), setdefault=True)
+ pending = session.transaction_data.setdefault('pendingeids', set())
actual = todelete - pending
pending |= actual
for eid in actual:
--- a/server/test/data/config1/application_hooks.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""hooks for config1
-
- Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-HOOKS = {"after_add_relation" : {"concerned_by" : [lambda: None]}}
--- a/server/test/data/config1/bootstrap_packages Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-# file generated by cubicweb-ctl
--- a/server/test/data/config1/server-ctl.conf Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# file generated by cubicweb-ctl
-
-APPLICATION HOME=/home/adim/etc/cubicweb.d/crmadim
-DEBUG=
-HOST=
-LOG TRESHOLD=LOG_DEBUG
-NS GROUP=cubicweb
-NS HOST=
-PID FILE=/home/adim/tmp/crmadim.pid
-PORT=
-QUERY LOG FILE=
-UID=1006
-PROFILE=
--- a/server/test/data/config1/sources Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# file generated by cubicweb-ctl
-
-[system]
-ADAPTER=native
-DBHOST=crater
-DBDRIVER=postgres
-DBNAME=whatever
-ENCODING=UTF-8
-SPLIT_RELATIONS = True
-
--- a/server/test/data/config2/application_hooks.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-"""hooks for config2
-
- Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
-:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
-"""
-
-HOOKS = {"after_delete_relation" : {"todo_by" : [lambda: 1]}}
--- a/server/test/data/config2/bootstrap_packages Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-# file generated by cubicweb-ctl
--- a/server/test/data/config2/server-ctl.conf Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# file generated by cubicweb-ctl
-
-APPLICATION HOME=/home/adim/etc/cubicweb.d/crmadim
-DEBUG=
-HOST=
-LOG TRESHOLD=LOG_DEBUG
-NS GROUP=cubicweb
-NS HOST=
-PID FILE=/home/adim/tmp/crmadim.pid
-PORT=
-QUERY LOG FILE=
-UID=1006
-PROFILE=
--- a/server/test/data/config2/sources Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# file generated by cubicweb-ctl
-
-[system]
-ADAPTER=native
-DBHOST=crater
-DBDRIVER=postgres
-DBNAME=whatever
-ENCODING=UTF-8
-SPLIT_RELATIONS = True
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/migratedapp/bootstrap_cubes Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,1 @@
+card,comment,folder,tag,basket,email,file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/migratedapp/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,119 @@
+"""
+
+: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 yams.buildobjs import (EntityType, RelationType, RelationDefinition,
+ SubjectRelation, ObjectRelation,
+ RichString, String, Int, Boolean, Datetime, Date)
+from yams.constraints import SizeConstraint, UniqueConstraint
+from cubicweb.schema import (WorkflowableEntityType, RQLConstraint,
+ ERQLExpression, RRQLExpression)
+
+class Affaire(EntityType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
+ 'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
+ 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
+ }
+
+ ref = String(fulltextindexed=True, indexed=True,
+ constraints=[SizeConstraint(16)])
+ sujet = String(fulltextindexed=True,
+ constraints=[SizeConstraint(256)])
+ concerne = SubjectRelation('Societe')
+
+class concerne(RelationType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', RRQLExpression('U has_update_permission S')),
+ 'delete': ('managers', RRQLExpression('O owned_by U')),
+ }
+
+class Note(EntityType):
+ permissions = {'read': ('managers', 'users', 'guests',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', ),
+ 'add': ('managers',
+ ERQLExpression('X ecrit_part PE, U in_group G, '
+ 'PE require_permission P, P name "add_note", '
+ 'P require_group G'),)}
+
+ date = Datetime()
+ type = String(maxsize=1)
+ whatever = Int()
+ mydate = Date(default='TODAY')
+ para = String(maxsize=512)
+ shortpara = String(maxsize=64)
+ ecrit_par = SubjectRelation('Personne', constraints=[RQLConstraint('S concerne A, O concerne A')])
+ attachment = SubjectRelation(('File', 'Image'))
+
+class ecrit_par(RelationType):
+ permissions = {'read': ('managers', 'users', 'guests',),
+ 'delete': ('managers', ),
+ 'add': ('managers',
+ RRQLExpression('O require_permission P, P name "add_note", '
+ 'U in_group G, P require_group G'),)
+ }
+ inlined = True
+ cardinality = '?*'
+
+
+class Folder2(EntityType):
+ """folders are used to classify entities. They may be defined as a tree.
+ When you include the Folder entity, all application specific entities
+ may then be classified using the "filed_under" relation.
+ """
+ name = String(required=True, indexed=True, internationalizable=True,
+ constraints=[UniqueConstraint(), SizeConstraint(64)])
+ description = RichString(fulltextindexed=True)
+
+ filed_under2 = ObjectRelation('*')
+
+
+class Personne(EntityType):
+ nom = String(fulltextindexed=True, required=True, maxsize=64)
+ prenom = String(fulltextindexed=True, maxsize=64)
+ civility = String(maxsize=1, default='M')
+ promo = String(vocabulary=('bon','pasbon'))
+ titre = String(fulltextindexed=True, maxsize=128)
+ adel = String(maxsize=128)
+ ass = String(maxsize=128)
+ web = String(maxsize=128)
+ tel = Int()
+ fax = Int()
+ datenaiss = Datetime()
+ test = Boolean()
+
+ travaille = SubjectRelation('Societe')
+ concerne = SubjectRelation('Affaire')
+ concerne2 = SubjectRelation('Affaire')
+ connait = SubjectRelation('Personne', symetric=True)
+
+class Societe(EntityType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'update': ('managers', 'owners'),
+ 'delete': ('managers', 'owners'),
+ 'add': ('managers', 'users',)
+ }
+
+ nom = String(maxsize=64, fulltextindexed=True)
+ web = String(maxsize=128)
+ tel = Int()
+ fax = Int()
+ rncs = String(maxsize=128)
+ ad1 = String(maxsize=128)
+ ad2 = String(maxsize=128)
+ ad3 = String(maxsize=128)
+ cp = String(maxsize=12)
+ ville= String(maxsize=32)
+
+ in_state = SubjectRelation('State', cardinality='?*')
+
+class evaluee(RelationDefinition):
+ subject = ('Personne', 'CWUser', 'Societe')
+ object = ('Note')
--- a/server/test/data/migrschema/Affaire.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-"""
-
-: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
-"""
-
-class Affaire(EntityType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
- 'update': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
- 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
- }
-
- ref = String(fulltextindexed=True, indexed=True,
- constraints=[SizeConstraint(16)])
- sujet = String(fulltextindexed=True,
- constraints=[SizeConstraint(256)])
-
-class concerne(RelationType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', RRQLExpression('U has_update_permission S')),
- 'delete': ('managers', RRQLExpression('O owned_by U')),
- }
-
--- a/server/test/data/migrschema/Folder2.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-"""
-
-: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.schema import format_constraint
-
-class Folder2(MetaUserEntityType):
- """folders are used to classify entities. They may be defined as a tree.
- When you include the Folder entity, all application specific entities
- may then be classified using the "filed_under" relation.
- """
- name = String(required=True, indexed=True, internationalizable=True,
- constraints=[UniqueConstraint(), SizeConstraint(64)])
- description_format = String(meta=True, internationalizable=True,
- default='text/rest', constraints=[format_constraint])
- description = String(fulltextindexed=True)
-
- filed_under2 = BothWayRelation(
- SubjectRelation('Folder2', description=_("parent folder")),
- ObjectRelation('*'),
- )
-
-
-class filed_under2(MetaUserRelationType):
- """indicates that an entity is classified under a folder"""
- # is_about has been renamed into filed_under
- #//* is_about Folder
- #* filed_under Folder
-
--- a/server/test/data/migrschema/Note.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-"""
-
-: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
-"""
-class Note(EntityType):
-
- permissions = {'read': ('managers', 'users', 'guests',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', ),
- 'add': ('managers',
- ERQLExpression('X ecrit_part PE, U in_group G, '
- 'PE require_permission P, P name "add_note", '
- 'P require_group G'),)}
-
- date = Datetime()
- type = String(maxsize=1)
- whatever = Int()
- mydate = Date(default='TODAY')
- para = String(maxsize=512)
- shortpara = String(maxsize=64)
- ecrit_par = SubjectRelation('Personne', constraints=[RQLConstraint('S concerne A, O concerne A')])
-
-class ecrit_par(RelationType):
- permissions = {'read': ('managers', 'users', 'guests',),
- 'delete': ('managers', ),
- 'add': ('managers',
- RRQLExpression('O require_permission P, P name "add_note", '
- 'U in_group G, P require_group G'),)
- }
- inlined = True
--- a/server/test/data/migrschema/Personne.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-nom ivarchar(64) NOT NULL
-prenom ivarchar(64)
-civility char(1) DEFAULT 'M'
-promo choice('bon','pasbon')
-titre ivarchar(128)
-adel varchar(128)
-ass varchar(128)
-web varchar(128)
-tel integer
-fax integer
-datenaiss datetime
-test boolean
--- a/server/test/data/migrschema/Societe.perms Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-Read: managers, users, guests
--- a/server/test/data/migrschema/Societe.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-nom ivarchar(64)
-web varchar(128)
-tel integer
-fax integer
-rncs varchar(32)
-ad1 varchar(128)
-ad2 varchar(128)
-ad3 varchar(128)
-cp varchar(12)
-ville varchar(32)
--- a/server/test/data/migrschema/relations.rel Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-Personne travaille Societe
-Personne evaluee Note
-CWUser evaluee Note
-Societe evaluee Note
-Personne concerne Affaire
-Affaire concerne Societe
-Personne concerne2 Affaire
-
-Personne connait Personne symetric
-
-Societe in_state State inline
-
-Note attachment File
-Note attachment Image
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/data/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,214 @@
+"""
+
+: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 yams.buildobjs import (EntityType, RelationType, RelationDefinition,
+ SubjectRelation, ObjectRelation,
+ RichString, String, Int, Boolean, Datetime)
+from yams.constraints import SizeConstraint
+from cubicweb.schema import (WorkflowableEntityType, RQLConstraint,
+ ERQLExpression, RRQLExpression)
+
+class Affaire(WorkflowableEntityType):
+ permissions = {
+ 'read': ('managers',
+ ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
+ 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
+ 'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')),
+ 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
+ }
+
+ ref = String(fulltextindexed=True, indexed=True,
+ constraints=[SizeConstraint(16)])
+ sujet = String(fulltextindexed=True,
+ constraints=[SizeConstraint(256)])
+ descr = RichString(fulltextindexed=True,
+ description=_('more detailed description'))
+
+ duration = Int()
+ invoiced = Int()
+
+ depends_on = SubjectRelation('Affaire')
+ require_permission = SubjectRelation('CWPermission')
+ concerne = SubjectRelation(('Societe', 'Note'))
+ todo_by = SubjectRelation('Personne')
+ documented_by = SubjectRelation('Card')
+
+
+class Societe(EntityType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
+ 'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
+ 'add': ('managers', 'users',)
+ }
+
+ nom = String(maxsize=64, fulltextindexed=True)
+ web = String(maxsize=128)
+ type = String(maxsize=128) # attribute in common with Note
+ tel = Int()
+ fax = Int()
+ rncs = String(maxsize=128)
+ ad1 = String(maxsize=128)
+ ad2 = String(maxsize=128)
+ ad3 = String(maxsize=128)
+ cp = String(maxsize=12)
+ ville= String(maxsize=32)
+
+
+class Division(Societe):
+ __specializes_schema__ = True
+
+class SubDivision(Division):
+ __specializes_schema__ = True
+ travaille_subdivision = ObjectRelation('Personne')
+
+from cubicweb.schemas.base import CWUser
+CWUser.get_relations('login').next().fulltextindexed = True
+
+class Note(EntityType):
+ date = String(maxsize=10)
+ type = String(maxsize=6)
+ para = String(maxsize=512)
+
+ migrated_from = SubjectRelation('Note')
+ attachment = SubjectRelation(('File', 'Image'))
+ inline1 = SubjectRelation('Affaire', inlined=True, cardinality='?*')
+ todo_by = SubjectRelation('CWUser')
+
+class Personne(EntityType):
+ nom = String(fulltextindexed=True, required=True, maxsize=64)
+ prenom = String(fulltextindexed=True, maxsize=64)
+ sexe = String(maxsize=1, default='M')
+ promo = String(vocabulary=('bon','pasbon'))
+ titre = String(fulltextindexed=True, maxsize=128)
+ adel = String(maxsize=128)
+ ass = String(maxsize=128)
+ web = String(maxsize=128)
+ tel = Int()
+ fax = Int()
+ datenaiss = Datetime()
+ test = Boolean()
+ description = String()
+ firstname = String(fulltextindexed=True, maxsize=64)
+
+ travaille = SubjectRelation('Societe')
+ concerne = SubjectRelation('Affaire')
+ connait = SubjectRelation('Personne')
+ inline2 = SubjectRelation('Affaire', inlined=True, cardinality='?*')
+ comments = ObjectRelation('Comment')
+
+
+class fiche(RelationType):
+ inlined = True
+ subject = 'Personne'
+ object = 'Card'
+ cardinality = '??'
+
+class multisource_inlined_rel(RelationType):
+ inlined = True
+ cardinality = '?*'
+ subject = ('Card', 'Note')
+ object = ('Affaire', 'Note')
+
+class ecrit_par(RelationType):
+ inlined = True
+
+class connait(RelationType):
+ symetric = True
+
+class concerne(RelationType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', RRQLExpression('U has_update_permission S')),
+ 'delete': ('managers', RRQLExpression('O owned_by U')),
+ }
+
+class travaille(RelationType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', RRQLExpression('U has_update_permission S')),
+ 'delete': ('managers', RRQLExpression('O owned_by U')),
+ }
+
+class para(RelationType):
+ permissions = {
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers', ERQLExpression('X in_state S, S name "todo"')),
+ 'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
+ }
+
+class test(RelationType):
+ permissions = {'read': ('managers', 'users', 'guests'),
+ 'delete': ('managers',),
+ 'add': ('managers',)}
+
+
+class in_state(RelationDefinition):
+ subject = 'Note'
+ object = 'State'
+ cardinality = '1*'
+ constraints=[RQLConstraint('S is ET, O state_of ET')]
+
+class wf_info_for(RelationDefinition):
+ subject = 'TrInfo'
+ object = 'Note'
+ cardinality = '1*'
+
+class multisource_rel(RelationDefinition):
+ subject = ('Card', 'Note')
+ object = 'Note'
+
+class multisource_crossed_rel(RelationDefinition):
+ subject = ('Card', 'Note')
+ object = 'Note'
+
+
+class see_also_1(RelationDefinition):
+ name = 'see_also'
+ subject = object = 'Folder'
+
+class see_also_2(RelationDefinition):
+ name = 'see_also'
+ subject = ('Bookmark', 'Note')
+ object = ('Bookmark', 'Note')
+
+class evaluee(RelationDefinition):
+ subject = ('Personne', 'CWUser', 'Societe')
+ object = ('Note')
+
+class ecrit_par_1(RelationDefinition):
+ name = 'ecrit_par'
+ subject = 'Note'
+ object ='Personne'
+ constraints = [RQLConstraint('E concerns P, X version_of P')]
+ cardinality = '?*'
+
+class ecrit_par_2(RelationDefinition):
+ name = 'ecrit_par'
+ subject = 'Note'
+ object ='CWUser'
+ cardinality='?*'
+
+
+class copain(RelationDefinition):
+ subject = object = 'CWUser'
+
+class tags(RelationDefinition):
+ subject = 'Tag'
+ object = ('CWUser', 'CWGroup', 'State', 'Note', 'Card', 'Affaire')
+
+class filed_under(RelationDefinition):
+ subject = ('Note', 'Affaire')
+ object = 'Folder'
+
+class require_permission(RelationDefinition):
+ subject = ('Card', 'Note', 'Personne')
+ object = 'CWPermission'
+
+class require_state(RelationDefinition):
+ subject = 'CWPermission'
+ object = 'State'
--- a/server/test/data/schema/Affaire.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-"""
-
-: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.schema import format_constraint
-
-class Affaire(WorkflowableEntityType):
- permissions = {
- 'read': ('managers',
- ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')),
- 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')),
- 'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')),
- 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')),
- }
-
- ref = String(fulltextindexed=True, indexed=True,
- constraints=[SizeConstraint(16)])
- sujet = String(fulltextindexed=True,
- constraints=[SizeConstraint(256)])
- descr_format = String(meta=True, internationalizable=True,
- default='text/rest', constraints=[format_constraint])
- descr = String(fulltextindexed=True,
- description=_('more detailed description'))
-
- duration = Int()
- invoiced = Int()
-
- depends_on = SubjectRelation('Affaire')
- require_permission = SubjectRelation('CWPermission')
-
-class concerne(RelationType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', RRQLExpression('U has_update_permission S')),
- 'delete': ('managers', RRQLExpression('O owned_by U')),
- }
-
-
--- a/server/test/data/schema/Note.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-date varchar(10)
-type char(6)
-para varchar(512)
--- a/server/test/data/schema/Personne.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-nom ivarchar(64) NOT NULL
-prenom ivarchar(64)
-sexe char(1) DEFAULT 'M'
-promo choice('bon','pasbon')
-titre ivarchar(128)
-adel varchar(128)
-ass varchar(128)
-web varchar(128)
-tel integer
-fax integer
-datenaiss datetime
-test boolean
-description text
-firstname ivarchar(64)
--- a/server/test/data/schema/Societe.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-"""
-
-: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
-"""
-class Societe(EntityType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
- 'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')),
- 'add': ('managers', 'users',)
- }
-
- nom = String(maxsize=64, fulltextindexed=True)
- web = String(maxsize=128)
- type = String(maxsize=128) # attribute in common with Note
- tel = Int()
- fax = Int()
- rncs = String(maxsize=128)
- ad1 = String(maxsize=128)
- ad2 = String(maxsize=128)
- ad3 = String(maxsize=128)
- cp = String(maxsize=12)
- ville= String(maxsize=32)
-
-
-class travaille(RelationType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', RRQLExpression('U has_update_permission S')),
- 'delete': ('managers', RRQLExpression('O owned_by U')),
- }
-
-
-class Division(Societe):
- __specializes_schema__ = True
-
-class SubDivision(Division):
- __specializes_schema__ = True
- travaille_subdivision = ObjectRelation('Personne')
--- a/server/test/data/schema/custom.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-"""
-
-: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
-"""
-
-
-class test(AttributeRelationType):
- permissions = {'read': ('managers', 'users', 'guests'),
- 'delete': ('managers',),
- 'add': ('managers',)}
-
-class fiche(RelationType):
- inlined = True
- subject = 'Personne'
- object = 'Card'
- cardinality = '??'
-
-class multisource_rel(RelationDefinition):
- subject = ('Card', 'Note')
- object = 'Note'
-
-class multisource_crossed_rel(RelationDefinition):
- subject = ('Card', 'Note')
- object = 'Note'
-
-class multisource_inlined_rel(RelationType):
- inlined = True
- cardinality = '?*'
- subject = ('Card', 'Note')
- object = ('Affaire', 'Note')
-
-
-class see_also(RelationDefinition):
- subject = ('Bookmark', 'Note')
- object = ('Bookmark', 'Note')
-
-_euser = import_schema('base').CWUser
-_euser.__relations__[0].fulltextindexed = True
--- a/server/test/data/schema/note.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-"""
-
-: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
-"""
-
-class para(AttributeRelationType):
- permissions = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers', ERQLExpression('X in_state S, S name "todo"')),
- 'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
- }
-
-class in_state(RelationDefinition):
- subject = 'Note'
- object = 'State'
- cardinality = '1*'
- constraints=[RQLConstraint('S is ET, O state_of ET')]
-
-class wf_info_for(RelationDefinition):
- subject = 'TrInfo'
- object = 'Note'
- cardinality = '1*'
--- a/server/test/data/schema/relations.rel Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-Personne travaille Societe
-Personne evaluee Note
-CWUser evaluee Note
-Societe evaluee Note
-Personne concerne Affaire
-Affaire concerne Societe
-Affaire concerne Note
-
-Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P
-Note ecrit_par CWUser inline CONSTRAINT
-Personne connait Personne symetric
-
-# not inlined intentionaly
-Comment comments Personne
-
-Note inline1 Affaire inline
-Personne inline2 Affaire inline
-
-Note todo_by CWUser
-Affaire todo_by Personne
-
-Folder see_also Folder
-
-
-Affaire documented_by Card
-
-CWUser copain CWUser
-
-Tag tags CWUser
-Tag tags CWGroup
-Tag tags State
-Tag tags Note
-Tag tags Card
-Tag tags Affaire
-
-Note filed_under Folder
-Affaire filed_under Folder
-
-Card require_permission CWPermission
-Note require_permission CWPermission
-Personne require_permission CWPermission
-
-CWPermission require_state State
-
-Note migrated_from Note
-
-Note attachment File
-Note attachment Image
--- a/server/test/unittest_config.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-"""tests for server config
-
-: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 os.path import join, dirname
-
-from logilab.common.testlib import TestCase, unittest_main
-
-from cubicweb.devtools import TestServerConfiguration
-
-class ConfigTC(TestCase):
-
- def test_load_hooks_twice(self):
- class vreg:
- @staticmethod
- def registry_objects(registry):
- return []
-
- cfg1 = TestServerConfiguration('data/config1')
- cfg1.bootstrap_cubes()
- cfg2 = TestServerConfiguration('data/config2')
- cfg2.bootstrap_cubes()
- self.failIf(cfg1.load_hooks(vreg) is cfg2.load_hooks(vreg))
- self.failUnless('after_add_relation' in cfg1.load_hooks(vreg))
- self.failUnless('after_delete_relation' in cfg2.load_hooks(vreg))
-
-
-if __name__ == '__main__':
- unittest_main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/server/test/unittest_extlite.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,53 @@
+import threading, os, time
+
+from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.db import get_connection
+
+class SQLiteTC(TestCase):
+ sqlite_file = '_extlite_test.sqlite'
+ def setUp(self):
+ cnx1 = get_connection('sqlite', database=self.sqlite_file)
+ cu = cnx1.cursor()
+ cu.execute('CREATE TABLE toto(name integer);')
+ cnx1.commit()
+ cnx1.close()
+
+ def tearDown(self):
+ try:
+ os.remove(self.sqlite_file)
+ except:
+ pass
+
+ def test(self):
+ lock = threading.Lock()
+
+ def run_thread():
+ cnx2 = get_connection('sqlite', database=self.sqlite_file)
+ lock.acquire()
+ cu = cnx2.cursor()
+ cu.execute('SELECT name FROM toto')
+ self.failIf(cu.fetchall())
+ cnx2.commit()
+ lock.release()
+ time.sleep(0.1)
+ lock.acquire()
+ cu.execute('SELECT name FROM toto')
+ self.failUnless(cu.fetchall())
+ lock.release()
+
+ cnx1 = get_connection('sqlite', database=self.sqlite_file)
+ lock.acquire()
+ thread = threading.Thread(target=run_thread)
+ thread.start()
+ cu = cnx1.cursor()
+ cu.execute('SELECT name FROM toto')
+ lock.release()
+ time.sleep(0.1)
+ cnx1.commit()
+ lock.acquire()
+ cu.execute("INSERT INTO toto(name) VALUES ('toto')")
+ cnx1.commit()
+ lock.release()
+
+if __name__ == '__main__':
+ unittest_main()
--- a/server/test/unittest_hookhelper.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_hookhelper.py Tue Aug 04 15:06:09 2009 +0200
@@ -41,10 +41,10 @@
from cubicweb.server import hooks, schemahooks
session = self.session
op1 = hooks.DelayedDeleteOp(session)
- op2 = schemahooks.DelErdefOp(session)
+ op2 = schemahooks.MemSchemaRDefDel(session)
# equivalent operation generated by op2 but replace it here by op3 so we
# can check the result...
- op3 = schemahooks.UpdateSchemaOp(session)
+ op3 = schemahooks.MemSchemaNotifyChanges(session)
op4 = hooks.DelayedDeleteOp(session)
op5 = hooks.CheckORelationOp(session)
self.assertEquals(session.pending_operations, [op1, op2, op4, op5, op3])
--- a/server/test/unittest_hooks.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_hooks.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,9 +6,13 @@
"""
from logilab.common.testlib import TestCase, unittest_main
+
+from datetime import datetime
+
+from cubicweb import (ConnectionError, RepositoryError, ValidationError,
+ AuthenticationError, BadConnectionId)
from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
-from cubicweb import ConnectionError, RepositoryError, ValidationError, AuthenticationError, BadConnectionId
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.repository import Repository
@@ -58,12 +62,12 @@
def test_delete_if_singlecard1(self):
self.assertEquals(self.repo.schema['in_state'].inlined, False)
- ueid, = self.execute('INSERT CWUser X: X login "toto", X upassword "hop", X in_group Y, X in_state S '
- 'WHERE Y name "users", S name "activated"')[0]
+ ueid = self.create_user('toto')
self.commit()
self.execute('SET X in_state S WHERE S name "deactivated", X eid %(x)s', {'x': ueid})
rset = self.execute('Any S WHERE X in_state S, X eid %(x)s', {'x': ueid})
self.assertEquals(len(rset), 1)
+ self.commit()
self.assertRaises(Exception, self.execute, 'SET X in_state S WHERE S name "deactivated", X eid %s' % ueid)
rset2 = self.execute('Any S WHERE X in_state S, X eid %(x)s', {'x': ueid})
self.assertEquals(rset.rows, rset2.rows)
@@ -242,13 +246,12 @@
class SchemaModificationHooksTC(RepositoryBasedTC):
- copy_schema = True
def setUp(self):
if not hasattr(self, '_repo'):
# first initialization
repo = self.repo # set by the RepositoryBasedTC metaclass
- # force to read schema from the database
+ # force to read schema from the database to get proper eid set on schema instances
repo.config._cubes = None
repo.fill_schema()
RepositoryBasedTC.setUp(self)
@@ -265,8 +268,8 @@
self.failIf(schema.has_entity('Societe2'))
self.failIf(schema.has_entity('concerne2'))
# schema should be update on insertion (after commit)
- self.execute('INSERT CWEType X: X name "Societe2", X description "", X meta FALSE, X final FALSE')
- self.execute('INSERT CWRType X: X name "concerne2", X description "", X meta FALSE, X final FALSE, X symetric FALSE')
+ self.execute('INSERT CWEType X: X name "Societe2", X description "", X final FALSE')
+ self.execute('INSERT CWRType X: X name "concerne2", X description "", X final FALSE, X symetric FALSE')
self.failIf(schema.has_entity('Societe2'))
self.failIf(schema.has_entity('concerne2'))
self.execute('SET X read_permission G WHERE X is CWEType, X name "Societe2", G is CWGroup')
@@ -471,6 +474,10 @@
self.commit()
# should not be able anymore to add personne without prenom
self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"')
+ self.execute('SET DEF cardinality "?1" '
+ 'WHERE DEF relation_type RT, DEF from_entity E,'
+ 'RT name "prenom", E name "Personne"')
+ self.commit()
class WorkflowHooksTC(RepositoryBasedTC):
@@ -610,5 +617,39 @@
self.failUnless(cu.execute("INSERT Note X: X type 'a', X in_state S WHERE S name 'todo'"))
cnx.commit()
+ def test_metadata_cwuri(self):
+ eid = self.execute('INSERT Note X')[0][0]
+ cwuri = self.execute('Any U WHERE X eid %s, X cwuri U' % eid)[0][0]
+ self.assertEquals(cwuri, self.repo.config['base-url'] + 'eid/%s' % eid)
+
+ def test_metadata_creation_modification_date(self):
+ _now = datetime.now()
+ eid = self.execute('INSERT Note X')[0][0]
+ creation_date, modification_date = self.execute('Any CD, MD WHERE X eid %s, '
+ 'X creation_date CD, '
+ 'X modification_date MD' % eid)[0]
+ self.assertEquals((creation_date - _now).seconds, 0)
+ self.assertEquals((modification_date - _now).seconds, 0)
+
+ def test_metadata__date(self):
+ _now = datetime.now()
+ eid = self.execute('INSERT Note X')[0][0]
+ creation_date = self.execute('Any D WHERE X eid %s, X creation_date D' % eid)[0][0]
+ self.assertEquals((creation_date - _now).seconds, 0)
+
+ def test_metadata_created_by(self):
+ eid = self.execute('INSERT Note X')[0][0]
+ self.commit() # fire operations
+ rset = self.execute('Any U WHERE X eid %s, X created_by U' % eid)
+ self.assertEquals(len(rset), 1) # make sure we have only one creator
+ self.assertEquals(rset[0][0], self.session.user.eid)
+
+ def test_metadata_owned_by(self):
+ eid = self.execute('INSERT Note X')[0][0]
+ self.commit() # fire operations
+ rset = self.execute('Any U WHERE X eid %s, X owned_by U' % eid)
+ self.assertEquals(len(rset), 1) # make sure we have only one owner
+ self.assertEquals(rset[0][0], self.session.user.eid)
+
if __name__ == '__main__':
unittest_main()
--- a/server/test/unittest_migractions.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_migractions.py Tue Aug 04 15:06:09 2009 +0200
@@ -3,10 +3,12 @@
"""
from datetime import date
+from os.path import join
from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb import ConfigurationError
from cubicweb.devtools.apptest import RepositoryBasedTC, get_versions
-
from cubicweb.schema import CubicWebSchemaLoader
from cubicweb.server.sqlutils import SQL_PREFIX
from cubicweb.server.repository import Repository
@@ -22,7 +24,6 @@
class MigrationCommandsTC(RepositoryBasedTC):
- copy_schema = True
def setUp(self):
if not hasattr(self, '_repo'):
@@ -32,10 +33,10 @@
repo.config._cubes = None
repo.fill_schema()
# hack to read the schema from data/migrschema
- CubicWebSchemaLoader.main_schema_directory = 'migrschema'
+ self.repo.config.appid = join('data', 'migratedapp')
global migrschema
migrschema = self.repo.config.load_schema()
- del CubicWebSchemaLoader.main_schema_directory
+ self.repo.config.appid = 'data'
assert 'Folder' in migrschema
self.repo.hm.deactivate_verification_hooks()
RepositoryBasedTC.setUp(self)
@@ -77,6 +78,7 @@
def test_add_datetime_with_default_value_attribute(self):
self.failIf('mydate' in self.schema)
+ self.failIf('shortpara' in self.schema)
self.mh.cmd_add_attribute('Note', 'mydate')
self.failUnless('mydate' in self.schema)
self.assertEquals(self.schema['mydate'].subjects(), ('Note', ))
@@ -132,20 +134,22 @@
self.failUnless('filed_under2' in self.schema)
self.failUnless(self.execute('CWRType X WHERE X name "filed_under2"'))
self.assertEquals(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()),
- ['created_by', 'creation_date', 'description', 'description_format', 'eid',
- 'filed_under2', 'has_text', 'identity', 'is', 'is_instance_of',
+ ['created_by', 'creation_date', 'cwuri',
+ 'description', 'description_format',
+ 'eid',
+ 'filed_under2', 'has_text',
+ 'identity', 'in_basket', 'is', 'is_instance_of',
'modification_date', 'name', 'owned_by'])
self.assertEquals([str(rs) for rs in self.schema['Folder2'].object_relations()],
['filed_under2', 'identity'])
self.assertEquals(sorted(str(e) for e in self.schema['filed_under2'].subjects()),
- ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
- 'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
+ sorted(str(e) for e in self.schema.entities() if not e.is_final()))
self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
eschema = self.schema.eschema('Folder2')
for cstr in eschema.constraints('name'):
self.failUnless(hasattr(cstr, 'eid'))
- def test_drop_entity_type(self):
+ def test_add_drop_entity_type(self):
self.mh.cmd_add_entity_type('Folder2')
todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True)
doneeid = self.mh.cmd_add_state(u'done', 'Folder2')
@@ -160,60 +164,59 @@
self.failIf(self.execute('State X WHERE NOT X state_of ET'))
self.failIf(self.execute('Transition X WHERE NOT X transition_of ET'))
- def test_add_relation_type(self):
+ def test_add_drop_relation_type(self):
self.mh.cmd_add_entity_type('Folder2', auto=False)
self.mh.cmd_add_relation_type('filed_under2')
self.failUnless('filed_under2' in self.schema)
self.assertEquals(sorted(str(e) for e in self.schema['filed_under2'].subjects()),
- ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File',
- 'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision'])
+ sorted(str(e) for e in self.schema.entities() if not e.is_final()))
self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',))
-
-
- def test_drop_relation_type(self):
- self.mh.cmd_add_entity_type('Folder2', auto=False)
- self.mh.cmd_add_relation_type('filed_under2')
- self.failUnless('filed_under2' in self.schema)
self.mh.cmd_drop_relation_type('filed_under2')
self.failIf('filed_under2' in self.schema)
- def test_add_relation_definition(self):
- self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State')
- self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()),
- ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision'])
- self.assertEquals(self.schema['in_state'].objects(), ('State',))
-
def test_add_relation_definition_nortype(self):
self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire')
self.assertEquals(self.schema['concerne2'].subjects(),
('Personne',))
self.assertEquals(self.schema['concerne2'].objects(), ('Affaire',))
+ self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire')
+ self.failIf('concerne2' in self.schema)
- def test_drop_relation_definition1(self):
- self.failUnless('concerne' in self.schema)
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+ def test_drop_relation_definition_existant_rtype(self):
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire', 'Personne'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire')
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire'])
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Division', 'Note', 'Societe', 'SubDivision'])
+ self.mh.cmd_add_relation_definition('Personne', 'concerne', 'Affaire')
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire', 'Personne'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+ # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+ self.maxeid = self.execute('Any MAX(X)')[0][0]
def test_drop_relation_definition_with_specialization(self):
- self.failUnless('concerne' in self.schema)
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire', 'Personne'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
self.mh.cmd_drop_relation_definition('Affaire', 'concerne', 'Societe')
- self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe')
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne'])
- self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note'])
-
- def test_drop_relation_definition2(self):
- self.failUnless('evaluee' in self.schema)
- self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note')
- self.failUnless('evaluee' in self.schema)
- self.assertEquals(sorted(self.schema['evaluee'].subjects()),
- ['CWUser', 'Division', 'Societe', 'SubDivision'])
- self.assertEquals(sorted(self.schema['evaluee'].objects()),
- ['Note'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire', 'Personne'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Affaire', 'Note'])
+ self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe')
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()),
+ ['Affaire', 'Personne'])
+ self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()),
+ ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision'])
+ # trick: overwrite self.maxeid to avoid deletion of just reintroduced types
+ self.maxeid = self.execute('Any MAX(X)')[0][0]
def test_rename_relation(self):
self.skip('implement me')
@@ -244,28 +247,15 @@
self.mh.cmd_change_relation_props('Personne', 'adel', 'String',
fulltextindexed=False)
- def test_synchronize_schema(self):
+ def test_sync_schema_props_perms(self):
cursor = self.mh.rqlcursor
nbrqlexpr_start = len(cursor.execute('RQLExpression X'))
migrschema['titre']._rproperties[('Personne', 'String')]['order'] = 7
migrschema['adel']._rproperties[('Personne', 'String')]['order'] = 6
migrschema['ass']._rproperties[('Personne', 'String')]['order'] = 5
-# expected = ['eid', 'has_text', 'creation_date', 'modification_date',
-# 'nom', 'prenom', 'civility', 'promo', 'ass', 'adel', 'titre',
-# 'web', 'tel', 'fax', 'datenaiss', 'test']
-# self.assertEquals([rs.type for rs in migrschema['Personne'].ordered_relations() if rs.is_final()],
-# expected)
migrschema['Personne'].description = 'blabla bla'
migrschema['titre'].description = 'usually a title'
migrschema['titre']._rproperties[('Personne', 'String')]['description'] = 'title for this person'
-# rinorderbefore = cursor.execute('Any O,N WHERE X is CWAttribute, X relation_type RT, RT name N,'
-# 'X from_entity FE, FE name "Personne",'
-# 'X ordernum O ORDERBY O')
-# expected = [u'creation_date', u'modification_date', u'nom', u'prenom',
-# u'sexe', u'promo', u'titre', u'adel', u'ass', u'web', u'tel',
-# u'fax', u'datenaiss', u'test', u'description']
-# self.assertListEquals(rinorderbefore, map(list, zip([0, 0]+range(1, len(expected)), expected)))
-
self.mh.cmd_sync_schema_props_perms(commit=False)
self.assertEquals(cursor.execute('Any D WHERE X name "Personne", X description D')[0][0],
@@ -276,16 +266,13 @@
'X from_entity FE, FE name "Personne",'
'X description D')[0][0],
'title for this person')
- # skip "sexe" and "description" since they aren't in the migration
- # schema and so behaviour is undefined
- # "civility" is also skipped since it may have been added by
- # test_rename_attribut :o/
- rinorder = [n for n, in cursor.execute('Any N ORDERBY O WHERE X is CWAttribute, X relation_type RT, RT name N,'
- 'X from_entity FE, FE name "Personne",'
- 'X ordernum O') if n not in ('sexe', 'description', 'civility')]
+ rinorder = [n for n, in cursor.execute(
+ 'Any N ORDERBY O WHERE X is CWAttribute, X relation_type RT, RT name N,'
+ 'X from_entity FE, FE name "Personne",'
+ 'X ordernum O')]
expected = [u'nom', u'prenom', u'promo', u'ass', u'adel', u'titre',
- u'web', u'tel', u'fax', u'datenaiss', u'test', u'firstname',
- u'creation_date', u'modification_date']
+ u'web', u'tel', u'fax', u'datenaiss', u'test', 'description', u'firstname',
+ u'creation_date', 'cwuri', u'modification_date']
self.assertEquals(rinorder, expected)
# test permissions synchronization ####################################
@@ -365,20 +352,19 @@
finally:
self.mh.cmd_set_size_constraint('CWEType', 'description', None)
- def test_add_remove_cube(self):
+ def test_add_remove_cube_and_deps(self):
cubes = set(self.config.cubes())
schema = self.repo.schema
- self.assertEquals(sorted(schema['see_also']._rproperties.keys()),
+ self.assertEquals(sorted((str(s), str(o)) for s, o in schema['see_also']._rproperties.keys()),
sorted([('EmailThread', 'EmailThread'), ('Folder', 'Folder'),
('Bookmark', 'Bookmark'), ('Bookmark', 'Note'),
('Note', 'Note'), ('Note', 'Bookmark')]))
try:
try:
- self.mh.cmd_remove_cube('email')
+ self.mh.cmd_remove_cube('email', removedeps=True)
# file was there because it's an email dependancy, should have been removed
- cubes.remove('email')
- cubes.remove('file')
- self.assertEquals(set(self.config.cubes()), cubes)
+ self.failIf('email' in self.config.cubes())
+ self.failIf('file' in self.config.cubes())
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failIf(ertype in schema, ertype)
@@ -392,17 +378,14 @@
self.assertEquals(sorted(schema['see_also'].objects()), ['Bookmark', 'Folder', 'Note'])
self.assertEquals(self.execute('Any X WHERE X pkey "system.version.email"').rowcount, 0)
self.assertEquals(self.execute('Any X WHERE X pkey "system.version.file"').rowcount, 0)
- self.failIf('email' in self.config.cubes())
- self.failIf('file' in self.config.cubes())
except :
import traceback
traceback.print_exc()
raise
finally:
self.mh.cmd_add_cube('email')
- cubes.add('email')
- cubes.add('file')
- self.assertEquals(set(self.config.cubes()), cubes)
+ self.failUnless('email' in self.config.cubes())
+ self.failUnless('file' in self.config.cubes())
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failUnless(ertype in schema, ertype)
@@ -420,8 +403,6 @@
email_version)
self.assertEquals(self.execute('Any V WHERE X value V, X pkey "system.version.file"')[0][0],
file_version)
- self.failUnless('email' in self.config.cubes())
- self.failUnless('file' in self.config.cubes())
# trick: overwrite self.maxeid to avoid deletion of just reintroduced
# types (and their associated tables!)
self.maxeid = self.execute('Any MAX(X)')[0][0]
@@ -429,6 +410,37 @@
# next test may fail complaining of missing tables
self.commit()
+
+ def test_add_remove_cube_no_deps(self):
+ cubes = set(self.config.cubes())
+ schema = self.repo.schema
+ try:
+ try:
+ self.mh.cmd_remove_cube('email')
+ cubes.remove('email')
+ self.failIf('email' in self.config.cubes())
+ self.failUnless('file' in self.config.cubes())
+ for ertype in ('Email', 'EmailThread', 'EmailPart',
+ 'sender', 'in_thread', 'reply_to'):
+ self.failIf(ertype in schema, ertype)
+ except :
+ import traceback
+ traceback.print_exc()
+ raise
+ finally:
+ self.mh.cmd_add_cube('email')
+ self.failUnless('email' in self.config.cubes())
+ # trick: overwrite self.maxeid to avoid deletion of just reintroduced
+ # types (and their associated tables!)
+ self.maxeid = self.execute('Any MAX(X)')[0][0]
+ # why this commit is necessary is unclear to me (though without it
+ # next test may fail complaining of missing tables
+ self.commit()
+
+ def test_remove_dep_cube(self):
+ ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file')
+ self.assertEquals(str(ex), "can't remove cube file, used as a dependency")
+
def test_set_state(self):
user = self.session.user
self.mh.set_state(user.eid, 'deactivated')
--- a/server/test/unittest_msplanner.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_msplanner.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
from logilab.common.decorators import clear_cache
from cubicweb.devtools import init_test_database
-from cubicweb.devtools.repotest import BasePlannerTC, do_monkey_patch, undo_monkey_patch, test_plan
+from cubicweb.devtools.repotest import BasePlannerTC, test_plan
class _SetGenerator(object):
"""singleton to easily create set using "s[0]" or "s[0,1,2]" for instance
@@ -50,16 +50,12 @@
{'X': 'CWRelation'}, {'X': 'CWPermission'}, {'X': 'CWProperty'},
{'X': 'CWRType'}, {'X': 'CWUser'}, {'X': 'Email'},
{'X': 'EmailAddress'}, {'X': 'EmailPart'}, {'X': 'EmailThread'},
+ {'X': 'ExternalUri'},
{'X': 'File'}, {'X': 'Folder'}, {'X': 'Image'},
{'X': 'Note'}, {'X': 'Personne'}, {'X': 'RQLExpression'},
{'X': 'Societe'}, {'X': 'State'}, {'X': 'SubDivision'},
{'X': 'Tag'}, {'X': 'TrInfo'}, {'X': 'Transition'}])
-def clear_ms_caches(repo):
- clear_cache(repo, 'rel_type_sources')
- clear_cache(repo, 'can_cross_relation')
- clear_cache(repo, 'is_multi_sources_relation')
- # XXX source_defs
# keep cnx so it's not garbage collected and the associated session is closed
repo, cnx = init_test_database('sqlite')
@@ -75,11 +71,7 @@
def setUp(self):
#_QuerierTC.setUp(self)
- clear_cache(repo, 'rel_type_sources')
- self.o = repo.querier
- self.session = repo._sessions.values()[0]
- self.pool = self.session.set_pool()
- self.schema = self.o.schema
+ self.setup()
# hijack Affaire security
affreadperms = list(self.schema['Affaire']._groups['read'])
self.prevrqlexpr_affaire = affreadperms[-1]
@@ -91,26 +83,11 @@
self.prevrqlexpr_user = userreadperms[-1]
userreadperms[-1] = ERQLExpression('X owned_by U')
self.schema['CWUser']._groups['read'] = tuple(userreadperms)
-
- self.sources = self.o._repo.sources
- self.system = self.sources[-1]
- self.sources.append(FakeUserROSource(self.o._repo, self.o.schema,
- {'uri': 'ldapuser'}))
- repo.sources_by_uri['ldapuser'] = self.sources[-1]
- self.ldap = self.sources[-1]
- self.sources.append(FakeCardSource(self.o._repo, self.o.schema,
- {'uri': 'cards'}))
- repo.sources_by_uri['cards'] = self.sources[-1]
- self.rql = self.sources[-1]
- do_monkey_patch()
- clear_ms_caches(repo)
+ self.add_source(FakeUserROSource, 'ldap')
+ self.add_source(FakeCardSource, 'cards')
def tearDown(self):
- undo_monkey_patch()
- del self.sources[-1]
- del self.sources[-1]
- del repo.sources_by_uri['ldapuser']
- del repo.sources_by_uri['cards']
+ super(BaseMSPlannerTC, self).tearDown()
# restore hijacked security
self.restore_orig_affaire_security()
self.restore_orig_euser_security()
@@ -163,7 +140,7 @@
"""retrieve Card X from both sources and return concatenation of results
"""
self._test('Any X, XT WHERE X is Card, X title XT',
- {self.system: {'X': s[0]}, self.rql: {'X': s[0]}}, False)
+ {self.system: {'X': s[0]}, self.cards: {'X': s[0]}}, False)
def test_simple_eid_specified(self):
"""retrieve CWUser X from system source (eid is specified, can locate the entity)
@@ -264,7 +241,7 @@
"""
State S could come from both rql source and system source,
but since X cannot come from the rql source, the solution
- {self.rql : 'S'} must be removed
+ {self.cards : 'S'} must be removed
"""
self._test('Any G,L WHERE X in_group G, X login L, G name "managers", '
'EXISTS(X copain T, T login L, T login in ("comme", "cochon")) OR '
@@ -276,12 +253,12 @@
def test_relation_need_split(self):
self._test('Any X, S WHERE X in_state S',
{self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2]},
- self.rql: {'X': s[2], 'S': s[2]}},
+ self.cards: {'X': s[2], 'S': s[2]}},
True)
def test_not_relation_need_split(self):
self._test('Any SN WHERE NOT X in_state S, S name SN',
- {self.rql: {'X': s[2], 'S': s[0, 1, 2]},
+ {self.cards: {'X': s[2], 'S': s[0, 1, 2]},
self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2]}},
True)
@@ -292,14 +269,14 @@
# linking 9999999 to a state
self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
{'x': 999999},
- {self.rql: {'x': s[0], 'S': s[0]},
+ {self.cards: {'x': s[0], 'S': s[0]},
self.system: {'x': s[0], 'S': s[0]}},
False)
def test_relation_restriction_ambigous_need_split(self):
self._test('Any X,T WHERE X in_state S, S name "pending", T tags X',
{self.system: {'X': s[0, 1, 2], 'S': s[0, 1, 2], 'T': s[0, 1, 2], 'tags': s[0, 1, 2]},
- self.rql: {'X': s[2], 'S': s[2]}},
+ self.cards: {'X': s[2], 'S': s[2]}},
True)
def test_simplified_var(self):
@@ -323,7 +300,7 @@
ueid = self.session.user.eid
self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
{'x': 999999,},
- {self.rql: {'Y': s[0]}, self.system: {'Y': s[0], 'x': s[0]}},
+ {self.cards: {'Y': s[0]}, self.system: {'Y': s[0], 'x': s[0]}},
True)
def test_crossed_relation_eid_1_invariant(self):
@@ -337,7 +314,7 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
{'x': 999999,},
- {self.rql: {'Y': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
+ {self.cards: {'Y': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
self.system: {'Y': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]}},
False)
@@ -345,7 +322,7 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
{'x': 999999},
- {self.rql: {'X': s[0], 'AD': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
+ {self.cards: {'X': s[0], 'AD': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]},
self.system: {'X': s[0], 'AD': s[0], 'multisource_crossed_rel': s[0], 'x': s[0]}},
True)
@@ -353,7 +330,7 @@
repo._type_source_cache[999999] = ('Note', 'system', 999999)
self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
{'x': 999999},
- {self.rql: {'X': s[0], 'AD': s[0]},
+ {self.cards: {'X': s[0], 'AD': s[0]},
self.system: {'X': s[0], 'AD': s[0], 'x': s[0]}},
True)
@@ -362,7 +339,7 @@
repo._type_source_cache[999998] = ('State', 'cards', 999998)
self._test('Any S,T WHERE S eid %(s)s, N eid %(n)s, N type T, N is Note, S is State',
{'n': 999999, 's': 999998},
- {self.rql: {'s': s[0], 'N': s[0]}}, False)
+ {self.cards: {'s': s[0], 'N': s[0]}}, False)
@@ -441,7 +418,7 @@
"""
self._test('Any X, XT WHERE X is Card, X title XT',
[('OneFetchStep', [('Any X,XT WHERE X is Card, X title XT', [{'X': 'Card', 'XT': 'String'}])],
- None, None, [self.rql, self.system], {}, [])])
+ None, None, [self.cards, self.system], {}, [])])
def test_simple_eid_specified(self):
"""retrieve CWUser X from system source (eid is specified, can locate the entity)
@@ -668,7 +645,7 @@
[('AggrStep', 'Any MAX(X)', None, None, 'table0', None,
[('FetchStep',
[('Any MAX(X) WHERE X is Card', [{'X': 'Card'}])],
- [self.rql, self.system], {}, {'MAX(X)': 'table0.C0'}, [])
+ [self.cards, self.system], {}, {'MAX(X)': 'table0.C0'}, [])
])
])
@@ -706,7 +683,7 @@
def test_3sources_ambigous(self):
self._test('Any X,T WHERE X owned_by U, U login "syt", X title T',
[('FetchStep', [('Any X,T WHERE X title T, X is Card', [{'X': 'Card', 'T': 'String'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'X': 'table0.C0', 'X.title': 'table0.C1'}, []),
('FetchStep', [('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
[self.ldap, self.system], None,
@@ -730,11 +707,11 @@
'X is Card, X creation_date < TODAY, X creation_date >= VR)))',
[('FetchStep', [('Any VR WHERE X creation_date < TODAY, X creation_date >= VR, X is Card',
[{'X': 'Card', 'VR': 'Datetime'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'VR': 'table0.C0', 'X.creation_date': 'table0.C0'}, []),
('FetchStep', [('Any V,VR WHERE V creation_date VR, V is Card',
[{'VR': 'Datetime', 'V': 'Card'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'VR': 'table1.C1', 'V': 'table1.C0', 'V.creation_date': 'table1.C1'}, []),
('OneFetchStep', [('Any V,MAX(VR) WHERE V creation_date VR, (V creation_date TODAY) OR (V creation_date < TODAY, NOT EXISTS(X creation_date >= VR, X is Card)), V is Card',
[{'X': 'Card', 'VR': 'Datetime', 'V': 'Card'}])],
@@ -749,7 +726,7 @@
[('OneFetchStep', [('Any X,R WHERE X is Note, X in_state S, X type R, NOT EXISTS(Y is Note, Y in_state S, Y type R, X identity Y), S is State',
[{'Y': 'Note', 'X': 'Note', 'S': 'State', 'R': 'String'}])],
None, None,
- [self.rql, self.system], {}, [])
+ [self.cards, self.system], {}, [])
])
def test_not_identity(self):
@@ -766,7 +743,7 @@
'NOT EXISTS(Y is Note, Y in_state S, Y type R)',
[('FetchStep', [('Any A,R WHERE Y in_state A, Y type R, A is State, Y is Note',
[{'Y': 'Note', 'A': 'State', 'R': 'String'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'A': 'table0.C0', 'R': 'table0.C1', 'Y.type': 'table0.C1'}, []),
('FetchStep', [('Any X,R WHERE X login R, X is CWUser', [{'X': 'CWUser', 'R': 'String'}])],
[self.ldap, self.system], None,
@@ -782,7 +759,7 @@
self.session = self._user_session()[1]
self._test('Any X WHERE X has_text "bla"',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
- [self.rql, self.system], None, {'E': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'E': 'table0.C0'}, []),
('UnionStep', None, None,
[('OneFetchStep',
[(u'Any X WHERE X has_text "bla", (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
@@ -809,7 +786,7 @@
# note: same as the above query but because of the subquery usage, the display differs (not printing solutions for each union)
self._test('Any X LIMIT 10 OFFSET 10 WHERE X has_text "bla"',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
- [self.rql, self.system], None, {'E': 'table1.C0'}, []),
+ [self.cards, self.system], None, {'E': 'table1.C0'}, []),
('UnionFetchStep', [
('FetchStep', [('Any X WHERE X has_text "bla", (EXISTS(X owned_by 5)) OR ((((EXISTS(D concerne C?, C owned_by 5, C type "X", X identity D, C is Division, D is Affaire)) OR (EXISTS(H concerne G?, G owned_by 5, G type "X", X identity H, G is SubDivision, H is Affaire))) OR (EXISTS(I concerne F?, F owned_by 5, F type "X", X identity I, F is Societe, I is Affaire))) OR (EXISTS(J concerne E?, E owned_by 5, X identity J, E is Note, J is Affaire))), X is Affaire',
[{'C': 'Division', 'E': 'Note', 'D': 'Affaire', 'G': 'SubDivision', 'F': 'Societe', 'I': 'Affaire', 'H': 'Affaire', 'J': 'Affaire', 'X': 'Affaire'}])],
@@ -886,7 +863,7 @@
self.session = self._user_session()[1]
self._test('Any MAX(X)',
[('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
- [self.rql, self.system], None, {'E': 'table1.C0'}, []),
+ [self.cards, self.system], None, {'E': 'table1.C0'}, []),
('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table2.C0'}, []),
('UnionFetchStep', [
@@ -895,15 +872,15 @@
('UnionFetchStep',
[('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
[{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])],
- [self.rql, self.system], {}, {'X': 'table0.C0'}, []),
+ [self.cards, self.system], {}, {'X': 'table0.C0'}, []),
('FetchStep',
- [('Any X WHERE X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
+ [('Any X WHERE X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
sorted([{'X': 'Bookmark'}, {'X': 'Comment'}, {'X': 'Division'},
{'X': 'CWCache'}, {'X': 'CWConstraint'}, {'X': 'CWConstraintType'},
{'X': 'CWEType'}, {'X': 'CWAttribute'}, {'X': 'CWGroup'},
{'X': 'CWRelation'}, {'X': 'CWPermission'}, {'X': 'CWProperty'},
{'X': 'CWRType'}, {'X': 'Email'}, {'X': 'EmailAddress'},
- {'X': 'EmailPart'}, {'X': 'EmailThread'}, {'X': 'File'},
+ {'X': 'EmailPart'}, {'X': 'EmailThread'}, {'X': 'ExternalUri'}, {'X': 'File'},
{'X': 'Folder'}, {'X': 'Image'}, {'X': 'Personne'},
{'X': 'RQLExpression'}, {'X': 'Societe'}, {'X': 'SubDivision'},
{'X': 'Tag'}, {'X': 'TrInfo'}, {'X': 'Transition'}]))],
@@ -925,9 +902,9 @@
self._test('Any ET, COUNT(X) GROUPBY ET ORDERBY ET WHERE X is ET',
[('FetchStep', [('Any X WHERE X is IN(Card, Note, State)',
[{'X': 'Card'}, {'X': 'Note'}, {'X': 'State'}])],
- [self.rql, self.system], None, {'X': 'table1.C0'}, []),
+ [self.cards, self.system], None, {'X': 'table1.C0'}, []),
('FetchStep', [('Any E WHERE E type "X", E is Note', [{'E': 'Note'}])],
- [self.rql, self.system], None, {'E': 'table2.C0'}, []),
+ [self.cards, self.system], None, {'E': 'table2.C0'}, []),
('FetchStep', [('Any X WHERE X is CWUser', [{'X': 'CWUser'}])],
[self.ldap, self.system], None, {'X': 'table3.C0'}, []),
('UnionFetchStep',
@@ -946,7 +923,7 @@
[self.system], {'X': 'table3.C0'}, {'ET': 'table0.C0', 'X': 'table0.C1'}, []),
# extra UnionFetchStep could be avoided but has no cost, so don't care
('UnionFetchStep',
- [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
+ [('FetchStep', [('Any ET,X WHERE X is ET, ET is CWEType, X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Image, Personne, RQLExpression, Societe, SubDivision, Tag, TrInfo, Transition)',
[{'X': 'Bookmark', 'ET': 'CWEType'}, {'X': 'Comment', 'ET': 'CWEType'},
{'X': 'Division', 'ET': 'CWEType'}, {'X': 'CWCache', 'ET': 'CWEType'},
{'X': 'CWConstraint', 'ET': 'CWEType'}, {'X': 'CWConstraintType', 'ET': 'CWEType'},
@@ -955,7 +932,9 @@
{'X': 'CWPermission', 'ET': 'CWEType'}, {'X': 'CWProperty', 'ET': 'CWEType'},
{'X': 'CWRType', 'ET': 'CWEType'}, {'X': 'Email', 'ET': 'CWEType'},
{'X': 'EmailAddress', 'ET': 'CWEType'}, {'X': 'EmailPart', 'ET': 'CWEType'},
- {'X': 'EmailThread', 'ET': 'CWEType'}, {'X': 'File', 'ET': 'CWEType'},
+ {'X': 'EmailThread', 'ET': 'CWEType'},
+ {'ET': 'CWEType', 'X': 'ExternalUri'},
+ {'X': 'File', 'ET': 'CWEType'},
{'X': 'Folder', 'ET': 'CWEType'}, {'X': 'Image', 'ET': 'CWEType'},
{'X': 'Personne', 'ET': 'CWEType'}, {'X': 'RQLExpression', 'ET': 'CWEType'},
{'X': 'Societe', 'ET': 'CWEType'}, {'X': 'SubDivision', 'ET': 'CWEType'},
@@ -982,7 +961,9 @@
{'ET': 'CWEType', 'X': 'CWProperty'}, {'ET': 'CWEType', 'X': 'CWRType'},
{'ET': 'CWEType', 'X': 'CWUser'}, {'ET': 'CWEType', 'X': 'Email'},
{'ET': 'CWEType', 'X': 'EmailAddress'}, {'ET': 'CWEType', 'X': 'EmailPart'},
- {'ET': 'CWEType', 'X': 'EmailThread'}, {'ET': 'CWEType', 'X': 'File'},
+ {'ET': 'CWEType', 'X': 'EmailThread'},
+ {'ET': 'CWEType', 'X': 'ExternalUri'},
+ {'ET': 'CWEType', 'X': 'File'},
{'ET': 'CWEType', 'X': 'Folder'}, {'ET': 'CWEType', 'X': 'Image'},
{'ET': 'CWEType', 'X': 'Note'}, {'ET': 'CWEType', 'X': 'Personne'},
{'ET': 'CWEType', 'X': 'RQLExpression'}, {'ET': 'CWEType', 'X': 'Societe'},
@@ -998,7 +979,7 @@
self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
('FetchStep',
[('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
[self.ldap, self.system], None, {'U': 'table1.C0'}, []),
@@ -1016,7 +997,7 @@
self._test('Any X, XT WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
('OneFetchStep',
[('Any X,XT WHERE X owned_by U, X title XT, U login "syt", EXISTS(U identity 5), U is CWUser, X is Card',
[{'U': 'CWUser', 'X': 'Card', 'XT': 'String'}])],
@@ -1034,7 +1015,7 @@
[self.system], {}, {'L': 'table0.C1', 'U': 'table0.C0', 'U.login': 'table0.C1'}, []),
('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
- [self.rql, self.system], None, {'X': 'table1.C0', 'X.title': 'table1.C1', 'XT': 'table1.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table1.C0', 'X.title': 'table1.C1', 'XT': 'table1.C1'}, []),
('OneFetchStep',
[('Any X,XT,U WHERE X owned_by U?, X title XT, X is Card',
[{'X': 'Card', 'XT': 'String'}])],
@@ -1051,7 +1032,7 @@
self._test('Any X, XT LIMIT 10 OFFSET 10 WHERE X is Card, X owned_by U, X title XT, U login "syt"',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'}, []),
('FetchStep',
[('Any U WHERE U login "syt", U is CWUser', [{'U': 'CWUser'}])],
[self.ldap, self.system], None, {'U': 'table1.C0'}, []),
@@ -1170,14 +1151,14 @@
None, None, [self.system], {}, []),
('OneFetchStep', [('Any X,S WHERE X in_state S, S is State, X is Note',
[{'X': 'Note', 'S': 'State'}])],
- None, None, [self.rql, self.system], {}, []),
+ None, None, [self.cards, self.system], {}, []),
])])
def test_relation_selection_need_split(self):
self._test('Any X,S,U WHERE X in_state S, X todo_by U',
[('FetchStep', [('Any X,S WHERE X in_state S, S is State, X is Note',
[{'X': 'Note', 'S': 'State'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'S': 'table0.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0', 'S': 'table0.C1'}, []),
('UnionStep', None, None,
[('OneFetchStep', [('Any X,S,U WHERE X in_state S, X todo_by U, S is State, U is CWUser, X is Note',
[{'X': 'Note', 'S': 'State', 'U': 'CWUser'}])],
@@ -1192,7 +1173,7 @@
self._test('Any X,U WHERE X in_state S, S name "pending", X todo_by U',
[('FetchStep', [('Any X WHERE X in_state S, S name "pending", S is State, X is Note',
[{'X': 'Note', 'S': 'State'}])],
- [self.rql, self.system], None, {'X': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0'}, []),
('UnionStep', None, None,
[('OneFetchStep', [('Any X,U WHERE X todo_by U, U is CWUser, X is Note',
[{'X': 'Note', 'U': 'CWUser'}])],
@@ -1207,7 +1188,7 @@
self._test('Any X,T WHERE X in_state S, S name "pending", T tags X',
[('FetchStep', [('Any X WHERE X in_state S, S name "pending", S is State, X is Note',
[{'X': 'Note', 'S': 'State'}])],
- [self.rql, self.system], None, {'X': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0'}, []),
('UnionStep', None, None, [
('OneFetchStep', [('Any X,T WHERE T tags X, T is Tag, X is Note',
[{'X': 'Note', 'T': 'Tag'}])],
@@ -1231,7 +1212,7 @@
self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
[('OneFetchStep', [('Any SN WHERE NOT 5 in_state S, S name SN, S is State',
[{'S': 'State', 'SN': 'String'}])],
- None, None, [self.rql, self.system], {}, [])],
+ None, None, [self.cards, self.system], {}, [])],
{'x': ueid})
def test_not_relation_no_split_external(self):
@@ -1242,20 +1223,20 @@
self._test('Any SN WHERE NOT X in_state S, X eid %(x)s, S name SN',
[('OneFetchStep', [('Any SN WHERE NOT 999999 in_state S, S name SN, S is State',
[{'S': 'State', 'SN': 'String'}])],
- None, None, [self.rql, self.system], {}, [])],
+ None, None, [self.cards, self.system], {}, [])],
{'x': 999999})
def test_not_relation_need_split(self):
self._test('Any SN WHERE NOT X in_state S, S name SN',
[('FetchStep', [('Any SN,S WHERE S name SN, S is State',
[{'S': 'State', 'SN': 'String'}])],
- [self.rql, self.system], None, {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
+ [self.cards, self.system], None, {'S': 'table0.C1', 'S.name': 'table0.C0', 'SN': 'table0.C0'},
[]),
('IntersectStep', None, None,
[('OneFetchStep',
[('Any SN WHERE NOT X in_state S, S name SN, S is State, X is Note',
[{'S': 'State', 'SN': 'String', 'X': 'Note'}])],
- None, None, [self.rql, self.system], {},
+ None, None, [self.cards, self.system], {},
[]),
('OneFetchStep',
[('Any SN WHERE NOT X in_state S, S name SN, S is State, X is IN(Affaire, CWUser)',
@@ -1270,7 +1251,7 @@
self._test('Any A,B,C,D WHERE A eid %(x)s,A creation_date B,A modification_date C, A todo_by D?',
[('FetchStep', [('Any A,B,C WHERE A eid 999999, A creation_date B, A modification_date C, A is Note',
[{'A': 'Note', 'C': 'Datetime', 'B': 'Datetime'}])],
- [self.rql], None,
+ [self.cards], None,
{'A': 'table0.C0', 'A.creation_date': 'table0.C1', 'A.modification_date': 'table0.C2', 'C': 'table0.C2', 'B': 'table0.C1'}, []),
#('FetchStep', [('Any D WHERE D is CWUser', [{'D': 'CWUser'}])],
# [self.ldap, self.system], None, {'D': 'table1.C0'}, []),
@@ -1299,7 +1280,7 @@
self._test('Any X WHERE X has_text "toto", X title "zoubidou"',
[('FetchStep', [(u'Any X WHERE X title "zoubidou", X is Card',
[{'X': 'Card'}])],
- [self.rql, self.system], None, {'X': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0'}, []),
('UnionStep', None, None, [
('OneFetchStep', [(u'Any X WHERE X has_text "toto", X is Card',
[{'X': 'Card'}])],
@@ -1315,7 +1296,7 @@
[('AggrStep', 'Any X ORDERBY DUMB_SORT(RF)', None, None, 'table0', None, [
('FetchStep', [('Any X,RF WHERE X type RF, X is Note',
[{'X': 'Note', 'RF': 'String'}])],
- [self.rql, self.system], {}, {'X': 'table0.C0', 'X.type': 'table0.C1', 'RF': 'table0.C1'}, []),
+ [self.cards, self.system], {}, {'X': 'table0.C0', 'X.type': 'table0.C1', 'RF': 'table0.C1'}, []),
])
])
@@ -1325,7 +1306,7 @@
None, None, 'table0', None,
[('FetchStep', [('Any X,RF WHERE X title RF, X is Card',
[{'X': 'Card', 'RF': 'String'}])],
- [self.rql, self.system], {},
+ [self.cards, self.system], {},
{'X': 'table0.C0', 'X.title': 'table0.C1', 'RF': 'table0.C1'}, []),
('FetchStep', [('Any X,RF WHERE X title RF, X is IN(Bookmark, EmailThread)',
[{'RF': 'String', 'X': 'Bookmark'},
@@ -1339,7 +1320,7 @@
self._test('Any X,Y WHERE X is Bookmark, Y is Card, X title T, Y title T',
[('FetchStep',
[('Any Y,T WHERE Y title T, Y is Card', [{'T': 'String', 'Y': 'Card'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'Y': 'table0.C0', 'Y.title': 'table0.C1'}, []),
('OneFetchStep',
[('Any X,Y WHERE X title T, Y title T, X is Bookmark, Y is Card',
@@ -1352,11 +1333,11 @@
self._test('Any X,Y WHERE X is Note, Y is Card, X type T, Y title T',
[('FetchStep',
[('Any X,T WHERE X type T, X is Note', [{'T': 'String', 'X': 'Note'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'X': 'table0.C0', 'X.type': 'table0.C1'}, []),
('FetchStep',
[('Any Y,T WHERE Y title T, Y is Card', [{'T': 'String', 'Y': 'Card'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table1.C1', 'Y': 'table1.C0', 'Y.title': 'table1.C1'}, []),
('OneFetchStep',
[('Any X,Y WHERE X type T, Y title T, X is Note, Y is Card',
@@ -1372,7 +1353,7 @@
[('FetchStep',
[('Any Y,D WHERE Y creation_date > D, Y is Card',
[{'D': 'Datetime', 'Y': 'Card'}])],
- [self.rql,self.system], None,
+ [self.cards,self.system], None,
{'D': 'table0.C1', 'Y': 'table0.C0', 'Y.creation_date': 'table0.C1'}, []),
('OneFetchStep',
[('Any X,Y WHERE X creation_date D, Y creation_date > D, X is Bookmark, Y is Card',
@@ -1415,7 +1396,7 @@
'X.title': 'table0.C1'}, []),
('FetchStep', [('Any X,T WHERE X is Card, X title T',
[{'T': 'String', 'X': 'Card'}])],
- [self.rql, self.system], {},
+ [self.cards, self.system], {},
{'N': 'table0.C1',
'T': 'table0.C1',
'X': 'table0.C0',
@@ -1459,7 +1440,7 @@
repo._type_source_cache[999999] = ('Note', 'system', 999999)
self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
[('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'Y': 'table0.C0', 'Y.type': 'table0.C1'}, []),
('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
[{'T': 'String', 'Y': 'Note'}])],
@@ -1472,7 +1453,7 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any Y WHERE X eid %(x)s, X multisource_crossed_rel Y',
[('OneFetchStep', [('Any Y WHERE 999999 multisource_crossed_rel Y, Y is Note', [{'Y': 'Note'}])],
- None, None, [self.rql, self.system], {}, [])
+ None, None, [self.cards, self.system], {}, [])
],
{'x': 999999,})
@@ -1480,12 +1461,12 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any Y,T WHERE X eid %(x)s, X multisource_crossed_rel Y, Y type T',
[('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'Y': 'table0.C0', 'Y.type': 'table0.C1'}, []),
('UnionStep', None, None,
[('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
[{'T': 'String', 'Y': 'Note'}])],
- None, None, [self.rql], None,
+ None, None, [self.cards], None,
[]),
('OneFetchStep', [('Any Y,T WHERE 999999 multisource_crossed_rel Y, Y type T, Y is Note',
[{'T': 'String', 'Y': 'Note'}])],
@@ -1499,7 +1480,7 @@
repo._type_source_cache[999999] = ('Note', 'system', 999999)
self._test('Any Y WHERE X eid %(x)s, NOT X multisource_crossed_rel Y',
[('FetchStep', [('Any Y WHERE Y is Note', [{'Y': 'Note'}])],
- [self.rql, self.system], None, {'Y': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'Y': 'table0.C0'}, []),
('OneFetchStep', [('Any Y WHERE NOT 999999 multisource_crossed_rel Y, Y is Note',
[{'Y': 'Note'}])],
None, None, [self.system],
@@ -1516,15 +1497,15 @@
repo._type_source_cache[999999] = ('Note', 'system', 999999)
self._test('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T',
[('FetchStep', [('Any X,T WHERE X type T, X is Note', [{'T': 'String', 'X': 'Note'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table0.C1', 'X': 'table0.C0', 'X.type': 'table0.C1'}, []),
('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])],
- [self.rql, self.system], None,
+ [self.cards, self.system], None,
{'T': 'table1.C1', 'Y': 'table1.C0', 'Y.type': 'table1.C1'}, []),
('UnionStep', None, None,
[('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
[{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
- None, None, [self.rql], None,
+ None, None, [self.cards], None,
[]),
('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note',
[{'T': 'String', 'X': 'Note', 'Y': 'Note'}])],
@@ -1543,7 +1524,7 @@
self._test('INSERT Note X: X in_state S, X type T WHERE S eid %(s)s, N eid %(n)s, N type T',
[('FetchStep', [('Any T WHERE N eid 999999, N type T, N is Note',
[{'N': 'Note', 'T': 'String'}])],
- [self.rql], None, {'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
+ [self.cards], None, {'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
('InsertStep',
[('RelationsStep',
[('OneFetchStep', [('Any 999998,T WHERE N type T, N is Note',
@@ -1560,7 +1541,7 @@
self._test('INSERT Note X: X in_state S, X type T, X migrated_from N WHERE S eid %(s)s, N eid %(n)s, N type T',
[('FetchStep', [('Any T,N WHERE N eid 999999, N type T, N is Note',
[{'N': 'Note', 'T': 'String'}])],
- [self.rql], None, {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
+ [self.cards], None, {'N': 'table0.C1', 'N.type': 'table0.C0', 'T': 'table0.C0'}, []),
('InsertStep',
[('RelationsStep',
[('OneFetchStep', [('Any 999998,T,N WHERE N type T, N is Note',
@@ -1580,7 +1561,7 @@
[('RelationsStep',
[('OneFetchStep', [('Any 999998,T WHERE N eid 999999, N type T, N is Note',
[{'N': 'Note', 'T': 'String'}])],
- None, None, [self.rql], {}, [])]
+ None, None, [self.cards], {}, [])]
)]
)],
{'n': 999999, 's': 999998})
@@ -1730,7 +1711,7 @@
self._test('Any X ORDERBY D DESC WHERE E eid %(x)s, E wf_info_for X, X modification_date D',
[('FetchStep', [('Any X,D WHERE X modification_date D, X is Note',
[{'X': 'Note', 'D': 'Datetime'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'D': 'table0.C1'}, []),
+ [self.cards, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'D': 'table0.C1'}, []),
('FetchStep', [('Any X,D WHERE X modification_date D, X is CWUser',
[{'X': 'CWUser', 'D': 'Datetime'}])],
[self.ldap, self.system], None, {'X': 'table1.C0', 'X.modification_date': 'table1.C1', 'D': 'table1.C1'}, []),
@@ -1786,7 +1767,7 @@
'MB depends_on B, B documented_by V, V multisource_rel P, NOT P eid %(p)s',
[('FetchStep', [('Any V WHERE V multisource_rel P, NOT P eid %s, P is Note, V is Card'%noteeid,
[{'P': 'Note', 'V': 'Card'}])],
- [self.rql, self.system], None, {'V': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'V': 'table0.C0'}, []),
('OneFetchStep', [('DISTINCT Any V WHERE MB documented_by %s, MB depends_on B, B documented_by V, B is Affaire, MB is Affaire, V is Card'%cardeid,
[{'B': 'Affaire', 'MB': 'Affaire', 'V': 'Card'}])],
None, None, [self.system], {'V': 'table0.C0'}, [])],
@@ -1804,7 +1785,7 @@
])
self._test('Any X WHERE X concerne Y, Y is Note',
[('FetchStep', [('Any Y WHERE Y is Note', [{'Y': 'Note'}])],
- [self.rql, self.system], None, {'Y': 'table0.C0'}, []),
+ [self.cards, self.system], None, {'Y': 'table0.C0'}, []),
('OneFetchStep', [('Any X WHERE X concerne Y, X is Affaire, Y is Note',
[{'X': 'Affaire', 'Y': 'Note'}])],
None, None, [self.system], {'Y': 'table0.C0'}, [])
@@ -1814,7 +1795,7 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any S,SUM(DUR),SUM(I),(SUM(I) - SUM(DUR)),MIN(DI),MAX(DI) GROUPBY S ORDERBY S WHERE A is Affaire, A duration DUR, A invoiced I, A modification_date DI, A in_state S, S name SN, (EXISTS(A concerne WP, W multisource_rel WP)) OR (EXISTS(A concerne W)), W eid %(n)s',
[('FetchStep', [('Any WP WHERE 999999 multisource_rel WP, WP is Note', [{'WP': 'Note'}])],
- [self.rql], None, {'WP': u'table0.C0'}, []),
+ [self.cards], None, {'WP': u'table0.C0'}, []),
('OneFetchStep', [('Any S,SUM(DUR),SUM(I),(SUM(I) - SUM(DUR)),MIN(DI),MAX(DI) GROUPBY S ORDERBY S WHERE A duration DUR, A invoiced I, A modification_date DI, A in_state S, S name SN, (EXISTS(A concerne WP, WP is Note)) OR (EXISTS(A concerne 999999)), A is Affaire, S is State',
[{'A': 'Affaire', 'DI': 'Datetime', 'DUR': 'Int', 'I': 'Int', 'S': 'State', 'SN': 'String', 'WP': 'Note'}])],
None, None, [self.system], {'WP': u'table0.C0'}, [])],
@@ -1824,7 +1805,7 @@
repo._type_source_cache[999999] = ('Note', 'cards', 999999)
self._test('Any X,Z WHERE X eid %(x)s, X multisource_rel Y, Z concerne X',
[('FetchStep', [('Any WHERE 999999 multisource_rel Y, Y is Note', [{'Y': 'Note'}])],
- [self.rql], None, {}, []),
+ [self.cards], None, {}, []),
('OneFetchStep', [('Any 999999,Z WHERE Z concerne 999999, Z is Affaire',
[{'Z': 'Affaire'}])],
None, None, [self.system], {}, [])],
@@ -1835,7 +1816,7 @@
repo._type_source_cache[999998] = ('Note', 'cards', 999998)
self._test('SET X migrated_from Y WHERE X eid %(x)s, Y multisource_rel Z, Z eid %(z)s, Y migrated_from Z',
[('FetchStep', [('Any Y WHERE Y multisource_rel 999998, Y is Note', [{'Y': 'Note'}])],
- [self.rql], None, {'Y': u'table0.C0'}, []),
+ [self.cards], None, {'Y': u'table0.C0'}, []),
('UpdateStep',
[('OneFetchStep', [('DISTINCT Any 999999,Y WHERE Y migrated_from 999998, Y is Note',
[{'Y': 'Note'}])],
@@ -1844,7 +1825,7 @@
{'x': 999999, 'z': 999998})
def test_nonregr10(self):
- repo._type_source_cache[999999] = ('CWUser', 'ldapuser', 999999)
+ repo._type_source_cache[999999] = ('CWUser', 'ldap', 999999)
self._test('Any X,AA,AB ORDERBY AA WHERE E eid %(x)s, E owned_by X, X login AA, X modification_date AB',
[('FetchStep',
[('Any X,AA,AB WHERE X login AA, X modification_date AB, X is CWUser',
@@ -1880,7 +1861,7 @@
self._test('Any X ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E see_also X',
[('FetchStep', [('Any X,Z WHERE X modification_date Z, X is Note',
[{'X': 'Note', 'Z': 'Datetime'}])],
- [self.rql, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'Z': 'table0.C1'},
+ [self.cards, self.system], None, {'X': 'table0.C0', 'X.modification_date': 'table0.C1', 'Z': 'table0.C1'},
[]),
('AggrStep', 'Any X ORDERBY Z DESC',
None, None, 'table1', None,
@@ -1958,6 +1939,29 @@
[])],
{'x': self.session.user.eid})
+ def test_nonregr14_1(self):
+ repo._type_source_cache[999999] = ('CWUser', 'ldap', 999999)
+ self._test('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
+ [('OneFetchStep', [('Any 999999 WHERE 999999 owned_by 999999', [{}])],
+ None, None, [self.system], {}, [])],
+ {'x': 999999, 'u': 999999})
+
+ def test_nonregr14_2(self):
+ repo._type_source_cache[999999] = ('CWUser', 'ldap', 999999)
+ repo._type_source_cache[999998] = ('Note', 'system', 999998)
+ self._test('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
+ [('OneFetchStep', [('Any 999998 WHERE 999998 owned_by 999999', [{}])],
+ None, None, [self.system], {}, [])],
+ {'x': 999998, 'u': 999999})
+
+ def test_nonregr14_3(self):
+ repo._type_source_cache[999999] = ('CWUser', 'system', 999999)
+ repo._type_source_cache[999998] = ('CWUser', 'ldap', 999998)
+ self._test('Any X WHERE X eid %(x)s, X owned_by U, U eid %(u)s',
+ [('OneFetchStep', [('Any 999998 WHERE 999998 owned_by 999999', [{}])],
+ None, None, [self.system], {}, [])],
+ {'x': 999998, 'u': 999999})
+
class MSPlannerTwoSameExternalSourcesTC(BasePlannerTC):
"""test planner related feature on a 3-sources repository:
@@ -1967,42 +1971,23 @@
repo = repo
def setUp(self):
- self.o = repo.querier
- self.session = repo._sessions.values()[0]
- self.pool = self.session.set_pool()
- self.schema = self.o.schema
- self.sources = self.o._repo.sources
- self.system = self.sources[-1]
- self.sources.append(FakeCardSource(self.o._repo, self.o.schema,
- {'uri': 'cards'}))
- repo.sources_by_uri['cards'] = self.sources[-1]
- self.rql = self.sources[-1]
- self.sources.append(FakeCardSource(self.o._repo, self.o.schema,
- {'uri': 'cards2'}))
- repo.sources_by_uri['cards2'] = self.sources[-1]
- self.rql2 = self.sources[-1]
- do_monkey_patch()
+ self.setup()
+ self.add_source(FakeCardSource, 'cards')
+ self.add_source(FakeCardSource, 'cards2')
self.planner = MSPlanner(self.o.schema, self.o._rqlhelper)
assert repo.sources_by_uri['cards2'].support_relation('multisource_crossed_rel')
assert 'multisource_crossed_rel' in repo.sources_by_uri['cards2'].cross_relations
assert repo.sources_by_uri['cards'].support_relation('multisource_crossed_rel')
assert 'multisource_crossed_rel' in repo.sources_by_uri['cards'].cross_relations
- clear_ms_caches(repo)
_test = test_plan
- def tearDown(self):
- undo_monkey_patch()
- del self.sources[-1]
- del self.sources[-1]
- del repo.sources_by_uri['cards']
- del repo.sources_by_uri['cards2']
def test_linked_external_entities(self):
repo._type_source_cache[999999] = ('Tag', 'system', 999999)
self._test('Any X,XT WHERE X is Card, X title XT, T tags X, T eid %(t)s',
[('FetchStep',
[('Any X,XT WHERE X title XT, X is Card', [{'X': 'Card', 'XT': 'String'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'X': 'table0.C0', 'X.title': 'table0.C1', 'XT': 'table0.C1'},
[]),
('OneFetchStep',
@@ -2018,7 +2003,7 @@
self._test('Any X,AD,AE WHERE E eid %(x)s, E migrated_from X, X in_state AD, AD name AE',
[('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
'AE': 'table0.C2', 'X': 'table0.C0'},
[]),
@@ -2034,14 +2019,14 @@
self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
[('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
'AE': 'table0.C2', 'X': 'table0.C0'},
[]),
('UnionStep', None, None,
[('OneFetchStep', [('Any X,AD,AE WHERE 999999 multisource_crossed_rel X, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
- None, None, [self.rql], None,
+ None, None, [self.cards], None,
[]),
('OneFetchStep', [('Any X,AD,AE WHERE 999999 multisource_crossed_rel X, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
@@ -2057,7 +2042,7 @@
self._test('Any X,AD,AE WHERE E eid %(x)s, E multisource_crossed_rel X, X in_state AD, AD name AE',
[('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
'AE': 'table0.C2', 'X': 'table0.C0'},
[]),
@@ -2072,18 +2057,18 @@
self._test('Any X,AD,AE WHERE E multisource_crossed_rel X, X in_state AD, AD name AE, E is Note',
[('FetchStep', [('Any X,AD,AE WHERE X in_state AD, AD name AE, AD is State, X is Note',
[{'AD': 'State', 'AE': 'String', 'X': 'Note'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'AD': 'table0.C1', 'AD.name': 'table0.C2',
'AE': 'table0.C2', 'X': 'table0.C0'},
[]),
('FetchStep', [('Any E WHERE E is Note', [{'E': 'Note'}])],
- [self.rql, self.rql2, self.system],
+ [self.cards, self.cards2, self.system],
None, {'E': 'table1.C0'},
[]),
('UnionStep', None, None,
[('OneFetchStep', [('Any X,AD,AE WHERE E multisource_crossed_rel X, AD name AE, AD is State, E is Note, X is Note',
[{'AD': 'State', 'AE': 'String', 'E': 'Note', 'X': 'Note'}])],
- None, None, [self.rql, self.rql2], None,
+ None, None, [self.cards, self.cards2], None,
[]),
('OneFetchStep', [('Any X,AD,AE WHERE E multisource_crossed_rel X, AD name AE, AD is State, E is Note, X is Note',
[{'AD': 'State', 'AE': 'String', 'E': 'Note', 'X': 'Note'}])],
@@ -2102,7 +2087,7 @@
self._test('Any S WHERE E eid %(x)s, E in_state S, NOT S name "moved"',
[('OneFetchStep', [('Any S WHERE 999999 in_state S, NOT S name "moved", S is State',
[{'S': 'State'}])],
- None, None, [self.rql], {}, []
+ None, None, [self.cards], {}, []
)],
{'x': 999999})
@@ -2111,11 +2096,74 @@
self._test('Any X,AA,AB WHERE E eid %(x)s, E in_state X, X name AA, X modification_date AB',
[('OneFetchStep', [('Any X,AA,AB WHERE 999999 in_state X, X name AA, X modification_date AB, X is State',
[{'AA': 'String', 'AB': 'Datetime', 'X': 'State'}])],
- None, None, [self.rql], {}, []
+ None, None, [self.cards], {}, []
+ )],
+ {'x': 999999})
+
+ def test_nonregr_eid_query(self):
+ self.repo._type_source_cache[999999] = ('Note', 'cards', 999999)
+ self._test('Any X WHERE X eid 999999',
+ [('OneFetchStep', [('Any 999999', [{}])],
+ None, None, [self.system], {}, []
)],
{'x': 999999})
+
+class FakeVCSSource(AbstractSource):
+ uri = 'ccc'
+ support_entities = {'Card': True, 'Note': True}
+ support_relations = {'multisource_inlined_rel': True,
+ 'multisource_rel': True}
+ #dont_cross_relations = set(('fiche', 'in_state'))
+ #cross_relations = set(('multisource_crossed_rel',))
+
+ def syntax_tree_search(self, *args, **kwargs):
+ return []
+
+class MSPlannerVCSSource(BasePlannerTC):
+ repo = repo
+
+ def setUp(self):
+ self.setup()
+ self.add_source(FakeVCSSource, 'vcs')
+ self.planner = MSPlanner(self.o.schema, self.o._rqlhelper)
+ _test = test_plan
+
+ def test_multisource_inlined_rel_skipped(self):
+ self._test('Any MAX(VC) '
+ 'WHERE VC multisource_inlined_rel R2, R para %(branch)s, VC in_state S, S name "published", '
+ '(EXISTS(R identity R2)) OR (EXISTS(R multisource_rel R2))',
+ [('FetchStep', [('Any VC WHERE VC multisource_inlined_rel R2, R para "???", (EXISTS(R identity R2)) OR (EXISTS(R multisource_rel R2)), R is Note, R2 is Note, VC is Note',
+ [{'R': 'Note', 'R2': 'Note', 'VC': 'Note'}])],
+ [self.vcs, self.system], None,
+ {'VC': 'table0.C0'},
+ []),
+ ('OneFetchStep', [(u'Any MAX(VC) WHERE VC in_state S, S name "published", S is State, VC is Note',
+ [{'S': 'State', 'VC': 'Note'}])],
+ None, None, [self.system],
+ {'VC': 'table0.C0'},
+ [])
+ ])
+
+ def test_fully_simplified_extsource(self):
+ self.repo._type_source_cache[999998] = ('Note', 'vcs', 999998)
+ self.repo._type_source_cache[999999] = ('Note', 'vcs', 999999)
+ self._test('Any X, Y WHERE NOT X multisource_rel Y, X eid 999998, Y eid 999999',
+ [('OneFetchStep', [('Any 999998,999999 WHERE NOT 999998 multisource_rel 999999', [{}])],
+ None, None, [self.vcs], {}, [])
+ ])
+
+ def test_nonregr_fully_simplified_extsource(self):
+ self.repo._type_source_cache[999998] = ('Note', 'vcs', 999998)
+ self.repo._type_source_cache[999999] = ('Note', 'vcs', 999999)
+ self.repo._type_source_cache[1000000] = ('Note', 'system', 1000000)
+ self._test('DISTINCT Any T,FALSE,L,M WHERE L eid 1000000, M eid 999999, T eid 999998',
+ [('OneFetchStep', [('DISTINCT Any 999998,FALSE,1000000,999999', [{}])],
+ None, None, [self.system], {}, [])
+ ])
+
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
--- a/server/test/unittest_multisources.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_multisources.py Tue Aug 04 15:06:09 2009 +0200
@@ -14,6 +14,8 @@
from cubicweb.devtools.apptest import RepositoryBasedTC
from cubicweb.devtools.repotest import do_monkey_patch, undo_monkey_patch
+TestServerConfiguration.no_sqlite_wrap = True
+
class TwoSourcesConfiguration(TestServerConfiguration):
sourcefile = 'sources_multi'
@@ -114,6 +116,7 @@
cu = cnx.cursor()
rset = cu.execute('Any X WHERE X has_text "card"')
self.assertEquals(len(rset), 5, zip(rset.rows, rset.description))
+ cnx.close()
def test_synchronization(self):
cu = cnx2.cursor()
--- a/server/test/unittest_querier.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_querier.py Tue Aug 04 15:06:09 2009 +0200
@@ -110,7 +110,7 @@
'ET': 'CWEType', 'ETN': 'String'}])
rql, solutions = partrqls[1]
self.assertEquals(rql, 'Any ETN,X WHERE X is ET, ET name ETN, ET is CWEType, '
- 'X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)')
+ 'X is IN(Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)')
self.assertListEquals(sorted(solutions),
sorted([{'X': 'Bookmark', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Card', 'ETN': 'String', 'ET': 'CWEType'},
@@ -131,6 +131,7 @@
{'X': 'CWProperty', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWRType', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'CWUser', 'ETN': 'String', 'ET': 'CWEType'},
+ {'X': 'ExternalUri', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'File', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Folder', 'ETN': 'String', 'ET': 'CWEType'},
{'X': 'Image', 'ETN': 'String', 'ET': 'CWEType'},
@@ -226,10 +227,10 @@
def test_select_2(self):
rset = self.execute('Any X ORDERBY N WHERE X is CWGroup, X name N')
- self.assertEquals(tuplify(rset.rows), [(3,), (1,), (4,), (2,)])
+ self.assertEquals(tuplify(rset.rows), [(1,), (2,), (3,), (4,)])
self.assertEquals(rset.description, [('CWGroup',), ('CWGroup',), ('CWGroup',), ('CWGroup',)])
rset = self.execute('Any X ORDERBY N DESC WHERE X is CWGroup, X name N')
- self.assertEquals(tuplify(rset.rows), [(2,), (4,), (1,), (3,)])
+ self.assertEquals(tuplify(rset.rows), [(4,), (3,), (2,), (1,)])
def test_select_3(self):
rset = self.execute('Any N GROUPBY N WHERE X is CWGroup, X name N')
@@ -272,7 +273,7 @@
def test_select_5(self):
rset = self.execute('Any X, TMP ORDERBY TMP WHERE X name TMP, X is CWGroup')
- self.assertEquals(tuplify(rset.rows), [(3, 'guests',), (1, 'managers',), (4, 'owners',), (2, 'users',)])
+ self.assertEquals(tuplify(rset.rows), [(1, 'guests',), (2, 'managers',), (3, 'owners',), (4, 'users',)])
self.assertEquals(rset.description, [('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',), ('CWGroup', 'String',)])
def test_select_6(self):
@@ -344,7 +345,8 @@
peid1 = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
rset = self.execute('Any X WHERE X eid %(x)s, P? connait X', {'x':peid1}, 'x')
self.assertEquals(rset.rows, [[peid1]])
- rset = self.execute('Any X WHERE X eid %(x)s, X require_permission P?', {'x':peid1}, 'x')
+ rset = self.execute('Any X WHERE X eid %(x)s, X require_permission P?',
+ {'x':peid1}, 'x')
self.assertEquals(rset.rows, [[peid1]])
def test_select_left_outer_join(self):
@@ -464,10 +466,12 @@
'WHERE RT name N, RDEF relation_type RT '
'HAVING COUNT(RDEF) > 10')
self.assertListEquals(rset.rows,
- [[u'description', 11], ['in_basket', 11],
- [u'name', 13], [u'created_by', 33],
- [u'creation_date', 33], [u'is', 33], [u'is_instance_of', 33],
- [u'modification_date', 33], [u'owned_by', 33]])
+ [[u'description', 11],
+ [u'name', 13], [u'created_by', 34],
+ [u'creation_date', 34], [u'cwuri', 34],
+ ['in_basket', 34],
+ [u'is', 34], [u'is_instance_of', 34],
+ [u'modification_date', 34], [u'owned_by', 34]])
def test_select_aggregat_having_dumb(self):
# dumb but should not raise an error
@@ -553,10 +557,10 @@
def test_select_limit_offset(self):
rset = self.execute('CWGroup X ORDERBY N LIMIT 2 WHERE X name N')
- self.assertEquals(tuplify(rset.rows), [(3,), (1,)])
+ self.assertEquals(tuplify(rset.rows), [(1,), (2,)])
self.assertEquals(rset.description, [('CWGroup',), ('CWGroup',)])
rset = self.execute('CWGroup X ORDERBY N LIMIT 2 OFFSET 2 WHERE X name N')
- self.assertEquals(tuplify(rset.rows), [(4,), (2,)])
+ self.assertEquals(tuplify(rset.rows), [(3,), (4,)])
def test_select_symetric(self):
self.execute("INSERT Personne X: X nom 'machin'")
--- a/server/test/unittest_repository.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_repository.py Tue Aug 04 15:06:09 2009 +0200
@@ -56,13 +56,12 @@
namecol = SQL_PREFIX + 'name'
finalcol = SQL_PREFIX + 'final'
try:
- sqlcursor = pool['system']
- sqlcursor.execute('SELECT %s FROM %s WHERE %s is NULL' % (
+ cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % (
namecol, table, finalcol))
- self.assertEquals(sqlcursor.fetchall(), [])
- sqlcursor.execute('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
+ self.assertEquals(cu.fetchall(), [])
+ cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s'
% (namecol, table, finalcol, namecol), {'final': 'TRUE'})
- self.assertEquals(sqlcursor.fetchall(), [(u'Boolean',), (u'Bytes',),
+ self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',),
(u'Date',), (u'Datetime',),
(u'Decimal',),(u'Float',),
(u'Int',),
@@ -227,12 +226,12 @@
# check order of attributes is respected
self.assertListEquals([r.type for r in schema.eschema('CWAttribute').ordered_relations()
if not r.type in ('eid', 'is', 'is_instance_of', 'identity',
- 'creation_date', 'modification_date',
+ 'creation_date', 'modification_date', 'cwuri',
'owned_by', 'created_by')],
- ['relation_type', 'from_entity', 'to_entity', 'constrained_by',
+ ['relation_type', 'from_entity', 'in_basket', 'to_entity', 'constrained_by',
'cardinality', 'ordernum',
'indexed', 'fulltextindexed', 'internationalizable',
- 'defaultval', 'description_format', 'description'])
+ 'defaultval', 'description', 'description_format'])
self.assertEquals(schema.eschema('CWEType').main_attribute(), 'name')
self.assertEquals(schema.eschema('State').main_attribute(), 'name')
@@ -325,6 +324,20 @@
self.assertRaises(BadConnectionId, repo.set_shared_data, cnxid, 'data', 0)
self.assertRaises(BadConnectionId, repo.get_shared_data, cnxid, 'data')
+ def test_schema_is_relation(self):
+ no_is_rset = self.execute('Any X WHERE NOT X is ET')
+ self.failIf(no_is_rset, no_is_rset.description)
+
+# def test_perfo(self):
+# self.set_debug(True)
+# from time import time, clock
+# t, c = time(), clock()
+# try:
+# self.create_user('toto')
+# finally:
+# self.set_debug(False)
+# print 'test time: %.3f (time) %.3f (cpu)' % ((time() - t), clock() - c)
+
class DataHelpersTC(RepositoryBasedTC):
@@ -358,38 +371,36 @@
entity.eid = -1
entity.complete = lambda x: None
self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system'])
- cursor = self.session.pool['system']
- cursor.execute('SELECT * FROM entities WHERE eid = -1')
- data = cursor.fetchall()
+ cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
+ data = cu.fetchall()
self.assertIsInstance(data[0][3], datetime)
data[0] = list(data[0])
data[0][3] = None
self.assertEquals(tuplify(data), [(-1, 'Personne', 'system', None, None)])
self.repo.delete_info(self.session, -1)
#self.repo.commit()
- cursor.execute('SELECT * FROM entities WHERE eid = -1')
- data = cursor.fetchall()
+ cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1')
+ data = cu.fetchall()
self.assertEquals(data, [])
class FTITC(RepositoryBasedTC):
def test_reindex_and_modified_since(self):
- cursor = self.session.pool['system']
eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0]
self.commit()
ts = datetime.now()
self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
- cursor.execute('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
- omtime = cursor.fetchone()[0]
+ cu = self.session.system_sql('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp)
+ omtime = cu.fetchone()[0]
# our sqlite datetime adapter is ignore seconds fraction, so we have to
# ensure update is done the next seconds
time.sleep(1 - (ts.second - int(ts.second)))
self.execute('SET X nom "tata" WHERE X eid %(x)s', {'x': eidp}, 'x')
self.commit()
self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1)
- cursor.execute('SELECT mtime FROM entities WHERE eid = %s' % eidp)
- mtime = cursor.fetchone()[0]
+ cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp)
+ mtime = cu.fetchone()[0]
self.failUnless(omtime < mtime)
self.commit()
date, modified, deleted = self.repo.entities_modified_since(('Personne',), omtime)
--- a/server/test/unittest_rql2sql.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_rql2sql.py Tue Aug 04 15:06:09 2009 +0200
@@ -251,9 +251,9 @@
# Any O WHERE NOT S corrected_in O, S eid %(x)s, S concerns P, O version_of P, O in_state ST, NOT ST name "published", O modification_date MTIME ORDERBY MTIME DESC LIMIT 9
('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P',
- '''SELECT DISTINCT O.cw_eid
+ '''SELECT O.cw_eid
FROM cw_Note AS S, cw_Personne AS O
-WHERE (S.cw_ecrit_par IS NULL OR S.cw_ecrit_par!=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''),
+WHERE NOT EXISTS(SELECT 1 WHERE S.cw_ecrit_par=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''),
('DISTINCT Any S ORDERBY stockproc(SI) WHERE NOT S ecrit_par O, S para SI',
'''SELECT T1.C0 FROM (SELECT DISTINCT S.cw_eid AS C0, STOCKPROC(S.cw_para) AS C1
@@ -698,7 +698,6 @@
FROM cw_Tag AS S, cw_Tag AS T, tags_relation AS rel_tags0
WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid'''),
-
('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid 6',
'''SELECT 5, rel_created_by0.eid_to
FROM created_by_relation AS rel_created_by0
@@ -736,25 +735,25 @@
WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS Y WHERE rel_evaluee0.eid_from=Y.cw_eid AND rel_evaluee0.eid_to=X.cw_eid)'''),
('Any X,T WHERE X title T, NOT X is Bookmark',
- '''SELECT DISTINCT X.cw_eid, X.cw_title
+ '''SELECT X.cw_eid, X.cw_title
FROM cw_Card AS X
-UNION
-SELECT DISTINCT X.cw_eid, X.cw_title
+UNION ALL
+SELECT X.cw_eid, X.cw_title
FROM cw_EmailThread AS X'''),
('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U',
- '''SELECT DISTINCT P.cw_pkey, P.cw_value
+ '''SELECT P.cw_pkey, P.cw_value
FROM cw_CWProperty AS P
WHERE P.cw_for_user IS NULL'''),
('Any S WHERE NOT X in_state S, X is IN(Affaire, CWUser)',
- '''SELECT DISTINCT S.cw_eid
-FROM cw_Affaire AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+ '''SELECT S.cw_eid
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid)
INTERSECT
-SELECT DISTINCT S.cw_eid
-FROM cw_CWUser AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
+SELECT S.cw_eid
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)'''),
]
OUTER_JOIN = [
@@ -1030,9 +1029,9 @@
WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
('Any N WHERE NOT N ecrit_par P, P nom "toto"',
- '''SELECT DISTINCT N.cw_eid
+ '''SELECT N.cw_eid
FROM cw_Note AS N, cw_Personne AS P
-WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND P.cw_nom=toto'''),
+WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND P.cw_nom=toto'''),
('Any P WHERE N ecrit_par P, N eid 0',
'''SELECT N.cw_ecrit_par
@@ -1045,9 +1044,9 @@
WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''),
('Any P WHERE NOT N ecrit_par P, P is Personne, N eid 512',
- '''SELECT DISTINCT P.cw_eid
+ '''SELECT P.cw_eid
FROM cw_Note AS N, cw_Personne AS P
-WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND N.cw_eid=512'''),
+WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND N.cw_eid=512'''),
('Any S,ES,T WHERE S state_of ET, ET name "CWUser", ES allowed_transition T, T destination_state S',
'''SELECT T.cw_destination_state, rel_allowed_transition1.eid_from, T.cw_eid
@@ -1070,23 +1069,23 @@
INTERSECT = [
('Any SN WHERE NOT X in_state S, S name SN',
- '''SELECT DISTINCT S.cw_name
-FROM cw_Affaire AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+ '''SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid)
INTERSECT
-SELECT DISTINCT S.cw_name
-FROM cw_CWUser AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)
+SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)
INTERSECT
-SELECT DISTINCT S.cw_name
-FROM cw_Note AS X, cw_State AS S
-WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''),
+SELECT S.cw_name
+FROM cw_State AS S
+WHERE NOT EXISTS(SELECT 1 FROM cw_Note AS X WHERE X.cw_in_state=S.cw_eid)'''),
('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)',
'''SELECT X.cw_nom
FROM cw_Personne AS X
WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Division AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)
-INTERSECT ALL
+INTERSECT
SELECT X.cw_nom
FROM cw_Personne AS X
WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Societe AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)'''),
@@ -1138,16 +1137,22 @@
def _norm_sql(self, sql):
return sql.strip()
- def _check(self, rql, sql, varmap=None):
+ def _check(self, rql, sql, varmap=None, args=None):
+ if args is None:
+ args = {'text': 'hip hop momo'}
try:
union = self._prepare(rql)
- r, args = self.o.generate(union, {'text': 'hip hop momo'},
+ r, nargs = self.o.generate(union, args,
varmap=varmap)
+ args.update(nargs)
self.assertLinesEquals((r % args).strip(), self._norm_sql(sql))
except Exception, ex:
- print rql
if 'r' in locals():
- print (r%args).strip()
+ try:
+ print (r%args).strip()
+ except KeyError:
+ print 'strange, missing substitution'
+ print r, nargs
print '!='
print sql.strip()
raise
@@ -1207,18 +1212,26 @@
FROM in_basket_relation AS rel_in_basket0
WHERE rel_in_basket0.eid_to=12''')
- def test_varmap(self):
+ def test_varmap1(self):
self._check('Any X,L WHERE X is CWUser, X in_group G, X login L, G name "users"',
'''SELECT T00.x, T00.l
FROM T00, cw_CWGroup AS G, in_group_relation AS rel_in_group0
WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid AND G.cw_name=users''',
varmap={'X': 'T00.x', 'X.login': 'T00.l'})
+
+ def test_varmap2(self):
self._check('Any X,L,GN WHERE X is CWUser, X in_group G, X login L, G name GN',
'''SELECT T00.x, T00.l, G.cw_name
FROM T00, cw_CWGroup AS G, in_group_relation AS rel_in_group0
WHERE rel_in_group0.eid_from=T00.x AND rel_in_group0.eid_to=G.cw_eid''',
varmap={'X': 'T00.x', 'X.login': 'T00.l'})
+ def test_varmap3(self):
+ self._check('Any %(x)s,D WHERE F data D, F is File',
+ 'SELECT 728, _TDF0.C0\nFROM _TDF0',
+ args={'x': 728},
+ varmap={'F.data': '_TDF0.C0', 'D': '_TDF0.C0'})
+
def test_is_null_transform(self):
union = self._prepare('Any X WHERE X login %(login)s')
r, args = self.o.generate(union, {'login': None})
@@ -1422,6 +1435,22 @@
'''SELECT COUNT(1)
WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''')
+ def test_attr_map(self):
+ def generate_ref(gen, linkedvar, rel):
+ linkedvar.accept(gen)
+ return 'VERSION_DATA(%s)' % linkedvar._q_sql
+ self.o.attr_map['Affaire.ref'] = generate_ref
+ try:
+ self._check('Any R WHERE X ref R',
+ '''SELECT VERSION_DATA(X.cw_eid)
+FROM cw_Affaire AS X''')
+ self._check('Any X WHERE X ref 1',
+ '''SELECT X.cw_eid
+FROM cw_Affaire AS X
+WHERE VERSION_DATA(X.cw_eid)=1''')
+ finally:
+ self.o.attr_map.clear()
+
class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC):
--- a/server/test/unittest_rqlrewrite.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_rqlrewrite.py Tue Aug 04 15:06:09 2009 +0200
@@ -107,7 +107,7 @@
"Any S WHERE S owned_by C, C eid %(u)s, A eid %(B)s, "
"EXISTS((C identity A) OR (C in_state D, E identity A, "
"E in_state D, D name 'subscribed'), D is State, E is CWUser), "
- "S is IN(Affaire, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)")
+ "S is IN(Affaire, Basket, Bookmark, CWAttribute, CWCache, CWConstraint, CWConstraintType, CWEType, CWGroup, CWPermission, CWProperty, CWRType, CWRelation, CWUser, Card, Comment, Division, Email, EmailAddress, EmailPart, EmailThread, ExternalUri, File, Folder, Image, Note, Personne, RQLExpression, Societe, State, SubDivision, Tag, TrInfo, Transition)")
def test_simplified_rqlst(self):
card_constraint = ('X in_state S, U in_group G, P require_state S,'
--- a/server/test/unittest_schemaserial.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_schemaserial.py Tue Aug 04 15:06:09 2009 +0200
@@ -23,15 +23,15 @@
def test_eschema2rql1(self):
self.assertListEquals(list(eschema2rql(schema.eschema('CWAttribute'))),
[
- ('INSERT CWEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
- {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the application schema',
- 'meta': True, 'name': u'CWAttribute', 'final': False})
+ ('INSERT CWEType X: X description %(description)s,X final %(final)s,X name %(name)s',
+ {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema',
+ 'name': u'CWAttribute', 'final': False})
])
def test_eschema2rql2(self):
self.assertListEquals(list(eschema2rql(schema.eschema('String'))), [
- ('INSERT CWEType X: X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s',
- {'description': u'', 'final': True, 'meta': True, 'name': u'String'})])
+ ('INSERT CWEType X: X description %(description)s,X final %(final)s,X name %(name)s',
+ {'description': u'', 'final': True, 'name': u'String'})])
def test_eschema2rql_specialization(self):
self.assertListEquals(list(specialize2rql(schema)),
@@ -44,8 +44,8 @@
def test_rschema2rql1(self):
self.assertListEquals(list(rschema2rql(schema.rschema('relation_type'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
- {'description': u'link a relation definition to its relation type', 'meta': True, 'symetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s',
+ {'description': u'link a relation definition to its relation type', 'symetric': False, 'name': u'relation_type', 'final' : False, 'fulltext_container': None, 'inlined': True}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'relation_type', 'description': u'', 'composite': u'object', 'oe': 'CWRType',
@@ -63,7 +63,7 @@
def test_rschema2rql2(self):
self.assertListEquals(list(rschema2rql(schema.rschema('add_permission'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'meta': True, 'symetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s', {'description': u'core relation giving to a group the permission to add an entity or relation type', 'symetric': False, 'name': u'add_permission', 'final': False, 'fulltext_container': None, 'inlined': False}),
('INSERT CWRelation X: X cardinality %(cardinality)s,X composite %(composite)s,X description %(description)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'add_permission', 'description': u'groups allowed to add entities/relations of this type', 'composite': None, 'oe': 'CWGroup', 'ordernum': 3, 'cardinality': u'**', 'se': 'CWEType'}),
@@ -79,8 +79,8 @@
def test_rschema2rql3(self):
self.assertListEquals(list(rschema2rql(schema.rschema('cardinality'))),
[
- ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s',
- {'description': u'', 'meta': False, 'symetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
+ ('INSERT CWRType X: X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s',
+ {'description': u'', 'symetric': False, 'name': u'cardinality', 'final': True, 'fulltext_container': None, 'inlined': False}),
('INSERT CWAttribute X: X cardinality %(cardinality)s,X defaultval %(defaultval)s,X description %(description)s,X fulltextindexed %(fulltextindexed)s,X indexed %(indexed)s,X internationalizable %(internationalizable)s,X ordernum %(ordernum)s,X relation_type ER,X from_entity SE,X to_entity OE WHERE SE name %(se)s,ER name %(rt)s,OE name %(oe)s',
{'rt': 'cardinality', 'description': u'subject/object cardinality', 'internationalizable': True, 'fulltextindexed': False, 'ordernum': 5, 'defaultval': None, 'indexed': False, 'cardinality': u'?1', 'oe': 'String', 'se': 'CWRelation'}),
@@ -94,37 +94,37 @@
('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
{'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}),
('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute',
- {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11', u'??', u'1?'"}),
+ {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}),
])
def test_updateeschema2rql1(self):
self.assertListEquals(list(updateeschema2rql(schema.eschema('CWAttribute'))),
- [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
- {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the application schema', 'meta': True, 'et': 'CWAttribute', 'final': False, 'name': u'CWAttribute'}),
+ [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
+ {'description': u'define a final relation: link a final relation type from a non final entity to a final entity type. used to build the instance schema', 'et': 'CWAttribute', 'final': False, 'name': u'CWAttribute'}),
])
def test_updateeschema2rql2(self):
self.assertListEquals(list(updateeschema2rql(schema.eschema('String'))),
- [('SET X description %(description)s,X final %(final)s,X meta %(meta)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
- {'description': u'', 'meta': True, 'et': 'String', 'final': True, 'name': u'String'})
+ [('SET X description %(description)s,X final %(final)s,X name %(name)s WHERE X is CWEType, X name %(et)s',
+ {'description': u'', 'et': 'String', 'final': True, 'name': u'String'})
])
def test_updaterschema2rql1(self):
self.assertListEquals(list(updaterschema2rql(schema.rschema('relation_type'))),
[
- ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
+ ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
{'rt': 'relation_type', 'symetric': False,
'description': u'link a relation definition to its relation type',
- 'meta': True, 'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
+ 'final': False, 'fulltext_container': None, 'inlined': True, 'name': u'relation_type'})
])
def test_updaterschema2rql2(self):
expected = [
- ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X meta %(meta)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
+ ('SET X description %(description)s,X final %(final)s,X fulltext_container %(fulltext_container)s,X inlined %(inlined)s,X name %(name)s,X symetric %(symetric)s WHERE X is CWRType, X name %(rt)s',
{'rt': 'add_permission', 'symetric': False,
'description': u'core relation giving to a group the permission to add an entity or relation type',
- 'meta': True, 'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'})
+ 'final': False, 'fulltext_container': None, 'inlined': False, 'name': u'add_permission'})
]
for i, (rql, args) in enumerate(updaterschema2rql(schema.rschema('add_permission'))):
yield self.assertEquals, (rql, args), expected[i]
--- a/server/test/unittest_security.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/test/unittest_security.py Tue Aug 04 15:06:09 2009 +0200
@@ -499,7 +499,7 @@
self.assertRaises(Unauthorized,
self.schema['Affaire'].check_perm, session, 'update', eid)
cu = cnx.cursor()
- cu.execute('SET X in_state S WHERE X ref "ARCT01", S name "abort"')
+ cu.execute('SET X in_state S WHERE X ref "ARCT01", S name "ben non"')
cnx.commit()
# though changing a user state (even logged user) is reserved to managers
rql = u"SET X in_state S WHERE X eid %(x)s, S name 'deactivated'"
@@ -508,5 +508,26 @@
# from the current state but Unauthorized if it exists but user can't pass it
self.assertRaises(ValidationError, cu.execute, rql, {'x': cnx.user(self.current_session()).eid}, 'x')
+ def test_trinfo_security(self):
+ aff = self.execute('INSERT Affaire X: X ref "ARCT01"').get_entity(0, 0)
+ self.commit()
+ # can change tr info comment
+ self.execute('SET TI comment %(c)s WHERE TI wf_info_for X, X ref "ARCT01"',
+ {'c': u'creation'})
+ self.commit()
+ aff.clear_related_cache('wf_info_for', 'object')
+ self.assertEquals(aff.latest_trinfo().comment, 'creation')
+ # but not from_state/to_state
+ self.execute('SET X in_state S WHERE X ref "ARCT01", S name "ben non"')
+ self.commit()
+ aff.clear_related_cache('wf_info_for', role='object')
+ trinfo = aff.latest_trinfo()
+ self.assertRaises(Unauthorized,
+ self.execute, 'SET TI from_state S WHERE TI eid %(ti)s, S name "ben non"',
+ {'ti': trinfo.eid}, 'ti')
+ self.assertRaises(Unauthorized,
+ self.execute, 'SET TI to_state S WHERE TI eid %(ti)s, S name "pitetre"',
+ {'ti': trinfo.eid}, 'ti')
+
if __name__ == '__main__':
unittest_main()
--- a/server/utils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/server/utils.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,6 +13,10 @@
from getpass import getpass
from random import choice
+from logilab.common.configuration import Configuration
+
+from cubicweb.server import SOURCE_TYPES
+
try:
from crypt import crypt
except ImportError:
@@ -83,6 +87,13 @@
return user, passwd
+def ask_source_config(sourcetype, inputlevel=0):
+ sconfig = Configuration(options=SOURCE_TYPES[sourcetype].options)
+ sconfig.adapter = sourcetype
+ sconfig.input_config(inputlevel=inputlevel)
+ return sconfig
+
+
class LoopTask(object):
"""threaded task restarting itself once executed"""
def __init__(self, interval, func):
--- a/skeleton/__pkginfo__.py.tmpl Tue Aug 04 11:43:03 2009 +0200
+++ b/skeleton/__pkginfo__.py.tmpl Tue Aug 04 15:06:09 2009 +0200
@@ -17,16 +17,16 @@
short_desc = '%(shortdesc)s'
long_desc = '''%(longdesc)s'''
-from os import listdir as _listdir
-from os.path import join, isdir
-
web = 'http://www.cubicweb.org/project/%%s' %% distname
pyversions = ['2.4']
-#from cubicweb.devtools.pkginfo import get_distutils_datafiles
-CUBES_DIR = join('share', 'cubicweb', 'cubes')
-THIS_CUBE_DIR = join(CUBES_DIR, '%(cubename)s')
+
+from os import listdir as _listdir
+from os.path import join, isdir, exists, dirname
+from glob import glob
+
+THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname)
def listdir(dirpath):
return [join(dirpath, fname) for fname in _listdir(dirpath)
@@ -34,21 +34,16 @@
and not fname.endswith('~')
and not isdir(join(dirpath, fname))]
-from glob import glob
-try:
- data_files = [
- # common files
- [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
- ]
- # check for possible extended cube layout
- for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'):
- if isdir(dirname):
- data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
- # Note: here, you'll need to add subdirectories if you want
- # them to be included in the debian package
-except OSError:
- # we are in an installed directory
- pass
+data_files = [
+ # common files
+ [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
+ ]
+# check for possible extended cube layout
+for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'):
+ if isdir(dirname):
+ data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
+# Note: here, you'll need to add subdirectories if you want
+# them to be included in the debian package
cube_eid = None # <=== FIXME if you need direct bug-subscription
--- a/sobjects/hooks.py Tue Aug 04 11:43:03 2009 +0200
+++ b/sobjects/hooks.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,11 +7,32 @@
"""
__docformat__ = "restructuredtext en"
+from datetime import datetime
+
+from cubicweb import RepositoryError
from cubicweb.common.uilib import soup2xhtml
from cubicweb.server.hooksmanager import Hook
from cubicweb.server.pool import PreCommitOperation
+class SetModificationDateOnStateChange(Hook):
+ """update entity's modification date after changing its state"""
+ events = ('after_add_relation',)
+ accepts = ('in_state',)
+
+ def call(self, session, fromeid, rtype, toeid):
+ if fromeid in session.transaction_data.get('neweids', ()):
+ # new entity, not needed
+ return
+ entity = session.entity_from_eid(fromeid)
+ try:
+ entity.set_attributes(modification_date=datetime.now())
+ except RepositoryError, ex:
+ # usually occurs if entity is coming from a read-only source
+ # (eg ldap user)
+ self.warning('cant change modification date for %s: %s', entity, ex)
+
+
class AddUpdateCWUserHook(Hook):
"""ensure user logins are stripped"""
events = ('before_add_entity', 'before_update_entity',)
@@ -26,7 +47,7 @@
beid = None # make pylint happy
def precommit_event(self):
session = self.session
- if not self.beid in session.query_data('pendingeids', ()):
+ if not self.beid in session.transaction_data.get('pendingeids', ()):
if not session.unsafe_execute('Any X WHERE X bookmarked_by U, X eid %(x)s',
{'x': self.beid}, 'x'):
session.unsafe_execute('DELETE Bookmark X WHERE X eid %(x)s',
--- a/sobjects/notification.py Tue Aug 04 11:43:03 2009 +0200
+++ b/sobjects/notification.py Tue Aug 04 15:06:09 2009 +0200
@@ -62,7 +62,7 @@
class RenderAndSendNotificationView(PreCommitOperation):
"""delay rendering of notification view until precommit"""
def precommit_event(self):
- if self.view.rset[0][0] in self.session.query_data('pendingeids', ()):
+ if self.view.rset and self.view.rset[0][0] in self.session.transaction_data.get('pendingeids', ()):
return # entity added and deleted in the same transaction
self.view.render_and_send(**getattr(self, 'viewargs', {}))
@@ -77,7 +77,7 @@
rset = entity.related('wf_info_for')
try:
view = session.vreg.select('views', 'notif_status_change',
- session, rset, row=0)
+ session, rset=rset, row=0)
except RegistryException:
return
comment = entity.printable_value('comment', format='text/plain')
@@ -100,7 +100,7 @@
rset = session.eid_rset(fromeid)
vid = 'notif_%s_%s' % (self.event, rtype)
try:
- view = session.vreg.select('views', vid, session, rset, row=0)
+ view = session.vreg.select('views', vid, session, rset=rset, row=0)
except RegistryException:
return
RenderAndSendNotificationView(session, view=view)
@@ -117,7 +117,7 @@
rset = entity.as_rset()
vid = 'notif_%s' % self.event
try:
- view = session.vreg.select('views', vid, session, rset, row=0)
+ view = session.vreg.select('views', vid, session, rset=rset, row=0)
except RegistryException:
return
RenderAndSendNotificationView(session, view=view)
@@ -133,6 +133,8 @@
* set a content attribute to define the content of the email (unless you
override call)
"""
+ # XXX refactor this class to work with len(rset) > 1
+
msgid_timestamp = True
def recipients(self):
@@ -141,7 +143,7 @@
return finder.recipients()
def subject(self):
- entity = self.entity(0, 0)
+ entity = self.entity(self.row or 0, self.col or 0)
subject = self.req._(self.message)
etype = entity.dc_type()
eid = entity.eid
@@ -154,7 +156,7 @@
return self.req.actual_session().user.login
def context(self, **kwargs):
- entity = self.entity(0, 0)
+ entity = self.entity(self.row or 0, self.col or 0)
for key, val in kwargs.iteritems():
if val and isinstance(val, unicode) and val.strip():
kwargs[key] = self.req._(val)
@@ -184,15 +186,19 @@
DeprecationWarning, stacklevel=1)
lang = self.vreg.property_value('ui.language')
recipients = zip(recipients, repeat(lang))
- entity = self.entity(0, 0)
- # if the view is using timestamp in message ids, no way to reference
- # previous email
- if not self.msgid_timestamp:
- refs = [self.construct_message_id(eid)
- for eid in entity.notification_references(self)]
+ if self.rset is not None:
+ entity = self.entity(self.row or 0, self.col or 0)
+ # if the view is using timestamp in message ids, no way to reference
+ # previous email
+ if not self.msgid_timestamp:
+ refs = [self.construct_message_id(eid)
+ for eid in entity.notification_references(self)]
+ else:
+ refs = ()
+ msgid = self.construct_message_id(entity.eid)
else:
refs = ()
- msgid = self.construct_message_id(entity.eid)
+ msgid = None
userdata = self.req.user_data()
origlang = self.req.lang
for emailaddr, lang in recipients:
@@ -278,7 +284,7 @@
"""
def context(self, **kwargs):
- entity = self.entity(0, 0)
+ entity = self.entity(self.row or 0, self.col or 0)
content = entity.printable_value(self.content_attr, format='text/plain')
if content:
contentformat = getattr(entity, self.content_attr + '_format', 'text/rest')
@@ -286,7 +292,7 @@
return super(ContentAddedView, self).context(content=content, **kwargs)
def subject(self):
- entity = self.entity(0, 0)
+ entity = self.entity(self.row or 0, self.col or 0)
return u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema),
entity.eid, self.user_login())
--- a/sobjects/supervising.py Tue Aug 04 11:43:03 2009 +0200
+++ b/sobjects/supervising.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,6 +10,7 @@
from cubicweb import UnknownEid
from cubicweb.selectors import none_rset
+from cubicweb.schema import display_name
from cubicweb.view import Component
from cubicweb.common.mail import format_mail
from cubicweb.server.hooksmanager import Hook
@@ -36,7 +37,8 @@
# don't record last_login_time update which are done
# automatically at login time
return False
- self.session.add_query_data('pendingchanges', (self._event(), args))
+ self.session.transaction_data.setdefault('pendingchanges', []).append(
+ (self._event(), args))
return True
def _event(self):
@@ -47,17 +49,15 @@
events = ('before_delete_entity',)
def _call(self, eid):
- entity = self.session.entity(eid)
+ entity = self.session.entity_from_eid(eid)
try:
title = entity.dc_title()
except:
# may raise an error during deletion process, for instance due to
# missing required relation
title = '#%s' % eid
- self.session.add_query_data('pendingchanges',
- ('delete_entity',
- (eid, str(entity.e_schema),
- title)))
+ self.session.transaction_data.setdefault('pendingchanges', []).append(
+ ('delete_entity', (eid, str(entity.e_schema), title)))
return True
@@ -227,7 +227,7 @@
uinfo = {'email': config['sender-addr'],
'name': config['sender-name']}
view = self._get_view()
- content = view.render(changes=session.query_data('pendingchanges'))
+ content = view.render(changes=session.transaction_data.get('pendingchanges'))
recipients = view.recipients()
msg = format_mail(uinfo, recipients, content, view.subject(), config=config)
self.to_send = [(msg, recipients)]
--- a/sobjects/test/unittest_notification.py Tue Aug 04 11:43:03 2009 +0200
+++ b/sobjects/test/unittest_notification.py Tue Aug 04 15:06:09 2009 +0200
@@ -74,7 +74,7 @@
u = self.create_user('toto', req=req)
assert u.req
self.execute('SET X in_state S WHERE X eid %s, S name "deactivated"' % u.eid)
- v = self.vreg.select('views', 'notif_status_change', req, u.rset, row=0)
+ v = self.vreg.select('views', 'notif_status_change', req, rset=u.rset, row=0)
content = v.render(row=0, comment='yeah',
previous_state='activated',
current_state='deactivated')
--- a/sobjects/test/unittest_supervising.py Tue Aug 04 11:43:03 2009 +0200
+++ b/sobjects/test/unittest_supervising.py Tue Aug 04 15:06:09 2009 +0200
@@ -45,7 +45,7 @@
view = sentops[0]._get_view()
self.assertEquals(view.recipients(), ['test@logilab.fr'])
self.assertEquals(view.subject(), '[data supervision] changes summary')
- data = view.render(changes=session.query_data('pendingchanges')).strip()
+ data = view.render(changes=session.transaction_data.get('pendingchanges')).strip()
data = re.sub('#\d+', '#EID', data)
data = re.sub('/\d+', '/EID', data)
self.assertTextEquals('''user admin has made the following change(s):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/spa2rql.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,207 @@
+"""SPARQL -> RQL translator
+
+: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
+"""
+from logilab.common import make_domains
+from rql import TypeResolverException
+from fyzz.yappsparser import parse
+from fyzz import ast
+
+from cubicweb.xy import xy
+
+
+class UnsupportedQuery(Exception): pass
+
+def order_limit_offset(sparqlst):
+ addons = ''
+ if sparqlst.orderby:
+ sortterms = ', '.join('%s %s' % (var.name.upper(), ascdesc.upper())
+ for var, ascdesc in sparqlst.orderby)
+ addons += ' ORDERBY %s' % sortterms
+ if sparqlst.limit:
+ addons += ' LIMIT %s' % sparqlst.limit
+ if sparqlst.offset:
+ addons += ' OFFSET %s' % sparqlst.offset
+ return addons
+
+
+class QueryInfo(object):
+ """wrapper class containing necessary information to generate a RQL query
+ from a sparql syntax tree
+ """
+ def __init__(self, sparqlst):
+ self.sparqlst = sparqlst
+ if sparqlst.selected == ['*']:
+ self.selection = [var.upper() for var in sparqlst.variables]
+ else:
+ self.selection = [var.name.upper() for var in sparqlst.selected]
+ self.possible_types = {}
+ self.infer_types_info = []
+ self.union_params = []
+ self.restrictions = []
+ self.literals = {}
+ self._litcount = 0
+
+ def add_literal(self, value):
+ key = chr(ord('a') + self._litcount)
+ self._litcount += 1
+ self.literals[key] = value
+ return key
+
+ def set_possible_types(self, var, varpossibletypes):
+ """set/restrict possible types for the given variable.
+
+ :return: True if something changed, else false.
+ :raise: TypeResolverException if no more type allowed
+ """
+ varpossibletypes = set(varpossibletypes)
+ try:
+ ctypes = self.possible_types[var]
+ nbctypes = len(ctypes)
+ ctypes &= varpossibletypes
+ if not ctypes:
+ raise TypeResolverException()
+ return len(ctypes) != nbctypes
+ except KeyError:
+ self.possible_types[var] = varpossibletypes
+ return True
+
+ def infer_types(self):
+ # XXX should use something similar to rql.analyze for proper type inference
+ modified = True
+ # loop to infer types until nothing changed
+ while modified:
+ modified = False
+ for yams_predicates, subjvar, obj in self.infer_types_info:
+ nbchoices = len(yams_predicates)
+ # get possible types for the subject variable, according to the
+ # current predicate
+ svptypes = set(s for s, r, o in yams_predicates)
+ if not '*' in svptypes:
+ if self.set_possible_types(subjvar, svptypes):
+ modified = True
+ # restrict predicates according to allowed subject var types
+ if subjvar in self.possible_types:
+ yams_predicates = [(s, r, o) for s, r, o in yams_predicates
+ if s == '*' or s in self.possible_types[subjvar]]
+ if isinstance(obj, ast.SparqlVar):
+ # make a valid rql var name
+ objvar = obj.name.upper()
+ # get possible types for the object variable, according to
+ # the current predicate
+ ovptypes = set(o for s, r, o in yams_predicates)
+ if not '*' in ovptypes:
+ if self.set_possible_types(objvar, ovptypes):
+ modified = True
+ # restrict predicates according to allowed object var types
+ if objvar in self.possible_types:
+ yams_predicates = [(s, r, o) for s, r, o in yams_predicates
+ if o == '*' or o in self.possible_types[objvar]]
+ # ensure this still make sense
+ if not yams_predicates:
+ raise TypeResolverException()
+ if len(yams_predicates) != nbchoices:
+ modified = True
+
+ def build_restrictions(self):
+ # now, for each predicate
+ for yams_predicates, subjvar, obj in self.infer_types_info:
+ rel = yams_predicates[0]
+ # if there are several yams relation type equivalences, we will have
+ # to generate several unioned rql queries
+ for s, r, o in yams_predicates[1:]:
+ if r != rel[1]:
+ self.union_params.append((yams_predicates, subjvar, obj))
+ break
+ # else we can simply add it to base rql restrictions
+ else:
+ restr = self.build_restriction(subjvar, rel[1], obj)
+ self.restrictions.append(restr)
+
+ def build_restriction(self, subjvar, rtype, obj):
+ if isinstance(obj, ast.SparqlLiteral):
+ key = self.add_literal(obj.value)
+ objvar = '%%(%s)s' % key
+ else:
+ assert isinstance(obj, ast.SparqlVar)
+ # make a valid rql var name
+ objvar = obj.name.upper()
+ # else we can simply add it to base rql restrictions
+ return '%s %s %s' % (subjvar, rtype, objvar)
+
+ def finalize(self):
+ """return corresponding rql query (string) / args (dict)"""
+ for varname, ptypes in self.possible_types.iteritems():
+ if len(ptypes) == 1:
+ self.restrictions.append('%s is %s' % (varname, iter(ptypes).next()))
+ unions = []
+ for releq, subjvar, obj in self.union_params:
+ thisunions = []
+ for st, rt, ot in releq:
+ thisunions.append([self.build_restriction(subjvar, rt, obj)])
+ if st != '*':
+ thisunions[-1].append('%s is %s' % (subjvar, st))
+ if isinstance(obj, ast.SparqlVar) and ot != '*':
+ objvar = obj.name.upper()
+ thisunions[-1].append('%s is %s' % (objvar, objvar))
+ if not unions:
+ unions = thisunions
+ else:
+ unions = zip(*make_domains([unions, thisunions]))
+ selection = 'Any ' + ', '.join(self.selection)
+ sparqlst = self.sparqlst
+ if sparqlst.distinct:
+ selection = 'DISTINCT ' + selection
+ if unions:
+ baserql = '%s WHERE %s' % (selection, ', '.join(self.restrictions))
+ rqls = ['(%s, %s)' % (baserql, ', '.join(unionrestrs))
+ for unionrestrs in unions]
+ rql = ' UNION '.join(rqls)
+ if sparqlst.orderby or sparqlst.limit or sparqlst.offset:
+ rql = '%s%s WITH %s BEING (%s)' % (
+ selection, order_limit_offset(sparqlst),
+ ', '.join(self.selection), rql)
+ else:
+ rql = '%s%s WHERE %s' % (selection, order_limit_offset(sparqlst),
+ ', '.join(self.restrictions))
+ return rql, self.literals
+
+
+class Sparql2rqlTranslator(object):
+ def __init__(self, yschema):
+ self.yschema = yschema
+
+ def translate(self, sparql):
+ sparqlst = parse(sparql)
+ if sparqlst.type != 'select':
+ raise UnsupportedQuery()
+ qi = QueryInfo(sparqlst)
+ for subj, predicate, obj in sparqlst.where:
+ if not isinstance(subj, ast.SparqlVar):
+ raise UnsupportedQuery()
+ # make a valid rql var name
+ subjvar = subj.name.upper()
+ if predicate == ('', 'a'):
+ # special 'is' relation
+ if not isinstance(obj, tuple):
+ raise UnsupportedQuery()
+ # restrict possible types for the subject variable
+ qi.set_possible_types(
+ subjvar, xy.yeq(':'.join(obj), isentity=True))
+ else:
+ # 'regular' relation (eg not 'is')
+ if not isinstance(predicate, tuple):
+ raise UnsupportedQuery()
+ # list of 3-uple
+ # (yams etype (subject), yams rtype, yams etype (object))
+ # where subject / object entity type may '*' if not specified
+ yams_predicates = xy.yeq(':'.join(predicate))
+ qi.infer_types_info.append((yams_predicates, subjvar, obj))
+ if not isinstance(obj, (ast.SparqlLiteral, ast.SparqlVar)):
+ raise UnsupportedQuery()
+ qi.infer_types()
+ qi.build_restrictions()
+ return qi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/stdlib.txt Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,18 @@
+addressbook
+basket
+blog
+book
+calendar
+comment
+company
+email
+file
+folder
+i18ncontent
+keyword
+link
+mailinglist
+person
+tag
+timeseries
+vcsfile
--- a/test/data/erqlexpr_on_ertype.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/data/erqlexpr_on_ertype.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from yams.buildobjs import EntityType, RelationType, SubjectRelation
+from cubicweb.schema import ERQLExpression
+
class ToTo(EntityType):
permissions = {
'read': ('managers',),
--- a/test/data/rqlexpr_on_ertype_read.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/data/rqlexpr_on_ertype_read.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from yams.buildobjs import EntityType, RelationType, SubjectRelation
+from cubicweb.schema import RRQLExpression
+
class ToTo(EntityType):
permissions = {
'read': ('managers',),
--- a/test/data/rrqlexpr_on_attr.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/data/rrqlexpr_on_attr.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from yams.buildobjs import EntityType, RelationType, String
+from cubicweb.schema import RRQLExpression
+
class ToTo(EntityType):
permissions = {
'read': ('managers',),
--- a/test/data/rrqlexpr_on_eetype.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/data/rrqlexpr_on_eetype.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+from yams.buildobjs import EntityType, String
+from cubicweb.schema import RRQLExpression
+
class ToTo(EntityType):
permissions = {
'read': ('managers', RRQLExpression('S bla Y'),),
--- a/test/data/schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/data/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+
+from yams.buildobjs import EntityType, String, SubjectRelation, RelationDefinition
+
class Personne(EntityType):
nom = String(required=True)
prenom = String()
--- a/test/unittest_cwconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_cwconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,6 @@
import sys
import os
from os.path import dirname, join, abspath
-from tempfile import mktemp
from logilab.common.testlib import TestCase, unittest_main
from logilab.common.changelog import Version
--- a/test/unittest_cwctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_cwctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,9 +13,9 @@
if os.environ.get('APYCOT_ROOT'):
root = os.environ['APYCOT_ROOT']
CUBES_DIR = '%s/local/share/cubicweb/cubes/' % root
- os.environ['CW_CUBES'] = CUBES_DIR
+ os.environ['CW_CUBES_PATH'] = CUBES_DIR
REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
- os.environ['CW_REGISTRY_DIR'] = REGISTRY_DIR
+ os.environ['CW_INSTANCES_DIR'] = REGISTRY_DIR
from cubicweb.cwconfig import CubicWebConfiguration
CubicWebConfiguration.load_cwctl_plugins()
--- a/test/unittest_entity.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_entity.py Tue Aug 04 15:06:09 2009 +0200
@@ -55,7 +55,9 @@
e.copy_relations(oe.eid)
self.assertEquals(len(e.ecrit_par), 1)
self.assertEquals(e.ecrit_par[0].eid, p.eid)
- self.assertEquals(len(e.reverse_tags), 0)
+ self.assertEquals(len(e.reverse_tags), 1)
+ # check meta-relations are not copied, set on commit
+ self.assertEquals(len(e.created_by), 0)
def test_copy_with_nonmeta_composite_inlined(self):
p = self.add_entity('Personne', nom=u'toto')
@@ -215,7 +217,7 @@
e = self.add_entity('Card', title=u'rest test', content=u'du :eid:`1:*ReST*`',
content_format=u'text/rest')
self.assertEquals(e.printable_value('content'),
- '<p>du <a class="reference" href="http://testing.fr/cubicweb/cwgroup/managers">*ReST*</a></p>\n')
+ '<p>du <a class="reference" href="http://testing.fr/cubicweb/cwgroup/guests">*ReST*</a></p>\n')
e['content'] = 'du <em>html</em> <ref rql="CWUser X">users</ref>'
e['content_format'] = 'text/html'
self.assertEquals(e.printable_value('content'),
@@ -296,6 +298,8 @@
self.assertEquals(e.printable_value('content'), e['content'])
e['content'] = u'été'
self.assertEquals(e.printable_value('content'), e['content'])
+ e['content'] = u'hop\r\nhop\nhip\rmomo'
+ self.assertEquals(e.printable_value('content'), u'hop\nhop\nhip\nmomo')
def test_fulltextindex(self):
@@ -320,19 +324,20 @@
def test_complete_relation(self):
self.execute('SET RT add_permission G WHERE RT name "wf_info_for", G name "managers"')
self.commit()
+ session = self.session()
try:
- eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
- 'X from_state S1, X to_state S2 WHERE '
- 'U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
+ eid = session.unsafe_execute(
+ 'INSERT TrInfo X: X comment "zou", X wf_info_for U, X from_state S1, X to_state S2 '
+ 'WHERE U login "admin", S1 name "activated", S2 name "deactivated"')[0][0]
trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
trinfo.complete()
self.failUnless(trinfo.relation_cached('from_state', 'subject'))
self.failUnless(trinfo.relation_cached('to_state', 'subject'))
self.failUnless(trinfo.relation_cached('wf_info_for', 'subject'))
# check with a missing relation
- eid = self.execute('INSERT TrInfo X: X comment "zou", X wf_info_for U,'
- 'X to_state S2 WHERE '
- 'U login "admin", S2 name "activated"')[0][0]
+ eid = session.unsafe_execute(
+ 'INSERT TrInfo X: X comment "zou", X wf_info_for U,X to_state S2 '
+ 'WHERE U login "admin", S2 name "activated"')[0][0]
trinfo = self.entity('Any X WHERE X eid %(x)s', {'x': eid}, 'x')
trinfo.complete()
self.failUnless(isinstance(trinfo.creation_date, datetime))
@@ -390,6 +395,11 @@
metainf['extid'] = 1234
self.assertEquals(note.absolute_url(), 'http://cubicweb2.com/note/1234')
+ def test_absolute_url_empty_field(self):
+ card = self.add_entity('Card', wikiid=u'', title=u'test')
+ self.assertEquals(card.absolute_url(),
+ 'http://testing.fr/cubicweb/card/eid/%s' % card.eid)
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
--- a/test/unittest_rset.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_rset.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,7 +6,7 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from __future__ import with_statement
+#from __future__ import with_statement
from logilab.common.testlib import TestCase, unittest_main
@@ -346,10 +346,9 @@
set(['CWGroup',]))
def test_printable_rql(self):
- rset = self.execute(u'CWEType X WHERE X final FALSE, X meta FALSE')
+ rset = self.execute(u'CWEType X WHERE X final FALSE')
self.assertEquals(rset.printable_rql(),
- 'Any X WHERE X final FALSE, X meta FALSE, X is CWEType')
-
+ 'Any X WHERE X final FALSE, X is CWEType')
def test_searched_text(self):
rset = self.execute(u'Any X WHERE X has_text "foobar"')
--- a/test/unittest_rtags.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_rtags.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,7 +6,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.rtags import RelationTags, RelationTagsSet
+from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict
class RelationTagsTC(TestCase):
@@ -53,12 +53,30 @@
rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary')
self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
set(('primary', 'secondary')))
- self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
- set(('primary', 'secondary')))
self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
set(('secondary',)))
self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'),
set())
+ def test_rtagdict_expansion(self):
+ rtags = RelationTagsDict()
+ rtags.tag_subject_of(('Societe', 'travaille', '*'),
+ {'key1': 'val1', 'key2': 'val1'})
+ rtags.tag_subject_of(('*', 'travaille', '*'),
+ {'key1': 'val0', 'key3': 'val0'})
+ rtags.tag_subject_of(('Societe', 'travaille', '*'),
+ {'key2': 'val2'})
+ self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+ {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'})
+ self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'),
+ {'key1': 'val0', 'key3': 'val0'})
+ self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'),
+ {})
+
+ rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4')
+ rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4')
+ self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'),
+ {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'})
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -16,6 +16,7 @@
from yams import BadSchemaDefinition
from yams.constraints import SizeConstraint, StaticVocabularyConstraint
from yams.buildobjs import RelationDefinition, EntityType, RelationType
+from yams.reader import PyFileReader
from cubicweb.schema import CubicWebSchema, CubicWebEntitySchema, \
RQLConstraint, CubicWebSchemaLoader, ERQLExpression, RRQLExpression, \
@@ -133,12 +134,6 @@
class SQLSchemaReaderClassTest(TestCase):
- def test_knownValues_include_schema_files(self):
- schema_files = loader.include_schema_files('Bookmark')
- for file in schema_files:
- self.assert_(isabs(file))
- self.assertListEquals([basename(f) for f in schema_files], ['Bookmark.py'])
-
def test_knownValues_load_schema(self):
schema = loader.load(config)
self.assert_(isinstance(schema, CubicWebSchema))
@@ -150,7 +145,7 @@
'CWCache', 'CWConstraint', 'CWConstraintType', 'CWEType',
'CWAttribute', 'CWGroup', 'EmailAddress', 'CWRelation',
'CWPermission', 'CWProperty', 'CWRType', 'CWUser',
- 'File', 'Float', 'Image', 'Int', 'Interval', 'Note',
+ 'ExternalUri', 'File', 'Float', 'Image', 'Int', 'Interval', 'Note',
'Password', 'Personne',
'RQLExpression',
'Societe', 'State', 'String', 'SubNote', 'Tag', 'Time',
@@ -163,7 +158,7 @@
'cardinality', 'comment', 'comment_format',
'composite', 'condition', 'connait', 'constrained_by', 'content',
- 'content_format', 'created_by', 'creation_date', 'cstrtype',
+ 'content_format', 'created_by', 'creation_date', 'cstrtype', 'cwuri',
'data', 'data_encoding', 'data_format', 'defaultval', 'delete_permission',
'description', 'description_format', 'destination_state',
@@ -179,7 +174,7 @@
'label', 'last_login_time', 'login',
- 'mainvars', 'meta', 'modification_date',
+ 'mainvars', 'modification_date',
'name', 'nom',
@@ -193,7 +188,7 @@
'tags', 'timestamp', 'title', 'to_entity', 'to_state', 'transition_of', 'travaille', 'type',
- 'upassword', 'update_permission', 'use_email',
+ 'upassword', 'update_permission', 'uri', 'use_email',
'value',
@@ -203,7 +198,7 @@
eschema = schema.eschema('CWUser')
rels = sorted(str(r) for r in eschema.subject_relations())
- self.assertListEquals(rels, ['created_by', 'creation_date', 'eid',
+ self.assertListEquals(rels, ['created_by', 'creation_date', 'cwuri', 'eid',
'evaluee', 'firstname', 'has_text', 'identity',
'in_group', 'in_state', 'is',
'is_instance_of', 'last_login_time',
@@ -233,7 +228,7 @@
self.loader = CubicWebSchemaLoader()
self.loader.defined = {}
self.loader.loaded_files = []
- self.loader._instantiate_handlers()
+ self.loader._pyreader = PyFileReader(self.loader)
def _test(self, schemafile, msg):
self.loader.handle_file(join(DATADIR, schemafile))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_spa2rql.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,180 @@
+from logilab.common.testlib import TestCase, unittest_main
+from cubicweb.devtools import TestServerConfiguration
+from cubicweb.xy import xy
+from cubicweb.spa2rql import Sparql2rqlTranslator
+
+xy.add_equivalence('Project', 'doap:Project')
+xy.add_equivalence('Project creation_date', 'doap:Project doap:created')
+xy.add_equivalence('Project name', 'doap:Project doap:name')
+
+
+config = TestServerConfiguration('data')
+config.bootstrap_cubes()
+schema = config.load_schema()
+
+
+class XYTC(TestCase):
+ def setUp(self):
+ self.tr = Sparql2rqlTranslator(schema)
+
+ def _test(self, sparql, rql, args={}):
+ qi = self.tr.translate(sparql)
+ self.assertEquals(qi.finalize(), (rql, args))
+
+ def XXX_test_base_01(self):
+ self._test('SELECT * WHERE { }', 'Any X')
+
+
+ def test_base_is(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?project
+ WHERE {
+ ?project a doap:Project;
+ }''', 'Any PROJECT WHERE PROJECT is Project')
+
+
+ def test_base_attr_sel(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?created
+ WHERE {
+ ?project a doap:Project;
+ doap:created ?created.
+ }''', 'Any CREATED WHERE PROJECT creation_date CREATED, PROJECT is Project')
+
+
+ def test_base_attr_sel_distinct(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT DISTINCT ?name
+ WHERE {
+ ?project a doap:Project;
+ doap:name ?name.
+ }''', 'DISTINCT Any NAME WHERE PROJECT name NAME, PROJECT is Project')
+
+
+ def test_base_attr_sel_reduced(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT REDUCED ?name
+ WHERE {
+ ?project a doap:Project;
+ doap:name ?name.
+ }''', 'Any NAME WHERE PROJECT name NAME, PROJECT is Project')
+
+
+ def test_base_attr_sel_limit_offset(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?name
+ WHERE {
+ ?project a doap:Project;
+ doap:name ?name.
+ }
+ LIMIT 20''', 'Any NAME LIMIT 20 WHERE PROJECT name NAME, PROJECT is Project')
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?name
+ WHERE {
+ ?project a doap:Project;
+ doap:name ?name.
+ }
+ LIMIT 20 OFFSET 10''', 'Any NAME LIMIT 20 OFFSET 10 WHERE PROJECT name NAME, PROJECT is Project')
+
+
+ def test_base_attr_sel_orderby(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?name
+ WHERE {
+ ?project a doap:Project;
+ doap:name ?name;
+ doap:created ?created.
+ }
+ ORDER BY ?name DESC(?created)''', 'Any NAME ORDERBY NAME ASC, CREATED DESC WHERE PROJECT name NAME, PROJECT creation_date CREATED, PROJECT is Project')
+
+
+ def test_base_any_attr_sel(self):
+ self._test('''
+ PREFIX dc: <http://purl.org/dc/elements/1.1/>
+ SELECT ?x ?cd
+ WHERE {
+ ?x dc:date ?cd;
+ }''', 'Any X, CD WHERE X creation_date CD')
+
+
+ def test_base_any_attr_sel_amb(self):
+ xy.add_equivalence('Version publication_date', 'doap:Version dc:date')
+ try:
+ self._test('''
+ PREFIX dc: <http://purl.org/dc/elements/1.1/>
+ SELECT ?x ?cd
+ WHERE {
+ ?x dc:date ?cd;
+ }''', '(Any X, CD WHERE , X creation_date CD) UNION (Any X, CD WHERE , X publication_date CD, X is Version)')
+ finally:
+ xy.remove_equivalence('Version publication_date', 'doap:Version dc:date')
+
+
+ def test_base_any_attr_sel_amb_limit_offset(self):
+ xy.add_equivalence('Version publication_date', 'doap:Version dc:date')
+ try:
+ self._test('''
+ PREFIX dc: <http://purl.org/dc/elements/1.1/>
+ SELECT ?x ?cd
+ WHERE {
+ ?x dc:date ?cd;
+ }
+ LIMIT 20 OFFSET 10''', 'Any X, CD LIMIT 20 OFFSET 10 WITH X, CD BEING ((Any X, CD WHERE , X creation_date CD) UNION (Any X, CD WHERE , X publication_date CD, X is Version))')
+ finally:
+ xy.remove_equivalence('Version publication_date', 'doap:Version dc:date')
+
+
+ def test_base_any_attr_sel_amb_orderby(self):
+ xy.add_equivalence('Version publication_date', 'doap:Version dc:date')
+ try:
+ self._test('''
+ PREFIX dc: <http://purl.org/dc/elements/1.1/>
+ SELECT ?x ?cd
+ WHERE {
+ ?x dc:date ?cd;
+ }
+ ORDER BY DESC(?cd)''', 'Any X, CD ORDERBY CD DESC WITH X, CD BEING ((Any X, CD WHERE , X creation_date CD) UNION (Any X, CD WHERE , X publication_date CD, X is Version))')
+ finally:
+ xy.remove_equivalence('Version publication_date', 'doap:Version dc:date')
+
+
+ def test_restr_attr(self):
+ self._test('''
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
+ SELECT ?project
+ WHERE {
+ ?project a doap:Project;
+ doap:name "cubicweb".
+ }''', 'Any PROJECT WHERE PROJECT name %(a)s, PROJECT is Project', {'a': 'cubicweb'})
+
+# # Two elements in the group
+# PREFIX : <http://example.org/ns#>
+# SELECT *
+# { :p :q :r OPTIONAL { :a :b :c }
+# :p :q :r OPTIONAL { :a :b :c }
+# }
+
+# PREFIX : <http://example.org/ns#>
+# SELECT *
+# {
+# { ?s ?p ?o } UNION { ?a ?b ?c }
+# }
+
+# PREFIX dob: <http://placetime.com/interval/gregorian/1977-01-18T04:00:00Z/P>
+# PREFIX time: <http://www.ai.sri.com/daml/ontologies/time/Time.daml#>
+# PREFIX dc: <http://purl.org/dc/elements/1.1/>
+# SELECT ?desc
+# WHERE {
+# dob:1D a time:ProperInterval;
+# dc:description ?desc.
+# }
+
+if __name__ == '__main__':
+ unittest_main()
--- a/test/unittest_utils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/test/unittest_utils.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.common.utils import make_uid, UStringIO, SizeConstrainedList
+from cubicweb.utils import make_uid, UStringIO, SizeConstrainedList
class MakeUidTC(TestCase):
--- a/toolsutils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/toolsutils.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,6 +7,8 @@
"""
__docformat__ = "restructuredtext en"
+# XXX move most of this in logilab.common (shellutils ?)
+
import os, sys
from os import listdir, makedirs, symlink, environ, chmod, walk, remove
from os.path import exists, join, abspath, normpath
@@ -14,6 +16,7 @@
from logilab.common.clcommands import Command as BaseCommand, \
main_run as base_main_run
from logilab.common.compat import any
+from logilab.common.shellutils import ASK
from cubicweb import warning
from cubicweb import ConfigurationError, ExecutionError
@@ -34,12 +37,12 @@
"""create a directory if it doesn't exist yet"""
try:
makedirs(directory)
- print 'created directory', directory
+ print '-> created directory %s.' % directory
except OSError, ex:
import errno
if ex.errno != errno.EEXIST:
raise
- print 'directory %s already exists' % directory
+ print '-> directory %s already exists, no need to create it.' % directory
def create_symlink(source, target):
"""create a symbolic link"""
@@ -56,7 +59,7 @@
def rm(whatever):
import shutil
shutil.rmtree(whatever)
- print 'removed %s' % whatever
+ print '-> removed %s' % whatever
def show_diffs(appl_file, ref_file, askconfirm=True):
"""interactivly replace the old file with the new file according to
@@ -69,7 +72,7 @@
if askconfirm:
print
print diffs
- action = raw_input('replace (N/y/q) ? ').lower()
+ action = ASK.ask('Replace ?', ('N','y','q'), 'N')
else:
action = 'y'
if action == 'y':
@@ -114,7 +117,7 @@
if fname.endswith('.tmpl'):
tfpath = tfpath[:-5]
if not askconfirm or not exists(tfpath) or \
- confirm('%s exists, overwrite?' % tfpath):
+ ASK.confirm('%s exists, overwrite?' % tfpath):
fill_templated_file(fpath, tfpath, context)
print '[generate] %s <-- %s' % (tfpath, fpath)
elif exists(tfpath):
@@ -133,26 +136,11 @@
if log:
log('set %s permissions to 0600', filepath)
else:
- print 'set %s permissions to 0600' % filepath
+ print '-> set %s permissions to 0600' % filepath
chmod(filepath, 0600)
-def confirm(question, default_is_yes=True):
- """ask for confirmation and return true on positive answer"""
- if default_is_yes:
- input_str = '%s [Y/n]: '
- else:
- input_str = '%s [y/N]: '
- answer = raw_input(input_str % (question)).strip().lower()
- if default_is_yes:
- if answer in ('n', 'no'):
- return False
- return True
- if answer in ('y', 'yes'):
- return True
- return False
-
def read_config(config_file):
- """read the application configuration from a file and return it as a
+ """read the instance configuration from a file and return it as a
dictionnary
:type config_file: str
@@ -289,5 +277,5 @@
password = optconfig.password
if not password:
password = getpass('password: ')
- return connect(user=user, password=password, host=optconfig.host, database=appid)
+ return connect(login=user, password=password, host=optconfig.host, database=appid)
--- a/utils.py Tue Aug 04 11:43:03 2009 +0200
+++ b/utils.py Tue Aug 04 15:06:09 2009 +0200
@@ -206,8 +206,13 @@
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 () {
+ def add_onload(self, jscode, jsoncall=False):
+ if jsoncall:
+ self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) {
+%s
+});""" % jscode)
+ else:
+ self.add_post_inline_script(u"""jQuery(document).ready(function () {
%s
});""" % jscode)
@@ -250,10 +255,10 @@
# 1/ variable declaration if any
if self.jsvars:
from simplejson import dumps
- w(u'<script type="text/javascript">\n')
+ w(u'<script type="text/javascript"><!--//--><![CDATA[//><!--\n')
for var, value in self.jsvars:
w(u'%s = %s;\n' % (var, dumps(value)))
- w(u'</script>\n')
+ w(u'//--><!]]></script>\n')
# 2/ css files
for cssfile, media in self.cssfiles:
w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
@@ -321,4 +326,18 @@
"""
# XXX deprecated, no more necessary
+def get_schema_property(eschema, rschema, role, property):
+ # XXX use entity.e_schema.role_rproperty(role, rschema, property, tschemas[0]) once yams > 0.23.0 is out
+ if role == 'subject':
+ targetschema = rschema.objects(eschema)[0]
+ return rschema.rproperty(eschema, targetschema, property)
+ targetschema = rschema.subjects(eschema)[0]
+ return rschema.rproperty(targetschema, eschema, property)
+def compute_cardinality(eschema, rschema, role):
+ if role == 'subject':
+ targetschema = rschema.objects(eschema)[0]
+ return rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+ targetschema = rschema.subjects(eschema)[0]
+ return rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+
--- a/view.py Tue Aug 04 11:43:03 2009 +0200
+++ b/view.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,15 +11,16 @@
from cStringIO import StringIO
-from logilab.common.deprecation import obsolete
-from logilab.mtconverter import html_escape
+from logilab.common.deprecation import deprecated
+from logilab.mtconverter import xml_escape
+from rql import nodes
from cubicweb import NotAnEntity
from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset
from cubicweb.selectors import require_group_compat, accepts_compat
from cubicweb.appobject import AppRsetObject
from cubicweb.utils import UStringIO, HTMLStream
-
+from cubicweb.schema import display_name
# robots control
NOINDEX = u'<meta name="ROBOTS" content="NOINDEX" />'
@@ -150,7 +151,7 @@
if stream is not None:
return self._stream.getvalue()
- dispatch = obsolete('.dispatch is deprecated, use .render')(render)
+ dispatch = deprecated('.dispatch is deprecated, use .render')(render)
# should default .call() method add a <div classs="section"> around each
# rset item
@@ -195,10 +196,27 @@
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)
+ rset = self.rset
+ if rset is None:
+ return self.build_url('view', vid=self.id)
+ coltypes = rset.column_types(0)
+ if len(coltypes) == 1:
+ etype = iter(coltypes).next()
+ if not self.schema.eschema(etype).is_final():
+ if len(rset) == 1:
+ entity = rset.get_entity(0, 0)
+ return entity.absolute_url(vid=self.id)
+ # don't want to generate /<etype> url if there is some restriction
+ # on something else than the entity type
+ restr = rset.syntax_tree().children[0].where
+ # XXX norestriction is not correct here. For instance, in cases like
+ # "Any P,N WHERE P is Project, P name N" norestriction should equal
+ # True
+ norestriction = (isinstance(restr, nodes.Relation) and
+ restr.is_types_restriction())
+ if norestriction:
+ return self.build_url(etype.lower(), vid=self.id)
+ return self.build_url('view', rql=rset.printable_rql(), vid=self.id)
def set_request_content_type(self):
"""set the content type returned by this view"""
@@ -212,14 +230,14 @@
self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs)
# XXX Template bw compat
- template = obsolete('.template is deprecated, use .view')(wview)
+ template = deprecated('.template is deprecated, use .view')(wview)
def whead(self, data):
self.req.html_headers.write(data)
def wdata(self, data):
"""simple helper that escapes `data` and writes into `self.w`"""
- self.w(html_escape(data))
+ self.w(xml_escape(data))
def html_headers(self):
"""return a list of html headers (eg something to be inserted between
@@ -313,10 +331,6 @@
category = 'startupview'
- def url(self):
- """return the url associated with this view. We can omit rql here"""
- return self.build_url('view', vid=self.id)
-
def html_headers(self):
"""return a list of html headers (eg something to be inserted between
<head> and </head> of the returned page
@@ -334,7 +348,7 @@
default_rql = None
- def __init__(self, req, rset, **kwargs):
+ def __init__(self, req, rset=None, **kwargs):
super(EntityStartupView, self).__init__(req, rset, **kwargs)
if rset is None:
# this instance is not in the "entityview" category
@@ -354,14 +368,6 @@
for i in xrange(len(rset)):
self.wview(self.id, rset, row=i, **kwargs)
- def url(self):
- """return the url associated with this view. We can omit rql if we are
- on a result set on which we do not apply.
- """
- if self.rset is None:
- return self.build_url(vid=self.id)
- return super(EntityStartupView, self).url()
-
class AnyRsetView(View):
"""base class for views applying on any non empty result sets"""
@@ -369,20 +375,20 @@
category = 'anyrsetview'
- def columns_labels(self, tr=True):
+ def columns_labels(self, mainindex=0, tr=True):
if tr:
- translate = display_name
+ translate = lambda val, req=self.req: display_name(req, val)
else:
- translate = lambda req, val: val
- rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support
+ translate = lambda val: val
+ # XXX [0] because of missing Union support
+ rqlstdescr = self.rset.syntax_tree().get_description(mainindex,
+ translate)[0]
labels = []
- for colindex, attr in enumerate(rqlstdescr):
+ for colindex, label in enumerate(rqlstdescr):
# compute column header
- if colindex == 0 or attr == 'Any': # find a better label
- label = ','.join(translate(self.req, et)
+ if label == 'Any': # find a better label
+ label = ','.join(translate(et)
for et in self.rset.column_types(colindex))
- else:
- label = translate(self.req, attr)
labels.append(label)
return labels
@@ -440,10 +446,10 @@
def cb(*args):
_cb(*args)
cbname = self.req.register_onetime_callback(cb, *args)
- return self.build_js(cbname, html_escape(msg or ''))
+ return self.build_js(cbname, xml_escape(msg or ''))
def build_update_js_call(self, cbname, msg):
- rql = html_escape(self.rset.printable_rql())
+ rql = xml_escape(self.rset.printable_rql())
return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % (
cbname, self.id, rql, msg, self.__registry__, self.div_id())
--- a/vregistry.py Tue Aug 04 11:43:03 2009 +0200
+++ b/vregistry.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,10 +1,10 @@
"""
-* the vregistry handle various type of objects interacting
- together. The vregistry handle registration of dynamically loaded
- objects and provide a convenient api access to those objects
+* the vregistry handles various types of objects interacting
+ together. The vregistry handles registration of dynamically loaded
+ objects and provides a convenient api to access those objects
according to a context
-* to interact with the vregistry, object should inherit from the
+* to interact with the vregistry, objects should inherit from the
VObject abstract class
* the selection procedure has been generalized by delegating to a
@@ -29,8 +29,15 @@
from warnings import warn
from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
-from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
+from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
+ RegistryOutOfDate)
+# XXX depending on cubicweb.web is ugly, we should deal with uicfg
+# reset with a good old event / callback system
+try:
+ from cubicweb.web import uicfg
+except ImportError: # cubicweb.web not installed
+ uicfg = None
def _toload_info(path, extrapath, _toload=None):
"""return a dictionary of <modname>: <modpath> and an ordered list of
@@ -140,6 +147,8 @@
def reset(self):
self._registries = {}
self._lastmodifs = {}
+ if uicfg is not None:
+ reload(uicfg)
def __getitem__(self, key):
return self._registries[key]
@@ -229,7 +238,7 @@
"""
if len(args) > 1:
warn('only the request param can not be named when calling select',
- DeprecationWarning, stacklevel=2)
+ DeprecationWarning, stacklevel=3)
score, winners = 0, []
for vobject in vobjects:
vobjectscore = vobject.__select__(vobject, *args, **kwargs)
@@ -266,7 +275,7 @@
oid = obj.id
except AttributeError:
continue
- if oid:
+ if oid and not '__abstract__' in obj.__dict__:
self.register(obj)
def register(self, obj, registryname=None, oid=None, clear=False):
@@ -283,7 +292,8 @@
# registered() is technically a classmethod but is not declared
# as such because we need to compose registered in some cases
vobject = obj.registered.im_func(obj, self)
- assert not vobject in vobjects, vobject
+ assert not vobject in vobjects, \
+ 'object %s is already registered' % vobject
assert callable(vobject.__select__), vobject
vobjects.append(vobject)
try:
@@ -292,33 +302,27 @@
vname = vobject.__class__.__name__
self.debug('registered vobject %s in registry %s with id %s',
vname, registryname, oid)
- # automatic reloading management
self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
def unregister(self, obj, registryname=None):
registryname = registryname or obj.__registry__
registry = self.registry(registryname)
removed_id = obj.classid()
- for registered in registry[obj.id]:
+ for registered in registry.get(obj.id, ()):
# use classid() to compare classes because vreg will probably
# have its own version of the class, loaded through execfile
if registered.classid() == removed_id:
# XXX automatic reloading management
- try:
- registry[obj.id].remove(registered)
- except KeyError:
- self.warning('can\'t remove %s, no id %s in the %s registry',
- removed_id, obj.id, registryname)
- except ValueError:
- self.warning('can\'t remove %s, not in the %s registry with id %s',
- removed_id, registryname, obj.id)
-# else:
-# # if objects is empty, remove oid from registry
-# if not registry[obj.id]:
-# del regcontent[oid]
+ registry[obj.id].remove(registered)
break
+ else:
+ self.warning('can\'t remove %s, no id %s in the %s registry',
+ removed_id, obj.id, registryname)
def register_and_replace(self, obj, replaced, registryname=None):
+ # XXXFIXME this is a duplication of unregister()
+ # remove register_and_replace in favor of unregister + register
+ # or simplify by calling unregister then register here
if hasattr(replaced, 'classid'):
replaced = replaced.classid()
registryname = registryname or obj.__registry__
@@ -338,6 +342,9 @@
def init_registration(self, path, extrapath=None):
# compute list of all modules that have to be loaded
self._toloadmods, filemods = _toload_info(path, extrapath)
+ # XXX is _loadedmods still necessary ? It seems like it's useful
+ # to avoid loading same module twice, especially with the
+ # _load_ancestors_then_object logic but this needs to be checked
self._loadedmods = {}
return filemods
@@ -360,7 +367,7 @@
sys.path.remove(webdir)
if CW_SOFTWARE_ROOT in sys.path:
sys.path.remove(CW_SOFTWARE_ROOT)
- # load views from each directory in the application's path
+ # load views from each directory in the instance's path
filemods = self.init_registration(path, extrapath)
change = False
for filepath, modname in filemods:
@@ -369,7 +376,7 @@
return change
def load_file(self, filepath, modname, force_reload=False):
- """load visual objects from a python file"""
+ """load app objects from a python file"""
from logilab.common.modutils import load_module_from_name
if modname in self._loadedmods:
return
@@ -385,22 +392,12 @@
# only load file if it was modified
if modified_on <= self._lastmodifs[filepath]:
return
- # if it was modified, unregister all exisiting objects
- # from this module, and keep track of what was unregistered
- unregistered = self.unregister_module_vobjects(modname)
- else:
- unregistered = None
+ # if it was modified, raise RegistryOutOfDate to reload everything
+ self.info('File %s changed since last visit', filepath)
+ raise RegistryOutOfDate()
# load the module
module = load_module_from_name(modname, use_sys=not force_reload)
self.load_module(module)
- # if something was unregistered, we need to update places where it was
- # referenced
- if unregistered:
- # oldnew_mapping = {}
- registered = self._loadedmods[modname]
- oldnew_mapping = dict((unregistered[name], registered[name])
- for name in unregistered if name in registered)
- self.update_registered_subclasses(oldnew_mapping)
self._lastmodifs[filepath] = modified_on
return True
@@ -462,68 +459,6 @@
return
self.register(cls)
- def unregister_module_vobjects(self, modname):
- """removes registered objects coming from a given module
-
- returns a dictionnary classid/class of all classes that will need
- to be updated after reload (i.e. vobjects referencing classes defined
- in the <modname> module)
- """
- unregistered = {}
- # browse each registered object
- for registry, objdict in self.items():
- for oid, objects in objdict.items():
- for obj in objects[:]:
- objname = obj.classid()
- # if the vobject is defined in this module, remove it
- if objname.startswith(modname):
- unregistered[objname] = obj
- objects.remove(obj)
- self.debug('unregistering %s in %s registry',
- objname, registry)
- # if not, check if the vobject can be found in baseclasses
- # (because we also want subclasses to be updated)
- else:
- if not isinstance(obj, type):
- obj = obj.__class__
- for baseclass in obj.__bases__:
- if hasattr(baseclass, 'classid'):
- baseclassid = baseclass.classid()
- if baseclassid.startswith(modname):
- unregistered[baseclassid] = baseclass
- # update oid entry
- if objects:
- objdict[oid] = objects
- else:
- del objdict[oid]
- return unregistered
-
- def update_registered_subclasses(self, oldnew_mapping):
- """updates subclasses of re-registered vobjects
-
- if baseviews.PrimaryView is changed, baseviews.py will be reloaded
- automatically and the new version of PrimaryView will be registered.
- But all existing subclasses must also be notified of this change, and
- that's what this method does
-
- :param oldnew_mapping: a dict mapping old version of a class to
- the new version
- """
- # browse each registered object
- for objdict in self.values():
- for objects in objdict.values():
- for obj in objects:
- if not isinstance(obj, type):
- obj = obj.__class__
- # build new baseclasses tuple
- newbases = tuple(oldnew_mapping.get(baseclass, baseclass)
- for baseclass in obj.__bases__)
- # update obj's baseclasses tuple (__bases__) if needed
- if newbases != obj.__bases__:
- self.debug('updating %s.%s base classes',
- obj.__module__, obj.__name__)
- obj.__bases__ = newbases
-
# init logging
set_log_methods(VObject, getLogger('cubicweb'))
set_log_methods(VRegistry, getLogger('cubicweb.registry'))
--- a/web/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -14,7 +14,7 @@
from datetime import datetime, date, timedelta
from simplejson import dumps
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
from cubicweb.common.uilib import urlquote
from cubicweb.web._exceptions import *
@@ -65,7 +65,7 @@
return json_dumps(function(*args, **kwargs))
return newfunc
-@obsolete('use req.build_ajax_replace_url() instead')
+@deprecated('use req.build_ajax_replace_url() instead')
def ajax_replace_url(nodeid, rql, vid=None, swap=False, **extraparams):
"""builds a replacePageChunk-like url
>>> ajax_replace_url('foo', 'Person P')
--- a/web/application.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/application.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
import sys
from time import clock, time
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
from rql import BadRQLQuery
@@ -21,8 +21,7 @@
from cubicweb.web import LOGGER, component
from cubicweb.web import (
StatusResponse, DirectResponse, Redirect, NotFound,
- RemoteCallFailed, ExplicitLogin, InvalidSession)
-from cubicweb.web.component import Component
+ RemoteCallFailed, ExplicitLogin, InvalidSession, RequestError)
# make session manager available through a global variable so the debug view can
# print information about web session
@@ -202,7 +201,7 @@
raise Redirect(req.build_url(path, **args))
def logout(self, req):
- """logout from the application by cleaning the session and raising
+ """logout from the instance by cleaning the session and raising
`AuthenticationError`
"""
self.session_manager.close_session(req.cnx)
@@ -219,11 +218,11 @@
session_handler_fact=CookieSessionHandler,
vreg=None):
super(CubicWebPublisher, self).__init__()
- # connect to the repository and get application's schema
+ # connect to the repository and get instance's schema
if vreg is None:
vreg = cwvreg.CubicWebRegistry(config, debug=debug)
self.vreg = vreg
- self.info('starting web application from %s', config.apphome)
+ self.info('starting web instance from %s', config.apphome)
self.repo = config.repository(vreg)
if not vreg.initialized:
self.config.init_cubes(self.repo.get_cubes())
@@ -273,11 +272,10 @@
finally:
self._logfile_lock.release()
- @obsolete("use vreg.select('controllers', ...)")
+ @deprecated("use vreg.select('controllers', ...)")
def select_controller(self, oid, req):
try:
- controller = self.vreg.select('controllers', oid, req=req,
- appli=self)
+ return self.vreg.select('controllers', oid, req=req, appli=self)
except NoSelectableObject:
raise Unauthorized(req._('not authorized'))
@@ -341,7 +339,7 @@
raise
except ValidationError, ex:
self.validation_error_handler(req, ex)
- except (Unauthorized, BadRQLQuery), ex:
+ except (Unauthorized, BadRQLQuery, RequestError), ex:
self.error_handler(req, ex, tb=False)
except Exception, ex:
self.error_handler(req, ex, tb=True)
@@ -397,8 +395,8 @@
return self.vreg.main_template(req, template, view=view)
def main_template_id(self, req):
- template = req.property_value('ui.main-template')
- if template not in self.vreg.registry('views') :
+ template = req.form.get('__template', req.property_value('ui.main-template'))
+ if template not in self.vreg.registry('views'):
template = 'main-template'
return template
--- a/web/box.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/box.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized, role as get_role, target as get_target
from cubicweb.selectors import (one_line_rset, primary_view,
@@ -74,7 +74,7 @@
.format_actions method
"""
if escape:
- title = html_escape(title)
+ title = xml_escape(title)
return self.box_action(self._action(title, path, **kwargs))
def _action(self, title, path, **kwargs):
--- a/web/component.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/component.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,7 +9,7 @@
_ = unicode
from logilab.common.deprecation import class_renamed
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import role
from cubicweb.utils import merge_dicts
@@ -37,7 +37,7 @@
property_defs = {
_('visible'): dict(type='Boolean', default=True,
- help=_('display the box or not')),
+ help=_('display the component or not')),
_('order'): dict(type='Int', default=99,
help=_('display order of the component')),
_('context'): dict(type='String', default='header',
@@ -117,7 +117,7 @@
def page_link(self, path, params, start, stop, content):
url = self.build_url(path, **merge_dicts(params, {self.start_param : start,
self.stop_param : stop,}))
- url = html_escape(url)
+ url = xml_escape(url)
if start == self.starting_from:
return self.selected_page_link_templ % (url, content, content)
return self.page_link_templ % (url, content, content)
@@ -130,7 +130,7 @@
stop = start + self.page_size - 1
url = self.build_url(**merge_dicts(params, {self.start_param : start,
self.stop_param : stop,}))
- url = html_escape(url)
+ url = xml_escape(url)
return self.previous_page_link_templ % (url, title, content)
def next_link(self, params, content='>>', title=_('next_results')):
@@ -140,7 +140,7 @@
stop = start + self.page_size - 1
url = self.build_url(**merge_dicts(params, {self.start_param : start,
self.stop_param : stop,}))
- url = html_escape(url)
+ url = xml_escape(url)
return self.next_page_link_templ % (url, title, content)
--- a/web/controller.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/controller.py Tue Aug 04 15:06:09 2009 +0200
@@ -184,6 +184,10 @@
elif '__redirectpath' in self.req.form:
# if redirect path was explicitly specified in the form, use it
path = self.req.form['__redirectpath']
+ if self._edited_entity and path != self._edited_entity.rest_path():
+ # XXX may be here on modification? if yes the message should be
+ # modified where __createdpath is detected (cw.web.request)
+ newparams['__createdpath'] = self._edited_entity.rest_path()
elif self._after_deletion_path:
# else it should have been set during form processing
path, params = self._after_deletion_path
Binary file web/data/accessories-text-editor.png has changed
--- a/web/data/cubicweb.ajax.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.ajax.js Tue Aug 04 15:06:09 2009 +0200
@@ -11,15 +11,20 @@
function _loadAjaxHtmlHead(node, head, tag, srcattr) {
var loaded = [];
- jQuery('head ' + tag).each(function(i) {
+ var jqtagfilter = tag + '[' + srcattr + ']';
+ jQuery('head ' + jqtagfilter).each(function(i) {
loaded.push(this.getAttribute(srcattr));
});
node.find(tag).each(function(i) {
- if (!loaded.contains(this.getAttribute(srcattr))) {
+ if (this.getAttribute(srcattr)) {
+ if (!loaded.contains(this.getAttribute(srcattr))) {
+ jQuery(this).appendTo(head);
+ }
+ } else {
jQuery(this).appendTo(head);
}
});
- node.find(tag).remove();
+ node.find(jqtagfilter).remove();
}
/*
@@ -54,8 +59,8 @@
if (typeof buildWidgets != 'undefined') {
buildWidgets(node);
}
- if (typeof roundedCornersOnLoad != 'undefined') {
- roundedCornersOnLoad();
+ if (typeof roundedCorners != 'undefined') {
+ roundedCorners(node);
}
loadDynamicFragments(node);
jQuery(CubicWeb).trigger('ajax-loaded');
@@ -226,6 +231,7 @@
// make sure the component is visible
removeElementClass(node, "hidden");
swapDOM(node, domnode);
+ postAjaxLoad(domnode);
}
});
d.addCallback(resetCursor);
--- a/web/data/cubicweb.compat.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.compat.js Tue Aug 04 15:06:09 2009 +0200
@@ -247,9 +247,10 @@
if ('name' in params){
try {
var node = document.createElement('<iframe name="'+params['name']+'">');
- }catch (ex) {
- var node = document.createElement('iframe');
- }
+ } catch (ex) {
+ var node = document.createElement('iframe');
+ node.id = node.name = params.name;
+ }
}
else{
var node = document.createElement('iframe');
--- a/web/data/cubicweb.css Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.css Tue Aug 04 15:06:09 2009 +0200
@@ -1,6 +1,6 @@
/*
* :organization: Logilab
- * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
/***************************************/
@@ -12,7 +12,7 @@
padding :0px;
}
-html, body {
+html, body {
background: #e2e2e2;
}
@@ -277,8 +277,8 @@
position: relative;
min-height: 800px;
}
-
-table#mainLayout{
+
+table#mainLayout{
margin:0px 3px;
}
@@ -321,7 +321,7 @@
/* boxes */
div.navboxes {
- margin-top: 8px;
+ margin-top: 8px;
}
div.boxFrame {
@@ -445,28 +445,25 @@
}
div.sideBoxTitle {
- padding: 0.2em 0px;
background: #cfceb7;
display: block;
font: bold 100% Georgia;
}
div.sideBox {
- padding: 0.2em 0px;
+ padding: 0 0 0.2em;
margin-bottom: 0.5em;
- background: #eeedd9;
- min-width: 21em;
- max-width: 50em;
}
-ul.sideBox li{
+ul.sideBox li{
list-style: none;
- background: none;
+ background: none;
padding: 0px 0px 1px 1px;
}
div.sideBoxBody {
padding: 0.2em 5px;
+ background: #eeedd9;
}
div.sideBoxBody a {
@@ -575,8 +572,6 @@
}
div.primaryRight{
- float:right;
-
}
div.metadata {
@@ -603,7 +598,7 @@
padding-bottom:0.4px
}
-div.row span.label{
+div.row span.label{
padding-right:1em
}
@@ -687,7 +682,7 @@
/***************************************/
table.listing {
- margin: 10px 0em;
+ padding: 10px 0em;
color: #000;
width: 100%;
border-right: 1px solid #dfdfdf;
@@ -772,20 +767,20 @@
/* addcombobox */
/***************************************/
-input#newopt{
- width:120px ;
+input#newopt{
+ width:120px ;
display:block;
float:left;
}
-div#newvalue{
+div#newvalue{
margin-top:2px;
}
#add_newopt{
background: #fffff8 url("go.png") 50% 50% no-repeat;
width: 20px;
- line-height: 20px;
+ line-height: 20px;
display:block;
float:left;
}
@@ -794,7 +789,7 @@
/* buttons */
/***************************************/
-input.button{
+input.button{
margin: 1em 1em 0px 0px;
border: 1px solid #edecd2;
border-color:#edecd2 #cfceb7 #cfceb7 #edecd2;
@@ -838,7 +833,7 @@
font-weight: bold;
}
-input.validateButton {
+input.validateButton {
margin: 1em 1em 0px 0px;
border: 1px solid #edecd2;
border-color:#edecd2 #cfceb7 #cfceb7 #edecd2;
--- a/web/data/cubicweb.edition.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.edition.js Tue Aug 04 15:06:09 2009 +0200
@@ -305,7 +305,9 @@
}
function _clearPreviousErrors(formid) {
+ jQuery('#' + formid + 'ErrorMessage').remove();
jQuery('#' + formid + ' span.error').remove();
+ jQuery('#' + formid + ' .error').removeClass('error');
}
function _displayValidationerrors(formid, eid, errors) {
@@ -324,7 +326,7 @@
field.before(span);
} else {
firsterrfield = formid;
- globalerrors.push(fieldname + ': ' + errmsg);
+ globalerrors.push(_(fieldname) + ' : ' + errmsg);
}
}
if (globalerrors.length) {
@@ -334,7 +336,7 @@
var innernode = UL(null, map(LI, globalerrors));
}
// insert DIV and innernode before the form
- var div = DIV({'class' : "errorMessage"});
+ var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'});
div.appendChild(innernode);
jQuery('#' + formid).before(div);
}
@@ -346,11 +348,11 @@
// Success
if (result[0]) {
if (onsuccess) {
- return onsuccess(result[1], formid);
+ onsuccess(result[1], formid);
} else {
document.location.href = result[1];
- return ;
}
+ return;
}
unfreezeFormButtons(formid);
// Failures
@@ -360,7 +362,7 @@
if ( !isArrayLike(descr) || descr.length != 2 ) {
log('got strange error :', descr);
updateMessage(descr);
- return ;
+ return;
}
_displayValidationerrors(formid, descr[0], descr[1]);
updateMessage(_("please correct errors below"));
@@ -368,7 +370,7 @@
if (onfailure){
onfailure(formid);
}
- return false;
+ return;
}
@@ -420,7 +422,7 @@
});
}
-$(document).ready(setFormsTarget);
+jQuery(document).ready(setFormsTarget);
/*
@@ -451,10 +453,12 @@
* @param rtype : the attribute being edited
* @param eid : the eid of the entity being edited
* @param reload: boolean to reload page if true (when changing URL dependant data)
+ * @param default_value : value if the field is empty
+ * @param lzone : html fragment (string) for a clic-zone triggering actual edition
*/
-function inlineValidateAttributeForm(formid, rtype, eid, divid, reload, default_value) {
+function inlineValidateAttributeForm(rtype, eid, divid, reload, default_value) {
try {
- var form = getNode(formid);
+ var form = getNode(divid+'-form');
if (typeof FCKeditorAPI != "undefined") {
for ( var name in FCKeditorAPI.__Instances ) {
var oEditor = FCKeditorAPI.__Instances[name] ;
@@ -471,11 +475,11 @@
return false;
}
d.addCallback(function (result, req) {
- handleFormValidationResponse(formid, noop, noop, result);
+ handleFormValidationResponse(divid+'-form', noop, noop, result);
if (reload) {
- document.location.href = result[1];
+ document.location.href = result[1].split('?')[0];
} else {
- var fieldview = getNode(divid);
+ var fieldview = getNode('value-' + divid);
// XXX using innerHTML is very fragile and won't work if
// we mix XHTML and HTML
fieldview.innerHTML = result[2];
@@ -484,7 +488,7 @@
// hide global error messages
jQuery('div.errorMessage').remove();
jQuery('#appMsg').hide();
- cancelInlineEdit(eid, rtype, divid);
+ hideInlineEdit(eid, rtype, divid);
}
}
return false;
@@ -492,30 +496,30 @@
return false;
}
-function inlineValidateRelationForm(formid, rtype, role, eid, divid, vid, default_value) {
+function inlineValidateRelationForm(rtype, role, eid, divid, reload, vid,
+ default_value, lzone) {
try {
- var form = getNode(formid);
+ var form = getNode(divid+'-form');
var relname = rtype + ':' + eid;
var newtarget = jQuery('[name=' + relname + ']').val();
var zipped = formContents(form);
- var d = asyncRemoteExec('edit_relation', 'apply', zipped[0], zipped[1], rtype, role,
- eid, vid, default_value);
+ var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]);
} catch (ex) {
log('got exception', ex);
return false;
}
d.addCallback(function (result, req) {
- handleFormValidationResponse(formid, noop, noop, result);
- var fieldview = getNode(divid);
- fieldview.innerHTML = result[2];
- // switch inline form off only if no error
- if (result[0]) {
- // hide global error messages
- jQuery('div.errorMessage').remove();
- jQuery('#appMsg').hide();
- var inputname = 'edit' + role[0] + '-' + relname;
- jQuery('input[name=' + inputname + ']').val(newtarget);
- cancelInlineEdit(eid, rtype, divid);
+ handleFormValidationResponse(divid+'-form', noop, noop, result);
+ if (reload) {
+ document.location.href = result[1];
+ } else {
+ if (result[0]) {
+ var d = asyncRemoteExec('reledit_form', eid, rtype, role, default_value, lzone);
+ d.addCallback(function (result) {
+ // XXX brittle ... replace with loadxhtml
+ jQuery('#'+divid+'-reledit').replaceWith(result);
+ });
+ }
}
return false;
});
@@ -526,12 +530,12 @@
/**** inline edition ****/
function showInlineEditionForm(eid, rtype, divid) {
jQuery('#' + divid).hide();
- jQuery('#' + divid + '-form').show();
+ jQuery('#' + divid+'-form').show();
}
-function cancelInlineEdit(eid, rtype, divid) {
+function hideInlineEdit(eid, rtype, divid) {
jQuery('#' + divid).show();
- jQuery('#' + divid + '-form').hide();
+ jQuery('#' + divid+'-form').hide();
}
CubicWeb.provide('edition.js');
--- a/web/data/cubicweb.facets.css Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.facets.css Tue Aug 04 15:06:09 2009 +0200
@@ -1,7 +1,7 @@
#filterbox fieldset{
margin: 0px;
padding: 0px;
-}
+}
div.facet {
margin-bottom: 8px;
@@ -14,11 +14,11 @@
font-size: 80%;
color: #000;
margin-bottom: 2px;
- cursor: pointer;
+ cursor: pointer;
font: bold 100% Georgia;
}
-div.facetTitle a {
+div.facetTitle a {
padding-left: 10px;
background: transparent url("puce.png") 0% 50% no-repeat;
}
@@ -26,12 +26,12 @@
div.facetBody {
}
-.opened{
- color: #000 !important;
+.opened{
+ color: #000 !important;
}
div.overflowed{
- height: 12em;
+ height: 12em;
overflow-y: auto;
}
@@ -50,12 +50,12 @@
}
div.facetValue img{
- float: left;
+ float: left;
background: #fff;
}
div.facetValue a {
- margin-left: 20px;
+ margin-left: 20px;
display: block;
margin-top: -6px; /* FIXME why do we need this ? */
}
@@ -78,11 +78,11 @@
}
-div.facetCheckBox{
+div.facetCheckBox{
line-height:0.8em;
}
-.facet input{
+.facet input{
margin-top:3px;
border:1px solid #ccc;
font-size:11px;
--- a/web/data/cubicweb.flot.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.flot.js Tue Aug 04 15:06:09 2009 +0200
@@ -1,32 +1,34 @@
function showTooltip(x, y, contents) {
$('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
- display: 'none',
- top: y + 5,
+ display: 'none',
+ top: y + 5,
left: x + 5,
border: '1px solid #fdd',
padding: '2px',
'background-color': '#fee',
opacity: 0.80
- }).appendTo("body").fadeIn(200);
+ }).appendTo("body").fadeIn(200);
}
var previousPoint = null;
function onPlotHover(event, pos, item) {
if (item) {
if (previousPoint != item.datapoint) {
- previousPoint = item.datapoint;
-
- $("#tooltip").remove();
- var x = item.datapoint[0].toFixed(2),
- y = item.datapoint[1].toFixed(2);
- if (item.datapoint.length == 3) {
- var x = new Date(item.datapoint[2]);
- x = x.toLocaleDateString() + ' ' + x.toLocaleTimeString();
- }
- showTooltip(item.pageX, item.pageY,
- item.series.label + ': (' + x + ' ; ' + y + ')');
+ previousPoint = item.datapoint;
+ $("#tooltip").remove();
+ var x = item.datapoint[0].toFixed(2),
+ y = item.datapoint[1].toFixed(2);
+ if (item.datapoint.length == 3) {
+ x = new Date(item.datapoint[2]);
+ x = x.toLocaleDateString() + ' ' + x.toLocaleTimeString();
+ } else if (item.datapoint.length == 4) {
+ x = new Date(item.datapoint[2]);
+ x = x.strftime(item.datapoint[3]);
}
+ showTooltip(item.pageX, item.pageY,
+ item.series.label + ': (' + x + ' ; ' + y + ')');
+ }
} else {
$("#tooltip").remove();
previousPoint = null;
--- a/web/data/cubicweb.formfilter.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.formfilter.js Tue Aug 04 15:06:09 2009 +0200
@@ -126,27 +126,35 @@
});
facet.find('div.facetCheckBox').click(function () {
var $this = jQuery(this);
+ if ($this.hasClass('facetValueDisabled')){
+ return
+ }
if ($this.hasClass('facetValueSelected')) {
$this.removeClass('facetValueSelected');
$this.find('img').each(function (i){
if (this.getAttribute('cubicweb:unselimg')){
this.setAttribute('src', UNSELECTED_BORDER_IMG);
+ this.setAttribute('alt', (_('not selected')));
}
else{
this.setAttribute('src', UNSELECTED_IMG);
+ this.setAttribute('alt', (_('not selected')));
}
});
var index = parseInt($this.attr('cubicweb:idx'));
- var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
- var nindex = parseInt(n.getAttribute('cubicweb:idx'));
- return nindex > index;
- }).length;
- index += shift;
- var parent = this.parentNode;
- var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
- if ( ! ($insertAfter.length == 1 && index == 0) ) {
- // only rearrange element if necessary
- $insertAfter.after(this);
+ // we dont need to move the element when cubicweb:idx == 0
+ if (index > 0){
+ var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) {
+ var nindex = parseInt(n.getAttribute('cubicweb:idx'));
+ return nindex > index;
+ }).length;
+ index += shift;
+ var parent = this.parentNode;
+ var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')');
+ if ( ! ($insertAfter.length == 1 && shift == 0) ) {
+ // only rearrange element if necessary
+ $insertAfter.after(this);
+ }
}
} else {
var lastSelected = facet.find('.facetValueSelected:last');
@@ -157,7 +165,8 @@
jQuery(parent).prepend(this);
}
jQuery(this).addClass('facetValueSelected');
- jQuery(this).find('img').attr('src', SELECTED_IMG);
+ var $img = jQuery(this).find('img');
+ $img.attr('src', SELECTED_IMG).attr('alt', (_('selected')));
}
buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs')));
facet.find('.facetBody').animate({scrollTop: 0}, '');
--- a/web/data/cubicweb.htmlhelpers.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.htmlhelpers.js Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,5 @@
CubicWeb.require('python.js');
+CubicWeb.require('jquery.corner.js');
/* returns the document's baseURI. (baseuri() uses document.baseURI if
* available and inspects the <base> tag manually otherwise.)
@@ -15,86 +16,16 @@
return '';
}
-function insertText(text, areaId) {
- var textarea = jQuery('#' + areaId);
- if (document.selection) { // IE
- var selLength;
- textarea.focus();
- sel = document.selection.createRange();
- selLength = sel.text.length;
- sel.text = text;
- sel.moveStart('character', selLength-text.length);
- sel.select();
- } else if (textarea.selectionStart || textarea.selectionStart == '0') { // mozilla
- var startPos = textarea.selectionStart;
- var endPos = textarea.selectionEnd;
- // insert text so that it replaces the [startPos, endPos] part
- textarea.value = textarea.value.substring(0,startPos) + text + textarea.value.substring(endPos,textarea.value.length);
- // set cursor pos at the end of the inserted text
- textarea.selectionStart = textarea.selectionEnd = startPos+text.length;
- textarea.focus();
- } else { // safety belt for other browsers
- textarea.value += text;
- }
-}
-/* taken from dojo toolkit */
-function setCaretPos(element, start, end){
- if(!end){ end = element.value.length; } // NOTE: Strange - should be able to put caret at start of text?
- // Mozilla
- // parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
- if(element.setSelectionRange){
- element.focus();
- element.setSelectionRange(start, end);
- } else if(element.createTextRange){ // IE
- var range = element.createTextRange();
- with(range){
- collapse(true);
- moveEnd('character', end);
- moveStart('character', start);
- select();
- }
- } else { //otherwise try the event-creation hack (our own invention)
- // do we need these?
- element.value = element.value;
- element.blur();
- element.focus();
- // figure out how far back to go
- var dist = parseInt(element.value.length)-end;
- var tchar = String.fromCharCode(37);
- var tcc = tchar.charCodeAt(0);
- for(var x = 0; x < dist; x++){
- var te = document.createEvent("KeyEvents");
- te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc);
- element.dispatchEvent(te);
- }
- }
-}
-
-function setProgressMessage(label) {
- var body = document.getElementsByTagName('body')[0];
- body.appendChild(DIV({id: 'progress'}, label));
- jQuery('#progress').show();
-}
-
-function resetProgressMessage() {
- var body = document.getElementsByTagName('body')[0];
- jQuery('#progress').hide();
-}
-
-
-/* set body's cursor to 'progress'
- */
+/* set body's cursor to 'progress' */
function setProgressCursor() {
var body = document.getElementsByTagName('body')[0];
body.style.cursor = 'progress';
}
-/*
- * reset body's cursor to default (mouse cursor). The main
+/* reset body's cursor to default (mouse cursor). The main
* purpose of this function is to be used as a callback in the
- * deferreds' callbacks chain.
- */
+ * deferreds' callbacks chain. */
function resetCursor(result) {
var body = document.getElementsByTagName('body')[0];
body.style.cursor = 'default';
@@ -137,7 +68,7 @@
*/
function firstSelected(selectNode) {
var selection = filter(attrgetter('selected'), selectNode.options);
- return (selection.length>0) ? getNodeAttribute(selection[0], 'value'):null;
+ return (selection.length > 0) ? getNodeAttribute(selection[0], 'value'):null;
}
/* toggle visibility of an element by its id
@@ -148,22 +79,12 @@
/* toggles visibility of login popup div */
+// XXX used exactly ONCE in basecomponents
function popupLoginBox() {
toggleVisibility('popupLoginBox');
jQuery('#__login:visible').focus();
}
-/*
- * return true (resp. false) if <element> (resp. doesn't) matches <properties>
- */
-function elementMatches(properties, element) {
- for (prop in properties) {
- if (getNodeAttribute(element, prop) != properties[prop]) {
- return false;
- }
- }
- return true;
-}
/* returns the list of elements in the document matching the tag name
* and the properties provided
@@ -174,9 +95,13 @@
* list() function)
*/
function getElementsMatching(tagName, properties, /* optional */ parent) {
- var filterfunc = partial(elementMatches, properties);
parent = parent || document;
- return filter(filterfunc, parent.getElementsByTagName(tagName));
+ return filter(function elementMatches(element) {
+ for (prop in properties) {
+ if (getNodeAttribute(element, prop) != properties[prop]) {
+ return false;}}
+ return true;},
+ parent.getElementsByTagName(tagName));
}
/*
@@ -196,15 +121,6 @@
forEach(filter(filterfunc, elements), function(cb) {cb.checked=checked;});
}
-/*
- * centers an HTML element on the screen
- */
-function centerElement(obj){
- var vpDim = getViewportDimensions();
- var elemDim = getElementDimensions(obj);
- setElementPosition(obj, {'x':((vpDim.w - elemDim.w)/2),
- 'y':((vpDim.h - elemDim.h)/2)});
-}
/* this function is a hack to build a dom node from html source */
function html2dom(source) {
@@ -227,18 +143,6 @@
function isTextNode(domNode) { return domNode.nodeType == 3; }
function isElementNode(domNode) { return domNode.nodeType == 1; }
-function changeLinkText(link, newText) {
- jQuery(link).text(newText);
-// for (var i=0; i<link.childNodes.length; i++) {
-// var node = link.childNodes[i];
-// if (isTextNode(node)) {
-// swapDOM(node, document.createTextNode(newText));
-// break;
-// }
-// }
-}
-
-
function autogrow(area) {
if (area.scrollHeight > area.clientHeight && !window.opera) {
if (area.rows < 20) {
@@ -246,15 +150,22 @@
}
}
}
+//============= page loading events ==========================================//
-//============= page loading events ==========================================//
-function roundedCornersOnLoad() {
- jQuery('div.sideBox').corner('bottom 6px');
- jQuery('div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month').corner('top 6px');
+CubicWeb.rounded = [
+ ['div.sideBoxBody', 'bottom 6px'],
+ ['div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month', 'top 6px']
+ ];
+
+function roundedCorners(node) {
+ node = jQuery(node);
+ for(var r=0; r < CubicWeb.rounded.length; r++) {
+ node.find(CubicWeb.rounded[r][0]).corner(CubicWeb.rounded[r][1]);
+ }
}
-jQuery(document).ready(roundedCornersOnLoad);
+jQuery(document).ready(function () {roundedCorners(this.body);});
+CubicWeb.provide('corners.js');
CubicWeb.provide('htmlhelpers.js');
-
--- a/web/data/cubicweb.login.css Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.login.css Tue Aug 04 15:06:09 2009 +0200
@@ -1,7 +1,7 @@
/* styles for the login popup and login form
*
* :organization: Logilab
- * :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+ * :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
*/
@@ -12,7 +12,7 @@
width: 26em;
padding: 0px 1px 1px;
font-weight: bold;
- background: #E4EAD8;
+ background: #E4EAD8;
}
div#popupLoginBox div#loginContent {
@@ -20,7 +20,7 @@
padding: 5px 3px 4px;
}
-div#loginBox {
+div#loginBox {
position : absolute;
top: 15%;
left : 50%;
@@ -32,12 +32,12 @@
text-align: center;
}
-div#loginBox h1 {
+div#loginBox h1 {
color: #FF7700;
font-size: 140%;
}
-div#loginTitle {
+div#loginTitle {
color: #fff;
font-weight: bold;
font-size: 140%;
@@ -46,32 +46,32 @@
background: #ff7700 url("banner.png") left top repeat-x;
}
-div#loginBox div#loginContent form {
+div#loginBox div#loginContent form {
padding-top: 1em;
- width: 90%;
- margin: auto;
+ width: 90%;
+ margin: auto;
}
-#popupLoginBox table td {
- padding: 0px 3px;
+#popupLoginBox table td {
+ padding: 0px 3px;
white-space: nowrap;
}
-#loginContent table {
+#loginContent table {
padding: 0px 0.5em;
margin: auto;
}
-#loginBox table td {
- padding: 0px 3px 0.6em;
+#loginBox table td {
+ padding: 0px 3px 0.6em;
white-space: nowrap;
}
-#loginBox .loginButton {
+#loginBox .loginButton {
margin-top: 0.6em;
}
-#loginContent input.data {
+#loginContent input.data {
width:12em;
}
@@ -79,5 +79,5 @@
border: 1px solid #edecd2;
border-color:#edecd2 #cfceb7 #cfceb7 #edecd2;
margin: 2px 0px 0px;
- background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
+ background: #f0eff0 url("gradient-grey-up.png") left top repeat-x;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/cubicweb.massmailing.js Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,23 @@
+
+function insertText(text, areaId) {
+ var textarea = jQuery('#' + areaId);
+ if (document.selection) { // IE
+ var selLength;
+ textarea.focus();
+ sel = document.selection.createRange();
+ selLength = sel.text.length;
+ sel.text = text;
+ sel.moveStart('character', selLength-text.length);
+ sel.select();
+ } else if (textarea.selectionStart || textarea.selectionStart == '0') { // mozilla
+ var startPos = textarea.selectionStart;
+ var endPos = textarea.selectionEnd;
+ // insert text so that it replaces the [startPos, endPos] part
+ textarea.value = textarea.value.substring(0,startPos) + text + textarea.value.substring(endPos,textarea.value.length);
+ // set cursor pos at the end of the inserted text
+ textarea.selectionStart = textarea.selectionEnd = startPos+text.length;
+ textarea.focus();
+ } else { // safety belt for other browsers
+ textarea.value += text;
+ }
+}
--- a/web/data/cubicweb.preferences.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.preferences.js Tue Aug 04 15:06:09 2009 +0200
@@ -4,6 +4,8 @@
* move me in a more appropriate place
*/
+var prefsValues = {};
+
function togglePrefVisibility(elemId) {
clearPreviousMessages();
jQuery('#' + elemId).toggleClass('hidden');
@@ -21,7 +23,6 @@
_toggleFieldset(fieldsetid, 0, linklabel, linkhref);
}
-
function _toggleFieldset(fieldsetid, closeaction, linklabel, linkhref){
jQuery('#'+fieldsetid).find('div.openlink').each(function(){
var link = A({'href' : "javascript:noop();",
@@ -75,7 +76,6 @@
jQuery('#err-value:' + formid).remove();
}
-
function checkValues(form, success){
var unfreezeButtons = false;
jQuery(form).find('select').each(function () {
@@ -100,8 +100,8 @@
}
function _checkValue(input, unfreezeButtons){
- var currentValueInput = jQuery("input[name=current-" + input.attr('name') + "]");
- if (currentValueInput.val() != input.val()){
+ var currentValue = prefsValues[input.attr('name')];
+ if (currentValue != input.val()){
input.addClass('changed');
unfreezeButtons = true;
}else{
@@ -112,45 +112,42 @@
return unfreezeButtons;
}
-
function setCurrentValues(form){
- jQuery(form).find('input[name^=current-value]').each(function () {
- var currentValueInput = jQuery(this);
- var name = currentValueInput.attr('name').split('-')[1];
- jQuery(form).find("[name=" + name + "]").each(function (){
- var input = jQuery(this);
- if(input.attr('type') == 'radio'){
- // NOTE: there seems to be a bug with jQuery(input).attr('checked')
- // in our case, we can't rely on its value, we use
- // the DOM API instead.
- if(input[0].checked){
- currentValueInput.val(input.val());
- }
- }else{
- currentValueInput.val(input.val());
- }
- });
+ jQuery(form).find('[name^=value]').each(function () {
+ var input = jQuery(this);
+ var name = input.attr('name');
+ if(input.attr('type') == 'radio'){
+ // NOTE: there seems to be a bug with jQuery(input).attr('checked')
+ // in our case, we can't rely on its value, we use
+ // the DOM API instead.
+ if(input[0].checked){
+ prefsValues[name] = input.val();
+ }
+ }else{
+ prefsValues[name] = input.val();
+ }
+ jQuery(form).find('input[name=edits-'+ name + ']').val(prefsValues[name]);
});
}
function initEvents(){
- jQuery('form').each(function() {
- var form = jQuery(this);
- freezeFormButtons(form.attr('id'));
- form.find('input[type=text]').keyup(function(){
- checkValues(form);
- });
- form.find('input[type=radio]').change(function(){
- checkValues(form);
- });
- form.find('select').change(function(){
- checkValues(form);
- });
- setCurrentValues(form);
+ jQuery('form').each(function() {
+ var form = jQuery(this);
+ //freezeFormButtons(form.attr('id'));
+ form.find('input.validateButton').attr('disabled', 'disabled');
+ form.find('input[type=text]').keyup(function(){
+ checkValues(form);
+ });
+ form.find('input[type=radio]').change(function(){
+ checkValues(form);
+ });
+ form.find('select').change(function(){
+ checkValues(form);
+ });
+ setCurrentValues(form);
});
}
$(document).ready(function() {
initEvents();
});
-
--- a/web/data/cubicweb.python.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.python.js Tue Aug 04 15:06:09 2009 +0200
@@ -15,12 +15,12 @@
return true;
}
return false;
-}
+};
Date.prototype.add = function(days) {
- var res = new Date()
- res.setTime(this.getTime() + (days * ONE_DAY))
- return res
+ var res = new Date();
+ res.setTime(this.getTime() + (days * ONE_DAY));
+ return res;
};
Date.prototype.sub = function(days) {
@@ -29,14 +29,14 @@
Date.prototype.iadd = function(days) {
// in-place add
- this.setTime(this.getTime() + (days * ONE_DAY))
+ this.setTime(this.getTime() + (days * ONE_DAY));
// avoid strange rounding problems !!
this.setHours(12);
};
Date.prototype.isub = function(days) {
// in-place sub
- this.setTime(this.getTime() - (days * ONE_DAY))
+ this.setTime(this.getTime() - (days * ONE_DAY));
};
/*
@@ -157,7 +157,7 @@
// ========== ARRAY EXTENSIONS ========== ///
Array.prototype.contains = function(element) {
return findValue(this, element) != -1;
-}
+};
// ========== END OF ARRAY EXTENSIONS ========== ///
@@ -170,14 +170,14 @@
*/
String.prototype.startsWith = function(prefix) {
return this.indexOf(prefix) == 0;
-}
+};
/* python-like endsWith method for js strings */
String.prototype.endsWith = function(suffix) {
var startPos = this.length - suffix.length;
if (startPos < 0) { return false; }
return this.lastIndexOf(suffix, startPos) == startPos;
-}
+};
/* python-like strip method for js strings */
String.prototype.strip = function() {
@@ -187,12 +187,12 @@
/* py-equiv: string in list */
String.prototype.in_ = function(values) {
return findValue(values, this) != -1;
-}
+};
/* py-equiv: str.join(list) */
String.prototype.join = function(args) {
return args.join(this);
-}
+};
/* python-like list builtin
* transforms an iterable in a js sequence
@@ -201,7 +201,7 @@
* [0,2,4,6,8]
*/
function list(iterable) {
- iterator = iter(iterable);
+ var iterator = iter(iterable);
var result = [];
while (true) {
/* iterates until StopIteration occurs */
@@ -267,14 +267,6 @@
function min() { return listMin(arguments); }
function max() { return listMax(arguments); }
-// tricky multiple assign
-// function assign(lst, varnames) {
-// var length = min(lst.length, varnames.length);
-// for(var i=0; i<length; i++) {
-// window[varnames[i]] = lst[i];
-// }
-// }
-
/*
* >>> d = dict(["x", "y", "z"], [0, 1, 2])
* >>> d['y']
@@ -335,7 +327,7 @@
function makeConstructor(userctor) {
return function() {
// this is a proxy to user's __init__
- if(userctor) {
+ if (userctor) {
userctor.apply(this, arguments);
}
};
@@ -369,7 +361,7 @@
}
}
var userctor = basemeths['__init__'];
- constructor = makeConstructor(userctor);
+ var constructor = makeConstructor(userctor);
// python-like interface
constructor.__name__ = name;
--- a/web/data/cubicweb.timeline-bundle.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.timeline-bundle.js Tue Aug 04 15:06:09 2009 +0200
@@ -80,7 +80,7 @@
SimileAjax.includeJavascriptFile(doc, urlPrefix + filenames[i]);
}
SimileAjax.loadingScriptsCount += filenames.length;
- SimileAjax.includeJavascriptFile(doc, SimileAjax.urlPrefix + "scripts/signal.js?" + filenames.length);
+ // XXX adim SimileAjax.includeJavascriptFile(doc, SimileAjax.urlPrefix + "scripts/signal.js?" + filenames.length);
};
SimileAjax.includeCssFile = function(doc, url) {
if (doc.body == null) {
@@ -208,7 +208,7 @@
// } else {
// SimileAjax.includeJavascriptFiles(document, SimileAjax.urlPrefix + "scripts/", javascriptFiles);
// }
- SimileAjax.includeCssFiles(document, SimileAjax.urlPrefix + "styles/", cssFiles);
+// SimileAjax.includeCssFiles(document, SimileAjax.urlPrefix + "styles/", cssFiles);
SimileAjax.loaded = true;
})();
@@ -3830,8 +3830,8 @@
includeJavascriptFiles(Timeline.urlPrefix, [ "timeline-bundle.js" ]);
includeCssFiles(Timeline.urlPrefix, [ "timeline-bundle.css" ]);
} else {
- includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
- includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
+ // XXX adim includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
+ // XXX adim includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
}
/*
@@ -3879,8 +3879,8 @@
for (var l = 0; l < supportedLocales.length; l++) {
var locale = supportedLocales[l];
if (loadLocale[locale]) {
- includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
- includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
+ // XXX adim includeJavascriptFiles(Timeline.urlPrefix + "scripts/l10n/" + locale + "/", localizedJavascriptFiles);
+ // XXX adim includeCssFiles(Timeline.urlPrefix + "styles/l10n/" + locale + "/", localizedCssFiles);
}
}
--- a/web/data/cubicweb.widgets.js Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/cubicweb.widgets.js Tue Aug 04 15:06:09 2009 +0200
@@ -184,8 +184,7 @@
Widgets.TreeView = defclass("TreeView", null, {
__init__: function(wdgnode) {
jQuery(wdgnode).treeview({toggle: toggleTree,
- prerendered: true
- });
+ prerendered: true});
}
});
--- a/web/data/external_resources Tue Aug 04 11:43:03 2009 +0200
+++ b/web/data/external_resources Tue Aug 04 15:06:09 2009 +0200
@@ -18,7 +18,7 @@
#IE_STYLESHEETS = DATADIR/cubicweb.ie.css
# Javascripts files to include in HTML headers
-#JAVASCRIPTS = DATADIR/jqyery.js, DATADIR/cubicweb.python.js, DATADIR/jquery.json.js, DATADIR/cubicweb.compat.js, DATADIR/cubicweb.htmlhelpers.js
+#JAVASCRIPTS = DATADIR/jquery.js, DATADIR/cubicweb.python.js, DATADIR/jquery.json.js, DATADIR/cubicweb.compat.js, DATADIR/cubicweb.htmlhelpers.js
# path to favicon (relative to the application main script, seen as a
# directory, hence .. when you are not using an absolute path)
--- a/web/data/goa.js Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-/*
- * functions specific to ginco on google appengine
- *
- * :organization: Logilab
- * :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
- * :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
- */
-
-/* overrides rql_for_eid function from htmlhelpers.hs */
-function rql_for_eid(eid) { return 'Any X WHERE X eid "' + eid + '"'; }
--- a/web/facet.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/facet.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,9 +10,9 @@
from itertools import chain
from copy import deepcopy
-from datetime import date
+from datetime import date, datetime, timedelta
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from logilab.common.graph import has_path
from logilab.common.decorators import cached
@@ -21,6 +21,7 @@
from rql import parse, nodes
from cubicweb import Unauthorized, typed_eid
+from cubicweb.schema import display_name
from cubicweb.utils import datetime2ticks, make_uid, ustrftime
from cubicweb.selectors import match_context_prop, partial_relation_possible
from cubicweb.appobject import AppRsetObject
@@ -70,7 +71,7 @@
def filter_hiddens(w, **kwargs):
for key, val in kwargs.items():
w(u'<input type="hidden" name="%s" value="%s" />' % (
- key, html_escape(val)))
+ key, xml_escape(val)))
def _may_be_removed(rel, schema, mainvar):
@@ -586,11 +587,11 @@
self.items.append(item)
def _render(self):
- title = html_escape(self.facet.title)
- facetid = html_escape(self.facet.id)
+ title = xml_escape(self.facet.title)
+ facetid = xml_escape(self.facet.id)
self.w(u'<div id="%s" class="facet">\n' % facetid)
self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
- (html_escape(facetid), title))
+ (xml_escape(facetid), title))
if self.facet.support_and():
_ = self.facet.req._
self.w(u'''<select name="%s" class="radio facetOperator" title="%s">
@@ -616,8 +617,8 @@
self.value = None
def _render(self):
- title = html_escape(self.facet.title)
- facetid = html_escape(self.facet.id)
+ title = xml_escape(self.facet.title)
+ facetid = xml_escape(self.facet.id)
self.w(u'<div id="%s" class="facet">\n' % facetid)
self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
(facetid, title))
@@ -660,7 +661,7 @@
facet.req.add_js('ui.slider.js')
facet.req.add_css('ui.all.css')
sliderid = make_uid('the slider')
- facetid = html_escape(self.facet.id)
+ facetid = xml_escape(self.facet.id)
facet.req.html_headers.add_onload(self.onload % {
'sliderid': sliderid,
'facetid': facetid,
@@ -668,7 +669,7 @@
'maxvalue': self.maxvalue,
'formatter': self.formatter,
})
- title = html_escape(self.facet.title)
+ title = xml_escape(self.facet.title)
self.w(u'<div id="%s" class="facet">\n' % facetid)
self.w(u'<div class="facetTitle" cubicweb:facetName="%s">%s</div>\n' %
(facetid, title))
@@ -683,8 +684,15 @@
class DateFacetRangeWidget(FacetRangeWidget):
+
formatter = 'function (value) {return (new Date(parseFloat(value))).strftime(DATE_FMT);}'
+
+ def round_max_value(self, d):
+ 'round to upper value to avoid filtering out the max value'
+ return datetime(d.year, d.month, d.day) + timedelta(days=1)
+
def __init__(self, facet, minvalue, maxvalue):
+ maxvalue = self.round_max_value(maxvalue)
super(DateFacetRangeWidget, self).__init__(facet,
datetime2ticks(minvalue),
datetime2ticks(maxvalue))
@@ -713,9 +721,9 @@
imgsrc = self.req.datadir_url + self.unselected_img
imgalt = self.req._('not selected')
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
- % (cssclass, html_escape(unicode(self.value))))
+ % (cssclass, xml_escape(unicode(self.value))))
self.w(u'<img src="%s" alt="%s"/> ' % (imgsrc, imgalt))
- self.w(u'<a href="javascript: {}">%s</a>' % html_escape(self.label))
+ self.w(u'<a href="javascript: {}">%s</a>' % xml_escape(self.label))
self.w(u'</div>')
class CheckBoxFacetWidget(HTMLWidget):
@@ -729,8 +737,8 @@
self.selected = selected
def _render(self):
- title = html_escape(self.facet.title)
- facetid = html_escape(self.facet.id)
+ title = xml_escape(self.facet.title)
+ facetid = xml_escape(self.facet.id)
self.w(u'<div id="%s" class="facet">\n' % facetid)
if self.selected:
cssclass = ' facetValueSelected'
@@ -741,10 +749,10 @@
imgsrc = self.req.datadir_url + self.unselected_img
imgalt = self.req._('not selected')
self.w(u'<div class="facetValue facetCheckBox%s" cubicweb:value="%s">\n'
- % (cssclass, html_escape(unicode(self.value))))
+ % (cssclass, xml_escape(unicode(self.value))))
self.w(u'<div class="facetCheckBoxWidget">')
self.w(u'<img src="%s" alt="%s" cubicweb:unselimg="true" /> ' % (imgsrc, imgalt))
- self.w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>' % (facetid,title))
+ self.w(u'<label class="facetTitle" cubicweb:facetName="%s"><a href="javascript: {}">%s</a></label>' % (facetid, title))
self.w(u'</div>\n')
self.w(u'</div>\n')
self.w(u'</div>\n')
--- a/web/formfields.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/formfields.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,13 +7,14 @@
"""
__docformat__ = "restructuredtext en"
+from warnings import warn
from datetime import datetime
-from logilab.mtconverter import html_escape
-from yams.constraints import SizeConstraint, StaticVocabularyConstraint
+from logilab.mtconverter import xml_escape
+from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
+ FormatConstraint)
-from cubicweb.schema import FormatConstraint
-from cubicweb.utils import ustrftime
+from cubicweb.utils import ustrftime, compute_cardinality
from cubicweb.common import tags, uilib
from cubicweb.web import INTERNAL_FIELD_VALUE
from cubicweb.web.formwidgets import (
@@ -62,6 +63,8 @@
:role:
when the field is linked to an entity attribute or relation, tells the
role of the entity in the relation (eg 'subject' or 'object')
+ :fieldset:
+ optional fieldset to which this field belongs to
"""
# default widget associated to this class of fields. May be overriden per
@@ -75,7 +78,7 @@
def __init__(self, name=None, id=None, label=None, help=None,
widget=None, required=False, initial=None,
choices=None, sort=True, internationalizable=False,
- eidparam=False, role='subject'):
+ eidparam=False, role='subject', fieldset=None):
self.name = name
self.id = id or name
self.label = label or name
@@ -87,6 +90,7 @@
self.internationalizable = internationalizable
self.eidparam = eidparam
self.role = role
+ self.fieldset = fieldset
self.init_widget(widget)
# ordering number for this field instance
self.creation_rank = Field.__creation_rank
@@ -101,10 +105,10 @@
return self.__unicode__().encode('utf-8')
def init_widget(self, widget):
- if widget is None and self.choices:
- widget = Select()
if widget is not None:
self.widget = widget
+ elif self.choices and not self.widget.vocabulary_widget:
+ self.widget = Select()
if isinstance(self.widget, type):
self.widget = self.widget()
@@ -157,14 +161,25 @@
"""render this field, which is part of form, using the given form
renderer
"""
- return self.get_widget(form).render(form, self)
+ widget = self.get_widget(form)
+ try:
+ return widget.render(form, self, renderer)
+ except TypeError:
+ warn('widget.render now take the renderer as third argument, please update %s implementation'
+ % widget.__class__.__name__, DeprecationWarning)
+ return widget.render(form, self)
def vocabulary(self, form):
"""return vocabulary for this field. This method will be called by
widgets which desire it."""
if self.choices is not None:
if callable(self.choices):
- vocab = self.choices(req=form.req)
+ try:
+ vocab = self.choices(form=form)
+ except TypeError:
+ warn('vocabulary method (eg field.choices) should now take '
+ 'the form instance as argument', DeprecationWarning)
+ vocab = self.choices(req=form.req)
else:
vocab = self.choices
if vocab and not isinstance(vocab[0], (list, tuple)):
@@ -197,9 +212,17 @@
widget = Select()
elif self.max_length and self.max_length < 257:
widget = TextInput()
+
super(StringField, self).init_widget(widget)
if isinstance(self.widget, TextArea):
self.init_text_area(self.widget)
+ elif isinstance(self.widget, TextInput):
+ self.init_text_input(self.widget)
+
+ def init_text_input(self, widget):
+ if self.max_length:
+ widget.attrs.setdefault('size', min(45, self.max_length))
+ widget.attrs.setdefault('maxlength', self.max_length)
def init_text_area(self, widget):
if self.max_length < 513:
@@ -213,6 +236,9 @@
super(RichTextField, self).__init__(**kwargs)
self.format_field = format_field
+ def init_text_area(self, widget):
+ pass
+
def get_widget(self, form):
if self.widget is None:
if self.use_fckeditor(form):
@@ -270,7 +296,7 @@
result = format_field.render(form, renderer)
else:
result = u''
- return result + self.get_widget(form).render(form, self)
+ return result + self.get_widget(form).render(form, self, renderer)
class FileField(StringField):
@@ -290,13 +316,13 @@
yield self.encoding_field
def render(self, form, renderer):
- wdgs = [self.get_widget(form).render(form, self)]
+ wdgs = [self.get_widget(form).render(form, self, renderer)]
if self.format_field or self.encoding_field:
divid = '%s-advanced' % form.context[self]['name']
wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
- (html_escape(uilib.toggle_action(divid)),
+ (xml_escape(uilib.toggle_action(divid)),
form.req._('show advanced fields'),
- html_escape(form.req.build_url('data/puce_down.png')),
+ xml_escape(form.req.build_url('data/puce_down.png')),
form.req._('show advanced fields')))
wdgs.append(u'<div id="%s" class="hidden">' % divid)
if self.format_field:
@@ -344,7 +370,7 @@
'You can either submit a new file using the browse button above'
', or edit file content online with the widget below.')
wdgs.append(u'<p><b>%s</b></p>' % msg)
- wdgs.append(TextArea(setdomid=False).render(form, self))
+ wdgs.append(TextArea(setdomid=False).render(form, self, renderer))
# XXX restore form context?
return '\n'.join(wdgs)
@@ -376,7 +402,7 @@
return formatstr % float(value)
def render_example(self, req):
- return self.format_value(req, 1.234)
+ return self.format_single_value(req, 1.234)
class DateField(StringField):
@@ -387,7 +413,7 @@
return value and ustrftime(value, req.property_value(self.format_prop)) or u''
def render_example(self, req):
- return self.format_value(req, datetime.now())
+ return self.format_single_value(req, datetime.now())
class DateTimeField(DateField):
@@ -395,7 +421,7 @@
class TimeField(DateField):
- format_prop = 'ui.datetime-format'
+ format_prop = 'ui.time-format'
widget = TextInput
@@ -406,6 +432,9 @@
name=name, widget=HiddenInput, eidparam=True)
self.visible_field = visible_field
+ def format_single_value(self, req, value):
+ return self.visible_field.format_single_value(req, value)
+
class RelationField(Field):
def __init__(self, **kwargs):
@@ -444,6 +473,18 @@
return value
+class CompoundField(Field):
+ def __init__(self, fields, *args, **kwargs):
+ super(CompoundField, self).__init__(*args, **kwargs)
+ self.fields = fields
+
+ def subfields(self, form):
+ return self.fields
+
+ def actual_fields(self, form):
+ return [self] + list(self.fields)
+
+
def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
"""return the most adapated widget to edit the relation
'subjschema rschema objschema' according to information found in the schema
@@ -451,21 +492,21 @@
fieldclass = None
if role == 'subject':
targetschema = rschema.objects(eschema)[0]
- card = rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+ card = compute_cardinality(eschema, rschema, role)
help = rschema.rproperty(eschema, targetschema, 'description')
if rschema.is_final():
if rschema.rproperty(eschema, targetschema, 'internationalizable'):
- kwargs['internationalizable'] = True
+ kwargs.setdefault('internationalizable', True)
def get_default(form, es=eschema, rs=rschema):
return es.default(rs)
- kwargs['initial'] = get_default
+ kwargs.setdefault('initial', get_default)
else:
targetschema = rschema.subjects(eschema)[0]
- card = rschema.rproperty(targetschema, eschema, 'cardinality')[1]
+ card = compute_cardinality(eschema, rschema, role)
help = rschema.rproperty(targetschema, eschema, 'description')
kwargs['required'] = card in '1+'
kwargs['name'] = rschema.type
- kwargs['help'] = help
+ kwargs.setdefault('help', help)
if rschema.is_final():
if skip_meta_attr and rschema in eschema.meta_attributes():
return None
--- a/web/formwidgets.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/formwidgets.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from datetime import date
from warnings import warn
-from cubicweb.common import tags
+from cubicweb.common import tags, uilib
from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE
@@ -22,6 +22,8 @@
# automatically set id and tabindex attributes ?
setdomid = True
settabindex = True
+ # does this widget expect a vocabulary
+ vocabulary_widget = False
def __init__(self, attrs=None, setdomid=None, settabindex=None):
if attrs is None:
@@ -41,7 +43,7 @@
if self.needs_css:
form.req.add_css(self.needs_css)
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
To override in concrete class
"""
@@ -66,7 +68,7 @@
"""abstract widget class for <input> tag based widgets"""
type = None
- def render(self, form, field):
+ def render(self, form, field, renderer):
"""render the widget for the given `field` of `form`.
Generate one <input> tag for each field's value
@@ -94,7 +96,7 @@
"""
type = 'password'
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
name, values, attrs = self._render_attrs(form, field)
assert len(values) == 1
@@ -103,9 +105,11 @@
confirmname = '%s-confirm:%s' % tuple(name.rsplit(':', 1))
except TypeError:
confirmname = '%s-confirm' % name
- inputs = [tags.input(name=name, value=values[0], type=self.type, id=id, **attrs),
+ inputs = [tags.input(name=name, value=values[0], type=self.type, id=id,
+ **attrs),
'<br/>',
- tags.input(name=confirmname, value=values[0], type=self.type, **attrs),
+ tags.input(name=confirmname, value=values[0], type=self.type,
+ **attrs),
' ', tags.span(form.req._('confirm password'),
**{'class': 'emphasis'})]
return u'\n'.join(inputs)
@@ -144,17 +148,22 @@
class TextArea(FieldWidget):
"""<textarea>"""
- def render(self, form, field):
+
+ def render(self, form, field, renderer):
name, values, attrs = self._render_attrs(form, field)
- attrs.setdefault('onkeypress', 'autogrow(this)')
- attrs.setdefault('cols', 80)
- attrs.setdefault('rows', 20)
+ attrs.setdefault('onkeyup', 'autogrow(this)')
if not values:
value = u''
elif len(values) == 1:
value = values[0]
else:
raise ValueError('a textarea is not supposed to be multivalued')
+ lines = value.splitlines()
+ linecount = len(lines)
+ for line in lines:
+ linecount += len(line) / 80
+ attrs.setdefault('cols', 80)
+ attrs.setdefault('rows', min(15, linecount + 2))
return tags.textarea(value, name=name, **attrs)
@@ -164,34 +173,43 @@
super(FCKEditor, self).__init__(*args, **kwargs)
self.attrs['cubicweb:type'] = 'wysiwyg'
- def render(self, form, field):
+ def render(self, form, field, renderer):
form.req.fckeditor_config()
- return super(FCKEditor, self).render(form, field)
+ return super(FCKEditor, self).render(form, field, renderer)
class Select(FieldWidget):
"""<select>, for field having a specific vocabulary"""
+ vocabulary_widget = True
+
def __init__(self, attrs=None, multiple=False):
super(Select, self).__init__(attrs)
self._multiple = multiple
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
if not 'size' in attrs:
attrs['size'] = self._multiple and '5' or '1'
options = []
optgroup_opened = False
- for label, value in field.vocabulary(form):
+ for option in field.vocabulary(form):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
if value is None:
# handle separator
if optgroup_opened:
options.append(u'</optgroup>')
- options.append(u'<optgroup label="%s">' % (label or ''))
+ oattrs.setdefault('label', label or '')
+ options.append(u'<optgroup %s>' % uilib.sgml_attributes(oattrs))
optgroup_opened = True
elif value in curvalues:
- options.append(tags.option(label, value=value, selected='selected'))
+ options.append(tags.option(label, value=value,
+ selected='selected', **oattrs))
else:
- options.append(tags.option(label, value=value))
+ options.append(tags.option(label, value=value, **oattrs))
if optgroup_opened:
options.append(u'</optgroup>')
return tags.select(name=name, multiple=self._multiple,
@@ -203,21 +221,28 @@
input will be generated for each possible value.
"""
type = 'checkbox'
+ vocabulary_widget = True
- def render(self, form, field):
+ def render(self, form, field, renderer):
name, curvalues, attrs = self._render_attrs(form, field)
domid = attrs.pop('id', None)
- sep = attrs.pop('separator', u'<br/>')
+ sep = attrs.pop('separator', u'<br/>\n')
options = []
- for i, (label, value) in enumerate(field.vocabulary(form)):
+ for i, option in enumerate(field.vocabulary(form)):
+ try:
+ label, value, oattrs = option
+ except ValueError:
+ label, value = option
+ oattrs = {}
iattrs = attrs.copy()
+ iattrs.update(oattrs)
if i == 0 and domid is not None:
- iattrs['id'] = domid
+ iattrs.setdefault('id', domid)
if value in curvalues:
iattrs['checked'] = u'checked'
tag = tags.input(name=name, type=self.type, value=value, **iattrs)
- options.append(tag + label + sep)
- return '\n'.join(options)
+ options.append(tag + label)
+ return sep.join(options)
class Radio(CheckBox):
@@ -226,6 +251,52 @@
"""
type = 'radio'
+
+# compound widgets #############################################################
+
+class IntervalWidget(FieldWidget):
+ """custom widget to display an interval composed by 2 fields. This widget
+ is expected to be used with a CompoundField containing the two actual
+ fields.
+
+ Exemple usage::
+
+from uicfg import autoform_field, autoform_section
+autoform_field.tag_attribute(('Concert', 'minprice'),
+ CompoundField(fields=(IntField(name='minprice'),
+ IntField(name='maxprice')),
+ label=_('price'),
+ widget=IntervalWidget()
+ ))
+# we've to hide the other field manually for now
+autoform_section.tag_attribute(('Concert', 'maxprice'), 'generated')
+ """
+ def render(self, form, field, renderer):
+ actual_fields = field.fields
+ assert len(actual_fields) == 2
+ return u'<div>%s %s %s %s</div>' % (
+ form.req._('from_interval_start'),
+ actual_fields[0].render(form, renderer),
+ form.req._('to_interval_end'),
+ actual_fields[1].render(form, renderer),
+ )
+
+
+class HorizontalLayoutWidget(FieldWidget):
+ """custom widget to display a set of fields grouped together horizontally
+ in a form. See `IntervalWidget` for example usage.
+ """
+ def render(self, form, field, renderer):
+ if self.attrs.get('display_label', True):
+ subst = self.attrs.get('label_input_substitution', '%(label)s %(input)s')
+ fields = [subst % {'label': renderer.render_label(form, f),
+ 'input': f.render(form, renderer)}
+ for f in field.subfields(form)]
+ else:
+ fields = [f.render(form, renderer) for f in field.subfields(form)]
+ return u'<div>%s</div>' % ' '.join(fields)
+
+
# javascript widgets ###########################################################
class DateTimePicker(TextInput):
@@ -251,8 +322,8 @@
req.html_headers.define_var('MONTHNAMES', monthnames)
req.html_headers.define_var('DAYNAMES', daynames)
- def render(self, form, field):
- txtwidget = super(DateTimePicker, self).render(form, field)
+ def render(self, form, field, renderer):
+ txtwidget = super(DateTimePicker, self).render(form, field, renderer)
self.add_localized_infos(form.req)
cal_button = self._render_calendar_popup(form, field)
return txtwidget + cal_button
@@ -291,7 +362,7 @@
if inputid is not None:
self.attrs['cubicweb:inputid'] = inputid
- def render(self, form, field):
+ def render(self, form, field, renderer):
self.add_media(form)
attrs = self._render_attrs(form, field)[-1]
return tags.div(**attrs)
@@ -362,8 +433,8 @@
etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
attrs['cubicweb:etype_from'] = etype_from
- def render(self, form, field):
- return super(AddComboBoxWidget, self).render(form, field) + u'''
+ def render(self, form, field, renderer):
+ return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
<div id="newvalue">
<input type="text" id="newopt" />
<a href="javascript:noop()" id="add_newopt"> </a></div>
@@ -389,7 +460,7 @@
self.cwaction = cwaction
self.attrs.setdefault('klass', 'validateButton')
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
attrs = self.attrs.copy()
if self.cwaction:
@@ -432,7 +503,7 @@
self.imgressource = imgressource
self.label = label
- def render(self, form, field=None):
+ def render(self, form, field=None, renderer=None):
label = form.req._(self.label)
imgsrc = form.req.external_resource(self.imgressource)
return '<a id="%(domid)s" href="%(href)s">'\
--- a/web/htmlwidgets.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/htmlwidgets.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,7 +9,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.utils import UStringIO
from cubicweb.common.uilib import toggle_action
@@ -81,7 +81,7 @@
self.w(u'<div class="%s">' % self._class)
if self.title:
if self.escape:
- title = '<span>%s</span>' % html_escape(self.title)
+ title = '<span>%s</span>' % xml_escape(self.title)
else:
title = '<span>%s</span>' % self.title
self.w(u'<div class="%s">%s</div>' % (self.title_class, title))
@@ -204,7 +204,7 @@
def __init__(self, href, label, _class='', title='', ident='', escape=False):
self.href = href
if escape:
- self.label = html_escape(label)
+ self.label = xml_escape(label)
else:
self.label = label
self._class = _class or ''
@@ -213,7 +213,7 @@
def _render(self):
link = u'<a href="%s" title="%s">%s</a>' % (
- html_escape(self.href), html_escape(self.title), self.label)
+ xml_escape(self.href), xml_escape(self.title), self.label)
if self.ident:
self.w(u'<li id="%s" class="%s">%s</li>\n' % (self.ident, self._class, link))
else:
--- a/web/request.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/request.py Tue Aug 04 15:06:09 2009 +0200
@@ -18,14 +18,15 @@
from rql.utils import rqlvar_maker
from logilab.common.decorators import cached
-from logilab.common.deprecation import obsolete
+from logilab.common.deprecation import deprecated
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.dbapi import DBAPIRequest
from cubicweb.common.mail import header
from cubicweb.common.uilib import remove_html_tags
from cubicweb.utils import SizeConstrainedList, HTMLHead
+from cubicweb.view import STRICT_DOCTYPE
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
RequestError, StatusResponse)
@@ -61,6 +62,7 @@
class CubicWebRequestBase(DBAPIRequest):
"""abstract HTTP request, should be extended according to the HTTP backend"""
+ json_request = False # to be set to True by json controllers
def __init__(self, vreg, https, form=None):
super(CubicWebRequestBase, self).__init__(vreg)
@@ -79,7 +81,7 @@
# to create a relation with another)
self.search_state = ('normal',)
# tabindex generator
- self.tabindexgen = count()
+ self.tabindexgen = count(1)
self.next_tabindex = self.tabindexgen.next
# page id, set by htmlheader template
self.pageid = None
@@ -91,7 +93,7 @@
or an anonymous connection is open
"""
super(CubicWebRequestBase, self).set_connection(cnx, user)
- # get request language:
+ # set request language
vreg = self.vreg
if self.user:
try:
@@ -114,6 +116,7 @@
def set_language(self, lang):
self._ = self.__ = self.translations[lang]
self.lang = lang
+ self.cnx.set_session_props(lang=lang)
self.debug('request language: %s', lang)
# input form parameters management ########################################
@@ -150,6 +153,12 @@
del self.form[k]
else:
self.form[k] = v
+ # special key for created entity, added in controller's reset method
+ # if no message set, we don't want this neither
+ if '__createdpath' in params and self.message:
+ self.message += ' (<a href="%s">%s</a>)' % (
+ self.build_url(params.pop('__createdpath')),
+ self._('click here to see created entity'))
def no_script_form_param(self, param, default=None, value=None):
"""ensure there is no script in a user form param
@@ -331,7 +340,6 @@
params[name] = value
params['eid'] = eid
if len(params) < minparams:
- print eid, params
raise RequestError(self._('missing parameters for entity %s') % eid)
return params
@@ -447,6 +455,9 @@
# high level methods for HTML headers management ##########################
+ def add_onload(self, jscode):
+ self.html_headers.add_onload(jscode, self.json_request)
+
def add_js(self, jsfiles, localfile=True):
"""specify a list of JS files to include in the HTML headers
:param jsfiles: a JS filename or a list of JS filenames
@@ -496,7 +507,7 @@
url = self.build_url('view', rql=rql, vid=vid, __notemplate=1,
**extraparams)
return "javascript: loadxhtml('%s', '%s', '%s')" % (
- nodeid, html_escape(url), replacemode)
+ nodeid, xml_escape(url), replacemode)
# urls/path management ####################################################
@@ -505,7 +516,7 @@
return self.base_url() + self.relative_path(includeparams)
def _datadir_url(self):
- """return url of the application's data directory"""
+ """return url of the instance's data directory"""
return self.base_url() + 'data%s/' % self.vreg.config.instance_md5_version()
def selected(self, url):
@@ -577,7 +588,7 @@
def relative_path(self, includeparams=True):
"""return the normalized path of the request (ie at least relative
- to the application's root, but some other normalization may be needed
+ to the instance's root, but some other normalization may be needed
so that the returned path may be used to compare to generated urls
:param includeparams:
@@ -619,7 +630,7 @@
auth, ex.__class__.__name__, ex)
return None, None
- @obsolete("use parse_accept_header('Accept-Language')")
+ @deprecated("use parse_accept_header('Accept-Language')")
def header_accept_language(self):
"""returns an ordered list of preferred languages"""
return [value.split('-')[0] for value in
@@ -689,6 +700,14 @@
return useragent and 'MSIE' in useragent
def xhtml_browser(self):
+ """return True if the browser is considered as xhtml compatible.
+
+ If the instance is configured to always return text/html and not
+ application/xhtml+xml, this method will always return False, even though
+ this is semantically different
+ """
+ if self.vreg.config['force-html-content-type']:
+ return False
useragent = self.useragent()
# * MSIE/Konqueror does not support xml content-type
# * Opera supports xhtml and handles namespaces properly but it breaks
@@ -703,5 +722,11 @@
return 'application/xhtml+xml'
return 'text/html'
+ def document_surrounding_div(self):
+ if self.xhtml_browser():
+ return (u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE +
+ u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
+ return u'<div>'
+
from cubicweb import set_log_methods
set_log_methods(CubicWebRequestBase, LOGGER)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/data/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,62 @@
+"""
+
+: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 yams.buildobjs import (EntityType, RelationType, RelationDefinition,
+ SubjectRelation, ObjectRelation,
+ String, Int, Datetime, Boolean, Float)
+from yams.constraints import IntervalBoundConstraint
+
+class Salesterm(EntityType):
+ described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
+ amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
+ reason = String(maxsize=20, vocabulary=[u'canceled', u'sold'])
+
+class tags(RelationDefinition):
+ subject = 'Tag'
+ object = ('BlogEntry', 'CWUser')
+
+class checked_by(RelationType):
+ subject = 'BlogEntry'
+ object = 'CWUser'
+ cardinality = '?*'
+ permissions = {
+ 'add': ('managers',),
+ 'read': ('managers', 'users'),
+ 'delete': ('managers',),
+ }
+
+class Personne(EntityType):
+ nom = String(fulltextindexed=True, required=True, maxsize=64)
+ prenom = String(fulltextindexed=True, maxsize=64)
+ sexe = String(maxsize=1, default='M')
+ promo = String(vocabulary=('bon','pasbon'))
+ titre = String(fulltextindexed=True, maxsize=128)
+ ass = String(maxsize=128)
+ web = String(maxsize=128)
+ tel = Int()
+ fax = Int()
+ datenaiss = Datetime()
+ test = Boolean()
+ description = String()
+ salary = Float()
+ travaille = SubjectRelation('Societe')
+ connait = ObjectRelation('CWUser')
+
+class Societe(EntityType):
+ nom = String(maxsize=64, fulltextindexed=True)
+ web = String(maxsize=128)
+ type = String(maxsize=128) # attribute in common with Note
+ tel = Int()
+ fax = Int()
+ rncs = String(maxsize=128)
+ ad1 = String(maxsize=128)
+ ad2 = String(maxsize=128)
+ ad3 = String(maxsize=128)
+ cp = String(maxsize=12)
+ ville= String(maxsize=32)
+
--- a/web/test/data/schema/Personne.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-nom ivarchar(64) NOT NULL
-prenom ivarchar(64)
-sexe char(1) DEFAULT 'M'
-promo choice('bon','pasbon')
-titre ivarchar(128)
-ass varchar(128)
-web varchar(128)
-tel integer
-fax integer
-datenaiss datetime
-test boolean
-description text
-salary float
--- a/web/test/data/schema/Societe.sql Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-nom ivarchar(64)
-web varchar(128)
-tel integer
-fax integer
-rncs varchar(32)
-ad1 varchar(128)
-ad2 varchar(128)
-ad3 varchar(128)
-cp varchar(12)
-ville varchar(32)
--- a/web/test/data/schema/relations.rel Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-Personne travaille Societe
-CWUser connait Personne
--- a/web/test/data/schema/testschema.py Tue Aug 04 11:43:03 2009 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-"""
-
-: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
-"""
-class Salesterm(EntityType):
- described_by_test = SubjectRelation('File', cardinality='1*', composite='subject')
- amount = Int(constraints=[IntervalBoundConstraint(0, 100)])
- reason = String(maxsize=20, vocabulary=[u'canceled', u'sold'])
-
-class tags(RelationDefinition):
- subject = 'Tag'
- object = ('BlogEntry', 'CWUser')
-
-class checked_by(RelationType):
- subject = 'BlogEntry'
- object = 'CWUser'
- cardinality = '?*'
- permissions = {
- 'add': ('managers',),
- 'read': ('managers', 'users'),
- 'delete': ('managers',),
- }
--- a/web/test/unittest_application.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_application.py Tue Aug 04 15:06:09 2009 +0200
@@ -307,7 +307,7 @@
self.assertEquals(cnx.password, origcnx.password)
self.assertEquals(cnx.anonymous_connection, False)
self.assertEquals(path, 'view')
- self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login})
+ self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login})
def _test_auth_fail(self, req):
self.assertRaises(AuthenticationError, self.app.connect, req)
@@ -351,8 +351,8 @@
req.form['__password'] = origcnx.password
self._test_auth_fail(req)
# option allow-email-login set
+ origcnx.login = address
self.set_option('allow-email-login', True)
- req, origcnx = self._init_auth('cookie')
req.form['__login'] = address
req.form['__password'] = origcnx.password
self._test_auth_succeed(req, origcnx)
--- a/web/test/unittest_form.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_form.py Tue Aug 04 15:06:09 2009 +0200
@@ -5,6 +5,9 @@
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
+#from __future__ import with_statement
+
+from xml.etree.ElementTree import fromstring
from logilab.common.testlib import unittest_main, mock_object
@@ -13,7 +16,7 @@
from cubicweb.web.formfields import (IntField, StringField, RichTextField,
DateTimeField, DateTimePicker,
FileField, EditableFileField)
-from cubicweb.web.formwidgets import PasswordInput
+from cubicweb.web.formwidgets import PasswordInput, Input
from cubicweb.web.views.forms import EntityFieldsForm, FieldsForm
from cubicweb.web.views.workflow import ChangeStateForm
from cubicweb.web.views.formrenderers import FormRenderer
@@ -96,6 +99,14 @@
inputs = pageinfo.find_tag('input', False)
self.failIf(any(attrs for t, attrs in inputs if attrs.get('name') == '__linkto'))
+ def test_reledit_composite_field(self):
+ rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"')
+ form = self.vreg.select_object('views', 'reledit', self.request(),
+ rset=rset, row=0, rtype='content')
+ data = form.render(row=0, rtype='content')
+ self.failUnless('edits-content' in data)
+ self.failUnless('edits-content_format' in data)
+
# form view tests #########################################################
def test_massmailing_formview(self):
@@ -140,17 +151,17 @@
def test_richtextfield_1(self):
self.req.use_fckeditor = lambda: False
- self._test_richtextfield('''<select id="description_format:%(eid)s" name="description_format:%(eid)s" size="1" style="display: block" tabindex="0">
+ self._test_richtextfield('''<select id="description_format:%(eid)s" name="description_format:%(eid)s" size="1" style="display: block" tabindex="1">
<option value="text/cubicweb-page-template">text/cubicweb-page-template</option>
<option value="text/html">text/html</option>
<option value="text/plain">text/plain</option>
<option selected="selected" value="text/rest">text/rest</option>
-</select><textarea cols="60" id="description:%(eid)s" name="description:%(eid)s" onkeypress="autogrow(this)" rows="5" tabindex="1"/>''')
+</select><textarea cols="80" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="2"></textarea>''')
def test_richtextfield_2(self):
self.req.use_fckeditor = lambda: True
- self._test_richtextfield('<input name="description_format:%(eid)s" style="display: block" type="hidden" value="text/rest"/><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeypress="autogrow(this)" rows="20" tabindex="0"/>')
+ self._test_richtextfield('<input name="description_format:%(eid)s" style="display: block" type="hidden" value="text/rest" /><textarea cols="80" cubicweb:type="wysiwyg" id="description:%(eid)s" name="description:%(eid)s" onkeyup="autogrow(this)" rows="2" tabindex="1"></textarea>')
def test_filefield(self):
@@ -161,14 +172,14 @@
data=Binary('new widgets system'))
form = FFForm(self.req, redirect_path='perdu.com', entity=file)
self.assertTextEquals(self._render_entity_field('data', form),
- '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+ '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="1" type="file" value="" />
<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
</div>
<br/>
-<input name="data:%(eid)s__detach" type="checkbox"/>
+<input name="data:%(eid)s__detach" type="checkbox" />
detach attached file
''' % {'eid': file.eid})
@@ -181,21 +192,21 @@
return 'ascii'
def form_field_format(self, field):
return 'text/plain'
- file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8',
+ file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8',
data=Binary('new widgets system'))
form = EFFForm(self.req, redirect_path='perdu.com', entity=file)
self.assertTextEquals(self._render_entity_field('data', form),
- '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="0" type="file" value=""/>
+ '''<input id="data:%(eid)s" name="data:%(eid)s" tabindex="1" type="file" value="" />
<a href="javascript: toggleVisibility('data:%(eid)s-advanced')" title="show advanced fields"><img src="http://testing.fr/cubicweb/data/puce_down.png" alt="show advanced fields"/></a>
<div id="data:%(eid)s-advanced" class="hidden">
-<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" name="data_format:%(eid)s" tabindex="1" type="text" value="text/plain"/><br/>
-<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" name="data_encoding:%(eid)s" tabindex="2" type="text" value="UTF-8"/><br/>
+<label for="data_format:%(eid)s">data_format</label><input id="data_format:%(eid)s" maxlength="50" name="data_format:%(eid)s" size="45" tabindex="2" type="text" value="text/plain" /><br/>
+<label for="data_encoding:%(eid)s">data_encoding</label><input id="data_encoding:%(eid)s" maxlength="20" name="data_encoding:%(eid)s" size="20" tabindex="3" type="text" value="UTF-8" /><br/>
</div>
<br/>
-<input name="data:%(eid)s__detach" type="checkbox"/>
+<input name="data:%(eid)s__detach" type="checkbox" />
detach attached file
<p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
-<textarea cols="80" name="data:%(eid)s" onkeypress="autogrow(this)" rows="20" tabindex="3">new widgets system</textarea>''' % {'eid': file.eid})
+<textarea cols="80" name="data:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="4">new widgets system</textarea>''' % {'eid': file.eid})
def test_passwordfield(self):
@@ -203,12 +214,20 @@
upassword = StringField(widget=PasswordInput)
form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity)
self.assertTextEquals(self._render_entity_field('upassword', form),
- '''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+ '''<input id="upassword:%(eid)s" name="upassword:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
<br/>
-<input name="upassword-confirm:%(eid)s" tabindex="0" type="password" value="__cubicweb_internal_field__"/>
+<input name="upassword-confirm:%(eid)s" tabindex="1" type="password" value="__cubicweb_internal_field__" />
<span class="emphasis">confirm password</span>''' % {'eid': self.entity.eid})
+ def test_datefield(self):
+ class DFForm(EntityFieldsForm):
+ creation_date = DateTimeField(widget=Input)
+ form = DFForm(self.req, entity=self.entity)
+ init, cur = (fromstring(self._render_entity_field(attr, form)).get('value')
+ for attr in ('edits-creation_date', 'creation_date'))
+ self.assertEquals(init, cur)
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_formfields.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_formfields.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,13 +6,13 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from logilab.common.testlib import TestCase, unittest_main
+from logilab.common.testlib import TestCase, unittest_main, mock_object as mock
from yams.constraints import StaticVocabularyConstraint, SizeConstraint
from cubicweb.devtools import TestServerConfiguration
from cubicweb.devtools.testlib import EnvBasedTC
-from cubicweb.web.formwidgets import PasswordInput, TextArea, Select
+from cubicweb.web.formwidgets import PasswordInput, TextArea, Select, Radio
from cubicweb.web.formfields import *
from cubicweb.web.views.forms import EntityFieldsForm
@@ -21,91 +21,104 @@
config = TestServerConfiguration('data')
config.bootstrap_cubes()
schema = config.load_schema()
-state_schema = schema['State']
-cwuser_schema = schema['CWUser']
-file_schema = schema['File']
-salesterm_schema = schema['Salesterm']
class GuessFieldTC(TestCase):
def test_state_fields(self):
- title_field = guess_field(state_schema, schema['name'])
+ title_field = guess_field(schema['State'], schema['name'])
self.assertIsInstance(title_field, StringField)
self.assertEquals(title_field.required, True)
-# synopsis_field = guess_field(state_schema, schema['synopsis'])
+# synopsis_field = guess_field(schema['State'], schema['synopsis'])
# self.assertIsInstance(synopsis_field, StringField)
# self.assertIsInstance(synopsis_field.widget, TextArea)
# self.assertEquals(synopsis_field.required, False)
# self.assertEquals(synopsis_field.help, 'an abstract for this state')
- description_field = guess_field(state_schema, schema['description'])
+ description_field = guess_field(schema['State'], schema['description'])
self.assertIsInstance(description_field, RichTextField)
self.assertEquals(description_field.required, False)
self.assertEquals(description_field.format_field, None)
- description_format_field = guess_field(state_schema, schema['description_format'])
+ description_format_field = guess_field(schema['State'], schema['description_format'])
self.assertEquals(description_format_field, None)
- description_format_field = guess_field(state_schema, schema['description_format'], skip_meta_attr=False)
+ description_format_field = guess_field(schema['State'], schema['description_format'], skip_meta_attr=False)
self.assertEquals(description_format_field.internationalizable, True)
self.assertEquals(description_format_field.sort, True)
self.assertEquals(description_format_field.initial(None), 'text/rest')
-
-# wikiid_field = guess_field(state_schema, schema['wikiid'])
+# wikiid_field = guess_field(schema['State'], schema['wikiid'])
# self.assertIsInstance(wikiid_field, StringField)
# self.assertEquals(wikiid_field.required, False)
def test_cwuser_fields(self):
- upassword_field = guess_field(cwuser_schema, schema['upassword'])
+ upassword_field = guess_field(schema['CWUser'], schema['upassword'])
self.assertIsInstance(upassword_field, StringField)
self.assertIsInstance(upassword_field.widget, PasswordInput)
self.assertEquals(upassword_field.required, True)
- last_login_time_field = guess_field(cwuser_schema, schema['last_login_time'])
+ last_login_time_field = guess_field(schema['CWUser'], schema['last_login_time'])
self.assertIsInstance(last_login_time_field, DateTimeField)
self.assertEquals(last_login_time_field.required, False)
- in_group_field = guess_field(cwuser_schema, schema['in_group'])
+ in_group_field = guess_field(schema['CWUser'], schema['in_group'])
self.assertIsInstance(in_group_field, RelationField)
self.assertEquals(in_group_field.required, True)
self.assertEquals(in_group_field.role, 'subject')
self.assertEquals(in_group_field.help, 'groups grant permissions to the user')
- owned_by_field = guess_field(cwuser_schema, schema['owned_by'], 'object')
+ owned_by_field = guess_field(schema['CWUser'], schema['owned_by'], 'object')
self.assertIsInstance(owned_by_field, RelationField)
self.assertEquals(owned_by_field.required, False)
self.assertEquals(owned_by_field.role, 'object')
def test_file_fields(self):
- data_format_field = guess_field(file_schema, schema['data_format'])
+ data_format_field = guess_field(schema['File'], schema['data_format'])
self.assertEquals(data_format_field, None)
- data_encoding_field = guess_field(file_schema, schema['data_encoding'])
+ data_encoding_field = guess_field(schema['File'], schema['data_encoding'])
self.assertEquals(data_encoding_field, None)
- data_field = guess_field(file_schema, schema['data'])
+ data_field = guess_field(schema['File'], schema['data'])
self.assertIsInstance(data_field, FileField)
self.assertEquals(data_field.required, True)
self.assertIsInstance(data_field.format_field, StringField)
self.assertIsInstance(data_field.encoding_field, StringField)
def test_constraints_priority(self):
- salesterm_field = guess_field(salesterm_schema, schema['reason'])
+ salesterm_field = guess_field(schema['Salesterm'], schema['reason'])
constraints = schema['reason'].rproperty('Salesterm', 'String', 'constraints')
self.assertEquals([c.__class__ for c in constraints],
[SizeConstraint, StaticVocabularyConstraint])
self.assertIsInstance(salesterm_field.widget, Select)
+
+ def test_bool_field_base(self):
+ field = guess_field(schema['CWAttribute'], schema['indexed'])
+ self.assertIsInstance(field, BooleanField)
+ self.assertEquals(field.required, False)
+ self.assertEquals(field.initial(None), None)
+ self.assertIsInstance(field.widget, Radio)
+ self.assertEquals(field.vocabulary(mock(req=mock(_=unicode))),
+ [(u'yes', '1'), (u'no', '')])
+
+ def test_bool_field_explicit_choices(self):
+ field = guess_field(schema['CWAttribute'], schema['indexed'],
+ choices=[(u'maybe', '1'), (u'no', '')])
+ self.assertIsInstance(field.widget, Radio)
+ self.assertEquals(field.vocabulary(mock(req=mock(_=unicode))),
+ [(u'maybe', '1'), (u'no', '')])
+
+
class MoreFieldsTC(EnvBasedTC):
def test_rtf_format_field(self):
req = self.request()
req.use_fckeditor = lambda: False
e = self.etype_instance('State')
form = EntityFieldsForm(req, entity=e)
- description_field = guess_field(state_schema, schema['description'])
+ description_field = guess_field(schema['State'], schema['description'])
description_format_field = description_field.get_format_field(form)
self.assertEquals(description_format_field.internationalizable, True)
self.assertEquals(description_format_field.sort, True)
--- a/web/test/unittest_magicsearch.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_magicsearch.py Tue Aug 04 15:06:09 2009 +0200
@@ -147,11 +147,6 @@
('CWUser C WHERE C use_email C1, C1 alias LIKE %(text)s', {'text': '%Logilab'}))
self.assertRaises(BadRQLQuery, transform, 'word1', 'word2', 'word3')
- def test_multiple_words_query(self):
- """tests multiple_words_query()"""
- self.assertEquals(self.proc._multiple_words_query(['a', 'b', 'c', 'd', 'e']),
- ('a b c d e',))
-
def test_quoted_queries(self):
"""tests how quoted queries are handled"""
queries = [
@@ -174,10 +169,11 @@
(u'Utilisateur P', (u"CWUser P",)),
(u'Utilisateur cubicweb', (u'CWUser C WHERE C has_text %(text)s', {'text': u'cubicweb'})),
(u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
- (u'Any X WHERE X is Something', (u"Any X WHERE X is Something",)),
]
for query, expected in queries:
self.assertEquals(self.proc.preprocess_query(query, self.req), expected)
+ self.assertRaises(BadRQLQuery,
+ self.proc.preprocess_query, 'Any X WHERE X is Something', self.req)
--- a/web/test/unittest_urlrewrite.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_urlrewrite.py Tue Aug 04 15:06:09 2009 +0200
@@ -29,12 +29,14 @@
self.assertListEquals(rules, [
('foo' , dict(rql='Foo F')),
('/index' , dict(vid='index2')),
- ('/schema', {'vid': 'schema'}),
+ ('/schema', dict(vid='schema')),
('/myprefs', dict(vid='propertiesform')),
('/siteconfig', dict(vid='systempropertiesform')),
+ ('/siteinfo', dict(vid='info')),
('/manage', dict(vid='manage')),
- ('/notfound', {'vid': '404'}),
- ('/error', {'vid': 'error'}),
+ ('/notfound', dict(vid='404')),
+ ('/error', dict(vid='error')),
+ ('/sparql', dict(vid='sparql')),
('/schema/([^/]+?)/?$', {'rql': r'Any X WHERE X is CWEType, X name "\1"', 'vid': 'eschema'}),
('/add/([^/]+?)/?$' , dict(vid='creation', etype=r'\1')),
('/doc/images/(.+?)/?$', dict(fid='\\1', vid='wdocimages')),
--- a/web/test/unittest_views_apacherewrite.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_views_apacherewrite.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,14 +12,14 @@
class ApacheURLRewriteTC(TestCase):
def test(self):
- class MyAppRules(ApacheURLRewrite):
+ class MyAppRules(ApacheURLRewrite):
rules = [
RewriteCond('logilab\.fr', match='host',
rules=[('/(.*)', r'http://www.logilab.fr/\1')],
action='redirect'),
RewriteCond('(www)\.logilab\.fr', match='host', action='stop'),
RewriteCond('/(data|json)/', match='path', action='stop'),
- RewriteCond('(?P<cat>.*)\.logilab\.fr', match='host',
+ RewriteCond('(?P<cat>.*)\.logilab\.fr', match='host',
rules=[('/(.*)', r'/m_%(cat)s/\1')]),
]
urlrewriter = MyAppRules()
--- a/web/test/unittest_views_basecontrollers.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_views_basecontrollers.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,16 +8,13 @@
import simplejson
from logilab.common.testlib import unittest_main, mock_object
-
-from cubicweb import Binary, Unauthorized
-from cubicweb.devtools._apptest import TestEnvironment
from cubicweb.devtools.apptest import EnvBasedTC, ControllerTC
-from cubicweb.common import ValidationError
+from cubicweb import Binary, NoSelectableObject, ValidationError
+from cubicweb.view import STRICT_DOCTYPE
from cubicweb.common.uilib import rql_for_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, Redirect, RequestError
-from cubicweb.web.views.basecontrollers import xhtml_wrap
from cubicweb.entities.authobjs import CWUser
@@ -515,7 +512,7 @@
def test_not_usable_by_guets(self):
self.login('anon')
req = self.request()
- self.assertRaises(Unauthorized, self.env.app.select_controller, 'sendmail', req)
+ self.assertRaises(NoSelectableObject, self.env.vreg.select, 'controllers', 'sendmail', req)
@@ -538,8 +535,13 @@
ctrl = self.ctrl(req)
rset = self.john.as_rset()
rset.req = req
- self.assertTextEquals(ctrl.publish(),
- xhtml_wrap(mock_object(req=req), ctrl.view('primary', rset)))
+ source = ctrl.publish()
+ self.failUnless(source.startswith('<?xml version="1.0"?>\n' + STRICT_DOCTYPE +
+ u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
+ )
+ req.xhtml_browser = lambda: False
+ source = ctrl.publish()
+ self.failUnless(source.startswith('<div>'))
# def test_json_exec(self):
# rql = 'Any T,N WHERE T is Tag, T name N'
--- a/web/test/unittest_views_baseviews.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_views_baseviews.py Tue Aug 04 15:06:09 2009 +0200
@@ -44,11 +44,16 @@
rset = self.execute('Any X WHERE X eid 1')
self.assertEquals(vid_from_rset(req, rset, self.schema), 'primary')
- def test_more_than_one_entity(self):
+ def test_more_than_one_entity_same_type(self):
req = self.request()
rset = self.execute('Any X WHERE X is CWUser')
- self.assertEquals(vid_from_rset(req, rset, self.schema), 'list')
+ self.assertEquals(vid_from_rset(req, rset, self.schema), 'adaptedlist')
rset = self.execute('Any X, L WHERE X login L')
+ self.assertEquals(vid_from_rset(req, rset, self.schema), 'adaptedlist')
+
+ def test_more_than_one_entity_diff_type(self):
+ req = self.request()
+ rset = self.execute('Any X WHERE X is IN (CWUser, CWGroup)')
self.assertEquals(vid_from_rset(req, rset, self.schema), 'list')
def test_more_than_one_entity_by_row(self):
@@ -86,7 +91,7 @@
rset = self.execute('Any X, D, CD, NOW - CD WHERE X is State, X description D, X creation_date CD, X eid %(x)s',
{'x': e.eid}, 'x')
req = self.request()
- view = self.vreg.select('views', 'table', req, rset)
+ view = self.vreg.select('views', 'table', req, rset=rset)
return e, rset, view
def test_headers(self):
@@ -103,9 +108,9 @@
def test_sortvalue_with_display_col(self):
e, rset, view = self._prepare_entity()
- rqlstdescr = rset.syntax_tree().get_description()[0] # XXX missing Union support
+ labels = view.columns_labels()
table = TableWidget(view)
- table.columns = view.get_columns(rqlstdescr, [1, 2], None, None, None, None, 0)
+ table.columns = view.get_columns(labels, [1, 2], None, None, None, None, 0)
expected = ['loo"ong blabla'[:10], e.creation_date.strftime('%Y-%m-%d %H:%M')]
got = [loadjson(value) for _, value in table.itercols(0)]
self.assertListEqual(got, expected)
--- a/web/test/unittest_views_editforms.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_views_editforms.py Tue Aug 04 15:06:09 2009 +0200
@@ -17,7 +17,7 @@
def test_custom_widget(self):
AEF.rfields_kwargs.tag_subject_of(('CWUser', 'login', '*'),
- {'widget':AutoCompletionWidget})
+ {'widget': AutoCompletionWidget(autocomplete_initfunc='get_logins')})
form = self.vreg.select('forms', 'edition', self.request(),
entity=self.user())
field = form.field_by_name('login')
@@ -25,12 +25,12 @@
AEF.rfields_kwargs.del_rtag('CWUser', 'login', '*', 'subject')
- def test_euser_relations_by_category(self):
+ def test_cwuser_relations_by_category(self):
#for (rtype, role, stype, otype), tag in AEF.rcategories._tagdefs.items():
# if rtype == 'tags':
# print rtype, role, stype, otype, ':', tag
e = self.etype_instance('CWUser')
- # see custom configuration in views.euser
+ # see custom configuration in views.cwuser
self.assertEquals(rbc(e, 'primary'),
[('login', 'subject'),
('upassword', 'subject'),
@@ -46,6 +46,7 @@
[('last_login_time', 'subject'),
('created_by', 'subject'),
('creation_date', 'subject'),
+ ('cwuri', 'subject'),
('modification_date', 'subject'),
('owned_by', 'subject'),
('bookmarked_by', 'object'),
@@ -97,6 +98,7 @@
self.assertListEquals(rbc(e, 'metadata'),
[('created_by', 'subject'),
('creation_date', 'subject'),
+ ('cwuri', 'subject'),
('modification_date', 'subject'),
('owned_by', 'subject'),
])
@@ -158,3 +160,4 @@
if __name__ == '__main__':
unittest_main()
+
--- a/web/test/unittest_viewselector.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_viewselector.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-# -*- coding: iso-8859-1 -*-
+# -*- coding: utf-8 -*-
"""XXX rename, split, reorganize this
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
@@ -22,8 +22,9 @@
('logout', actions.LogoutAction)]
SITEACTIONS = [('siteconfig', actions.SiteConfigurationAction),
('manage', actions.ManageAction),
- ('schema', schema.ViewSchemaAction)]
-
+ ('schema', schema.ViewSchemaAction),
+ ('siteinfo', actions.SiteInfoAction),
+ ]
class ViewSelectorTC(EnvBasedTC):
@@ -59,6 +60,9 @@
print 'missing', [v for v in content.keys() if not v in expected]
raise
+ def setUp(self):
+ super(VRegistryTC, self).setUp()
+ assert self.vreg['views']['propertiesform']
def test_possible_views_none_rset(self):
req = self.request()
@@ -70,7 +74,7 @@
('manage', startup.ManageView),
('owl', owl.OWLView),
('propertiesform', cwproperties.CWPropertiesForm),
- ('schema', startup.SchemaView),
+ ('schema', schema.SchemaView),
('systempropertiesform', cwproperties.SystemCWPropertiesForm)])
def test_possible_views_noresult(self):
@@ -81,7 +85,8 @@
def test_possible_views_one_egroup(self):
rset, req = self.env.get_rset_and_req('CWGroup X WHERE X name "managers"')
self.assertListEqual(self.pviews(req, rset),
- [('csvexport', csvexport.CSVRsetView),
+ [('adaptedlist', baseviews.AdaptedListView),
+ ('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -103,7 +108,8 @@
def test_possible_views_multiple_egroups(self):
rset, req = self.env.get_rset_and_req('CWGroup X')
self.assertListEqual(self.pviews(req, rset),
- [('csvexport', csvexport.CSVRsetView),
+ [('adaptedlist', baseviews.AdaptedListView),
+ ('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -122,6 +128,31 @@
('xml', xmlrss.XMLView),
])
+ def test_propertiesform_admin(self):
+ assert self.vreg['views']['propertiesform']
+ rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
+ rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+ self.failUnless(self.vreg.select('views', 'propertiesform', req1, rset=None))
+ self.failUnless(self.vreg.select('views', 'propertiesform', req1, rset=rset1))
+ self.failUnless(self.vreg.select('views', 'propertiesform', req2, rset=rset2))
+
+ def test_propertiesform_anon(self):
+ self.login('anon')
+ rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
+ rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "anon"')
+ self.assertRaises(NoSelectableObject, self.vreg.select, 'views', 'propertiesform', req1, rset=None)
+ self.assertRaises(NoSelectableObject, self.vreg.select, 'views', 'propertiesform', req1, rset=rset1)
+ self.assertRaises(NoSelectableObject, self.vreg.select, 'views', 'propertiesform', req1, rset=rset2)
+
+ def test_propertiesform_jdoe(self):
+ self.create_user('jdoe')
+ self.login('jdoe')
+ rset1, req1 = self.env.get_rset_and_req('CWUser X WHERE X login "admin"')
+ rset2, req2 = self.env.get_rset_and_req('CWUser X WHERE X login "jdoe"')
+ self.failUnless(self.vreg.select('views', 'propertiesform', req1, rset=None))
+ self.assertRaises(NoSelectableObject, self.vreg.select, 'views', 'propertiesform', req1, rset=rset1)
+ self.failUnless(self.vreg.select('views', 'propertiesform', req2, rset=rset2))
+
def test_possible_views_multiple_different_types(self):
rset, req = self.env.get_rset_and_req('Any X')
self.assertListEqual(self.pviews(req, rset),
@@ -156,7 +187,8 @@
def test_possible_views_multiple_eusers(self):
rset, req = self.env.get_rset_and_req('CWUser X')
self.assertListEqual(self.pviews(req, rset),
- [('csvexport', csvexport.CSVRsetView),
+ [('adaptedlist', baseviews.AdaptedListView),
+ ('csvexport', csvexport.CSVRsetView),
('ecsvexport', csvexport.CSVEntityView),
('editable-table', tableview.EditableTableView),
('filetree', treeview.FileTreeView),
@@ -173,6 +205,7 @@
('text', baseviews.TextView),
('treeview', treeview.TreeView),
('vcard', vcard.VCardCWUserView),
+ ('wfhistory', workflow.WFHistoryView),
('xbel', xbel.XbelView),
('xml', xmlrss.XMLView),
])
--- a/web/test/unittest_web.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/test/unittest_web.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,19 +6,17 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
from logilab.common.testlib import TestCase, unittest_main
-from cubicweb.web import ajax_replace_url as arurl
+from cubicweb.devtools.fake import FakeRequest
class AjaxReplaceUrlTC(TestCase):
def test_ajax_replace_url(self):
+ req = FakeRequest()
+ arurl = req.build_ajax_replace_url
# NOTE: for the simplest use cases, we could use doctest
- 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', 'list'),
+ "javascript: loadxhtml('foo', 'http://testing.fr/cubicweb/view?rql=Person%20P&__notemplate=1&vid=list', 'replace')")
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"});')
+ '''javascript: loadxhtml('foo', 'http://testing.fr/cubicweb/view?age=12&rql=Person%20P&__notemplate=1&vid=oneline&name=bar', 'replace')''')
if __name__ == '__main__':
--- a/web/uicfg.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/uicfg.py Tue Aug 04 15:06:09 2009 +0200
@@ -68,7 +68,8 @@
__docformat__ = "restructuredtext en"
from cubicweb import neg_role
-from cubicweb.rtags import RelationTags, RelationTagsBool, RelationTagsSet
+from cubicweb.rtags import (RelationTags, RelationTagsBool,
+ RelationTagsSet, RelationTagsDict)
from cubicweb.web import formwidgets
@@ -85,7 +86,8 @@
card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role)
composed = rschema.rproperty(sschema, oschema, 'composite') == neg_role(role)
if rschema.is_final():
- if rschema.meta or oschema.type in ('Password', 'Bytes'):
+ if rschema.meta or sschema.is_metadata(rschema) \
+ or oschema.type in ('Password', 'Bytes'):
section = 'hidden'
else:
section = 'attributes'
@@ -101,34 +103,33 @@
init_primaryview_section,
frozenset(('attributes', 'relations',
'sideboxes', 'hidden')))
-for rtype in ('eid', 'creation_date', 'modification_date',
- 'is', 'is_instance_of', 'identity',
- 'owned_by', 'created_by',
- 'in_state', 'wf_info_for', 'require_permission',
- 'from_entity', 'to_entity',
- 'see_also'):
- primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
- primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
-primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
-primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
-
-for attr in ('name', 'meta', 'final'):
- primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
-for attr in ('name', 'meta', 'final', 'symetric', 'inlined'):
- primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
-class DisplayCtrlRelationTags(RelationTags):
+class DisplayCtrlRelationTags(RelationTagsDict):
def __init__(self, *args, **kwargs):
super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs)
self._counter = 0
def tag_relation(self, key, tag):
- assert isinstance(tag, dict)
- super(DisplayCtrlRelationTags, self).tag_relation(key, tag)
+ tag = super(DisplayCtrlRelationTags, self).tag_relation(key, tag)
self._counter += 1
tag.setdefault('order', self._counter)
+ def tag_subject_of(self, key, tag):
+ subj, rtype, obj = key
+ if obj != '*':
+ self.warning('using explict target type in display_ctrl.tag_subject_of() '
+ 'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)',
+ subj, rtype, subj, rtype, obj)
+ super(DisplayCtrlRelationTags, self).tag_subject_of((subj, rtype, '*'), tag)
+
+ def tag_object_of(self, key, tag):
+ subj, rtype, obj = key
+ if subj != '*':
+ self.warning('using explict subject type in display_ctrl.tag_object_of() '
+ 'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)',
+ rtype, obj, subj, rtype, obj)
+ super(DisplayCtrlRelationTags, self).tag_object_of(('*', rtype, obj), tag)
def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role):
if role == 'subject':
@@ -137,11 +138,8 @@
else:
sschema = '*'
label = '%s_%s' % (rschema, role)
- displayinfo = rtag.get(sschema, rschema, oschema, role)
- if displayinfo is None:
- displayinfo = {}
- rtag.tag_relation((sschema, rschema, oschema, role), displayinfo)
- displayinfo.setdefault('label', label)
+ rtag.setdefault((sschema, rschema, oschema, role), 'label', label)
+ rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag._counter)
primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
init_primaryview_display_ctrl)
@@ -154,7 +152,11 @@
# * 'schema'
# * 'subobject' (not displayed by default)
-indexview_etype_section = {'EmailAddress': 'subobject'}
+indexview_etype_section = {'EmailAddress': 'subobject',
+ 'CWUser': 'system',
+ 'CWGroup': 'system',
+ 'CWPermission': 'system',
+ }
# autoform.AutomaticEntityForm configuration ##################################
@@ -170,7 +172,7 @@
card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
if sschema.is_metadata(rschema):
- section = 'generated'
+ section = 'metadata'
elif card in '1+':
if not rschema.is_final() and composed:
section = 'generated'
@@ -185,61 +187,17 @@
autoform_section = RelationTags('autoform_section', init_autoform_section,
set(('primary', 'secondary', 'generic',
'metadata', 'generated')))
-# use primary and not generated for eid since it has to be an hidden
-autoform_section.tag_attribute(('*', 'eid'), 'primary')
-autoform_section.tag_attribute(('*', 'description'), 'secondary')
-autoform_section.tag_attribute(('*', 'creation_date'), 'metadata')
-autoform_section.tag_attribute(('*', 'modification_date'), 'metadata')
-autoform_section.tag_attribute(('*', 'has_text'), 'generated')
-autoform_section.tag_subject_of(('*', 'in_state', '*'), 'primary')
-autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata')
-autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata')
-autoform_section.tag_subject_of(('*', 'is', '*'), 'generated')
-autoform_section.tag_object_of(('*', 'is', '*'), 'generated')
-autoform_section.tag_subject_of(('*', 'is_instance_of', '*'), 'generated')
-autoform_section.tag_object_of(('*', 'is_instance_of', '*'), 'generated')
-autoform_section.tag_subject_of(('*', 'identity', '*'), 'generated')
-autoform_section.tag_object_of(('*', 'identity', '*'), 'generated')
-autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated')
-autoform_section.tag_subject_of(('*', 'wf_info_for', '*'), 'generated')
-autoform_section.tag_object_of(('*', 'wf_info_for', '*'), 'generated')
-autoform_section.tag_subject_of(('*', 'for_user', '*'), 'generated')
-autoform_section.tag_object_of(('*', 'for_user', '*'), 'generated')
-autoform_section.tag_subject_of(('CWPermission', 'require_group', '*'), 'primary')
-autoform_section.tag_attribute(('CWEType', 'final'), 'generated')
-autoform_section.tag_attribute(('CWRType', 'final'), 'generated')
-autoform_section.tag_attribute(('CWUser', 'firstname'), 'secondary')
-autoform_section.tag_attribute(('CWUser', 'surname'), 'secondary')
-autoform_section.tag_attribute(('CWUser', 'last_login_time'), 'metadata')
-autoform_section.tag_subject_of(('CWUser', 'in_group', '*'), 'primary')
-autoform_section.tag_object_of(('*', 'owned_by', 'CWUser'), 'generated')
-autoform_section.tag_object_of(('*', 'created_by', 'CWUser'), 'generated')
-autoform_section.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'metadata')
-autoform_section.tag_attribute(('Bookmark', 'path'), 'primary')
-autoform_section.tag_subject_of(('*', 'use_email', '*'), 'generated') # inlined actually
-autoform_section.tag_subject_of(('*', 'primary_email', '*'), 'generic')
-
# relations'field class
autoform_field = RelationTags('autoform_field')
# relations'field explicit kwargs (given to field's __init__)
-autoform_field_kwargs = RelationTags()
-autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
- {'widget': formwidgets.TextInput})
-autoform_field_kwargs.tag_attribute(('Bookmark', 'path'),
- {'widget': formwidgets.TextInput})
-
-
+autoform_field_kwargs = RelationTagsDict()
# inlined view flag for non final relations: when True for an entry, the
# entity(ies) at the other end of the relation will be editable from the
# form of the edited entity
autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
-autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
-autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
-autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
-autoform_is_inlined.tag_subject_of(('CWRelation', 'to_entity', '*'), True)
# set of tags of the form <action>_on_new on relations. <action> is a
@@ -260,28 +218,4 @@
actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
init_actionbox_appearsin_addmenu)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'is', '*'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'is', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'is_instance_of', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'identity', '*'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'identity', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'owned_by', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'created_by', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
-actionbox_appearsin_addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'state_of', 'CWEType'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'transition_of', 'CWEType'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'owned_by', 'CWUser'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'created_by', 'CWUser'), False)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
-actionbox_appearsin_addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
-actionbox_appearsin_addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
-actionbox_appearsin_addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
--- a/web/views/__init__.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/__init__.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
import os
-from tempfile import mktemp
+import tempfile
from rql import nodes
@@ -53,9 +53,15 @@
return True
return False
-VID_BY_MIMETYPE = {'text/xml': 'xml',
- # XXX rss, owl...
- }
+# FIXME: VID_BY_MIMETYPE is unfortunately a bit too naive since
+# some browsers (e.g. FF2) send a bunch of mimetypes in
+# the Accept header, for instance:
+# text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,
+# text/plain;q=0.8,image/png,*/*;q=0.5
+VID_BY_MIMETYPE = {
+ #'text/xml': 'xml',
+ # XXX rss, owl...
+}
def vid_from_rset(req, rset, schema):
"""given a result set, return a view id"""
if rset is None:
@@ -75,6 +81,8 @@
if req.search_state[0] == 'normal':
return 'primary'
return 'outofcontext-search'
+ if len(rset.column_types(0)) == 1:
+ return 'adaptedlist'
return 'list'
return 'table'
@@ -103,9 +111,12 @@
def cell_call(self, row=0, col=0):
self.row, self.col = row, col # in case one need it
- tmpfile = mktemp('.png')
+ _, tmpfile = tempfile.mkstemp('.png')
try:
self._generate(tmpfile)
self.w(open(tmpfile).read())
finally:
- os.unlink(tmpfile)
+ try:
+ os.unlink(tmpfile)
+ except Exception, ex:
+ self.warning('cant delete %s: %s', tmpfile, ex)
--- a/web/views/actions.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/actions.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,6 +6,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from cubicweb.vregistry import objectify_selector
from cubicweb.selectors import (EntitySelector,
@@ -14,12 +15,11 @@
authenticated_user, match_user_groups, match_search_state,
has_permission, has_add_permission,
)
+from cubicweb.web import uicfg
from cubicweb.web.action import Action
from cubicweb.web.views import linksearch_select_url, vid_from_rset
from cubicweb.web.views.autoform import AutomaticEntityForm
-_ = unicode
-
class has_editable_relation(EntitySelector):
"""accept if some relations for an entity found in the result set is
@@ -41,11 +41,11 @@
return 0
@objectify_selector
-def match_searched_etype(cls, req, rset, **kwargs):
+def match_searched_etype(cls, req, rset=None, **kwargs):
return req.match_search_state(rset)
@objectify_selector
-def view_is_not_default_view(cls, req, rset, **kwargs):
+def view_is_not_default_view(cls, req, rset=None, **kwargs):
# interesting if it propose another view than the current one
vid = req.form.get('vid')
if vid and vid != vid_from_rset(req, rset, cls.schema):
@@ -53,7 +53,7 @@
return 0
@objectify_selector
-def addable_etype_empty_rset(cls, req, rset, **kwargs):
+def addable_etype_empty_rset(cls, req, rset=None, **kwargs):
if rset is not None and not rset.rowcount:
rqlst = rset.syntax_tree()
if len(rqlst.children) > 1:
@@ -67,7 +67,7 @@
return 1
return 0
-# generic primary actions #####################################################
+# generic 'main' actions #######################################################
class SelectAction(Action):
"""base class for link search actions. By default apply on
@@ -147,7 +147,7 @@
return self.build_url('view', rql=self.rset.rql, vid='muledit')
-# generic secondary actions ###################################################
+# generic "more" actions #######################################################
class ManagePermissionsAction(Action):
id = 'managepermission'
@@ -287,8 +287,41 @@
title = _('manage')
order = 20
+class SiteInfoAction(ManagersAction):
+ id = 'siteinfo'
+ title = _('info')
+ order = 30
+ __select__ = match_user_groups('users','managers')
+
from logilab.common.deprecation import class_moved
from cubicweb.web.views.bookmark import FollowAction
FollowAction = class_moved(FollowAction)
+## default actions ui configuration ###########################################
+
+addmenu = uicfg.actionbox_appearsin_addmenu
+addmenu.tag_subject_of(('*', 'is', '*'), False)
+addmenu.tag_object_of(('*', 'is', '*'), False)
+addmenu.tag_subject_of(('*', 'is_instance_of', '*'), False)
+addmenu.tag_object_of(('*', 'is_instance_of', '*'), False)
+addmenu.tag_subject_of(('*', 'identity', '*'), False)
+addmenu.tag_object_of(('*', 'identity', '*'), False)
+addmenu.tag_subject_of(('*', 'owned_by', '*'), False)
+addmenu.tag_subject_of(('*', 'created_by', '*'), False)
+addmenu.tag_subject_of(('*', 'require_permission', '*'), False)
+addmenu.tag_subject_of(('*', 'wf_info_for', '*'), False)
+addmenu.tag_object_of(('*', 'wf_info_for', '*'), False)
+addmenu.tag_object_of(('*', 'state_of', 'CWEType'), True)
+addmenu.tag_object_of(('*', 'transition_of', 'CWEType'), True)
+addmenu.tag_object_of(('*', 'relation_type', 'CWRType'), True)
+addmenu.tag_object_of(('*', 'from_entity', 'CWEType'), False)
+addmenu.tag_object_of(('*', 'to_entity', 'CWEType'), False)
+addmenu.tag_object_of(('*', 'in_group', 'CWGroup'), True)
+addmenu.tag_object_of(('*', 'owned_by', 'CWUser'), False)
+addmenu.tag_object_of(('*', 'created_by', 'CWUser'), False)
+addmenu.tag_object_of(('*', 'bookmarked_by', 'CWUser'), True)
+addmenu.tag_subject_of(('Transition', 'destination_state', '*'), True)
+addmenu.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
+addmenu.tag_object_of(('*', 'destination_state', 'State'), True)
+addmenu.tag_subject_of(('State', 'allowed_transition', '*'), True)
--- a/web/views/authentication.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/authentication.py Tue Aug 04 15:06:09 2009 +0200
@@ -36,7 +36,10 @@
# calling cnx.user() check connection validity, raise
# BadConnectionId on failure
user = cnx.user(req)
- if login and user.login != login:
+ # check cnx.login and not user.login, since in case of login by
+ # email, login and cnx.login are the email while user.login is the
+ # actual user login
+ if login and cnx.login != login:
cnx.close()
raise InvalidSession('login mismatch')
except BadConnectionId:
@@ -53,18 +56,6 @@
req.set_connection(cnx, user)
return cnx
- def login_from_email(self, login):
- # XXX should not be called from web interface
- session = self.repo.internal_session()
- try:
- rset = session.execute('Any L WHERE U login L, U primary_email M, '
- 'M address %(login)s', {'login': login})
- if rset.rowcount == 1:
- login = rset[0][0]
- finally:
- session.close()
- return login
-
def authenticate(self, req, _login=None, _password=None):
"""authenticate user and return corresponding user object
@@ -79,8 +70,6 @@
login, password = _login, _password
else:
login, password = req.get_authorization()
- if self.vreg.config['allow-email-login'] and '@' in (login or u''):
- login = self.login_from_email(login)
if not login:
# No session and no login -> try anonymous
login, password = self.vreg.config.anonymous_user()
--- a/web/views/autoform.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/autoform.py Tue Aug 04 15:06:09 2009 +0200
@@ -14,7 +14,7 @@
from cubicweb.web import stdmsgs, uicfg
from cubicweb.web.form import FieldNotFound
from cubicweb.web.formfields import guess_field
-from cubicweb.web.formwidgets import Button, SubmitButton
+from cubicweb.web import formwidgets
from cubicweb.web.views import forms, editforms
@@ -33,9 +33,9 @@
cwtarget = 'eformframe'
cssclass = 'entityForm'
copy_nav_params = True
- form_buttons = [SubmitButton(),
- Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
- Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
+ form_buttons = [formwidgets.SubmitButton(),
+ formwidgets.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
+ formwidgets.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
attrcategories = ('primary', 'secondary')
# class attributes below are actually stored in the uicfg module since we
# don't want them to be reloaded
@@ -137,8 +137,10 @@
rschema = cls_or_self.schema.rschema(name)
# XXX use a sample target type. Document this.
tschemas = rschema.targets(eschema, role)
- fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role, tschemas[0])
- kwargs = cls_or_self.rfields_kwargs.etype_get(eschema, rschema, role, tschemas[0])
+ fieldcls = cls_or_self.rfields.etype_get(eschema, rschema, role,
+ tschemas[0])
+ kwargs = cls_or_self.rfields_kwargs.etype_get(eschema, rschema,
+ role, tschemas[0])
if kwargs is None:
kwargs = {}
if fieldcls:
@@ -290,7 +292,7 @@
by default true if there is no related entity and we need at least one
"""
- return not existant and card in '1+'
+ return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
def should_display_add_new_relation_link(self, rschema, existant, card):
"""return true if we should add a link to add a new creation form
@@ -305,3 +307,51 @@
def etype_relation_field(etype, rtype, role='subject'):
eschema = AutomaticEntityForm.schema.eschema(etype)
return AutomaticEntityForm.field_by_name(rtype, role, eschema)
+
+
+## default form ui configuration ##############################################
+
+# use primary and not generated for eid since it has to be an hidden
+uicfg.autoform_section.tag_attribute(('*', 'eid'), 'primary')
+uicfg.autoform_section.tag_attribute(('*', 'description'), 'secondary')
+uicfg.autoform_section.tag_attribute(('*', 'creation_date'), 'metadata')
+uicfg.autoform_section.tag_attribute(('*', 'modification_date'), 'metadata')
+uicfg.autoform_section.tag_attribute(('*', 'cwuri'), 'metadata')
+uicfg.autoform_section.tag_attribute(('*', 'has_text'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'in_state', '*'), 'primary')
+uicfg.autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata')
+uicfg.autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata')
+uicfg.autoform_section.tag_subject_of(('*', 'is', '*'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'is', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'is_instance_of', '*'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'is_instance_of', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'identity', '*'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'identity', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'wf_info_for', '*'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'wf_info_for', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('*', 'for_user', '*'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'for_user', '*'), 'generated')
+uicfg.autoform_section.tag_subject_of(('CWPermission', 'require_group', '*'), 'primary')
+uicfg.autoform_section.tag_attribute(('CWEType', 'final'), 'generated')
+uicfg.autoform_section.tag_attribute(('CWRType', 'final'), 'generated')
+uicfg.autoform_section.tag_attribute(('CWUser', 'firstname'), 'secondary')
+uicfg.autoform_section.tag_attribute(('CWUser', 'surname'), 'secondary')
+uicfg.autoform_section.tag_attribute(('CWUser', 'last_login_time'), 'metadata')
+uicfg.autoform_section.tag_subject_of(('CWUser', 'in_group', '*'), 'primary')
+uicfg.autoform_section.tag_object_of(('*', 'owned_by', 'CWUser'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'created_by', 'CWUser'), 'generated')
+uicfg.autoform_section.tag_object_of(('*', 'bookmarked_by', 'CWUser'), 'metadata')
+uicfg.autoform_section.tag_attribute(('Bookmark', 'path'), 'primary')
+uicfg.autoform_section.tag_subject_of(('*', 'use_email', '*'), 'generated') # inlined actually
+uicfg.autoform_section.tag_subject_of(('*', 'primary_email', '*'), 'generic')
+
+uicfg.autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'),
+ {'widget': formwidgets.TextInput})
+uicfg.autoform_field_kwargs.tag_attribute(('Bookmark', 'path'),
+ {'widget': formwidgets.TextInput})
+
+uicfg.autoform_is_inlined.tag_subject_of(('*', 'use_email', '*'), True)
+uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'relation_type', '*'), True)
+uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'from_entity', '*'), True)
+uicfg.autoform_is_inlined.tag_subject_of(('CWRelation', 'to_entity', '*'), True)
--- a/web/views/basecomponents.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/basecomponents.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,11 +11,12 @@
__docformat__ = "restructuredtext en"
_ = unicode
+from logilab.mtconverter import xml_escape
from rql import parse
from cubicweb.selectors import yes, two_etypes_rset, match_form_params
from cubicweb.schema import display_name
-from cubicweb.common.uilib import html_escape, toggle_action
+from cubicweb.common.uilib import toggle_action
from cubicweb.web import component
from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator,
BoxLink)
@@ -29,6 +30,7 @@
"""build the rql input form, usually displayed in the header"""
id = 'rqlinput'
property_defs = VISIBLE_PROP_DEF
+ visible = False
def call(self, view=None):
if hasattr(view, 'filter_box_context_info'):
@@ -46,7 +48,7 @@
<input type="submit" value="" class="rqlsubmit" tabindex="%s" />
</fieldset>
''' % (not self.propval('visible') and 'hidden' or '',
- self.build_url('view'), html_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
+ self.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(),
req.next_tabindex()))
if self.req.search_state[0] != 'normal':
self.w(u'<input type="hidden" name="__mode" value="%s"/>'
@@ -55,7 +57,7 @@
class ApplLogo(component.Component):
- """build the application logo, usually displayed in the header"""
+ """build the instance logo, usually displayed in the header"""
id = 'logo'
property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
@@ -109,11 +111,6 @@
self.w(self.req._('anonymous'))
self.w(u''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
% (self.req._('i18n_login_popup')))
- # FIXME maybe have an other option to explicitely authorise registration
- # also provide a working register view
-# if self.config['anonymous-user']:
-# self.w(u''' [<a class="logout" href="?vid=register">%s</a>]'''
-# % (self.req._('i18n_register_user')))
else:
self.w(self.req._('anonymous'))
self.w(u' [<a class="logout" href="%s">%s</a>]'
@@ -121,8 +118,8 @@
class ApplicationMessage(component.Component):
- """display application's messages given using the __message parameter
- into a special div section
+ """display messages given using the __message parameter into a special div
+ section
"""
__select__ = yes()
id = 'applmessages'
@@ -141,15 +138,17 @@
class ApplicationName(component.Component):
- """display the application name"""
+ """display the instance name"""
id = 'appliname'
property_defs = VISIBLE_PROP_DEF
# don't want user to hide this component using an cwproperty
site_wide = True
def call(self):
- self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
- self.req.base_url(), self.req.property_value('ui.site-title')))
+ title = self.req.property_value('ui.site-title')
+ if title:
+ self.w(u'<span id="appliName"><a href="%s">%s</a></span>' % (
+ self.req.base_url(), title))
class SeeAlsoVComponent(component.RelatedObjectsVComponent):
@@ -204,7 +203,7 @@
url = self.build_url(rql=newrql, __restrrql=restrrql,
__restrtype=etype, __restrtypes=','.join(restrtypes))
html.append(u'<span><a href="%s">%s</a></span>' % (
- html_escape(url), elabel))
+ xml_escape(url), elabel))
rqlst.recover()
if on_etype:
url = self.build_url(rql=restrrql)
--- a/web/views/basecontrollers.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/basecontrollers.py Tue Aug 04 15:06:09 2009 +0200
@@ -15,7 +15,7 @@
import simplejson
from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid
from cubicweb.utils import strptime
@@ -33,16 +33,6 @@
except ImportError: # gae
HAS_SEARCH_RESTRICTION = False
-
-def xhtml_wrap(self, source):
- # XXX factor out, watch view.py ~ Maintemplate.doctype
- if self.req.xhtml_browser():
- dt = STRICT_DOCTYPE
- else:
- dt = STRICT_DOCTYPE_NOEXT
- head = u'<?xml version="1.0"?>\n' + dt
- return head + u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">%s</div>' % source.strip()
-
def jsonize(func):
"""decorator to sets correct content_type and calls `simplejson.dumps` on
results
@@ -58,7 +48,8 @@
def wrapper(self, *args, **kwargs):
self.req.set_content_type(self.req.html_content_type())
result = func(self, *args, **kwargs)
- return xhtml_wrap(self, result)
+ return ''.join((self.req.document_surrounding_div(), result.strip(),
+ u'</div>'))
wrapper.__name__ = func.__name__
return wrapper
@@ -78,7 +69,7 @@
id = 'login'
def publish(self, rset=None):
- """log in the application"""
+ """log in the instance"""
if self.config['auth-mode'] == 'http':
# HTTP authentication
raise ExplicitLogin()
@@ -91,7 +82,7 @@
id = 'logout'
def publish(self, rset=None):
- """logout from the application"""
+ """logout from the instance"""
return self.appli.session_handler.logout(self.req)
@@ -178,52 +169,59 @@
req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y'))
+def _validation_error(req, ex):
+ req.cnx.rollback()
+ forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True)
+ foreid = ex.entity
+ eidmap = req.data.get('eidmap', {})
+ for var, eid in eidmap.items():
+ if foreid == eid:
+ foreid = var
+ break
+ return (foreid, ex.errors)
+
+def _validate_form(req, vreg):
+ # XXX should use the `RemoteCallFailed` mechanism
+ try:
+ ctrl = vreg.select('controllers', 'edit', req=req)
+ except NoSelectableObject:
+ return (False, {None: req._('not authorized')})
+ try:
+ ctrl.publish(None)
+ except ValidationError, ex:
+ return (False, _validation_error(req, ex))
+ except Redirect, ex:
+ try:
+ req.cnx.commit() # ValidationError may be raise on commit
+ except ValidationError, ex:
+ return (False, _validation_error(req, ex))
+ else:
+ return (True, ex.location)
+ except Exception, ex:
+ req.cnx.rollback()
+ req.exception('unexpected error while validating form')
+ return (False, req._(str(ex).decode('utf-8')))
+ return (False, '???')
+
+
class FormValidatorController(Controller):
id = 'validateform'
+ def response(self, domid, status, args):
+ self.req.set_content_type('text/html')
+ jsargs = simplejson.dumps( (status, args) )
+ return """<script type="text/javascript">
+ window.parent.handleFormValidationResponse('%s', null, null, %s);
+</script>""" % (domid, jsargs)
+
def publish(self, rset=None):
- vreg = self.vreg
- try:
- ctrl = vreg.select('controllers', 'edit', self.req,
- appli=self.appli)
- except NoSelectableObject:
- status, args = (False, {None: self.req._('not authorized')})
- else:
- try:
- ctrl.publish(None, fromjson=True)
- except ValidationError, err:
- status, args = self.validation_error(err)
- except Redirect, err:
- try:
- self.req.cnx.commit() # ValidationError may be raise on commit
- except ValidationError, err:
- status, args = self.validation_error(err)
- else:
- status, args = (True, err.location)
- except Exception, err:
- self.req.cnx.rollback()
- self.exception('unexpected error in validateform')
- try:
- status, args = (False, self.req._(unicode(err)))
- except UnicodeError:
- status, args = (False, repr(err))
- else:
- status, args = (False, '???')
- self.req.set_content_type('text/html')
- jsarg = simplejson.dumps( (status, args) )
+ self.req.json_request = True
+ # XXX unclear why we have a separated controller here vs
+ # js_validate_form on the json controller
+ status, args = _validate_form(self.req, self.vreg)
domid = self.req.form.get('__domid', 'entityForm').encode(
self.req.encoding)
- return """<script type="text/javascript">
- window.parent.handleFormValidationResponse('%s', null, null, %s);
-</script>""" % (domid, simplejson.dumps( (status, args) ))
-
- def validation_error(self, err):
- self.req.cnx.rollback()
- try:
- eid = err.entity.eid
- except AttributeError:
- eid = err.entity
- return (False, (eid, err.errors))
+ return self.response(domid, status, args)
class JSonController(Controller):
@@ -238,10 +236,13 @@
note: it's the responsability of js_* methods to set the correct
response content type
"""
+ self.req.json_request = True
self.req.pageid = self.req.form.get('pageid')
- fname = self.req.form['fname']
try:
+ fname = self.req.form['fname']
func = getattr(self, 'js_%s' % fname)
+ except KeyError:
+ raise RemoteCallFailed('no method specified')
except AttributeError:
raise RemoteCallFailed('no %s method' % fname)
# no <arg> attribute means the callback takes no argument
@@ -375,26 +376,8 @@
return self.validate_form(action, names, values)
def validate_form(self, action, names, values):
- # XXX this method (and correspoding js calls) should use the new
- # `RemoteCallFailed` mechansim
self.req.form = self._rebuild_posted_form(names, values, action)
- vreg = self.vreg
- try:
- ctrl = vreg.select('controllers', 'edit', self.req)
- except NoSelectableObject:
- return (False, {None: self.req._('not authorized')})
- try:
- ctrl.publish(None, fromjson=True)
- except ValidationError, err:
- self.req.cnx.rollback()
- return (False, (err.entity, err.errors))
- except Redirect, redir:
- return (True, redir.location)
- except Exception, err:
- self.req.cnx.rollback()
- self.exception('unexpected error in js_validateform')
- return (False, self.req._(str(err).decode('utf-8')))
- return (False, '???')
+ return _validate_form(self.req, self.vreg)
@jsonize
def js_edit_field(self, action, names, values, rtype, eid, default):
@@ -405,27 +388,17 @@
rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype,
{'x': eid}, 'x')
entity = rset.get_entity(0, 0)
- value = entity.printable_value(rtype)
- return (success, args, value or default)
+ value = entity.printable_value(rtype) or default
+ return (success, args, value)
else:
return (success, args, None)
@jsonize
- def js_edit_relation(self, action, names, values,
- rtype, role, eid, vid, default):
- success, args = self.validate_form(action, names, values)
- if success:
- entity = self.req.eid_rset(eid).get_entity(0, 0)
- rset = entity.related(rtype, role)
- if rset:
- output = self.view(vid, rset)
- if vid == 'textoutofcontext':
- output = html_escape(output)
- else:
- output = default
- return (success, args, output)
- else:
- return (success, args, None)
+ def js_reledit_form(self, eid, rtype, role, default, lzone):
+ """XXX we should get rid of this and use loadxhtml"""
+ entity = self.req.eid_rset(eid).get_entity(0, 0)
+ return entity.view('reledit', rtype=rtype, role=role,
+ default=default, landing_zone=lzone)
@jsonize
def js_i18n(self, msgids):
@@ -589,7 +562,8 @@
self.smtp.sendmail(helo_addr, [recipient], msg.as_string())
def publish(self, rset=None):
- # XXX this allow anybody with access to an cubicweb application to use it as a mail relay
+ # XXX this allows users with access to an cubicweb instance to use it as
+ # a mail relay
body = self.req.form['mailbody']
subject = self.req.form['subject']
for recipient in self.recipients():
--- a/web/views/baseforms.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/baseforms.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,7 +12,7 @@
from simplejson import dumps
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from logilab.common.decorators import cached
from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat,
@@ -148,7 +148,7 @@
output = []
for name, value, iid in self._hiddens:
if isinstance(value, basestring):
- value = html_escape(value)
+ value = xml_escape(value)
if iid:
output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
% (iid, name, value))
@@ -249,14 +249,14 @@
w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
(_('cancel this insert'), row[2]))
w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], html_escape(row[5])))
+ % (row[1], row[4], xml_escape(row[5])))
w(u'</td>')
w(u'</tr>')
w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
w(u'<th class="labelCol">')
w(u'<span>%s</span>' % _('add relation'))
w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+ % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
w(u'<option value="">%s</option>' % _('select a relation'))
for i18nrtype, rschema, target in srels_by_cat:
# more entities to link to
@@ -551,10 +551,10 @@
ctx = {'action' : self.build_url('edit'),
'error': self.error_message(),
'progress': _('validating...'),
- 'url': html_escape(req.url()),
+ 'url': xml_escape(req.url()),
'formid': self.id,
- 'redirectvid': html_escape(form.get('__redirectvid', 'list')),
- 'redirectrql': html_escape(form.get('__redirectrql', self.rset.printable_rql())),
+ 'redirectvid': xml_escape(form.get('__redirectvid', 'list')),
+ 'redirectrql': xml_escape(form.get('__redirectrql', self.rset.printable_rql())),
'attrheaders': u'\n'.join(attrheaders),
'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
'okvalue': _('button_ok').capitalize(),
@@ -583,7 +583,7 @@
wdg = entity.get_widget
wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
if rschema.type != 'eid'] # XXX both (add, delete)
- seid = html_escape(dumps(eid))
+ seid = xml_escape(dumps(eid))
for wobj in wdgfactories:
if isinstance(wobj, ComboBoxWidget):
wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
--- a/web/views/basetemplates.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/basetemplates.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,13 +8,14 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.vregistry import objectify_selector
from cubicweb.selectors import match_kwargs
-from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW, STRICT_DOCTYPE
+from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW
from cubicweb.utils import make_uid, UStringIO
+
# main templates ##############################################################
class LogInOutTemplate(MainTemplate):
@@ -31,14 +32,14 @@
def template_header(self, content_type, view=None, page_title='', additional_headers=()):
w = self.whead
# explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
- w(u'<base href="%s"></base>' % html_escape(self.req.base_url()))
+ w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
% (content_type, self.req.encoding))
w(NOINDEX)
w(NOFOLLOW)
w(u'\n'.join(additional_headers) + u'\n')
self.wview('htmlheader', rset=self.rset)
- w(u'<title>%s</title>\n' % html_escape(page_title))
+ w(u'<title>%s</title>\n' % xml_escape(page_title))
class LogInTemplate(LogInOutTemplate):
@@ -60,7 +61,7 @@
if self.config['anonymous-user']:
indexurl = self.build_url('view', vid='index', __message=msg)
w(u'<p><a href="%s">%s</a><p>' % (
- html_escape(indexurl),
+ xml_escape(indexurl),
self.req._('go back to the index page')))
@objectify_selector
@@ -83,15 +84,15 @@
def call(self, view):
view.set_request_content_type()
view.set_stream()
- xhtml_wrap = (self.req.form.has_key('__notemplate') and view.templatable
- and view.content_type == self.req.html_content_type())
- if xhtml_wrap:
- view.w(u'<?xml version="1.0"?>\n' + STRICT_DOCTYPE)
- view.w(u'<div xmlns="http://www.w3.org/1999/xhtml" xmlns:cubicweb="http://www.logilab.org/2008/cubicweb">')
- # have to replace our unicode stream using view's binary stream
- view.render()
- if xhtml_wrap:
+ if (self.req.form.has_key('__notemplate') and view.templatable
+ and view.content_type == self.req.html_content_type()):
+ view.w(self.req.document_surrounding_div())
+ view.render()
view.w(u'</div>')
+ else:
+ view.render()
+ # have to replace our stream by view's stream (which may be a binary
+ # stream)
self._stream = view._stream
@@ -110,7 +111,7 @@
w(u'<div id="pageContent">\n')
vtitle = self.req.form.get('vtitle')
if vtitle:
- w(u'<h1 class="vtitle">%s</h1>\n' % html_escape(vtitle))
+ w(u'<h1 class="vtitle">%s</h1>\n' % xml_escape(vtitle))
# display entity type restriction component
etypefilter = self.vreg.select_vobject('components', 'etypenavigation',
self.req, rset=self.rset)
@@ -137,13 +138,14 @@
w = self.whead
lang = self.req.lang
self.write_doctype()
- w(u'<base href="%s" />' % html_escape(self.req.base_url()))
+ # explictly close the <base> tag to avoid IE 6 bugs while browsing DOM
+ w(u'<base href="%s"></base>' % xml_escape(self.req.base_url()))
w(u'<meta http-equiv="content-type" content="%s; charset=%s"/>\n'
% (content_type, self.req.encoding))
w(u'\n'.join(additional_headers) + u'\n')
self.wview('htmlheader', rset=self.rset)
if page_title:
- w(u'<title>%s</title>\n' % html_escape(page_title))
+ w(u'<title>%s</title>\n' % xml_escape(page_title))
def template_body_header(self, view):
w = self.w
@@ -212,7 +214,7 @@
% (content_type, self.req.encoding))
w(u'\n'.join(additional_headers))
self.wview('htmlheader', rset=self.rset)
- w(u'<title>%s</title>\n' % html_escape(page_title))
+ w(u'<title>%s</title>\n' % xml_escape(page_title))
self.w(u'<body>\n')
def template_footer(self, view=None):
@@ -234,7 +236,7 @@
whead(u'\n'.join(additional_headers) + u'\n')
self.wview('htmlheader', rset=self.rset)
w = self.w
- w(u'<title>%s</title>\n' % html_escape(page_title))
+ w(u'<title>%s</title>\n' % xml_escape(page_title))
w(u'<body>\n')
w(u'<div id="page">')
w(u'<table width="100%" height="100%" border="0"><tr>\n')
@@ -252,7 +254,7 @@
w(u'<div id="pageContent">\n')
vtitle = self.req.form.get('vtitle')
if vtitle:
- w(u'<h1 class="vtitle">%s</h1>' % html_escape(vtitle))
+ w(u'<h1 class="vtitle">%s</h1>' % xml_escape(vtitle))
def topleft_header(self):
logo = self.vreg.select_vobject('components', 'logo', self.req,
@@ -301,7 +303,7 @@
self.req, rset=self.rset)
if urlgetter is not None:
self.whead(u'<link rel="alternate" type="application/rss+xml" title="RSS feed" href="%s"/>\n'
- % html_escape(urlgetter.feed_url()))
+ % xml_escape(urlgetter.feed_url()))
def pageid(self):
req = self.req
@@ -441,7 +443,7 @@
self.w(u'<div id="%s" class="%s">' % (id, klass))
if title:
self.w(u'<div id="loginTitle">%s</div>'
- % self.req.property_value('ui.site-title'))
+ % (self.req.property_value('ui.site-title') or u' '))
self.w(u'<div id="loginContent">\n')
if message:
@@ -462,7 +464,7 @@
def login_form(self, id):
_ = self.req._
self.w(u'<form method="post" action="%s" id="login_form">\n'
- % html_escape(login_form_url(self.config, self.req)))
+ % xml_escape(login_form_url(self.config, self.req)))
self.w(u'<table>\n')
self.w(u'<tr>\n')
msg = (self.config['allow-email-login'] and _('login or email')) or _('login')
--- a/web/views/baseviews.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/baseviews.py Tue Aug 04 15:06:09 2009 +0200
@@ -2,7 +2,7 @@
* noresult, final
* primary, sidebox
-* secondary, oneline, incontext, outofcontext, text
+* oneline, incontext, outofcontext, text
* list
@@ -17,10 +17,11 @@
from rql import nodes
-from logilab.mtconverter import TransformError, html_escape, xml_escape
+from logilab.mtconverter import TransformError, xml_escape, xml_escape
from cubicweb import NoSelectableObject
-from cubicweb.selectors import yes, empty_rset
+from cubicweb.selectors import yes, empty_rset, one_etype_rset
+from cubicweb.schema import display_name
from cubicweb.view import EntityView, AnyRsetView, View
from cubicweb.common.uilib import cut, printable_value
@@ -100,6 +101,7 @@
self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
+# XXX deprecated
class SecondaryView(EntityView):
id = 'secondary'
title = _('secondary')
@@ -121,8 +123,8 @@
"""the one line view for an entity: linked text view
"""
entity = self.entity(row, col)
- self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
- self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
+ self.w(u'<a href="%s">' % xml_escape(entity.absolute_url()))
+ self.w(xml_escape(self.view('text', self.rset, row=row, col=col)))
self.w(u'</a>')
@@ -179,9 +181,6 @@
self.w(u'</div>')
-# new default views for finner control in general views , to use instead of
-# oneline / secondary
-
class InContextTextView(TextView):
id = 'textincontext'
title = None # not listed as a possible view
@@ -205,8 +204,8 @@
entity = self.entity(row, col)
desc = cut(entity.dc_description(), 50)
self.w(u'<a href="%s" title="%s">' % (
- html_escape(entity.absolute_url()), html_escape(desc)))
- self.w(html_escape(self.view('textincontext', self.rset,
+ xml_escape(entity.absolute_url()), xml_escape(desc)))
+ self.w(xml_escape(self.view('textincontext', self.rset,
row=row, col=col)))
self.w(u'</a>')
@@ -218,8 +217,8 @@
entity = self.entity(row, col)
desc = cut(entity.dc_description(), 50)
self.w(u'<a href="%s" title="%s">' % (
- html_escape(entity.absolute_url()), html_escape(desc)))
- self.w(html_escape(self.view('textoutofcontext', self.rset,
+ xml_escape(entity.absolute_url()), xml_escape(desc)))
+ self.w(xml_escape(self.view('textoutofcontext', self.rset,
row=row, col=col)))
self.w(u'</a>')
@@ -236,6 +235,7 @@
:param listid: the DOM id to use for the root element
"""
+ # XXX much of the behaviour here should probably be outside this view
if subvid is None and 'subvid' in self.req.form:
subvid = self.req.form.pop('subvid') # consume it
if listid:
@@ -258,28 +258,6 @@
self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
self.w(u'</li>\n')
- def url(self):
- """overrides url method so that by default, the view list is called
- with sorted entities
- """
- coltypes = self.rset.column_types(0)
- # don't want to generate the rql if there is some restriction on
- # something else than the entity type
- if len(coltypes) == 1:
- # XXX norestriction is not correct here. For instance, in cases like
- # Any P,N WHERE P is Project, P name N
- # norestriction should equal True
- restr = self.rset.syntax_tree().children[0].where
- norestriction = (isinstance(restr, nodes.Relation) and
- restr.is_types_restriction())
- if norestriction:
- etype = iter(coltypes).next()
- return self.build_url(etype.lower(), vid=self.id)
- if len(self.rset) == 1:
- entity = self.rset.get_entity(0, 0)
- return self.build_url(entity.rest_path(), vid=self.id)
- return self.build_url(rql=self.rset.printable_rql(), vid=self.id)
-
class ListItemView(EntityView):
id = 'listitem'
@@ -307,6 +285,31 @@
redirect_vid = 'incontext'
+class AdaptedListView(EntityView):
+ """list of entities of the same type"""
+ id = 'adaptedlist'
+ __select__ = EntityView.__select__ & one_etype_rset()
+ item_vid = 'adaptedlistitem'
+
+ @property
+ def title(self):
+ etype = iter(self.rset.column_types(0)).next()
+ return display_name(self.req, etype, form='plural')
+
+ def call(self, **kwargs):
+ """display a list of entities by calling their <item_vid> view"""
+ if not 'vtitle' in self.req.form:
+ self.w(u'<h1>%s</h1>' % self.title)
+ super(AdaptedListView, self).call(**kwargs)
+
+ def cell_call(self, row, col=0, vid=None, **kwargs):
+ self.wview(self.item_vid, self.rset, row=row, col=col, vid=vid, **kwargs)
+
+
+class AdaptedListItemView(ListItemView):
+ id = 'adaptedlistitem'
+
+
class CSVView(SimpleListView):
id = 'csv'
redirect_vid = 'incontext'
--- a/web/views/bookmark.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/bookmark.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized
from cubicweb.selectors import implements
@@ -35,12 +35,12 @@
entity = self.complete_entity(row, col)
self.w(u' ')
self.w(u"<span class='title'><b>")
- self.w(u"%s : %s" % (self.req._('Bookmark'), html_escape(entity.title)))
+ self.w(u"%s : %s" % (self.req._('Bookmark'), xml_escape(entity.title)))
self.w(u"</b></span>")
self.w(u'<br/><br/><div class="content"><a href="%s">' % (
- html_escape(entity.actual_url())))
+ xml_escape(entity.actual_url())))
self.w(u'</a>')
- self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(html_escape(u.name())
+ self.w(u'<p>%s%s</p>' % (self.req._('Used by:'), ', '.join(xml_escape(u.name())
for u in entity.bookmarked_by)))
self.w(u'</div>')
@@ -75,8 +75,8 @@
else:
dlink = None
for bookmark in rset.entities():
- label = '<a href="%s">%s</a>' % (html_escape(bookmark.action_url()),
- html_escape(bookmark.title))
+ label = '<a href="%s">%s</a>' % (xml_escape(bookmark.action_url()),
+ xml_escape(bookmark.title))
if candelete:
dlink = u'[<a href="javascript:removeBookmark(%s)" title="%s">-</a>]' % (
bookmark.eid, _('delete this bookmark'))
@@ -85,7 +85,7 @@
if eschema.has_perm(req, 'add') and rschema.has_perm(req, 'add', toeid=ueid):
boxmenu = BoxMenu(req._('manage bookmarks'))
linkto = 'bookmarked_by:%s:subject' % ueid
- # use a relative path so that we can move the application without
+ # use a relative path so that we can move the instance without
# loosing bookmarks
path = req.relative_path()
url = self.create_url(self.etype, __linkto=linkto, path=path)
--- a/web/views/boxes.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/boxes.py Tue Aug 04 15:06:09 2009 +0200
@@ -16,12 +16,12 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
-from cubicweb.rtags import RelationTags
from cubicweb.selectors import match_user_groups, non_final_entity
+from cubicweb.view import EntityView
+from cubicweb.schema import display_name
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
-from cubicweb.view import EntityView
from cubicweb.web import uicfg
from cubicweb.web.box import BoxTemplate
@@ -144,20 +144,16 @@
if 'in_state' in entity.e_schema.subject_relations() and entity.in_state:
_ = self.req._
state = entity.in_state[0]
- transitions = list(state.transitions(entity))
- if transitions:
- menu_title = u'%s: %s' % (_('state'), state.view('text'))
- menu_items = []
- for tr in transitions:
- url = entity.absolute_url(vid='statuschange', treid=tr.eid)
- menu_items.append(self.mk_action(_(tr.name), url))
- box.append(BoxMenu(menu_title, menu_items))
- # when there are no possible transition, put state if the menu if
- # there are some other actions
- elif not box.is_empty():
- menu_title = u'<a title="%s">%s: <i>%s</i></a>' % (
- _('no possible transition'), _('state'), state.view('text'))
- box.append(RawBoxItem(menu_title, 'boxMainactions'))
+ menu_title = u'%s: %s' % (_('state'), state.view('text'))
+ menu_items = []
+ for tr in state.transitions(entity):
+ url = entity.absolute_url(vid='statuschange', treid=tr.eid)
+ menu_items.append(self.mk_action(_(tr.name), url))
+ wfurl = self.build_url('cwetype/%s'%entity.e_schema, vid='workflow')
+ menu_items.append(self.mk_action(_('view workflow'), wfurl))
+ wfurl = entity.absolute_url(vid='wfhistory')
+ menu_items.append(self.mk_action(_('view history'), wfurl))
+ box.append(BoxMenu(menu_title, menu_items))
return None
def linkto_url(self, entity, rtype, etype, target):
@@ -191,7 +187,7 @@
else:
rql = ''
form = self.formdef % (req.build_url('view'), req.next_tabindex(),
- html_escape(rql), req.next_tabindex())
+ xml_escape(rql), req.next_tabindex())
title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
box.append(BoxHtml(form))
--- a/web/views/calendar.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/calendar.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from datetime import datetime, date, timedelta
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.interfaces import ICalendarable
from cubicweb.selectors import implements
@@ -82,7 +82,7 @@
for i in range(len(self.rset.rows)):
task = self.complete_entity(i)
self.w(u'<div class="vevent">')
- self.w(u'<h3 class="summary">%s</h3>' % html_escape(task.dc_title()))
+ self.w(u'<h3 class="summary">%s</h3>' % xml_escape(task.dc_title()))
self.w(u'<div class="description">%s</div>'
% task.dc_description(format='text/html'))
if task.start:
@@ -244,8 +244,8 @@
prevlink, nextlink = self._prevnext_links(curdate) # XXX
self.w(u'<tr><th><a href="%s"><<</a></th><th colspan="5">%s %s</th>'
u'<th><a href="%s">>></a></th></tr>' %
- (html_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
- curdate.year, html_escape(nextlink)))
+ (xml_escape(prevlink), self.req._(curdate.strftime('%B').lower()),
+ curdate.year, xml_escape(nextlink)))
# output header
self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
@@ -292,7 +292,7 @@
__redirectparams=self.req.build_url_params(year=curdate.year, month=curmonth),
__redirectvid=self.id
)
- self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (html_escape(url), self.req._(u'add')))
+ self.w(u'<div class="cmd"><a href="%s">%s</a></div>' % (xml_escape(url), self.req._(u'add')))
self.w(u' ')
self.w(u'</div>')
self.w(u'<div class="cellContent">')
@@ -307,7 +307,7 @@
__redirectvid=self.id
)
- self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
+ self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
task.view('tooltip', w=self.w )
self.w(u'</div>')
else:
@@ -388,9 +388,9 @@
self.w(u'<tr><th class="transparent"></th>')
self.w(u'<th><a href="%s"><<</a></th><th colspan="5">%s %s %s</th>'
u'<th><a href="%s">>></a></th></tr>' %
- (html_escape(prevlink), first_day_of_week.year,
+ (xml_escape(prevlink), first_day_of_week.year,
self.req._(u'week'), first_day_of_week.isocalendar()[1],
- html_escape(nextlink)))
+ xml_escape(nextlink)))
# output header
self.w(u'<tr>')
@@ -429,7 +429,7 @@
__redirectvid=self.id
)
extra = ' ondblclick="addCalendarItem(event, hmin=8, hmax=20, year=%s, month=%s, day=%s, duration=2, baseurl=\'%s\')"' % (
- wdate.year, wdate.month, wdate.day, html_escape(url))
+ wdate.year, wdate.month, wdate.day, xml_escape(url))
else:
extra = ""
self.w(u'<div class="columndiv"%s>'% extra)
@@ -501,7 +501,7 @@
__redirectvid=self.id
)
- self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % html_escape(url))
+ self.w(u'<div class="tooltip" ondblclick="stopPropagation(event); window.location.assign(\'%s\'); return false;">' % xml_escape(url))
task.view('tooltip', w=self.w)
self.w(u'</div>')
if task.start is None:
--- a/web/views/csvexport.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/csvexport.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,6 +7,7 @@
"""
__docformat__ = "restructuredtext en"
+from cubicweb.schema import display_name
from cubicweb.common.uilib import UnicodeCSVWriter
from cubicweb.view import EntityView, AnyRsetView
--- a/web/views/cwproperties.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/cwproperties.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,18 +8,18 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from logilab.common.decorators import cached
from cubicweb import UnknownProperty
from cubicweb.selectors import (one_line_rset, none_rset, implements,
- match_user_groups)
+ match_user_groups, objectify_selector)
from cubicweb.view import StartupView
from cubicweb.web import uicfg, stdmsgs
from cubicweb.web.form import FormViewMixIn
from cubicweb.web.formfields import FIELDS, StringField
-from cubicweb.web.formwidgets import Select, Button, SubmitButton
+from cubicweb.web.formwidgets import Select, TextInput, Button, SubmitButton
from cubicweb.web.views import primary, formrenderers
@@ -64,6 +64,7 @@
class SystemCWPropertiesForm(FormViewMixIn, StartupView):
+ """site-wide properties edition form"""
id = 'systempropertiesform'
__select__ = none_rset() & match_user_groups('managers')
@@ -94,7 +95,6 @@
return status
def call(self, **kwargs):
- """The default view representing the application's index"""
self.req.add_js(('cubicweb.edition.js', 'cubicweb.preferences.js', 'cubicweb.ajax.js'))
self.req.add_css('cubicweb.preferences.css')
vreg = self.vreg
@@ -139,7 +139,6 @@
w(u'<h2 class="propertiesform">%s</h2>\n' %
(make_togglable_link('fieldset_' + group, label.capitalize())))
w(u'<div id="fieldset_%s" %s>' % (group, status))
-
# create selection
sorted_objects = sorted((self.req.__('%s_%s' % (group, o)), o, f)
for o, f in objects.iteritems())
@@ -155,7 +154,7 @@
docmsgid = '%s_%s_description' % (group, oid)
doc = _(docmsgid)
if doc != docmsgid:
- w(u'<div class="helper">%s</div>' % html_escape(doc).capitalize())
+ w(u'<div class="helper">%s</div>' % xml_escape(doc).capitalize())
w(u'</div>')
w(u'<fieldset id="field_%(oid)s_%(group)s" class="%(group)s preferences hidden">'
% {'oid':oid, 'group':group})
@@ -217,21 +216,22 @@
eidparam=True))
subform.vreg = self.vreg
subform.form_add_hidden('pkey', key, eidparam=True)
- subform.form_add_hidden("current-value:%s" % entity.eid,)
form.form_add_subform(subform)
return subform
-def is_user_prefs(cls, req, rset, row=None, col=0, **kwargs):
+
+@objectify_selector
+def is_user_prefs(cls, req, rset=None, row=None, col=0, **kwargs):
return req.user.eid == rset[row or 0][col]
class CWPropertiesForm(SystemCWPropertiesForm):
+ """user's preferences properties edition form"""
id = 'propertiesform'
__select__ = (
- # we don't want guests to be able to come here
- match_user_groups('users', 'managers') &
- (none_rset() | ((one_line_rset() & is_user_prefs) &
- (one_line_rset() & match_user_groups('managers'))))
+ (none_rset() & match_user_groups('users','managers'))
+ | (one_line_rset() & match_user_groups('users') & is_user_prefs())
+ | (one_line_rset() & match_user_groups('managers') & implements('CWUser'))
)
title = _('preferences')
@@ -260,7 +260,7 @@
class PlaceHolderWidget(object):
- def render(self, form, field):
+ def render(self, form, field, renderer):
domid = form.context[field]['id']
# empty span as well else html validation fail (label is refering to
# this id)
@@ -273,7 +273,7 @@
self.value = value
self.msg = msg
- def render(self, form, field):
+ def render(self, form, field, renderer):
domid = form.context[field]['id']
value = '<span class="value" id="%s">%s</span>' % (domid, self.value)
if self.msg:
@@ -292,14 +292,13 @@
wdg.attrs['tabindex'] = form.req.next_tabindex()
wdg.attrs['onchange'] = "javascript:setPropValueWidget('%s', %s)" % (
form.edited_entity.eid, form.req.next_tabindex())
- return wdg.render(form, self)
+ return wdg.render(form, self, renderer)
def vocabulary(self, form):
entity = form.edited_entity
_ = form.req._
if entity.has_eid():
return [(_(entity.pkey), entity.pkey)]
- # key beginning with 'system.' should usually not be edited by hand
choices = entity.vreg.user_property_keys()
return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices))
@@ -314,7 +313,7 @@
wdg = self.get_widget(form)
if tabindex is not None:
wdg.attrs['tabindex'] = tabindex
- return wdg.render(form, self)
+ return wdg.render(form, self, renderer)
def form_init(self, form):
entity = form.edited_entity
@@ -346,15 +345,15 @@
else:
self.choices = vocab
wdg = Select()
+ elif pdef['type'] == 'String': # else we'll get a TextArea by default
+ wdg = TextInput()
else:
- wdg = FIELDS[pdef['type']].widget()
+ field = FIELDS[pdef['type']]()
+ wdg = field.widget
if pdef['type'] == 'Boolean':
- self.choices = [(form.req._('yes'), '1'), (form.req._('no'), '')]
- elif pdef['type'] in ('Float', 'Int'):
- wdg.attrs.setdefault('size', 3)
+ self.choices = field.vocabulary(form)
self.widget = wdg
-
uicfg.autoform_field.tag_attribute(('CWProperty', 'pkey'), PropertyKeyField)
uicfg.autoform_field.tag_attribute(('CWProperty', 'value'), PropertyValueField)
--- a/web/views/cwuser.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/cwuser.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import one_line_rset, implements, match_user_groups
from cubicweb.view import EntityView
@@ -25,7 +25,7 @@
def url(self):
login = self.rset.get_entity(self.row or 0, self.col or 0).login
- return self.build_url('cwuser/%s'%login, vid='epropertiesform')
+ return self.build_url('cwuser/%s'%login, vid='propertiesform')
class FoafView(EntityView):
@@ -52,14 +52,14 @@
<foaf:primaryTopic rdf:resource="%s"/>
</foaf:PersonalProfileDocument>''' % (entity.absolute_url(), entity.absolute_url()))
self.w(u'<foaf:Person rdf:ID="%s">\n' % entity.eid)
- self.w(u'<foaf:name>%s</foaf:name>\n' % html_escape(entity.dc_long_title()))
+ self.w(u'<foaf:name>%s</foaf:name>\n' % xml_escape(entity.dc_long_title()))
if entity.surname:
self.w(u'<foaf:family_name>%s</foaf:family_name>\n'
- % html_escape(entity.surname))
+ % xml_escape(entity.surname))
if entity.firstname:
self.w(u'<foaf:givenname>%s</foaf:givenname>\n'
- % html_escape(entity.firstname))
+ % xml_escape(entity.firstname))
emailaddr = entity.get_email()
if emailaddr:
- self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % html_escape(emailaddr))
+ self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % xml_escape(emailaddr))
self.w(u'</foaf:Person>\n')
--- a/web/views/debug.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/debug.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from time import strftime, localtime
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import none_rset, match_user_groups
from cubicweb.view import StartupView
@@ -21,7 +21,7 @@
w(u'<ul>')
for key in sorted(dict):
w(u'<li><span class="label">%s</span>: <span>%s</span></li>' % (
- html_escape(str(key)), html_escape(repr(dict[key]))))
+ xml_escape(str(key)), xml_escape(repr(dict[key]))))
w(u'</ul>')
@@ -38,7 +38,7 @@
if sessions:
w(u'<ul>')
for sid, session in sessions:
- w(u'<li>%s (last usage: %s)<br/>' % (html_escape(str(session)),
+ w(u'<li>%s (last usage: %s)<br/>' % (xml_escape(str(session)),
strftime('%Y-%m-%d %H:%M:%S',
localtime(session.timestamp))))
dict_to_html(w, session.data)
--- a/web/views/editcontroller.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/editcontroller.py Tue Aug 04 15:06:09 2009 +0200
@@ -25,9 +25,8 @@
class EditController(ViewController):
id = 'edit'
- def publish(self, rset=None, fromjson=False):
+ def publish(self, rset=None):
"""edit / create / copy / delete entity / relations"""
- self.fromjson = fromjson
for key in self.req.form:
# There should be 0 or 1 action
if key.startswith('__action_'):
@@ -113,9 +112,8 @@
entity = execute(rql, formparams).get_entity(0, 0)
eid = entity.eid
except ValidationError, ex:
- # ex.entity may be an int or an entity instance
self._to_create[formparams['eid']] = ex.entity
- if self.fromjson:
+ if self.req.json_request: # XXX (syt) why?
ex.entity = formparams['eid']
raise
self._to_create[formparams['eid']] = eid
--- a/web/views/editforms.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/editforms.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,17 +13,17 @@
from simplejson import dumps
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
specified_etype_implements, yes)
-from cubicweb.utils import make_uid
+from cubicweb.utils import make_uid, compute_cardinality, get_schema_property
from cubicweb.view import EntityView
from cubicweb.common import tags
-from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
-from cubicweb.web.form import FormViewMixIn
-from cubicweb.web.formfields import RelationField
-from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton, Select
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param, uicfg
+from cubicweb.web.form import FormViewMixIn, FieldNotFound
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
from cubicweb.web.views import forms
@@ -38,12 +38,36 @@
entities
"""
js = u"javascript: togglePendingDelete('%s', %s);" % (
- nodeid, html_escape(dumps(eid)))
+ nodeid, xml_escape(dumps(eid)))
return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
js, nodeid, label)
-class DeleteConfForm(FormViewMixIn, EntityView):
+class DeleteConfForm(forms.CompositeForm):
+ id = 'deleteconf'
+ __select__ = non_final_entity()
+
+ domid = 'deleteconf'
+ copy_nav_params = True
+ form_buttons = [Button(stdmsgs.YES, cwaction='delete'),
+ Button(stdmsgs.NO, cwaction='cancel')]
+ @property
+ def action(self):
+ return self.build_url('edit')
+
+ def __init__(self, *args, **kwargs):
+ super(DeleteConfForm, self).__init__(*args, **kwargs)
+ done = set()
+ for entity in self.rset.entities():
+ if entity.eid in done:
+ continue
+ done.add(entity.eid)
+ subform = self.vreg.select('forms', 'base', self.req, entity=entity,
+ mainform=False)
+ self.form_add_subform(subform)
+
+
+class DeleteConfFormView(FormViewMixIn, EntityView):
"""form used to confirm deletion of some entities"""
id = 'deleteconf'
title = _('delete')
@@ -59,20 +83,10 @@
% _('this action is not reversible!'))
# XXX above message should have style of a warning
w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
- form = self.vreg.select('forms', 'composite', req, domid='deleteconf',
- copy_nav_params=True,
- action=self.build_url('edit'), onsubmit=onsubmit,
- form_buttons=[Button(stdmsgs.YES, cwaction='delete'),
- Button(stdmsgs.NO, cwaction='cancel')])
- done = set()
+ form = self.vreg.select('forms', self.id, req, rset=self.rset,
+ onsubmit=onsubmit)
w(u'<ul>\n')
for entity in self.rset.entities():
- if entity.eid in done:
- continue
- done.add(entity.eid)
- subform = self.vreg.select('forms', 'base', req, entity=entity,
- mainform=False)
- form.form_add_subform(subform)
# don't use outofcontext view or any other that may contain inline edition form
w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
href=entity.absolute_url()))
@@ -87,55 +101,111 @@
"""
id = 'reledit'
__select__ = non_final_entity() & match_kwargs('rtype')
-
# FIXME editableField class could be toggleable from userprefs
- onsubmit = ("return inlineValidateAttributeForm('%(divid)s-form', '%(rtype)s', "
- "'%(eid)s', '%(divid)s', %(reload)s, '%(default)s');")
- ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+ # add metadata to allow edition of metadata attributes (not considered by
+ # edition form by default)
+ attrcategories = ('primary', 'secondary', 'metadata')
+
+ _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+ _defaultlandingzone = (u'<img title="%(msg)s" '
+ 'src="data/accessories-text-editor.png" '
+ 'alt="%(msg)s"/>')
+ _landingzonemsg = _('click to edit this field')
+ # default relation vids according to cardinality
+ _one_rvid = 'incontext'
+ _many_rvid = 'csv'
+
+ def _compute_best_vid(self, entity, rtype, role):
+ if compute_cardinality(entity.e_schema,
+ entity.schema.rschema(rtype),
+ role) in '+*':
+ return self._many_rvid
+ return self._one_rvid
- def cell_call(self, row, col, rtype=None, role='subject', reload=False,
- vid='textoutofcontext', default=None):
- """display field to edit entity's `rtype` relation on double-click"""
- rschema = self.schema.rschema(rtype)
+ def _build_landing_zone(self, lzone):
+ return lzone or self._defaultlandingzone % {'msg' : xml_escape(self.req._(self._landingzonemsg))}
+
+ def _build_renderer(self, entity, rtype, role):
+ return self.vreg.select_object('formrenderers', 'base', self.req,
+ entity=entity,
+ display_label=False, display_help=False,
+ display_fields=[(rtype, role)],
+ table_class='', button_bar_class='buttonbar',
+ display_progress_div=False)
+
+ def cell_call(self, row, col, rtype=None, role='subject',
+ reload=False, # controls reloading the whole page after change
+ rvid=None, # vid to be applied to other side of rtype
+ default=None, # default value
+ landing_zone=None # prepend value with a separate html element to click onto
+ # (esp. needed when values are links)
+ ):
+ """display field to edit entity's `rtype` relation on click"""
+ assert rtype
+ assert role in ('subject', 'object')
+ if default is None:
+ default = xml_escape(self.req._('<no value>'))
entity = self.entity(row, col)
- if not default:
- default = self.req._('not specified')
+ rschema = entity.schema.rschema(rtype)
+ lzone = self._build_landing_zone(landing_zone)
+ # compute value, checking perms, build form
if rschema.is_final():
- if getattr(entity, rtype) is None:
- value = default
- else:
- value = entity.printable_value(rtype)
+ value = entity.printable_value(rtype)
+ etype = str(entity.e_schema)
+ ttype = rschema.targets(etype, role)[0]
+ afs = uicfg.autoform_section.etype_get(etype, rtype, role, ttype)
+ if not (afs in self.attrcategories and entity.has_perm('update')):
+ self.w(value)
+ return
+ value = value or default
+ self._attribute_form(entity, value, rtype, role, reload,
+ row, col, default, lzone)
else:
+ dispctrl = uicfg.primaryview_display_ctrl.etype_get(entity.e_schema,
+ rtype, role)
+ vid = dispctrl.get('vid', 'reledit')
+ if vid != 'reledit': # reledit explicitly disabled
+ self.wview(vid, entity.related(rtype, role))
+ return
+ if rvid is None:
+ rvid = self._compute_best_vid(entity, rtype, role)
rset = entity.related(rtype, role)
- # XXX html_escape but that depends of the actual vid
- value = html_escape(self.view(vid, rset, 'null') or default)
- if not entity.has_perm('update'):
- self.w(value)
- return
- if rschema.is_final():
- form = self._build_attribute_form(entity, value, rtype, role,
- reload, row, col, default)
- else:
- form = self._build_relation_form(entity, value, rtype, role,
- row, col, vid, default)
- renderer = self.vreg.select('formrenderers', 'base', self.req,
- entity=entity,
- display_label=False, display_help=False,
- display_fields=[(rtype, role)],
- table_class='', button_bar_class='buttonbar',
- display_progress_div=False)
- self.w(form.form_render(renderer=renderer))
+ if rset:
+ value = self.view(rvid, rset)
+ else:
+ value = default
+ # XXX check autoform_section. what if 'generic'?
+ if role == 'subject' and not rschema.has_perm(self.req, 'add',
+ fromeid=entity.eid):
+ return self.w(value)
+ elif role == 'object' and not rschema.has_perm(self.req, 'add',
+ toeid=entity.eid):
+ return self.w(value)
+ elif get_schema_property(entity.e_schema, rschema,
+ role, 'composite') == role:
+ self.warning('reledit cannot be applied : (... %s %s [composite])'
+ % (rtype, entity.e_schema))
+ return self.w(value)
+ self._relation_form(entity, value, rtype, role, reload, rvid,
+ default, lzone)
- def _build_relation_form(self, entity, value, rtype, role, row, col, vid, default):
- entity = self.entity(row, col)
+
+ def _relation_form(self, entity, value, rtype, role, reload, rvid, default, lzone):
+ """xxx-reledit div (class=field)
+ +-xxx div (class="editableField")
+ | +-landing zone
+ +-value
+ +-form-xxx div
+ """
divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
- event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : vid,
- 'default' : default, 'role' : role}
- onsubmit = ("return inlineValidateRelationForm('%(divid)s-form', '%(rtype)s', "
- "'%(role)s', '%(eid)s', '%(divid)s', '%(vid)s', '%(default)s');"
+ event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'vid' : rvid,
+ 'reload' : dumps(reload), 'default' : default, 'role' : role,
+ 'lzone' : lzone}
+ onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
+ "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');"
% event_data)
- cancelclick = "cancelInlineEdit(%s,\'%s\',\'%s\')" % (
+ cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')" % (
entity.eid, rtype, divid)
form = self.vreg.select('forms', 'base', self.req, entity=entity,
domid='%s-form' % divid, cssstyle='display: none',
@@ -143,30 +213,58 @@
form_buttons=[SubmitButton(),
Button(stdmsgs.BUTTON_CANCEL,
onclick=cancelclick)])
- form.append_field(RelationField(name=rtype, role=role, sort=True,
- widget=Select(),
- label=u' '))
- self.w(tags.div(value, klass='editableField', id=divid,
- ondblclick=self.ondblclick % event_data))
- return form
+ field = guess_field(entity.e_schema, entity.schema.rschema(rtype), role)
+ form.append_field(field)
+ w = self.w
+ w(u'<div id="%s-reledit" class="field">' % divid)
+ w(tags.div(lzone, klass='editableField', id=divid,
+ onclick=self._onclick % event_data))
+ w(value)
+ renderer = self._build_renderer(entity, rtype, role)
+ w(form.form_render(renderer=renderer))
+ w(u'</div>')
- def _build_attribute_form(self, entity, value, rtype, role, reload, row, col, default):
+ def _attribute_form(self, entity, value, rtype, role, reload, row, col, default, lzone):
+ """div (class=field)
+ +-xxx div
+ | +-xxx div (class=editableField)
+ | | +-landing zone
+ | +-value-xxx div
+ | +-value
+ +-form-xxx div
+ """
eid = entity.eid
divid = 'd%s' % make_uid('%s-%s' % (rtype, eid))
event_data = {'divid' : divid, 'eid' : eid, 'rtype' : rtype,
'reload' : dumps(reload), 'default' : default}
+ onsubmit = ("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', "
+ "%(reload)s, '%(default)s');")
buttons = [SubmitButton(stdmsgs.BUTTON_OK),
Button(stdmsgs.BUTTON_CANCEL,
- onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (
+ onclick="hideInlineEdit(%s,\'%s\',\'%s\')" % (
eid, rtype, divid))]
form = self.vreg.select('forms', 'edition', self.req, rset=self.rset,
row=row, col=col, form_buttons=buttons,
+ attrcategories=self.attrcategories,
domid='%s-form' % divid, action='#',
cssstyle='display: none',
- onsubmit=self.onsubmit % event_data)
- self.w(tags.div(value, klass='editableField', id=divid,
- ondblclick=self.ondblclick % event_data))
- return form
+ onsubmit=onsubmit % event_data)
+ try:
+ field = form.field_by_name(rtype, role)
+ except FieldNotFound:
+ self.w(value)
+ return
+ w = self.w
+ w(u'<div class="field">')
+ w(u'<div id="%s" style="display: inline">' % divid)
+ w(tags.div(lzone, klass='editableField',
+ onclick=self._onclick % event_data))
+ w(u'<div id="value-%s" style="display: inline">%s</div>' %
+ (divid, value))
+ w(u'</div>')
+ renderer = self._build_renderer(entity, rtype, role)
+ w(form.form_render(renderer=renderer))
+ w(u'</div>')
class EditionFormView(FormViewMixIn, EntityView):
@@ -360,8 +458,13 @@
divid = '%s-%s-%s' % (peid, rtype, entity.eid)
title = self.schema.rschema(rtype).display_name(self.req, role)
removejs = self.removejs % (peid, rtype,entity.eid)
+ countkey = '%s_count' % rtype
+ try:
+ self.req.data[countkey] += 1
+ except:
+ self.req.data[countkey] = 1
self.w(form.form_render(divid=divid, title=title, removejs=removejs,
- **kwargs))
+ counter=self.req.data[countkey], **kwargs))
def add_hiddens(self, form, entity, peid, rtype, role):
# to ease overriding (see cubes.vcsfile.views.forms for instance)
--- a/web/views/editviews.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/editviews.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,7 +11,7 @@
from simplejson import dumps
from logilab.common.decorators import cached
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import typed_eid
from cubicweb.view import EntityView
@@ -64,10 +64,10 @@
erset = entity.as_rset()
if self.req.match_search_state(erset):
self.w(u'<a href="%s" title="%s">%s</a> <a href="%s" title="%s">[...]</a>' % (
- html_escape(linksearch_select_url(self.req, erset)),
+ xml_escape(linksearch_select_url(self.req, erset)),
self.req._('select this entity'),
- html_escape(entity.view('textoutofcontext')),
- html_escape(entity.absolute_url(vid='primary')),
+ xml_escape(entity.view('textoutofcontext')),
+ xml_escape(entity.absolute_url(vid='primary')),
self.req._('view detail for this entity')))
else:
entity.view('outofcontext', w=self.w)
@@ -111,7 +111,7 @@
</select>
</div>
""" % (hidden and 'hidden' or '', divid, selectid,
- html_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+ xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
'\n'.join(options))
def _get_select_options(self, entity, rschema, target):
@@ -126,13 +126,13 @@
for eview, reid in form.form_field_vocabulary(field, limit):
if reid is None:
options.append('<option class="separator">-- %s --</option>'
- % html_escape(eview))
+ % xml_escape(eview))
else:
optionid = relation_id(eid, rtype, target, reid)
if optionid not in pending_inserts:
# prefix option's id with letters to make valid XHTML wise
options.append('<option id="id%s" value="%s">%s</option>' %
- (optionid, reid, html_escape(eview)))
+ (optionid, reid, xml_escape(eview)))
return options
def _get_search_options(self, entity, rschema, target, targettypes):
@@ -145,7 +145,7 @@
__mode=mode)
options.append((eschema.display_name(self.req),
'<option value="%s">%s %s</option>' % (
- html_escape(url), _('Search for'), eschema.display_name(self.req))))
+ xml_escape(url), _('Search for'), eschema.display_name(self.req))))
return [o for l, o in sorted(options)]
def _get_basket_options(self, entity, rschema, target, targettypes):
@@ -156,7 +156,7 @@
target, targettypes):
optionid = relation_id(entity.eid, rtype, target, basketeid)
options.append('<option id="%s" value="%s">%s %s</option>' % (
- optionid, basketeid, _('link to each item in'), html_escape(basketname)))
+ optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
return options
def _get_basket_links(self, ueid, target, targettypes):
--- a/web/views/emailaddress.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/emailaddress.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,8 +7,9 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
+from cubicweb.schema import display_name
from cubicweb.selectors import implements
from cubicweb.common import Unauthorized
from cubicweb.web.views import baseviews, primary
@@ -79,9 +80,9 @@
if entity.reverse_primary_email:
self.w(u'<b>')
if entity.alias:
- self.w(u'%s <' % html_escape(entity.alias))
- self.w('<a href="%s">%s</a>' % (html_escape(entity.absolute_url()),
- html_escape(entity.display_address())))
+ self.w(u'%s <' % xml_escape(entity.alias))
+ self.w('<a href="%s">%s</a>' % (xml_escape(entity.absolute_url()),
+ xml_escape(entity.display_address())))
if entity.alias:
self.w(u'>\n')
if entity.reverse_primary_email:
@@ -99,18 +100,17 @@
if entity.reverse_primary_email:
self.w(u'<b>')
if entity.alias:
- mailto = u'%s <%s>' % (entity.alias, entity.display_address())
+ alias = entity.alias
elif entity.reverse_use_email:
- mailto = "mailto:%s <%s>" % \
- (entity.reverse_use_email[0].dc_title(),
- entity.display_address())
+ alias = entity.reverse_use_email[0].dc_title()
+ else:
+ alias = None
+ if alias:
+ mailto = "mailto:%s <%s>" % (alias, entity.display_address())
else:
mailto = "mailto:%s" % entity.display_address()
- self.w(u'<a href="%s">%s</a>' % (html_escape(mailto),
- html_escape(entity.display_address())))
-
- if entity.alias:
- self.w(u'>\n')
+ self.w(u'<a href="%s">%s</a>' % (xml_escape(mailto),
+ xml_escape(entity.display_address())))
if entity.reverse_primary_email:
self.w(u'</b>')
--- a/web/views/facets.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/facets.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,17 +9,18 @@
from simplejson import dumps
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.vregistry import objectify_selector
from cubicweb.selectors import (non_final_entity, two_lines_rset,
match_context_prop, yes, relation_possible)
from cubicweb.web.box import BoxTemplate
from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet,
- prepare_facets_rqlst, filter_hiddens)
+ prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst,
+ _prepare_vocabulary_rqlst)
@objectify_selector
-def contextview_selector(cls, req, rset, row=None, col=None, view=None,
+def contextview_selector(cls, req, rset=None, row=None, col=None, view=None,
**kwargs):
if view and getattr(view, 'filter_box_context_info', lambda: None)():
return 1
@@ -38,6 +39,11 @@
order = 1
roundcorners = True
+ needs_css = 'cubicweb.facets.css'
+ needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js')
+
+ bk_linkbox_template = u'<div class="facetTitle">%s</div>'
+
def facetargs(self):
"""this method returns the list of extra arguments that should
be used by the facet
@@ -56,8 +62,8 @@
def call(self, view=None):
req = self.req
- req.add_js( ('cubicweb.ajax.js', 'cubicweb.formfilter.js') )
- req.add_css('cubicweb.facets.css')
+ req.add_js( self.needs_js )
+ req.add_css( self.needs_css)
if self.roundcorners:
req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");')
rset, vid, divid, paginate = self._get_context(view)
@@ -77,20 +83,11 @@
widgets.append(wdg)
if not widgets:
return
+ if self.bk_linkbox_template:
+ self.display_bookmark_link(rset)
w = self.w
- eschema = self.schema.eschema('Bookmark')
- if eschema.has_perm(req, 'add'):
- bk_path = 'view?rql=%s' % rset.printable_rql()
- bk_title = req._('my custom search')
- linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
- bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
- bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
- w(u'<div class="facetTitle"><a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a></div>' % (
- html_escape(bk_base_url),
- html_escape(bk_add_url),
- req._('bookmark this search')))
w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">' % (
- divid, html_escape(dumps([divid, vid, paginate, self.facetargs()]))))
+ divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()]))))
w(u'<fieldset>')
hiddens = {'facets': ','.join(wdg.facet.id for wdg in widgets),
'baserql': baserql}
@@ -106,6 +103,20 @@
import cubicweb
cubicweb.info('after facets with rql: %s' % repr(rqlst))
+ def display_bookmark_link(self, rset):
+ eschema = self.schema.eschema('Bookmark')
+ if eschema.has_perm(self.req, 'add'):
+ bk_path = 'view?rql=%s' % rset.printable_rql()
+ bk_title = self.req._('my custom search')
+ linkto = 'bookmarked_by:%s:subject' % self.req.user.eid
+ bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto)
+ bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto)
+ bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
+ xml_escape(bk_base_url),
+ xml_escape(bk_add_url),
+ self.req._('bookmark this search'))
+ self.w(self.bk_linkbox_template % bk_link)
+
def get_facets(self, rset, mainvar):
return self.vreg.possible_vobjects('facets', self.req, rset=rset,
context='facetbox',
@@ -153,6 +164,21 @@
return
self.rqlst.add_type_restriction(self.filtered_variable, value)
+ def possible_values(self):
+ """return a list of possible values (as string since it's used to
+ compare to a form value in javascript) for this facet
+ """
+ rqlst = self.rqlst
+ rqlst.save_state()
+ try:
+ _cleanup_rqlst(rqlst, self.filtered_variable)
+ etype_var = _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, self.role)
+ attrvar = rqlst.make_variable()
+ rqlst.add_selected(attrvar)
+ rqlst.add_relation(etype_var, 'name', attrvar)
+ return [etype for _, etype in self.rqlexec(rqlst.as_string())]
+ finally:
+ rqlst.recover()
class HasTextFacet(AbstractFacet):
__select__ = relation_possible('has_text', 'subject') & match_context_prop()
--- a/web/views/formrenderers.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/formrenderers.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
from logilab.common import dictattr
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from simplejson import dumps
@@ -85,6 +85,8 @@
return '\n'.join(data)
def render_label(self, form, field):
+ if field.label is None:
+ return u''
label = self.req._(field.label)
attrs = {'for': form.context[field]['id']}
if field.required:
@@ -94,6 +96,8 @@
def render_help(self, form, field):
help = []
descr = field.help
+ if callable(descr):
+ descr = descr(form)
if descr:
help.append('<div class="helper">%s</div>' % self.req._(descr))
example = field.example_format(self.req)
@@ -142,17 +146,17 @@
else:
action = form.action
tag = ('<form action="%s" method="post" enctype="%s"' % (
- html_escape(action or '#'), enctype))
+ xml_escape(action or '#'), enctype))
if form.domid:
tag += ' id="%s"' % form.domid
if form.onsubmit:
- tag += ' onsubmit="%s"' % html_escape(form.onsubmit % dictattr(form))
+ tag += ' onsubmit="%s"' % xml_escape(form.onsubmit % dictattr(form))
if form.cssstyle:
- tag += ' style="%s"' % html_escape(form.cssstyle)
+ tag += ' style="%s"' % xml_escape(form.cssstyle)
if form.cssclass:
- tag += ' class="%s"' % html_escape(form.cssclass)
+ tag += ' class="%s"' % xml_escape(form.cssclass)
if form.cwtarget:
- tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+ tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
return tag + '>'
def display_field(self, form, field):
@@ -186,24 +190,44 @@
return fields
def _render_fields(self, fields, w, form):
- w(u'<table class="%s">' % self.table_class)
+ byfieldset = {}
for field in fields:
- w(u'<tr>')
- if self.display_label:
- w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
- error = form.form_field_error(field)
- if error:
- w(u'<td class="error">')
- w(error)
- else:
- w(u'<td>')
- w(field.render(form, self))
- if self.display_help:
- w(self.render_help(form, field))
- w(u'</td></tr>')
- w(u'</table>')
+ byfieldset.setdefault(field.fieldset, []).append(field)
+ if form.fieldsets_in_order:
+ fieldsets = form.fieldsets_in_order
+ else:
+ fieldsets = byfieldset.keys()
+ for fieldset in fieldsets:
+ try:
+ fields = byfieldset.pop(fieldset)
+ except KeyError:
+ self.warning('no such fieldset: %s (%s)', fieldset, form)
+ continue
+ w(u'<fieldset class="%s">' % (fieldset or u'default'))
+ if fieldset:
+ w(u'<legend>%s</legend>' % self.req._(fieldset))
+ w(u'<table class="%s">' % self.table_class)
+ for field in fields:
+ w(u'<tr class="%s_%s_row">' % (field.name, field.role))
+ if self.display_label:
+ w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+ error = form.form_field_error(field)
+ if error:
+ w(u'<td class="error">')
+ w(error)
+ else:
+ w(u'<td>')
+ w(field.render(form, self))
+ if self.display_help:
+ w(self.render_help(form, field))
+ w(u'</td></tr>')
+ w(u'</table></fieldset>')
+ if byfieldset:
+ self.warning('unused fieldsets: %s', ', '.join(byfieldset))
def render_buttons(self, w, form):
+ if not form.form_buttons:
+ return
w(u'<table class="%s">\n<tr>\n' % self.button_bar_class)
for button in form.form_buttons:
w(u'<td>%s</td>\n' % button.render(form))
@@ -211,12 +235,29 @@
class BaseFormRenderer(FormRenderer):
- """use form_renderer_id = 'base' if you want base FormRenderer without
- adaptation by selection
+ """use form_renderer_id = 'base' if you want base FormRenderer layout even
+ when selected for an entity
"""
id = 'base'
+class EntityBaseFormRenderer(BaseFormRenderer):
+ """use form_renderer_id = 'base' if you want base FormRenderer layout even
+ when selected for an entity
+ """
+ __select__ = entity_implements('Any')
+
+ def display_field(self, form, field):
+ if not super(EntityBaseFormRenderer, self).display_field(form, field):
+ if isinstance(field, HiddenInitialValueField):
+ field = field.visible_field
+ ismeta = form.edited_entity.e_schema.is_metadata(field.name)
+ return ismeta is not None and (
+ ismeta[0] in self.display_fields or
+ (ismeta[0], 'subject') in self.display_fields)
+ return True
+
+
class HTableFormRenderer(FormRenderer):
"""display fields horizontally in a table
@@ -265,22 +306,42 @@
"""specific renderer for multiple entities edition form (muledit)"""
id = 'composite'
+ _main_display_fields = None
+
def render_fields(self, w, form, values):
if not form.is_subform:
w(u'<table class="listing">')
+ subfields = [field for field in form.forms[0].fields
+ if self.display_field(form, field)
+ and field.is_visible()]
+ if subfields:
+ # main form, display table headers
+ w(u'<tr class="header">')
+ w(u'<th align="left">%s</th>' %
+ tags.input(type='checkbox',
+ title=self.req._('toggle check boxes'),
+ onclick="setCheckboxesState('eid', this.checked)"))
+ for field in subfields:
+ w(u'<th>%s</th>' % self.req._(field.label))
+ w(u'</tr>')
super(EntityCompositeFormRenderer, self).render_fields(w, form, values)
if not form.is_subform:
w(u'</table>')
+ if self._main_display_fields:
+ super(EntityCompositeFormRenderer, self)._render_fields(
+ self._main_display_fields, w, form)
def _render_fields(self, fields, w, form):
if form.is_subform:
entity = form.edited_entity
values = form.form_previous_values
qeid = eid_param('eid', entity.eid)
- cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid))
+ cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % \
+ xml_escape(dumps(entity.eid))
w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
# XXX turn this into a widget used on the eid field
- w(u'<td>%s</td>' % checkbox('eid', entity.eid, checked=qeid in values))
+ w(u'<td>%s</td>' % checkbox('eid', entity.eid,
+ checked=qeid in values))
for field in fields:
error = form.form_field_error(field)
if error:
@@ -288,27 +349,24 @@
w(error)
else:
w(u'<td>')
- if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox, fwdgs.Radio)):
+ if isinstance(field.widget, (fwdgs.Select, fwdgs.CheckBox,
+ fwdgs.Radio)):
field.widget.attrs['onchange'] = cbsetstate
elif isinstance(field.widget, fwdgs.Input):
field.widget.attrs['onkeypress'] = cbsetstate
+ # XXX else
w(u'<div>%s</div>' % field.render(form, self))
- w(u'</td>')
+ w(u'</td>\n')
+ w(u'</tr>')
else:
- # main form, display table headers
- w(u'<tr class="header">')
- w(u'<th align="left">%s</th>'
- % tags.input(type='checkbox', title=self.req._('toggle check boxes'),
- onclick="setCheckboxesState('eid', this.checked)"))
- for field in self.forms[0].fields:
- if self.display_field(form, field) and field.is_visible():
- w(u'<th>%s</th>' % self.req._(field.label))
- w(u'</tr>')
+ self._main_display_fields = fields
-class EntityFormRenderer(FormRenderer):
+class EntityFormRenderer(EntityBaseFormRenderer):
"""specific renderer for entity edition form (edition)"""
- __select__ = entity_implements('Any') & yes()
+ id = 'default'
+ # needs some additional points in some case (XXX explain cases)
+ __select__ = EntityBaseFormRenderer.__select__ & yes()
_options = FormRenderer._options + ('display_relations_form',)
display_relations_form = True
@@ -388,7 +446,7 @@
w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
(_('cancel this insert'), row[2]))
w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], html_escape(row[5])))
+ % (row[1], row[4], xml_escape(row[5])))
w(u'</td>')
w(u'</tr>')
w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
@@ -396,7 +454,7 @@
w(u'<span>%s</span>' % _('add relation'))
w(u'<select id="relationSelector_%s" tabindex="%s" '
'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), html_escape(dumps(eid))))
+ % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
w(u'<option value="">%s</option>' % _('select a relation'))
for i18nrtype, rschema, target in srels_by_cat:
# more entities to link to
@@ -480,7 +538,7 @@
w(u'<div class="iformBody">')
values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema)
w(u'<div class="iformTitle"><span>%(title)s</span> '
- '#<span class="icounter">1</span> '
+ '#<span class="icounter">%(counter)s</span> '
'[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
% values)
# cleanup values
--- a/web/views/forms.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/forms.py Tue Aug 04 15:06:09 2009 +0200
@@ -20,16 +20,49 @@
class FieldsForm(form.Form):
+ """base class for fields based forms.
+
+ The following attributes may be either set on subclasses or given on
+ form selection to customize the generated form:
+
+ * `needs_js`: sequence of javascript files that should be added to handle
+ this form (through `req.add_js`)
+
+ * `needs_css`: sequence of css files that should be added to handle this
+ form (through `req.add_css`)
+
+ * `domid`: value for the "id" attribute of the <form> tag
+
+ * `action`: value for the "action" attribute of the <form> tag
+
+ * `onsubmit`: value for the "onsubmit" attribute of the <form> tag
+
+ * `cssclass`: value for the "class" attribute of the <form> tag
+
+ * `cssstyle`: value for the "style" attribute of the <form> tag
+
+ * `cwtarget`: value for the "cubicweb:target" attribute of the <form> tag
+
+ * `redirect_path`: relative to redirect to after submitting the form
+
+ * `copy_nav_params`: flag telling if navigation paramenters should be copied
+ back in hidden input
+
+ * `form_buttons`: form buttons sequence (button widgets instances)
+
+ * `form_renderer_id`: id of the form renderer to use to render the form
+
+ * `fieldsets_in_order`: fieldset name sequence, to control order
+ """
id = 'base'
is_subform = False
+ internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
- # attributes overrideable through __init__
- internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
+ # attributes overrideable by subclasses or through __init__
needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',)
needs_css = ('cubicweb.form.css',)
domid = 'form'
- title = None
action = None
onsubmit = "return freezeFormButtons('%(domid)s');"
cssclass = None
@@ -37,8 +70,9 @@
cwtarget = None
redirect_path = None
copy_nav_params = False
- form_buttons = None # form buttons (button widgets instances)
+ form_buttons = None
form_renderer_id = 'default'
+ fieldsets_in_order = None
def __init__(self, req, rset=None, row=None, col=None,
submitmsg=None, mainform=True,
@@ -119,8 +153,8 @@
def form_add_hidden(self, name, value=None, **kwargs):
"""add an hidden field to the form"""
- field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
- **kwargs)
+ kwargs.setdefault('widget', fwdgs.HiddenInput)
+ field = StringField(name=name, initial=value, **kwargs)
if 'id' in kwargs:
# by default, hidden input don't set id attribute. If one is
# explicitly specified, ensure it will be set
@@ -267,7 +301,7 @@
self.edited_entity = self.complete_entity(self.row or 0, self.col or 0)
self.form_add_hidden('__type', eidparam=True)
self.form_add_hidden('eid')
- if kwargs.get('mainform'):
+ if kwargs.get('mainform', True): # mainform default to true in parent
self.form_add_hidden(u'__maineid', self.edited_entity.eid)
# If we need to directly attach the new object to another one
if self.req.list_form_param('__linkto'):
--- a/web/views/ibreadcrumbs.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/ibreadcrumbs.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
from cubicweb.interfaces import IBreadCrumbs
@@ -21,7 +21,7 @@
def bc_title(entity):
textsize = entity.req.property_value('navigation.short-line-size')
- return html_escape(cut(entity.dc_title(), textsize))
+ return xml_escape(cut(entity.dc_title(), textsize))
class BreadCrumbEntityVComponent(EntityVComponent):
@@ -64,7 +64,7 @@
url, title = part
textsize = self.req.property_value('navigation.short-line-size')
self.w(u'<a href="%s">%s</a>' % (
- html_escape(url), html_escape(cut(title, textsize))))
+ xml_escape(url), xml_escape(cut(title, textsize))))
else:
textsize = self.req.property_value('navigation.short-line-size')
self.w(cut(unicode(part), textsize))
@@ -81,6 +81,6 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
- desc = html_escape(cut(entity.dc_description(), 50))
+ desc = xml_escape(cut(entity.dc_description(), 50))
self.w(u'<a href="%s" title="%s">%s</a>' % (
- html_escape(entity.absolute_url()), desc, bc_title(entity)))
+ xml_escape(entity.absolute_url()), desc, bc_title(entity)))
--- a/web/views/idownloadable.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/idownloadable.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import BINARY_ENCODINGS, TransformError, html_escape
+from logilab.mtconverter import BINARY_ENCODINGS, TransformError, xml_escape
from cubicweb.view import EntityView
from cubicweb.selectors import (one_line_rset, score_entity,
@@ -31,12 +31,12 @@
if title is None:
title = req._('download')
w(u'<div class="sideBoxTitle downloadBoxTitle"><span>%s</span></div>'
- % html_escape(title))
+ % xml_escape(title))
w(u'<div class="sideBox downloadBox"><div class="sideBoxBody">')
w(u'<a href="%s"><img src="%s" alt="%s"/> %s</a>'
- % (html_escape(entity.download_url()),
+ % (xml_escape(entity.download_url()),
req.external_resource('DOWNLOAD_ICON'),
- _('download icon'), html_escape(label or entity.dc_title())))
+ _('download icon'), xml_escape(label or entity.dc_title())))
w(u'</div>')
w(u'</div>\n</div>\n')
@@ -92,8 +92,8 @@
def cell_call(self, row, col, title=None, **kwargs):
entity = self.entity(row, col)
- url = html_escape(entity.download_url())
- self.w(u'<a href="%s">%s</a>' % (url, html_escape(title or entity.dc_title())))
+ url = xml_escape(entity.download_url())
+ self.w(u'<a href="%s">%s</a>' % (url, xml_escape(title or entity.dc_title())))
class IDownloadablePrimaryView(primary.PrimaryView):
@@ -122,11 +122,11 @@
__select__ = implements(IDownloadable)
def cell_call(self, row, col, title=None, **kwargs):
- """the secondary view is a link to download the file"""
+ """the oneline view is a link to download the file"""
entity = self.entity(row, col)
- url = html_escape(entity.absolute_url())
- name = html_escape(entity.download_file_name())
- durl = html_escape(entity.download_url())
+ url = xml_escape(entity.absolute_url())
+ name = xml_escape(title or entity.download_file_name())
+ durl = xml_escape(entity.download_url())
self.w(u'<a href="%s">%s</a> [<a href="%s">%s</a>]' %
(url, name, durl, self.req._('download')))
@@ -144,9 +144,21 @@
self.wview(self.id, rset, row=i, col=0)
self.w(u'</div>')
- def cell_call(self, row, col):
+ def cell_call(self, row, col, width=None, height=None, link=False):
entity = self.entity(row, col)
#if entity.data_format.startswith('image/'):
- self.w(u'<img src="%s" alt="%s"/>' % (html_escape(entity.download_url()),
- html_escape(entity.download_file_name())))
+ imgtag = u'<img src="%s" alt="%s" ' % (xml_escape(entity.download_url()),
+ xml_escape(entity.download_file_name()))
+ if width:
+ imgtag += u'width="%i" ' % width
+ if height:
+ imgtag += u'height="%i" ' % height
+ imgtag += u'/>'
+ if link:
+ self.w(u'<a href="%s" alt="%s">%s</a>' % (entity.absolute_url(vid='download'),
+ self.req._('download image'),
+ imgtag))
+ else:
+ self.w(imgtag)
+
--- a/web/views/iprogress.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/iprogress.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import implements
from cubicweb.interfaces import IProgress, IMileStone
@@ -99,7 +99,7 @@
colname = meth(ecls)
else:
colname = _(column)
- self.w(u'<th>%s</th>' % html_escape(colname))
+ self.w(u'<th>%s</th>' % xml_escape(colname))
self.w(u'</tr></thead>\n')
@@ -117,7 +117,7 @@
def build_state_cell(self, entity):
"""``state`` column cell renderer"""
- return html_escape(self.req._(entity.state))
+ return xml_escape(self.req._(entity.state))
def build_eta_date_cell(self, entity):
"""``eta_date`` column cell renderer"""
--- a/web/views/isioc.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/isioc.py Tue Aug 04 15:06:09 2009 +0200
@@ -7,7 +7,7 @@
"""
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.view import EntityView
from cubicweb.selectors import implements
@@ -45,11 +45,11 @@
def cell_call(self, row, col):
entity = self.complete_entity(row, col)
- sioct = html_escape(entity.isioc_type())
+ sioct = xml_escape(entity.isioc_type())
self.w(u'<sioc:%s rdf:about="%s">\n'
- % (sioct, html_escape(entity.absolute_url())))
+ % (sioct, xml_escape(entity.absolute_url())))
self.w(u'<dcterms:title>%s</dcterms:title>'
- % html_escape(entity.dc_title()))
+ % xml_escape(entity.dc_title()))
self.w(u'<dcterms:created>%s</dcterms:created>'
% entity.creation_date)
self.w(u'<dcterms:modified>%s</dcterms:modified>'
@@ -66,25 +66,25 @@
def cell_call(self, row, col):
entity = self.complete_entity(row, col)
- sioct = html_escape(entity.isioc_type())
+ sioct = xml_escape(entity.isioc_type())
self.w(u'<sioc:%s rdf:about="%s">\n'
- % (sioct, html_escape(entity.absolute_url())))
+ % (sioct, xml_escape(entity.absolute_url())))
self.w(u'<dcterms:title>%s</dcterms:title>'
- % html_escape(entity.dc_title()))
+ % xml_escape(entity.dc_title()))
self.w(u'<dcterms:created>%s</dcterms:created>'
% entity.creation_date)
self.w(u'<dcterms:modified>%s</dcterms:modified>'
% entity.modification_date)
if entity.content:
self.w(u'<sioc:content>%s</sioc:content>'''
- % html_escape(entity.isioc_content()))
+ % xml_escape(entity.isioc_content()))
if entity.related('entry_of'):
self.w(u'<sioc:has_container rdf:resource="%s"/>\n'
- % html_escape(entity.isioc_container().absolute_url()))
+ % xml_escape(entity.isioc_container().absolute_url()))
if entity.creator:
self.w(u'<sioc:has_creator>\n')
self.w(u'<sioc:User rdf:about="%s">\n'
- % html_escape(entity.creator.absolute_url()))
+ % xml_escape(entity.creator.absolute_url()))
self.w(entity.creator.view('foaf'))
self.w(u'</sioc:User>\n')
self.w(u'</sioc:has_creator>\n')
--- a/web/views/magicsearch.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/magicsearch.py Tue Aug 04 15:06:09 2009 +0200
@@ -42,7 +42,7 @@
:param translations: the reverted l10n dict
:type schema: `cubicweb.schema.Schema`
- :param schema: the application's schema
+ :param schema: the instance's schema
"""
# var_types is used as a map : var_name / var_type
vartypes = {}
@@ -94,7 +94,7 @@
:param ambiguous_nodes: a map : relation_node / (var_name, available_translations)
:type schema: `cubicweb.schema.Schema`
- :param schema: the application's schema
+ :param schema: the instance's schema
"""
# Now, try to resolve ambiguous translations
for relation, (var_name, translations_found) in ambiguous_nodes.items():
@@ -170,10 +170,7 @@
"""
priority = 2
def preprocess_query(self, uquery, req):
- try:
- rqlst = parse(uquery, print_errors=False)
- except (RQLSyntaxError, BadRQLQuery), err:
- return uquery,
+ rqlst = parse(uquery, print_errors=False)
schema = self.vreg.schema
# rql syntax tree will be modified in place if necessary
translate_rql_tree(rqlst, trmap(self.config, schema, req.lang), schema)
@@ -204,7 +201,7 @@
elif len(words) == 3:
args = self._three_words_query(*words)
else:
- args = self._multiple_words_query(words)
+ raise
return args
def _get_entity_type(self, word):
@@ -291,11 +288,6 @@
self._complete_rql(word3, etype, searchattr=rtype))
return rql, {'text': word3}
- def _multiple_words_query(self, words):
- """specific process for more than 3 words query"""
- return ' '.join(words),
-
-
def _expand_shortcut(self, etype, rtype, searchstr):
"""Expands shortcut queries on a non final relation to use has_text or
the main attribute (according to possible entity type) if '%' is used in the
--- a/web/views/management.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/management.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,12 +9,12 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user
from cubicweb.view import AnyRsetView, StartupView, EntityView
from cubicweb.common.uilib import html_traceback, rest_traceback
-from cubicweb.web import formwidgets
+from cubicweb.web import formwidgets as wdgs
from cubicweb.web.formfields import guess_field
SUBMIT_MSGID = _('Submit bug report')
@@ -84,8 +84,8 @@
_ = self.req._
w(u'<h1><span class="etype">%s</span> <a href="%s">%s</a></h1>'
% (entity.dc_type().capitalize(),
- html_escape(entity.absolute_url()),
- html_escape(entity.dc_title())))
+ xml_escape(entity.absolute_url()),
+ xml_escape(entity.dc_title())))
# first show permissions defined by the schema
self.w('<h2>%s</h2>' % _('schema\'s permissions definitions'))
self.schema_definition(entity.e_schema)
@@ -109,7 +109,7 @@
msg = self.req._('ownerships have been changed')
form = self.vreg.select('forms', 'base', self.req, entity=entity,
form_renderer_id='base', submitmsg=msg,
- form_buttons=[formwidgets.SubmitButton()],
+ form_buttons=[wdgs.SubmitButton()],
domid='ownership%s' % entity.eid,
__redirectvid='security',
__redirectpath=entity.rest_path())
@@ -140,7 +140,7 @@
# and this will replace %s by %25s
delurl += '&__delete=%s:require_permission:%%s' % entity.eid
dellinktempl = u'[<a href="%s" title="%s">-</a>] ' % (
- html_escape(delurl), _('delete this permission'))
+ xml_escape(delurl), _('delete this permission'))
else:
dellinktempl = None
w(u'<table class="schemaInfo">')
@@ -160,13 +160,11 @@
self.w(self.req._('no associated permissions'))
def require_permission_edit_form(self, entity):
- w = self.w
- _ = self.req._
newperm = self.vreg.etype_class('CWPermission')(self.req, None)
newperm.eid = self.req.varmaker.next()
- w(u'<p>%s</p>' % _('add a new permission'))
+ self.w(u'<p>%s</p>' % self.req._('add a new permission'))
form = self.vreg.select('forms', 'base', self.req, entity=newperm,
- form_buttons=[formwidgets.SubmitButton()],
+ form_buttons=[wdgs.SubmitButton()],
domid='reqperm%s' % entity.eid,
__redirectvid='security',
__redirectpath=entity.rest_path())
@@ -176,7 +174,7 @@
cwpermschema = newperm.e_schema
if permnames is not None:
field = guess_field(cwpermschema, self.schema.rschema('name'),
- widget=formwidgets.Select({'size': 1}),
+ widget=wdgs.Select({'size': 1}),
choices=permnames)
else:
field = guess_field(cwpermschema, self.schema.rschema('name'))
@@ -186,7 +184,7 @@
field = guess_field(cwpermschema, self.schema.rschema('require_group'))
form.append_field(field)
renderer = self.vreg.select('formrenderers', 'htable', self.req,
- display_progress_div=False)
+ rset=None, display_progress_div=False)
self.w(form.form_render(renderer=renderer))
@@ -217,14 +215,14 @@
if excinfo is not None and self.config['print-traceback']:
if exclass is None:
w(u'<div class="tb">%s</div>'
- % html_escape(ex).replace("\n","<br />"))
+ % xml_escape(ex).replace("\n","<br />"))
else:
w(u'<div class="tb">%s: %s</div>'
- % (exclass, html_escape(ex).replace("\n","<br />")))
+ % (exclass, xml_escape(ex).replace("\n","<br />")))
w(u'<hr />')
w(u'<div class="tb">%s</div>' % html_traceback(excinfo, ex, ''))
else:
- w(u'<div class="tb">%s</div>' % (html_escape(ex).replace("\n","<br />")))
+ w(u'<div class="tb">%s</div>' % (xml_escape(ex).replace("\n","<br />")))
# if excinfo is not None, it's probably not a bug
if excinfo is None:
return
@@ -243,18 +241,20 @@
submiturl = self.config['submit-url']
submitmail = self.config['submit-mail']
if submiturl or submitmail:
- form = self.vreg.select('forms', 'base', self.req,
+ form = self.vreg.select('forms', 'base', self.req, rset=None,
mainform=False)
binfo = text_error_description(ex, excinfo, req, eversion, cversions)
- form.form_add_hidden('description', binfo)
+ form.form_add_hidden('description', binfo,
+ # we must use a text area to keep line breaks
+ widget=wdgs.TextArea({'class': 'hidden'}))
form.form_add_hidden('__bugreporting', '1')
if submitmail:
- form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)]
+ form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)]
form.action = req.build_url('reportbug')
w(form.form_render())
if submiturl:
form.form_add_hidden('description_format', 'text/rest')
- form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)]
+ form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)]
form.action = submiturl
w(form.form_render())
@@ -269,7 +269,7 @@
return unicode(repr(ex), encoding, 'replace')
def text_error_description(ex, excinfo, req, eversion, cubes):
- binfo = rest_traceback(excinfo, html_escape(ex))
+ binfo = rest_traceback(excinfo, xml_escape(ex))
binfo += u'\n\n:URL: %s\n' % req.url()
if not '__bugreporting' in req.form:
binfo += u'\n:form params:\n'
@@ -283,7 +283,7 @@
class ProcessInformationView(StartupView):
id = 'info'
- __select__ = none_rset() & match_user_groups('managers')
+ __select__ = none_rset() & match_user_groups('users', 'managers')
title = _('server information')
@@ -319,7 +319,7 @@
self.w(u'<table border="1">')
for attr in env.keys():
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
- % (attr, html_escape(env[attr])))
+ % (attr, xml_escape(env[attr])))
self.w(u'</table>')
self.w(u'<h3>%s</h3>' % _('Request'))
self.w(u'<table border="1">')
@@ -328,7 +328,7 @@
'search_state', 'the_request', 'unparsed_uri', 'uri'):
val = getattr(req, attr)
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
- % (attr, html_escape(val)))
+ % (attr, xml_escape(val)))
self.w(u'</table>')
server = req.server
self.w(u'<h3>%s</h3>' % _('Server'))
@@ -338,6 +338,6 @@
if attr.startswith('_') or callable(val):
continue
self.w(u'<tr><th align="left">%s</th><td>%s</td></tr>'
- % (attr, html_escape(val)))
+ % (attr, xml_escape(val)))
self.w(u'</table>')
--- a/web/views/massmailing.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/massmailing.py Tue Aug 04 15:06:09 2009 +0200
@@ -123,7 +123,7 @@
def call(self):
req = self.req
- req.add_js('cubicweb.widgets.js')
+ req.add_js('cubicweb.widgets.js', 'cubicweb.massmailing.js')
req.add_css('cubicweb.mailform.css')
from_addr = '%s <%s>' % (req.user.dc_title(), req.user.get_email())
form = self.vreg.select('forms', 'massmailing', self.req, rset=self.rset,
--- a/web/views/navigation.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/navigation.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,8 +10,8 @@
from rql.nodes import VariableRef, Constant
-from logilab.mtconverter import html_escape
-from logilab.common.deprecation import obsolete
+from logilab.mtconverter import xml_escape
+from logilab.common.deprecation import deprecated
from cubicweb.interfaces import IPrevNext
from cubicweb.selectors import (paginated_rset, sorted_rset,
@@ -136,7 +136,7 @@
def format_link_content(self, startstr, stopstr):
text = u'%s - %s' % (startstr.lower()[:self.nb_chars],
stopstr.lower()[:self.nb_chars])
- return html_escape(text)
+ return xml_escape(text)
def write_links(self, params, blocklist):
self.w(u'<div class="pagination">')
@@ -159,7 +159,7 @@
nav.clean_params(params)
# make a link to see them all
if show_all_option:
- url = html_escape(self.build_url(__force_display=1, **params))
+ url = xml_escape(self.build_url(__force_display=1, **params))
w(u'<p><a href="%s">%s</a></p>\n'
% (url, req._('show %s results') % len(rset)))
rset.limit(offset=start, limit=stop-start, inplace=True)
@@ -168,7 +168,7 @@
# monkey patch base View class to add a .pagination(req, rset, w, forcedisplay)
# method to be called on view's result set and printing pages index in the view
from cubicweb.view import View
-View.pagination = obsolete('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
+View.pagination = deprecated('.pagination is deprecated, use paginate')(limit_rset_using_paged_nav)
def paginate(view, show_all_option=True, w=None, page_size=None):
limit_rset_using_paged_nav(view, view.req, view.rset, w or view.w,
@@ -198,24 +198,24 @@
self.w(self.previous_link(previous, textsize))
self.w(u'</div>')
self.req.html_headers.add_raw('<link rel="prev" href="%s" />'
- % html_escape(previous.absolute_url()))
+ % xml_escape(previous.absolute_url()))
if next:
self.w(u'<div class="nextEntity right">')
self.w(self.next_link(next, textsize))
self.w(u'</div>')
self.req.html_headers.add_raw('<link rel="next" href="%s" />'
- % html_escape(next.absolute_url()))
+ % xml_escape(next.absolute_url()))
self.w(u'</div>')
self.w(u'<div class="clear"></div>')
def previous_link(self, previous, textsize):
return u'<a href="%s" title="%s"><< %s</a>' % (
- html_escape(previous.absolute_url()),
+ xml_escape(previous.absolute_url()),
self.req._('i18nprevnext_previous'),
- html_escape(cut(previous.dc_title(), textsize)))
+ xml_escape(cut(previous.dc_title(), textsize)))
def next_link(self, next, textsize):
return u'<a href="%s" title="%s">%s >></a>' % (
- html_escape(next.absolute_url()),
+ xml_escape(next.absolute_url()),
self.req._('i18nprevnext_next'),
- html_escape(cut(next.dc_title(), textsize)))
+ xml_escape(cut(next.dc_title(), textsize)))
--- a/web/views/old_calendar.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/old_calendar.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
from datetime import date, time, timedelta
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.interfaces import ICalendarViews
from cubicweb.utils import ONEDAY, ONEWEEK, date_range, first_day, last_day, previous_month, next_month, days_in_month
@@ -46,13 +46,13 @@
next2 = next_month(date, bigshift)
rql = self.rset.printable_rql()
return self.NAV_HEADER % (
- html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year,
month=prev2.month)),
- html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year,
month=prev1.month)),
- html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year,
month=next1.month)),
- html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year,
month=next2.month)))
@@ -91,7 +91,7 @@
rows.append(u'<tr>%s%s</tr>' % (WEEKNUM_CELL % day.isocalendar()[1], ''.join(current_row)))
url = self.build_url(rql=rql, vid='calendarmonth',
year=first_day.year, month=first_day.month)
- monthlink = u'<a href="%s">%s</a>' % (html_escape(url), umonth)
+ monthlink = u'<a href="%s">%s</a>' % (xml_escape(url), umonth)
return CALENDAR(self.req) % (monthlink, '\n'.join(rows))
def _mk_schedule(self, begin, end, itemvid='calendaritem'):
@@ -203,7 +203,7 @@
umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
url = self.build_url(rql=rql, vid=self.id,
year=cur_month.year, month=cur_month.month)
- self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (html_escape(url),
+ self.w(u'<th colspan="2"><a href="%s">%s</a></th>' % (xml_escape(url),
umonth))
self.w(u'</tr>')
_ = self.req._
@@ -272,7 +272,7 @@
umonth = self.format_date(monday, '%B %Y')
url = self.build_url(rql=rql, vid='calendarmonth',
year=monday.year, month=monday.month)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+ monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
self.w(u'<tr><th colspan="3">%s %s (%s)</th></tr>' \
% (_('week'), monday.isocalendar()[1], monthlink))
for day in date_range(monday, sunday):
@@ -295,10 +295,10 @@
next2 = date + ONEWEEK * bigshift
rql = self.rset.printable_rql()
return self.NAV_HEADER % (
- html_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
- html_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
- html_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
- html_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=prev2.year, week=prev2.isocalendar()[1])),
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=prev1.year, week=prev1.isocalendar()[1])),
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=next1.year, week=next1.isocalendar()[1])),
+ xml_escape(self.build_url(rql=rql, vid=self.id, year=next2.year, week=next2.isocalendar()[1])))
@@ -326,7 +326,7 @@
if day.weekday() == 6:
url = self.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+ weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
rows.append(current_row)
@@ -334,7 +334,7 @@
current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
url = self.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url), day.isocalendar()[1])
+ weeklink = '<a href="%s">%s</a>' % (xml_escape(url), day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
rows.append(current_row)
# build two rows for each week: am & pm
@@ -350,7 +350,7 @@
# tigh everything together
url = self.build_url(rql=rql, vid='ampmcalendarmonth',
year=first_day.year, month=first_day.month)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+ monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
@@ -367,7 +367,7 @@
umonth = u'%s %s' % (self.format_date(cur_month, '%B'), cur_month.year)
url = self.build_url(rql=rql, vid=self.id,
year=cur_month.year, month=cur_month.month)
- self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (html_escape(url),
+ self.w(u'<th colspan="3"><a href="%s">%s</a></th>' % (xml_escape(url),
umonth))
self.w(u'</tr>')
_ = self.req._
@@ -417,7 +417,7 @@
if day.weekday() == 6:
url = self.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+ weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
rows.append(current_row)
@@ -425,7 +425,7 @@
current_row.extend([(NO_CELL, NO_CELL, NO_CELL)] * (6-day.weekday()))
url = self.build_url(rql=rql, vid='ampmcalendarweek',
year=day.year, week=day.isocalendar()[1])
- weeklink = '<a href="%s">%s</a>' % (html_escape(url),
+ weeklink = '<a href="%s">%s</a>' % (xml_escape(url),
day.isocalendar()[1])
current_row.append(WEEKNUM_CELL % weeklink)
rows.append(current_row)
@@ -442,7 +442,7 @@
# tigh everything together
url = self.build_url(rql=rql, vid='ampmcalendarmonth',
year=first_day.year, month=first_day.month)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url),
+ monthlink = '<a href="%s">%s</a>' % (xml_escape(url),
umonth)
return CALENDAR(self.req) % (monthlink, '\n'.join(formatted_rows))
@@ -461,7 +461,7 @@
umonth = self.format_date(monday, '%B %Y')
url = self.build_url(rql=rql, vid='ampmcalendarmonth',
year=monday.year, month=monday.month)
- monthlink = '<a href="%s">%s</a>' % (html_escape(url), umonth)
+ monthlink = '<a href="%s">%s</a>' % (xml_escape(url), umonth)
w(u'<tr>%s</tr>' % (
WEEK_TITLE % (_('week'), monday.isocalendar()[1], monthlink)))
w(u'<tr><th>%s</th><th> </th></tr>'% _(u'Date'))
--- a/web/views/owl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/owl.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,14 +6,14 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
from logilab.mtconverter import TransformError, xml_escape
from cubicweb.view import StartupView, EntityView
+from cubicweb.selectors import none_rset, match_view
from cubicweb.web.action import Action
-from cubicweb.selectors import none_rset, match_view
-
-_ = unicode
+from cubicweb.web.views import schema
OWL_CARD_MAP = {'1': '<rdf:type rdf:resource="&owl;FunctionalProperty"/>',
'?': '<owl:maxCardinality rdf:datatype="&xsd;int">1</owl:maxCardinality>',
@@ -55,8 +55,6 @@
OWL_CLOSING_ROOT = u'</rdf:RDF>'
-DEFAULT_SKIP_RELS = frozenset(('is', 'is_instance_of', 'identity',
- 'owned_by', 'created_by'))
class OWLView(StartupView):
"""This view export in owl format schema database. It is the TBOX"""
@@ -69,36 +67,36 @@
skipmeta = int(self.req.form.get('skipmeta', True))
if writeprefix:
self.w(OWL_OPENING_ROOT % {'appid': self.schema.name})
- self.visit_schema(skipmeta=skipmeta)
+ self.visit_schema(skiptypes=skipmeta and schema.SKIP_TYPES or ())
if writeprefix:
self.w(OWL_CLOSING_ROOT)
- def visit_schema(self, skiprels=DEFAULT_SKIP_RELS, skipmeta=True):
+ def should_display_rschema(self, rschema):
+ return not rschema in self.skiptypes and (
+ rschema.has_local_role('read') or
+ rschema.has_perm(self.req, 'read'))
+
+ def visit_schema(self, skiptypes):
"""get a layout for a whole schema"""
- entities = sorted([eschema for eschema in self.schema.entities()
- if not eschema.is_final()])
- if skipmeta:
- entities = [eschema for eschema in entities
- if not eschema.meta]
+ self.skiptypes = skiptypes
+ entities = sorted(eschema for eschema in self.schema.entities()
+ if not eschema.is_final() or eschema in skiptypes)
self.w(u'<!-- classes definition -->')
for eschema in entities:
- self.visit_entityschema(eschema, skiprels)
+ self.visit_entityschema(eschema)
self.w(u'<!-- property definition -->')
- self.visit_property_schema(eschema, skiprels)
+ self.visit_property_schema(eschema)
self.w(u'<!-- datatype property -->')
self.visit_property_object_schema(eschema)
- def visit_entityschema(self, eschema, skiprels=()):
+ def visit_entityschema(self, eschema):
"""get a layout for an entity OWL schema"""
self.w(u'<owl:Class rdf:ID="%s">'% eschema)
self.w(u'<!-- relations -->')
for rschema, targetschemas, role in eschema.relation_definitions():
- if rschema.type in skiprels:
- continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ if not self.should_display_rschema(rschema):
continue
for oeschema in targetschemas:
- label = rschema.type
if role == 'subject':
card = rschema.rproperty(eschema, oeschema, 'cardinality')[0]
else:
@@ -110,58 +108,43 @@
<owl:onProperty rdf:resource="#%s"/>
%s
</owl:Restriction>
-</rdfs:subClassOf>
-''' % (label, cardtag))
+</rdfs:subClassOf>''' % (rschema, cardtag))
self.w(u'<!-- attributes -->')
-
for rschema, aschema in eschema.attribute_definitions():
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
- continue
- aname = rschema.type
- if aname == 'eid':
+ if not self.should_display_rschema(rschema):
continue
self.w(u'''<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="#%s"/>
<rdf:type rdf:resource="&owl;FunctionalProperty"/>
</owl:Restriction>
-</rdfs:subClassOf>'''
- % aname)
+</rdfs:subClassOf>''' % rschema)
self.w(u'</owl:Class>')
- def visit_property_schema(self, eschema, skiprels=()):
+ def visit_property_schema(self, eschema):
"""get a layout for property entity OWL schema"""
for rschema, targetschemas, role in eschema.relation_definitions():
- if rschema.type in skiprels:
- continue
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
+ if not self.should_display_rschema(rschema):
continue
for oeschema in targetschemas:
- label = rschema.type
self.w(u'''<owl:ObjectProperty rdf:ID="%s">
<rdfs:domain rdf:resource="#%s"/>
<rdfs:range rdf:resource="#%s"/>
-</owl:ObjectProperty>
-''' % (label, eschema, oeschema.type))
+</owl:ObjectProperty>''' % (rschema, eschema, oeschema.type))
def visit_property_object_schema(self, eschema):
for rschema, aschema in eschema.attribute_definitions():
- if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
- continue
- aname = rschema.type
- if aname == 'eid':
+ if not self.should_display_rschema(rschema):
continue
self.w(u'''<owl:DatatypeProperty rdf:ID="%s">
<rdfs:domain rdf:resource="#%s"/>
<rdfs:range rdf:resource="%s"/>
-</owl:DatatypeProperty>'''
- % (aname, eschema, OWL_TYPE_MAP[aschema.type]))
+</owl:DatatypeProperty>''' % (rschema, eschema, OWL_TYPE_MAP[aschema.type]))
class OWLABOXView(EntityView):
'''This view represents a part of the ABOX for a given entity.'''
-
id = 'owlabox'
title = _('owlabox')
templatable = False
@@ -173,8 +156,8 @@
self.cell_call(i, 0)
self.w(OWL_CLOSING_ROOT)
- def cell_call(self, row, col, skiprels=DEFAULT_SKIP_RELS):
- self.wview('owlaboxitem', self.rset, row=row, col=col, skiprels=skiprels)
+ def cell_call(self, row, col):
+ self.wview('owlaboxitem', self.rset, row=row, col=col)
class OWLABOXItemView(EntityView):
@@ -183,13 +166,13 @@
templatable = False
content_type = 'application/xml' # 'text/xml'
- def cell_call(self, row, col, skiprels=DEFAULT_SKIP_RELS):
+ def cell_call(self, row, col):
entity = self.complete_entity(row, col)
eschema = entity.e_schema
self.w(u'<%s rdf:ID="%s">' % (eschema, entity.eid))
self.w(u'<!--attributes-->')
for rschema, aschema in eschema.attribute_definitions():
- if rschema.type in skiprels:
+ if rschema.meta:
continue
if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
continue
@@ -204,7 +187,7 @@
pass
self.w(u'<!--relations -->')
for rschema, targetschemas, role in eschema.relation_definitions():
- if rschema.type in skiprels:
+ if rschema.meta:
continue
if not (rschema.has_local_role('read') or rschema.has_perm(self.req, 'read')):
continue
--- a/web/views/plots.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/plots.py Tue Aug 04 15:06:09 2009 +0200
@@ -13,20 +13,20 @@
from simplejson import dumps
from logilab.common import flatten
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid, UStringIO, datetime2ticks
from cubicweb.vregistry import objectify_selector
from cubicweb.web.views import baseviews
@objectify_selector
-def at_least_two_columns(cls, req, rset, *args, **kwargs):
+def at_least_two_columns(cls, req, rset=None, *args, **kwargs):
if not rset:
return 0
return len(rset.rows[0]) >= 2
@objectify_selector
-def all_columns_are_numbers(cls, req, rset, *args, **kwargs):
+def all_columns_are_numbers(cls, req, rset=None, *args, **kwargs):
"""accept result set with at least one line and two columns of result
all columns after second must be of numerical types"""
for etype in rset.description[0]:
@@ -35,14 +35,14 @@
return 1
@objectify_selector
-def second_column_is_number(cls, req, rset, *args, **kwargs):
+def second_column_is_number(cls, req, rset=None, *args, **kwargs):
etype = rset.description[0][1]
if etype not in ('Int', 'Float'):
return 0
return 1
@objectify_selector
-def columns_are_date_then_numbers(cls, req, rset, *args, **kwargs):
+def columns_are_date_then_numbers(cls, req, rset=None, *args, **kwargs):
etypes = rset.description[0]
if etypes[0] not in ('Date', 'Datetime'):
return 0
@@ -167,7 +167,7 @@
piechart.size(width, height)
if self.title:
piechart.title(self.title)
- self.w(u'<img src="%s" />' % html_escape(piechart.url))
+ self.w(u'<img src="%s" />' % xml_escape(piechart.url))
class PieChartView(baseviews.AnyRsetView):
id = 'piechart'
--- a/web/views/primary.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/primary.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,14 +10,14 @@
from warnings import warn
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import Unauthorized
from cubicweb.view import EntityView
+from cubicweb.schema import display_name
from cubicweb.web import uicfg
-
class PrimaryView(EntityView):
"""the full view of an non final entity"""
id = 'primary'
@@ -39,12 +39,11 @@
def cell_call(self, row, col):
self.row = row
- # XXX move render_entity implementation here
- self.render_entity(self.complete_entity(row, col))
self.maxrelated = self.req.property_value('navigation.related-limit')
+ entity = self.complete_entity(row, col)
+ self.render_entity(entity)
def render_entity(self, entity):
- """return html to display the given entity"""
self.render_entity_title(entity)
self.render_entity_metadata(entity)
# entity's attributes and relations, excluding meta data
@@ -52,27 +51,15 @@
boxes = self._prepare_side_boxes(entity)
if boxes or hasattr(self, 'render_side_related'):
self.w(u'<table width="100%"><tr><td style="width: 75%">')
- self.w(u'<div>')
self.w(u'<div class="mainInfo">')
- try:
- self.render_entity_attributes(entity)
- except TypeError: # XXX bw compat
- warn('siderelations argument of render_entity_attributes is '
- 'deprecated (%s)' % self.__class__)
- self.render_entity_attributes(entity, [])
+ self.content_navigation_components('navcontenttop')
+ self.render_entity_attributes(entity)
+ if self.main_related_section:
+ self.render_entity_relations(entity)
self.w(u'</div>')
- self.content_navigation_components('navcontenttop')
- if self.main_related_section:
- try:
- self.render_entity_relations(entity)
- except TypeError: # XXX bw compat
- warn('siderelations argument of render_entity_relations is '
- 'deprecated')
- self.render_entity_relations(entity, [])
- self.w(u'</div>')
+ # side boxes
if boxes or hasattr(self, 'render_side_related'):
self.w(u'</td><td>')
- # side boxes
self.w(u'<div class="primaryRight">')
if hasattr(self, 'render_side_related'):
warn('render_side_related is deprecated')
@@ -82,7 +69,6 @@
self.w(u'</td></tr></table>')
self.content_navigation_components('navcontentbottom')
-
def content_navigation_components(self, context):
self.w(u'<div class="%s">' % context)
for comp in self.vreg.possible_vobjects('contentnavigation', self.req,
@@ -98,7 +84,7 @@
def render_entity_title(self, entity):
"""default implementation return dc_title"""
- title = html_escape(entity.dc_title())
+ title = xml_escape(entity.dc_title())
if title:
self.w(u'<h1><span class="etype">%s</span> %s</h1>'
% (entity.dc_type().capitalize(), title))
@@ -115,7 +101,7 @@
def render_entity_attributes(self, entity, siderelations=None):
for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'):
- vid = dispctrl.get('vid', 'reledit')
+ vid = dispctrl.get('vid', 'reledit')
if rschema.is_final() or vid == 'reledit':
value = entity.view(vid, rtype=rschema.type, role=role)
else:
@@ -239,3 +225,21 @@
self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
self.req._('see them all')))
self.w(u'</div>')
+
+## default primary ui configuration ###########################################
+
+for rtype in ('eid', 'creation_date', 'modification_date', 'cwuri',
+ 'is', 'is_instance_of', 'identity',
+ 'owned_by', 'created_by',
+ 'in_state', 'wf_info_for', 'require_permission',
+ 'from_entity', 'to_entity',
+ 'see_also'):
+ uicfg.primaryview_section.tag_subject_of(('*', rtype, '*'), 'hidden')
+ uicfg.primaryview_section.tag_object_of(('*', rtype, '*'), 'hidden')
+uicfg.primaryview_section.tag_subject_of(('*', 'use_email', '*'), 'attributes')
+uicfg.primaryview_section.tag_subject_of(('*', 'primary_email', '*'), 'hidden')
+
+for attr in ('name', 'final'):
+ uicfg.primaryview_section.tag_attribute(('CWEType', attr), 'hidden')
+for attr in ('name', 'final', 'symetric', 'inlined'):
+ uicfg.primaryview_section.tag_attribute(('CWRType', attr), 'hidden')
--- a/web/views/schema.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/schema.py Tue Aug 04 15:06:09 2009 +0200
@@ -9,30 +9,180 @@
from itertools import cycle
-from logilab.mtconverter import html_escape
-from yams import schema2dot as s2d
+from logilab.mtconverter import xml_escape
+from yams import BASE_TYPES, schema2dot as s2d
-from cubicweb.selectors import implements, yes
+from cubicweb.selectors import implements, yes, match_user_groups
+from cubicweb.schema import META_RTYPES, SCHEMA_TYPES
from cubicweb.schemaviewer import SchemaViewer
from cubicweb.view import EntityView, StartupView
from cubicweb.common import tags, uilib
-from cubicweb.web import action
-from cubicweb.web.views import TmpFileViewMixin, primary, baseviews, tabs
-from cubicweb.web.facet import AttributeFacet
+from cubicweb.web import action, facet
+from cubicweb.web.views import TmpFileViewMixin
+from cubicweb.web.views import primary, baseviews, tabs, management
+
+ALWAYS_SKIP_TYPES = BASE_TYPES | SCHEMA_TYPES
+SKIP_TYPES = ALWAYS_SKIP_TYPES | META_RTYPES
+SKIP_TYPES.update(set(('Transition', 'State', 'TrInfo',
+ 'CWUser', 'CWGroup',
+ 'CWCache', 'CWProperty', 'CWPermission',
+ 'ExternalUri')))
+
+def skip_types(req):
+ if int(req.form.get('skipmeta', True)):
+ return SKIP_TYPES
+ return ALWAYS_SKIP_TYPES
+
+# global schema view ###########################################################
+
+class SchemaView(tabs.TabsMixin, StartupView):
+ id = 'schema'
+ title = _('instance schema')
+ tabs = [_('schema-text'), _('schema-image')]
+ default_tab = 'schema-text'
+
+ def call(self):
+ """display schema information"""
+ self.req.add_js('cubicweb.ajax.js')
+ self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
+ self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
+ self.render_tabs(self.tabs, self.default_tab)
+
+
+class SchemaTabImageView(StartupView):
+ id = 'schema-image'
+
+ def call(self):
+ self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
+ u'meta-data, but you can also display a <a href="%s">complete '
+ u'schema with meta-data</a>.</div>')
+ % xml_escape(self.build_url('view', vid='schemagraph', skipmeta=0)))
+ self.w(u'<img src="%s" alt="%s"/>\n' % (
+ xml_escape(self.req.build_url('view', vid='schemagraph', skipmeta=1)),
+ self.req._("graphical representation of the instance'schema")))
+
+
+class SchemaTabTextView(StartupView):
+ id = 'schema-text'
+
+ def call(self):
+ rset = self.req.execute('Any X ORDERBY N WHERE X is CWEType, X name N, '
+ 'X final FALSE')
+ self.wview('table', rset, displayfilter=True)
-class ViewSchemaAction(action.Action):
- id = 'schema'
- __select__ = yes()
+class ManagerSchemaPermissionsView(StartupView, management.SecurityViewMixIn):
+ id = 'schema-security'
+ __select__ = StartupView.__select__ & match_user_groups('managers')
+
+ def call(self, display_relations=True):
+ self.req.add_css('cubicweb.acl.css')
+ skiptypes = skip_types(self.req)
+ formparams = {}
+ formparams['sec'] = self.id
+ if not skiptypes:
+ formparams['skipmeta'] = u'0'
+ schema = self.schema
+ # compute entities
+ entities = sorted(eschema for eschema in schema.entities()
+ if not (eschema.is_final() or eschema in skiptypes))
+ # compute relations
+ if display_relations:
+ relations = sorted(rschema for rschema in schema.relations()
+ if not (rschema.is_final()
+ or rschema in skiptypes
+ or rschema in META_RTYPES))
+ else:
+ relations = []
+ # index
+ _ = self.req._
+ self.w(u'<div id="schema_security"><a id="index" href="index"/>')
+ self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
+ self.w(u'<h4>%s</h4>' % _('Entities').capitalize())
+ ents = []
+ for eschema in sorted(entities):
+ url = xml_escape(self.build_url('schema', **formparams))
+ ents.append(u'<a class="grey" href="%s#%s">%s</a> (%s)' % (
+ url, eschema.type, eschema.type, _(eschema.type)))
+ self.w(u', '.join(ents))
+ self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
+ rels = []
+ for rschema in sorted(relations):
+ url = xml_escape(self.build_url('schema', **formparams))
+ rels.append(u'<a class="grey" href="%s#%s">%s</a> (%s), ' % (
+ url , rschema.type, rschema.type, _(rschema.type)))
+ self.w(u', '.join(ents))
+ # entities
+ self.display_entities(entities, formparams)
+ # relations
+ if relations:
+ self.display_relations(relations, formparams)
+ self.w(u'</div>')
- title = _("site schema")
- category = 'siteactions'
- order = 30
+ def display_entities(self, entities, formparams):
+ _ = self.req._
+ self.w(u'<a id="entities" href="entities"/>')
+ self.w(u'<h2 class="schema">%s</h2>' % _('permissions for entities').capitalize())
+ for eschema in entities:
+ self.w(u'<a id="%s" href="%s"/>' % (eschema.type, eschema.type))
+ self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
+ url = xml_escape(self.build_url('schema', **formparams) + '#index')
+ self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
+ url, self.req.external_resource('UP_ICON'), _('up')))
+ self.w(u'</h3>')
+ self.w(u'<div style="margin: 0px 1.5em">')
+ self.schema_definition(eschema, link=False)
+ # display entity attributes only if they have some permissions modified
+ modified_attrs = []
+ for attr, etype in eschema.attribute_definitions():
+ if self.has_schema_modified_permissions(attr, attr.ACTIONS):
+ modified_attrs.append(attr)
+ if modified_attrs:
+ self.w(u'<h4>%s</h4>' % _('attributes with modified permissions:').capitalize())
+ self.w(u'</div>')
+ self.w(u'<div style="margin: 0px 6em">')
+ for attr in modified_attrs:
+ self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attr.type, _(attr.type)))
+ self.schema_definition(attr, link=False)
+ self.w(u'</div>')
- def url(self):
- return self.build_url(self.id)
+ def display_relations(self, relations, formparams):
+ _ = self.req._
+ self.w(u'<a id="relations" href="relations"/>')
+ self.w(u'<h2 class="schema">%s </h2>' % _('permissions for relations').capitalize())
+ for rschema in relations:
+ self.w(u'<a id="%s" href="%s"/>' % (rschema.type, rschema.type))
+ self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
+ url = xml_escape(self.build_url('schema', **formparams) + '#index')
+ self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (
+ url, self.req.external_resource('UP_ICON'), _('up')))
+ self.w(u'</h3>')
+ self.w(u'<div style="margin: 0px 1.5em">')
+ subjects = [str(subj) for subj in rschema.subjects()]
+ self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (
+ _('subject_plural:'),
+ ', '.join(str(subj) for subj in rschema.subjects()),
+ ', '.join(_(str(subj)) for subj in rschema.subjects())))
+ self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (
+ _('object_plural:'),
+ ', '.join(str(obj) for obj in rschema.objects()),
+ ', '.join(_(str(obj)) for obj in rschema.objects())))
+ self.schema_definition(rschema, link=False)
+ self.w(u'</div>')
+class SchemaUreportsView(StartupView):
+ id = 'schema-block'
+
+ def call(self):
+ viewer = SchemaViewer(self.req)
+ layout = viewer.visit_schema(self.schema, display_relations=True,
+ skiptypes=skip_types(self.req))
+ self.w(uilib.ureport_as_html(layout))
+
+
+# CWAttribute / CWRelation #####################################################
+
class CWRDEFPrimaryView(primary.PrimaryView):
__select__ = implements('CWAttribute', 'CWRelation')
cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
@@ -40,7 +190,8 @@
def render_entity_title(self, entity):
self.w(u'<h1><span class="etype">%s</span> %s</h1>'
% (entity.dc_type().capitalize(),
- html_escape(entity.dc_long_title())))
+ xml_escape(entity.dc_long_title())))
+
# CWEType ######################################################################
@@ -56,15 +207,12 @@
if final:
self.w(u'</em>')
-SKIPPED_RELS = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
- 'has_text',)
class CWETypePrimaryView(tabs.TabsMixin, primary.PrimaryView):
__select__ = implements('CWEType')
title = _('in memory entity schema')
main_related_section = False
- skip_rels = SKIPPED_RELS
- tabs = [_('cwetype-schema-text'), _('cwetype-schema-image'),
+ tabs = [_('cwetype-schema-text'), _('cwetype-schema-image'),
_('cwetype-schema-permissions'), _('cwetype-workflow')]
default_tab = 'cwetype-schema-text'
@@ -81,11 +229,10 @@
def cell_call(self, row, col):
entity = self.entity(row, col)
self.w(u'<h2>%s</h2>' % _('Attributes'))
- rset = self.req.execute('Any N,F,D,GROUP_CONCAT(C),I,J,DE,A '
- 'GROUPBY N,F,D,AA,A,I,J,DE '
+ rset = self.req.execute('Any N,F,D,I,J,DE,A '
'ORDERBY AA WHERE A is CWAttribute, '
'A ordernum AA, A defaultval D, '
- 'A constrained_by C?, A description DE, '
+ 'A description DE, '
'A fulltextindexed I, A internationalizable J, '
'A relation_type R, R name N, '
'A to_entity O, O name F, '
@@ -93,25 +240,22 @@
{'x': entity.eid})
self.wview('editable-table', rset, 'null', displayfilter=True)
self.w(u'<h2>%s</h2>' % _('Relations'))
- rset = self.req.execute('Any N,C,F,M,K,D,A ORDERBY N '
- 'WITH N,C,F,M,D,K,A BEING ('
- '(Any N,C,F,M,K,D,A '
- 'ORDERBY N WHERE A is CWRelation, '
- 'A description D, A composite K?, '
- 'A relation_type R, R name N, '
- 'A to_entity O, O name F, '
- 'A cardinality C, O meta M, '
- 'A from_entity S, S eid %(x)s)'
- ' UNION '
- '(Any N,C,F,M,K,D,A '
- 'ORDERBY N WHERE A is CWRelation, '
- 'A description D, A composite K?, '
- 'A relation_type R, R name N, '
- 'A from_entity S, S name F, '
- 'A cardinality C, S meta M, '
- 'A to_entity O, O eid %(x)s))'
- ,{'x': entity.eid})
- self.wview('editable-table', rset, 'null', displayfilter=True)
+ rset = self.req.execute(
+ 'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
+ 'WHERE A is CWRelation, A description D, A composite K?, '
+ 'A relation_type R, R name RN, A to_entity TT, TT name TTN, '
+ 'A cardinality C, A from_entity S, S eid %(x)s',
+ {'x': entity.eid})
+ self.wview('editable-table', rset, 'null', displayfilter=True,
+ displaycols=range(6), mainindex=5)
+ rset = self.req.execute(
+ 'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN '
+ 'WHERE A is CWRelation, A description D, A composite K?, '
+ 'A relation_type R, R name RN, A from_entity TT, TT name TTN, '
+ 'A cardinality C, A to_entity O, O eid %(x)s',
+ {'x': entity.eid})
+ self.wview('editable-table', rset, 'null', displayfilter=True,
+ displaycols=range(6), mainindex=5)
class CWETypeSImageView(EntityView):
@@ -122,8 +266,8 @@
entity = self.entity(row, col)
url = entity.absolute_url(vid='schemagraph')
self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(url),
- html_escape(self.req._('graphical schema for %s') % entity.name)))
+ xml_escape(url),
+ xml_escape(self.req._('graphical schema for %s') % entity.name)))
class CWETypeSPermView(EntityView):
id = 'cwetype-schema-permissions'
@@ -133,22 +277,22 @@
entity = self.entity(row, col)
self.w(u'<h2>%s</h2>' % _('Add permissions'))
rset = self.req.execute('Any P WHERE X add_permission P, '
- 'X eid %(x)s',
+ 'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Read permissions'))
rset = self.req.execute('Any P WHERE X read_permission P, '
- 'X eid %(x)s',
+ 'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Update permissions'))
rset = self.req.execute('Any P WHERE X update_permission P, '
- 'X eid %(x)s',
+ 'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
self.w(u'<h2>%s</h2>' % _('Delete permissions'))
rset = self.req.execute('Any P WHERE X delete_permission P, '
- 'X eid %(x)s',
+ 'X eid %(x)s',
{'x': entity.eid})
self.wview('outofcontext', rset, 'null')
@@ -160,8 +304,8 @@
entity = self.entity(row, col)
if entity.reverse_state_of:
self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(entity.absolute_url(vid='ewfgraph')),
- html_escape(self.req._('graphical workflow for %s') % entity.name)))
+ xml_escape(entity.absolute_url(vid='ewfgraph')),
+ xml_escape(self.req._('graphical workflow for %s') % entity.name)))
else:
self.w(u'<p>%s</p>' % _('There is no workflow defined for this entity.'))
@@ -186,94 +330,59 @@
# schema images ###############################################################
-class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
- def __init__(self, req):
- # FIXME: colors are arbitrary
- self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
- '#000000', '#888888') ).next
+class RestrictedSchemaVisitorMixIn(object):
+ def __init__(self, req, *args, **kwargs):
self.req = req
-
- def display_attr(self, rschema):
- return not rschema.meta and (rschema.has_local_role('read')
- or rschema.has_perm(self.req, 'read'))
+ super(RestrictedSchemaVisitorMixIn, self).__init__(*args, **kwargs)
- # XXX remove this method once yams > 0.20 is out
- def node_properties(self, eschema):
- """return default DOT drawing options for an entity schema"""
- label = ['{', eschema.type, '|']
- label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
- if rel.final and self.display_attr(rel)))
- label.append(r'\l}') # trailing \l ensure alignement of the last one
- return {'label' : ''.join(label), 'shape' : "record",
- 'fontname' : "Courier", 'style' : "filled"}
+ def should_display_schema(self, rschema):
+ return (super(RestrictedSchemaVisitorMixIn, self).should_display_schema(rschema)
+ and (rschema.has_local_role('read')
+ or rschema.has_perm(self.req, 'read')))
- def edge_properties(self, rschema, subjnode, objnode):
- kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
- # symetric rels are handled differently, let yams decide what's best
- if not rschema.symetric:
- kwargs['color'] = self.nextcolor()
- kwargs['fontcolor'] = kwargs['color']
- # dot label decoration is just awful (1 line underlining the label
- # + 1 line going to the closest edge spline point)
- kwargs['decorate'] = 'false'
- return kwargs
+ def should_display_attr(self, rschema):
+ return (super(RestrictedSchemaVisitorMixIn, self).should_display_attr(rschema)
+ and (rschema.has_local_role('read')
+ or rschema.has_perm(self.req, 'read')))
-class RestrictedSchemaVisitorMiIn:
- def __init__(self, req, *args, **kwargs):
- # hack hack hack
- assert len(self.__class__.__bases__) == 2
- self.__parent = self.__class__.__bases__[1]
- self.__parent.__init__(self, *args, **kwargs)
- self.req = req
-
- def nodes(self):
- for etype, eschema in self.__parent.nodes(self):
- if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
- yield eschema.type, eschema
-
- def edges(self):
- for setype, oetype, rschema in self.__parent.edges(self):
- if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
- yield setype, oetype, rschema
-
-
-class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
+class FullSchemaVisitor(RestrictedSchemaVisitorMixIn, s2d.FullSchemaVisitor):
pass
-class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
+class OneHopESchemaVisitor(RestrictedSchemaVisitorMixIn,
+ s2d.OneHopESchemaVisitor):
pass
-class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
+class OneHopRSchemaVisitor(RestrictedSchemaVisitorMixIn,
+ s2d.OneHopRSchemaVisitor):
pass
class SchemaImageView(TmpFileViewMixin, StartupView):
id = 'schemagraph'
+ content_type = 'image/png'
- content_type = 'image/png'
- skip_rels = SKIPPED_RELS
def _generate(self, tmpfile):
"""display global schema information"""
- skipmeta = not int(self.req.form.get('withmeta', 0))
- visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+ print 'skipedtypes', skip_types(self.req)
+ visitor = FullSchemaVisitor(self.req, self.schema,
+ skiptypes=skip_types(self.req))
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
+
class CWETypeSchemaImageView(TmpFileViewMixin, EntityView):
id = 'schemagraph'
__select__ = implements('CWEType')
-
content_type = 'image/png'
- skip_rels = SKIPPED_RELS
def _generate(self, tmpfile):
"""display schema information for an entity"""
entity = self.entity(self.row, self.col)
eschema = self.vreg.schema.eschema(entity.name)
- visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+ visitor = OneHopESchemaVisitor(self.req, eschema,
+ skiptypes=skip_types(self.req))
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
+
class CWRTypeSchemaImageView(CWETypeSchemaImageView):
__select__ = implements('CWRType')
@@ -283,17 +392,23 @@
entity = self.entity(self.row, self.col)
rschema = self.vreg.schema.rschema(entity.name)
visitor = OneHopRSchemaVisitor(self.req, rschema)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor)
+
+
+# misc: facets, actions ########################################################
-### facets
+class CWFinalFacet(facet.AttributeFacet):
+ id = 'cwfinal-facet'
+ __select__ = facet.AttributeFacet.__select__ & implements('CWEType', 'CWRType')
+ rtype = 'final'
-class CWMetaFacet(AttributeFacet):
- id = 'cwmeta-facet'
- __select__ = AttributeFacet.__select__ & implements('CWEType')
- rtype = 'meta'
+class ViewSchemaAction(action.Action):
+ id = 'schema'
+ __select__ = yes()
-class CWFinalFacet(AttributeFacet):
- id = 'cwfinal-facet'
- __select__ = AttributeFacet.__select__ & implements('CWEType')
- rtype = 'final'
+ title = _("site schema")
+ category = 'siteactions'
+ order = 30
+
+ def url(self):
+ return self.build_url(self.id)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/sparql.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,122 @@
+"""SPARQL integration
+
+: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
+"""
+v__docformat__ = "restructuredtext en"
+
+import rql
+from yams import xy
+
+from lxml import etree
+from lxml.builder import E
+
+from cubicweb.view import StartupView, AnyRsetView
+from cubicweb.web import Redirect, form, formfields, formwidgets as fwdgs
+from cubicweb.web.views import forms, urlrewrite
+try:
+ from cubicweb.spa2rql import Sparql2rqlTranslator
+except ImportError:
+ # fyzz not available (only a recommends)
+ Sparql2rqlTranslator = None
+
+class SparqlForm(forms.FieldsForm):
+ id = 'sparql'
+ sparql = formfields.StringField(help=_('type here a sparql query'))
+ resultvid = formfields.StringField(choices=((_('table'), 'table'),
+ (_('sparql xml'), 'sparqlxml')),
+ widget=fwdgs.Radio,
+ initial='table')
+ form_buttons = [fwdgs.SubmitButton()]
+ @property
+ def action(self):
+ return self.req.url()
+
+
+class SparqlFormView(form.FormViewMixIn, StartupView):
+ id = 'sparql'
+ def call(self):
+ form = self.vreg.select('forms', 'sparql', self.req)
+ self.w(form.form_render())
+ sparql = self.req.form.get('sparql')
+ vid = self.req.form.get('resultvid', 'table')
+ if sparql:
+ try:
+ qinfo = Sparql2rqlTranslator(self.schema).translate(sparql)
+ except rql.TypeResolverException, ex:
+ self.w(self.req._('can not resolve entity types:') + u' ' + unicode('ex'))
+ except UnsupportedQuery:
+ self.w(self.req._('we are not yet ready to handle this query'))
+ except xy.UnsupportedVocabulary, ex:
+ self.w(self.req._('unknown vocabulary:') + u' ' + unicode('ex'))
+ if vid == 'sparqlxml':
+ url = self.build_url('view', rql=qinfo.finalize(), vid=vid)
+ raise Redirect(url)
+ rset = self.req.execute(qinfo.finalize())
+ self.wview(vid, rset, 'null')
+
+
+## sparql resultset views #####################################################
+
+YAMS_XMLSCHEMA_MAPPING = {
+ 'String': 'string',
+ 'Int': 'integer',
+ 'Float': 'float',
+ 'Boolean': 'boolean',
+ 'Datetime': 'dateTime',
+ 'Date': 'date',
+ 'Time': 'time',
+ # XXX the following types don't have direct mapping
+ 'Decimal': 'string',
+ 'Interval': 'duration',
+ 'Password': 'string',
+ 'Bytes': 'base64Binary',
+ }
+
+def xmlschema(yamstype):
+ return 'http://www.w3.org/2001/XMLSchema#%s' % YAMS_XMLSCHEMA_MAPPING[yamstype]
+
+class SparqlResultXmlView(AnyRsetView):
+ """The spec can be found here: http://www.w3.org/TR/rdf-sparql-XMLres/
+ """
+ id = 'sparqlxml'
+ content_type = 'application/sparql-results+xml'
+ templatable = False
+
+ def call(self):
+ # XXX handle UNION
+ rqlst = self.rset.syntax_tree().children[0]
+ varnames = [var.name for var in rqlst.selection]
+ results = E.results()
+ for rowidx in xrange(len(self.rset)):
+ result = E.result()
+ for colidx, varname in enumerate(varnames):
+ result.append(self.cell_binding(rowidx, colidx, varname))
+ results.append(result)
+ sparql = E.sparql(E.head(*(E.variable(name=name) for name in varnames)),
+ results)
+ self.w(u'<?xml version="1.0"?>\n')
+ self.w(etree.tostring(sparql, encoding=unicode, pretty_print=True))
+
+ def cell_binding(self, row, col, varname):
+ celltype = self.rset.description[row][col]
+ if self.schema.eschema(celltype).is_final():
+ cellcontent = self.view('cell', self.rset, row=row, col=col)
+ return E.binding(E.literal(cellcontent,
+ datatype=xmlschema(celltype)),
+ name=varname)
+ else:
+ entity = self.entity(row, col)
+ return E.binding(E.uri(entity.absolute_url()), name=varname)
+
+ def set_request_content_type(self):
+ """overriden to set the correct filetype and filename"""
+ self.req.set_content_type(self.content_type,
+ filename='sparql.xml',
+ encoding=self.req.encoding)
+
+def registration_callback(vreg):
+ if Sparql2rqlTranslator is not None:
+ vreg.register_all(globals().values(), __name__)
--- a/web/views/startup.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/startup.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,14 +10,12 @@
_ = unicode
from logilab.common.textutils import unormalize
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.view import StartupView
from cubicweb.selectors import match_user_groups, implements
-from cubicweb.common.uilib import ureport_as_html
+from cubicweb.schema import display_name
from cubicweb.web import ajax_replace_url, uicfg, httpcache
-from cubicweb.web.views import tabs
-from cubicweb.web.views.management import SecurityViewMixIn
class ManageView(StartupView):
id = 'manage'
@@ -31,8 +29,6 @@
uicfg.indexview_etype_section.setdefault(eschema, 'schema')
elif eschema.is_subobject(strict=True):
uicfg.indexview_etype_section.setdefault(eschema, 'subobject')
- elif eschema.meta:
- uicfg.indexview_etype_section.setdefault(eschema, 'system')
else:
uicfg.indexview_etype_section.setdefault(eschema, 'application')
@@ -40,7 +36,7 @@
return False
def call(self, **kwargs):
- """The default view representing the application's management"""
+ """The default view representing the instance's management"""
self.req.add_css('cubicweb.manageview.css')
self.w(u'<div>\n')
if not self.display_folders():
@@ -76,7 +72,7 @@
else:
href = req.build_url('view', vid='creation', etype='Card', wikiid='index')
label = self.req._('create an index page')
- self.w(u'<br/><a href="%s">%s</a>\n' % (html_escape(href), label))
+ self.w(u'<br/><a href="%s">%s</a>\n' % (xml_escape(href), label))
def folders(self):
self.w(u'<h4>%s</h4>\n' % self.req._('Browse by category'))
@@ -91,7 +87,7 @@
if v.category != 'startupview' or v.id in ('index', 'tree', 'manage'):
continue
self.w('<p><a href="%s">%s</a></p>' % (
- html_escape(v.url()), html_escape(self.req._(v.title).capitalize())))
+ xml_escape(v.url()), xml_escape(self.req._(v.title).capitalize())))
def entities(self):
schema = self.schema
@@ -139,14 +135,9 @@
etype = eschema.type
label = display_name(req, etype, 'plural')
nb = req.execute('Any COUNT(X) WHERE X is %s' % etype)[0][0]
- if nb > 1:
- view = self.vreg.select('views', 'list', req,
- rset=req.etype_rset(etype))
- url = view.url()
- else:
- url = self.build_url('view', rql='%s X' % etype)
+ url = self.build_url(etype)
etypelink = u' <a href="%s">%s</a> (%d)' % (
- html_escape(url), label, nb)
+ xml_escape(url), label, nb)
yield (label, etypelink, self.add_entity_link(eschema, req))
def add_entity_link(self, eschema, req):
@@ -154,168 +145,14 @@
if not eschema.has_perm(req, 'add'):
return u''
return u'[<a href="%s" title="%s">+</a>]' % (
- html_escape(self.create_url(eschema.type)),
+ xml_escape(self.create_url(eschema.type)),
self.req.__('add a %s' % eschema))
class IndexView(ManageView):
id = 'index'
- title = _('index')
+ title = _('view_index')
def display_folders(self):
return 'Folder' in self.schema and self.req.execute('Any COUNT(X) WHERE X is Folder')[0][0]
-class SchemaView(tabs.TabsMixin, StartupView):
- id = 'schema'
- title = _('application schema')
- tabs = [_('schema-text'), _('schema-image')]
- default_tab = 'schema-text'
-
- def call(self):
- """display schema information"""
- self.req.add_js('cubicweb.ajax.js')
- self.req.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
- self.w(u'<h1>%s</h1>' % _('Schema of the data model'))
- self.render_tabs(self.tabs, self.default_tab)
-
-class SchemaTabImageView(StartupView):
- id = 'schema-image'
-
- def call(self):
- self.w(_(u'<div>This schema of the data model <em>excludes</em> the '
- u'meta-data, but you can also display a <a href="%s">complete '
- u'schema with meta-data</a>.</div>')
- % html_escape(self.build_url('view', vid='schemagraph', withmeta=1)))
- self.w(u'<img src="%s" alt="%s"/>\n' % (
- html_escape(self.req.build_url('view', vid='schemagraph', withmeta=0)),
- self.req._("graphical representation of the application'schema")))
-
-class SchemaTabTextView(StartupView):
- id = 'schema-text'
-
- def call(self):
- self.w(u'<p>%s</p>' % _('This is the list of types defined in the data '
- 'model ofin this application.'))
- self.w(u'<p>%s</p>' % _('<em>meta</em> is True for types that are defined by the '
- 'framework itself (e.g. User and Group). '
- '<em>final</em> is True for types that can not be the '
- 'subject of a relation (e.g. Int and String).'))
- rset = self.req.execute('Any X,M,F ORDERBY N WHERE X is CWEType, X name N, '
- 'X meta M, X final F')
- self.wview('editable-table', rset, displayfilter=True)
-
-
-class ManagerSchemaPermissionsView(StartupView, SecurityViewMixIn):
- id = 'schema_security'
- __select__ = StartupView.__select__ & match_user_groups('managers')
-
- def call(self, display_relations=True,
- skiprels=('is', 'is_instance_of', 'identity', 'owned_by', 'created_by')):
- _ = self.req._
- formparams = {}
- formparams['sec'] = self.id
- formparams['withmeta'] = int(self.req.form.get('withmeta', True))
- schema = self.schema
- # compute entities
- entities = [eschema for eschema in schema.entities()
- if not eschema.is_final()]
- if not formparams['withmeta']:
- entities = [eschema for eschema in entities
- if not eschema.meta]
- # compute relations
- relations = []
- if display_relations:
- relations = [rschema for rschema in schema.relations()
- if not (rschema.is_final() or rschema.type in skiprels)]
- if not formparams['withmeta']:
- relations = [rschema for rschema in relations
- if not rschema.meta]
- # index
- self.w(u'<div id="schema_security"><a id="index" href="index"/>')
- self.w(u'<h2 class="schema">%s</h2>' % _('index').capitalize())
- self.w(u'<h4>%s</h4>' % _('Entities').capitalize())
- ents = []
- for eschema in sorted(entities):
- url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type)
- ents.append(u'<a class="grey" href="%s">%s</a> (%s)' % (url, eschema.type, _(eschema.type)))
- self.w('%s' % ', '.join(ents))
- self.w(u'<h4>%s</h4>' % (_('relations').capitalize()))
- rels = []
- for eschema in sorted(relations):
- url = html_escape(self.build_url('schema', **formparams) + '#' + eschema.type)
- rels.append(u'<a class="grey" href="%s">%s</a> (%s), ' % (url , eschema.type, _(eschema.type)))
- self.w('%s' % ', '.join(ents))
- # entities
- self.display_entities(entities, formparams)
- # relations
- if relations:
- self.display_relations(relations, formparams)
- self.w(u'</div>')
-
- def display_entities(self, entities, formparams):
- _ = self.req._
- self.w(u'<a id="entities" href="entities"/>')
- self.w(u'<h2 class="schema">%s</h2>' % _('permissions for entities').capitalize())
- for eschema in sorted(entities):
- self.w(u'<a id="%s" href="%s"/>' % (eschema.type, eschema.type))
- self.w(u'<h3 class="schema">%s (%s) ' % (eschema.type, _(eschema.type)))
- url = html_escape(self.build_url('schema', **formparams) + '#index')
- self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (url, self.req.external_resource('UP_ICON'), _('up')))
- self.w(u'</h3>')
- self.w(u'<div style="margin: 0px 1.5em">')
- self.schema_definition(eschema, link=False)
-
- # display entity attributes only if they have some permissions modified
- modified_attrs = []
- for attr, etype in eschema.attribute_definitions():
- if self.has_schema_modified_permissions(attr, attr.ACTIONS):
- modified_attrs.append(attr)
- if modified_attrs:
- self.w(u'<h4>%s</h4>' % _('attributes with modified permissions:').capitalize())
- self.w(u'</div>')
- self.w(u'<div style="margin: 0px 6em">')
- for attr in modified_attrs:
- self.w(u'<h4 class="schema">%s (%s)</h4> ' % (attr.type, _(attr.type)))
- self.schema_definition(attr, link=False)
- self.w(u'</div>')
- else:
- self.w(u'</div>')
-
-
- def display_relations(self, relations, formparams):
- _ = self.req._
- self.w(u'<a id="relations" href="relations"/>')
- self.w(u'<h2 class="schema">%s </h2>' % _('permissions for relations').capitalize())
- for rschema in sorted(relations):
- self.w(u'<a id="%s" href="%s"/>' % (rschema.type, rschema.type))
- self.w(u'<h3 class="schema">%s (%s) ' % (rschema.type, _(rschema.type)))
- url = html_escape(self.build_url('schema', **formparams) + '#index')
- self.w(u'<a href="%s"><img src="%s" alt="%s"/></a>' % (url, self.req.external_resource('UP_ICON'), _('up')))
- self.w(u'</h3>')
- self.w(u'<div style="margin: 0px 1.5em">')
- subjects = [str(subj) for subj in rschema.subjects()]
- self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (_('subject_plural:'),
- ', '.join( [str(subj) for subj in rschema.subjects()]),
- ', '.join( [_(str(subj)) for subj in rschema.subjects()])))
- self.w(u'<div><strong>%s</strong> %s (%s)</div>' % (_('object_plural:'),
- ', '.join( [str(obj) for obj in rschema.objects()]),
- ', '.join( [_(str(obj)) for obj in rschema.objects()])))
- self.schema_definition(rschema, link=False)
- self.w(u'</div>')
-
-
-class SchemaUreportsView(StartupView):
- id = 'schematext'
-
- def call(self):
- from cubicweb.schemaviewer import SchemaViewer
- skipmeta = int(self.req.form.get('skipmeta', True))
- schema = self.schema
- viewer = SchemaViewer(self.req)
- layout = viewer.visit_schema(schema, display_relations=True,
- skiprels=('is', 'is_instance_of', 'identity',
- 'owned_by', 'created_by'),
- skipmeta=skipmeta)
- self.w(ureport_as_html(layout))
-
-
--- a/web/views/tableview.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/tableview.py Tue Aug 04 15:06:09 2009 +0200
@@ -10,7 +10,7 @@
from simplejson import dumps
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import nonempty_rset, match_form_params
from cubicweb.utils import make_uid
@@ -55,7 +55,7 @@
# drop False / None values from vidargs
vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
self.w(u'<form method="post" cubicweb:facetargs="%s" action="">' %
- html_escape(dumps([divid, 'table', False, vidargs])))
+ xml_escape(dumps([divid, 'table', False, vidargs])))
self.w(u'<fieldset id="%sForm" class="%s">' % (divid, hidden and 'hidden' or ''))
self.w(u'<input type="hidden" name="divid" value="%s" />' % divid)
filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets), baserql=baserql)
@@ -93,7 +93,7 @@
def call(self, title=None, subvid=None, displayfilter=None, headers=None,
displaycols=None, displayactions=None, actions=(), divid=None,
- cellvids=None, cellattrs=None):
+ cellvids=None, cellattrs=None, mainindex=None):
"""Dumps a table displaying a composite query
:param title: title added before table
@@ -101,19 +101,18 @@
:param displayfilter: filter that selects rows to display
:param headers: columns' titles
"""
- rset = self.rset
req = self.req
req.add_js('jquery.tablesorter.js')
req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css'))
- rqlst = rset.syntax_tree()
- # get rql description first since the filter form may remove some
- # necessary information
- rqlstdescr = rqlst.get_description()[0] # XXX missing Union support
- mainindex = self.main_var_index()
+ # compute label first since the filter form may remove some necessary
+ # information from the rql syntax tree
+ if mainindex is None:
+ mainindex = self.main_var_index()
+ computed_labels = self.columns_labels(mainindex)
hidden = True
if not subvid and 'subvid' in req.form:
subvid = req.form.pop('subvid')
- divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(rset))
+ divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.rset))
actions = list(actions)
if mainindex is None:
displayfilter, displayactions = False, False
@@ -140,6 +139,7 @@
actions += self.form_filter(divid, displaycols, displayfilter,
displayactions)
elif displayfilter:
+ req.add_css('cubicweb.facets.css')
actions += self.show_hide_actions(divid, True)
self.w(u'<div id="%s"' % divid)
if displayactions:
@@ -153,8 +153,8 @@
self.render_actions(divid, actions)
# render table
table = TableWidget(self)
- for column in self.get_columns(rqlstdescr, displaycols, headers, subvid,
- cellvids, cellattrs, mainindex):
+ for column in self.get_columns(computed_labels, displaycols, headers,
+ subvid, cellvids, cellattrs, mainindex):
table.append_column(column)
table.render(self.w)
self.w(u'</div>\n')
@@ -178,7 +178,7 @@
box = MenuWidget('', 'tableActionsBox', _class='', islist=False)
label = '<img src="%s" alt="%s"/>' % (
self.req.datadir_url + 'liveclipboard-icon.png',
- html_escape(self.req._('action(s) on this selection')))
+ xml_escape(self.req._('action(s) on this selection')))
menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
ident='%sActions' % divid)
box.append(menu)
@@ -187,20 +187,15 @@
box.render(w=self.w)
self.w(u'<div class="clear"/>')
- def get_columns(self, rqlstdescr, displaycols, headers, subvid, cellvids,
- cellattrs, mainindex):
+ def get_columns(self, computed_labels, displaycols, headers, subvid,
+ cellvids, cellattrs, mainindex):
columns = []
- for colindex, attr in enumerate(rqlstdescr):
+ for colindex, label in enumerate(computed_labels):
if colindex not in displaycols:
continue
# compute column header
if headers is not None:
label = headers[displaycols.index(colindex)]
- elif colindex == 0 or attr == 'Any': # find a better label
- label = ','.join(display_name(self.req, et)
- for et in self.rset.column_types(colindex))
- else:
- label = display_name(self.req, attr)
if colindex == mainindex:
label += ' (%s)' % self.rset.rowcount
column = TableColumn(label, colindex)
@@ -213,7 +208,6 @@
column.append_renderer(self.finalview, colindex)
else:
column.append_renderer(subvid or 'incontext', colindex)
-
if cellattrs and colindex in cellattrs:
for name, value in cellattrs[colindex].iteritems():
column.add_attr(name, value)
@@ -296,7 +290,7 @@
title = None
def call(self, title=None, subvid=None, headers=None, divid=None,
- displaycols=None, displayactions=None):
+ displaycols=None, displayactions=None, mainindex=None):
"""Dumps a table displaying a composite query"""
actrql = self.req.form['actualrql']
self.ensure_ro_rql(actrql)
@@ -311,7 +305,8 @@
title = self.req.form.pop('title')
if title:
self.w(u'<h2>%s</h2>\n' % title)
- mainindex = self.main_var_index()
+ if mainindex is None:
+ mainindex = self.main_var_index()
if mainindex is not None:
actions = self.form_filter(divid, displaycols, displayactions, True)
else:
--- a/web/views/tabs.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/tabs.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb import NoSelectableObject, role
from cubicweb.selectors import partial_has_related_entities
@@ -24,7 +24,7 @@
"""
def _prepare_bindings(self, vid, reloadable):
- self.req.html_headers.add_onload(u"""
+ self.req.add_onload(u"""
jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) {
load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s);
});""" % {'event': 'load_%s' % vid, 'vid': vid,
@@ -47,7 +47,7 @@
elif rset:
urlparams['rql'] = rset.printable_rql()
w(u'<div id="lazy-%s" cubicweb:loadurl="%s">' % (
- vid, html_escape(self.build_url('json', **urlparams))))
+ vid, xml_escape(self.build_url('json', **urlparams))))
if show_spinbox:
w(u'<img src="data/loading.gif" id="%s-hole" alt="%s"/>'
% (vid, self.req._('loading')))
@@ -59,7 +59,7 @@
on dom readyness
"""
self.req.add_js('cubicweb.lazy.js')
- self.req.html_headers.add_onload("trigger_load('%s');" % vid)
+ self.req.add_onload("trigger_load('%s');" % vid)
class TabsMixin(LazyViewMixin):
@@ -93,22 +93,11 @@
return selected_tabs
def render_tabs(self, tabs, default, entity=None):
- # tabbed views do no support concatenation
- # hence we delegate to the default tab if there is more than on entity
- # in the result set
+ # delegate to the default tab if there is more than one entity
+ # in the result set (tabs are pretty useless there)
if entity and len(self.rset) > 1:
entity.view(default, w=self.w)
return
- # XXX (syt) fix below add been introduced at some point to fix something
- # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean
- # way. We must not consider form['rql'] here since it introduces some
- # other failures on non rql queries (plain text, shortcuts,... handled by
- # magicsearch) which has a single result whose primary view is using tabs
- # (https://www.logilab.net/cwo/ticket/342789)
- #rql = self.req.form.get('rql')
- #if rql:
- # self.req.execute(rql).get_entity(0,0).view(default, w=self.w)
- # return
self.req.add_css('ui.tabs.css')
self.req.add_js(('ui.core.js', 'ui.tabs.js',
'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js'))
@@ -118,11 +107,8 @@
active_tab = self.active_tab(tabs, default)
# build the html structure
w = self.w
- if entity:
- w(u'<div id="entity-tabs-%s">' % entity.eid)
- else:
- uid = make_uid('tab')
- w(u'<div id="entity-tabs-%s">' % uid)
+ uid = entity and entity.eid or make_uid('tab')
+ w(u'<div id="entity-tabs-%s">' % uid)
w(u'<ul>')
for tab in tabs:
w(u'<li>')
@@ -143,14 +129,14 @@
w(u'</div>')
# call the set_tab() JS function *after* each tab is generated
# because the callback binding needs to be done before
- self.req.html_headers.add_onload(u"""
- jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
- set_tab('%(vid)s', '%(cookiename)s');
- """ % {'tabindex' : tabs.index(active_tab),
- 'vid' : active_tab,
- 'eeid' : (entity and entity.eid or uid),
- 'cookiename' : self.cookie_name})
-
+ # XXX make work history: true
+ self.req.add_onload(u'''
+ jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s });
+ set_tab('%(vid)s', '%(cookiename)s');
+''' % {'tabindex' : tabs.index(active_tab),
+ 'vid' : active_tab,
+ 'eeid' : (entity and entity.eid or uid),
+ 'cookiename' : self.cookie_name})
class EntityRelationView(EntityView):
"""view displaying entity related stuff.
--- a/web/views/timeline.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/timeline.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,7 +11,7 @@
import simplejson
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.interfaces import ICalendarable
from cubicweb.selectors import implements
@@ -68,7 +68,7 @@
if start is None and stop is None:
return None
event_data = {'start': start.strftime(self.date_fmt),
- 'title': html_escape(entity.dc_title()),
+ 'title': xml_escape(entity.dc_title()),
'description': entity.dc_description(format='text/html'),
'link': entity.absolute_url(),
}
@@ -95,7 +95,7 @@
additional = u''
self.w(u'<div class="widget" cubicweb:wdgtype="%s" '
u'cubicweb:loadtype="auto" cubicweb:loadurl="%s" %s >' %
- (self.widget_class, html_escape(loadurl),
+ (self.widget_class, xml_escape(loadurl),
additional))
self.w(u'</div>')
--- a/web/views/timetable.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/timetable.py Tue Aug 04 15:06:09 2009 +0200
@@ -6,7 +6,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.interfaces import ITimetableViews
from cubicweb.selectors import implements
@@ -138,7 +138,7 @@
for user, width in zip(users, widths):
self.w(u'<th colspan="%s">' % max(MIN_COLS, width))
if user != u"*":
- user.view('secondary', w=self.w)
+ user.view('oneline', w=self.w)
else:
self.w(user)
self.w(u'</th>')
@@ -190,7 +190,7 @@
if value:
task_descr, first_row = value
if first_row:
- url = html_escape(task_descr.task.absolute_url(vid="edition"))
+ url = xml_escape(task_descr.task.absolute_url(vid="edition"))
self.w(u'<td rowspan="%d" class="%s %s" onclick="document.location=\'%s\'"> <div>' % (
task_descr.lines, task_descr.color, filled_klasses[kj], url))
task_descr.task.view('tooltip', w=self.w)
--- a/web/views/treeview.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/treeview.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
from logilab.common.decorators import monkeypatch
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.utils import make_uid
from cubicweb.interfaces import ITree
@@ -16,7 +16,7 @@
from cubicweb.view import EntityView
def treecookiename(treeid):
- return str('treestate-%s' % treeid)
+ return str('%s-treestate' % treeid)
class TreeView(EntityView):
id = 'treeview'
@@ -26,7 +26,7 @@
def call(self, subvid=None, treeid=None, initial_load=True):
if subvid is None:
- subvid = self.req.form.pop('subvid', 'oneline') # consume it
+ subvid = self.req.form.pop('treesubvid', 'oneline') # consume it
if treeid is None:
treeid = self.req.form.pop('treeid', None)
if treeid is None:
@@ -39,7 +39,7 @@
self.w(u'</ul>')
if initial_load and not self.req.form.get('fname'):
self.req.add_css('jquery.treeview.css')
- self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js'))
+ self.req.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js'))
self.req.html_headers.add_onload(u"""
jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid)
@@ -106,17 +106,18 @@
liclasses = []
is_last = row == len(self.rset) - 1
is_open = self.open_state(entity.eid, treeid)
- if not hasattr(entity, 'is_leaf') or entity.is_leaf():
+ is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf()
+ if is_leaf:
if is_last:
liclasses.append('last')
w(u'<li class="%s">' % u' '.join(liclasses))
else:
rql = entity.children_rql() % {'x': entity.eid}
- url = html_escape(self.build_url('json', rql=rql, vid=parentvid,
+ url = xml_escape(self.build_url('json', rql=rql, vid=parentvid,
pageid=self.req.pageid,
treeid=treeid,
fname='view',
- subvid=vid))
+ treesubvid=vid))
divclasses = ['hitarea']
if is_open:
liclasses.append('collapsable')
@@ -145,7 +146,7 @@
w(u'<ul class="placeholder"><li>place holder</li></ul>')
# the local node info
self.wview(vid, self.rset, row=row, col=col)
- if is_open: # => not leaf => rql is defined
+ if is_open and not is_leaf: # => rql is defined
self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False)
w(u'</li>')
--- a/web/views/urlpublishing.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/urlpublishing.py Tue Aug 04 15:06:09 2009 +0200
@@ -144,18 +144,12 @@
<etype>[[/<attribute name>]/<attribute value>]*
"""
priority = 2
- def __init__(self, urlpublisher):
- super(RestPathEvaluator, self).__init__(urlpublisher)
- self.etype_map = {}
- for etype in self.schema.entities():
- etype = str(etype)
- self.etype_map[etype.lower()] = etype
def evaluate_path(self, req, parts):
if not (0 < len(parts) < 4):
raise PathDontMatch()
try:
- etype = self.etype_map[parts.pop(0).lower()]
+ etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()]
except KeyError:
raise PathDontMatch()
cls = self.vreg.etype_class(etype)
--- a/web/views/urlrewrite.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/urlrewrite.py Tue Aug 04 15:06:09 2009 +0200
@@ -76,9 +76,11 @@
('/index', dict(vid='index')),
('/myprefs', dict(vid='propertiesform')),
('/siteconfig', dict(vid='systempropertiesform')),
+ ('/siteinfo', dict(vid='info')),
('/manage', dict(vid='manage')),
('/notfound', dict(vid='404')),
('/error', dict(vid='error')),
+ ('/sparql', dict(vid='sparql')),
(rgx('/schema/([^/]+?)/?'), dict(vid='eschema', rql=r'Any X WHERE X is CWEType, X name "\1"')),
(rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')),
(rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
--- a/web/views/workflow.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/workflow.py Tue Aug 04 15:06:09 2009 +0200
@@ -11,13 +11,14 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from logilab.common.graph import escape, GraphGenerator, DotBackend
from cubicweb import Unauthorized, view
from cubicweb.selectors import (implements, has_related_entities,
relation_possible, match_form_params)
from cubicweb.interfaces import IWorkflowable
+from cubicweb.view import EntityView
from cubicweb.web import stdmsgs, action, component, form
from cubicweb.web.form import FormViewMixIn
from cubicweb.web.formfields import StringField, RichTextField
@@ -67,12 +68,9 @@
return entity.rest_path()
-class WFHistoryVComponent(component.EntityVComponent):
- """display the workflow history for entities supporting it"""
+class WFHistoryView(EntityView):
id = 'wfhistory'
- __select__ = (component.EntityVComponent.__select__
- & relation_possible('wf_info_for', role='object'))
- context = 'navcontentbottom'
+ __select__ = relation_possible('wf_info_for', role='object')
title = _('Workflow history')
def cell_call(self, row, col, view=None):
@@ -102,6 +100,16 @@
displaycols=displaycols, headers=headers)
+class WFHistoryVComponent(component.EntityVComponent):
+ """display the workflow history for entities supporting it"""
+ id = 'wfhistory'
+ __select__ = WFHistoryView.__select__ & component.EntityVComponent.__select__
+ context = 'navcontentbottom'
+ title = _('Workflow history')
+
+ def cell_call(self, row, col, view=None):
+ self.wview('wfhistory', self.rset, row=row, col=col, view=view)
+
# workflow entity types views #################################################
class CellView(view.EntityView):
@@ -109,7 +117,7 @@
__select__ = implements('TrInfo')
def cell_call(self, row, col, cellvid=None):
- self.w(self.entity(row, col).printable_value('comment'))
+ self.w(self.entity(row, col).view('reledit', rtype='comment'))
class StateInContextView(view.EntityView):
@@ -118,7 +126,7 @@
__select__ = implements('State')
def cell_call(self, row, col):
- self.w(html_escape(self.view('textincontext', self.rset,
+ self.w(xml_escape(self.view('textincontext', self.rset,
row=row, col=col)))
@@ -145,8 +153,8 @@
self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
% display_name(self.req, entity.name)))
self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(entity.absolute_url(vid='ewfgraph')),
- html_escape(self.req._('graphical workflow for %s') % entity.name)))
+ xml_escape(entity.absolute_url(vid='ewfgraph')),
+ xml_escape(self.req._('graphical workflow for %s') % entity.name)))
class WorkflowDotPropsHandler(object):
--- a/web/views/xbel.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/xbel.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,7 +8,7 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from cubicweb.selectors import implements
from cubicweb.view import EntityView
@@ -42,8 +42,8 @@
def cell_call(self, row, col):
entity = self.complete_entity(row, col)
- self.w(u'<bookmark href="%s">' % html_escape(self.url(entity)))
- self.w(u' <title>%s</title>' % html_escape(entity.dc_title()))
+ self.w(u'<bookmark href="%s">' % xml_escape(self.url(entity)))
+ self.w(u' <title>%s</title>' % xml_escape(entity.dc_title()))
self.w(u'</bookmark>')
def url(self, entity):
--- a/web/views/xmlrss.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/views/xmlrss.py Tue Aug 04 15:06:09 2009 +0200
@@ -77,7 +77,7 @@
w = self.w
rset, descr = self.rset, self.rset.description
eschema = self.schema.eschema
- labels = self.columns_labels(False)
+ labels = self.columns_labels(tr=False)
w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
for rowindex, row in enumerate(self.rset):
--- a/web/webconfig.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/webconfig.py Tue Aug 04 15:06:09 2009 +0200
@@ -1,4 +1,4 @@
-"""common web configuration for twisted/modpython applications
+"""common web configuration for twisted/modpython instances
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -6,6 +6,7 @@
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
+_ = unicode
import os
from os.path import join, exists, split
@@ -16,7 +17,6 @@
from cubicweb.toolsutils import read_config
from cubicweb.cwconfig import CubicWebConfiguration, register_persistent_options, merge_options
-_ = unicode
register_persistent_options( (
# site-wide only web ui configuration
@@ -60,7 +60,7 @@
class WebConfiguration(CubicWebConfiguration):
- """the WebConfiguration is a singleton object handling application's
+ """the WebConfiguration is a singleton object handling instance's
configuration and preferences
"""
cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['web/views'])
@@ -80,22 +80,16 @@
'if anonymous-user is set',
'group': 'main', 'inputlevel': 1,
}),
- ('allow-email-login',
- {'type' : 'yn',
- 'default': False,
- 'help': 'allow users to login with their primary email if set',
- 'group': 'main', 'inputlevel': 2,
- }),
('query-log-file',
{'type' : 'string',
'default': None,
- 'help': 'web application query log file',
+ 'help': 'web instance query log file',
'group': 'main', 'inputlevel': 2,
}),
- ('pyro-application-id',
+ ('pyro-instance-id',
{'type' : 'string',
- 'default': Method('default_application_id'),
- 'help': 'CubicWeb application identifier in the Pyro name server',
+ 'default': Method('default_instance_id'),
+ 'help': 'CubicWeb instance identifier in the Pyro name server',
'group': 'pyro-client', 'inputlevel': 1,
}),
# web configuration
@@ -151,6 +145,13 @@
'sessions. Default to 2 min.',
'group': 'web', 'inputlevel': 2,
}),
+ ('force-html-content-type',
+ {'type' : 'yn',
+ 'default': False,
+ 'help': 'force text/html content type for your html pages instead of cubicweb user-agent based'\
+ 'deduction of an appropriate content type',
+ 'group': 'web', 'inputlevel': 2,
+ }),
('embed-allowed',
{'type' : 'regexp',
'default': None,
@@ -162,7 +163,7 @@
('submit-url',
{'type' : 'string',
'default': Method('default_submit_url'),
- 'help': ('URL that may be used to report bug in this application '
+ 'help': ('URL that may be used to report bug in this instance '
'by direct access to the project\'s (jpl) tracker, '
'if you want this feature on. The url should looks like '
'http://mytracker.com/view?__linkto=concerns:1234:subject&etype=Ticket&type=bug&vid=creation '
@@ -174,7 +175,7 @@
('submit-mail',
{'type' : 'string',
'default': None,
- 'help': ('Mail used as recipient to report bug in this application, '
+ 'help': ('Mail used as recipient to report bug in this instance, '
'if you want this feature on'),
'group': 'web', 'inputlevel': 2,
}),
@@ -221,7 +222,7 @@
# don't use @cached: we want to be able to disable it while this must still
# be cached
def repository(self, vreg=None):
- """return the application's repository object"""
+ """return the instance's repository object"""
try:
return self.__repo
except AttributeError:
@@ -229,7 +230,7 @@
if self.repo_method == 'inmemory':
repo = get_repository('inmemory', vreg=vreg, config=self)
else:
- repo = get_repository('pyro', self['pyro-application-id'],
+ repo = get_repository('pyro', self['pyro-instance-id'],
config=self)
self.__repo = repo
return repo
@@ -304,7 +305,7 @@
yield join(fpath)
def load_configuration(self):
- """load application's configuration files"""
+ """load instance's configuration files"""
super(WebConfiguration, self).load_configuration()
# load external resources definition
self._build_ext_resources()
--- a/web/webctl.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/webctl.py Tue Aug 04 15:06:09 2009 +0200
@@ -8,24 +8,23 @@
"""
__docformat__ = "restructuredtext en"
-from cubicweb.toolsutils import CommandHandler, confirm
-
+from cubicweb import underline_title
+from cubicweb.toolsutils import CommandHandler
+from logilab.common.shellutils import ASK
class WebCreateHandler(CommandHandler):
cmdname = 'create'
def bootstrap(self, cubes, inputlevel=0):
"""bootstrap this configuration"""
- print '** generic web configuration'
+ print '\n'+underline_title('Generic web configuration')
config = self.config
if config.repo_method == 'pyro':
- print
- print '** repository server configuration'
- print '-' * 72
+ print '\n'+underline_title('Repository server configuration')
config.input_config('pyro-client', inputlevel)
- if confirm('allow anonymous access', False):
+ if ASK.confirm('Allow anonymous access ?', False):
config.global_set_option('anonymous-user', 'anon')
config.global_set_option('anonymous-password', 'anon')
def postcreate(self):
- """hooks called once application's initialization has been completed"""
+ """hooks called once instance's initialization has been completed"""
--- a/web/widgets.py Tue Aug 04 11:43:03 2009 +0200
+++ b/web/widgets.py Tue Aug 04 15:06:09 2009 +0200
@@ -12,7 +12,7 @@
from datetime import datetime
-from logilab.mtconverter import html_escape
+from logilab.mtconverter import xml_escape
from yams.constraints import SizeConstraint, StaticVocabularyConstraint
@@ -247,9 +247,9 @@
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
if isinstance(value, basestring):
- value = html_escape(value)
+ value = xml_escape(value)
if isinstance(dvalue, basestring):
- dvalue = html_escape(dvalue)
+ dvalue = xml_escape(dvalue)
return u'%s<input type="%s" name="%s" value="%s" %s/>' % (
self.hidden_input(entity, value), self.input_type,
self.rname, dvalue, self.format_attrs())
@@ -323,9 +323,9 @@
value = self.current_value(entity)
dvalue = self.current_display_value(entity)
if isinstance(value, basestring):
- value = html_escape(value)
+ value = xml_escape(value)
if isinstance(dvalue, basestring):
- dvalue = html_escape(dvalue)
+ dvalue = xml_escape(dvalue)
iid = self.attrs.pop('id')
if self.required(entity):
cssclass = u' required'
@@ -337,7 +337,7 @@
'iid': iid,
'hidden': self.hidden_input(entity, value),
'wdgtype': self.wdgtype,
- 'url': html_escape(dataurl),
+ 'url': xml_escape(dataurl),
'tabindex': self.attrs.pop('tabindex'),
'value': dvalue,
'attrs': self.format_attrs(),
@@ -398,7 +398,7 @@
editor = self._edit_render_textarea(entity, with_format)
value = self.current_value(entity)
if isinstance(value, basestring):
- value = html_escape(value)
+ value = xml_escape(value)
return u'%s%s' % (self.hidden_input(entity, value), editor)
def _edit_render_textarea(self, entity, with_format):
@@ -406,7 +406,7 @@
self.attrs.setdefault('rows', 20)
dvalue = self.current_display_value(entity)
if isinstance(dvalue, basestring):
- dvalue = html_escape(dvalue)
+ dvalue = xml_escape(dvalue)
if entity.use_fckeditor(self.name):
entity.req.fckeditor_config()
if with_format:
@@ -418,7 +418,7 @@
hidden = u'<input type="hidden" name="edits-%s" value="%s"/>\n'\
'<input type="hidden" name="%s" value="text/html"/>\n' % (
frname, format, frname)
- return u'%s<textarea cubicweb:type="wysiwyg" onkeypress="autogrow(this)" name="%s" %s>%s</textarea>' % (
+ return u'%s<textarea cubicweb:type="wysiwyg" onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
hidden, self.rname, self.format_attrs(), dvalue)
if with_format and entity.e_schema.has_metadata(self.name, 'format'):
fmtwdg = entity.get_widget(self.name + '_format')
@@ -426,7 +426,7 @@
self.attrs['tabindex'] = entity.req.next_tabindex()
else:
fmtwdgstr = ''
- return u'%s<br/><textarea onkeypress="autogrow(this)" name="%s" %s>%s</textarea>' % (
+ return u'%s<br/><textarea onkeyup="autogrow(this)" name="%s" %s>%s</textarea>' % (
fmtwdgstr, self.rname, self.format_attrs(), dvalue)
@@ -472,9 +472,9 @@
or entity.e_schema.has_metadata(self.name, 'encoding')):
divid = '%s-%s-advanced' % (self.name, entity.eid)
wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' %
- (html_escape(toggle_action(divid)),
+ (xml_escape(toggle_action(divid)),
req._('show advanced fields'),
- html_escape(req.build_url('data/puce_down.png')),
+ xml_escape(req.build_url('data/puce_down.png')),
req._('show advanced fields')))
wdgs.append(u'<div id="%s" class="hidden">' % divid)
for extraattr in ('_format', '_encoding'):
@@ -572,7 +572,7 @@
res.append(u'<optgroup label="%s"/>' % (label or ''))
else:
value, flag = self.form_value(entity, value, dvalues)
- res.append(u'<option value="%s" %s>%s</option>' % (value, flag, html_escape(label)))
+ res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
res.append(u'</select>')
return '\n'.join(res)
@@ -658,7 +658,7 @@
res.append(u'<optgroup label="%s"/>' % (label or ''))
else:
value, flag = self.form_value(entity, value, dvalues)
- res.append(u'<option value="%s" %s>%s</option>' % (value, flag, html_escape(label)))
+ res.append(u'<option value="%s" %s>%s</option>' % (value, flag, xml_escape(label)))
res.append(u'</select>')
res.append(u'<div id="newvalue">')
res.append(u'<input type="text" id="newopt" />')
@@ -819,7 +819,7 @@
url = getattr(entity, self.name)
if not url:
return u''
- url = html_escape(url)
+ url = xml_escape(url)
return u'<a href="%s">%s</a>' % (url, url)
class EmbededURLWidget(StringWidget):
@@ -828,7 +828,7 @@
url = getattr(entity, self.name)
if not url:
return u''
- aurl = html_escape(entity.build_url('embed', url=url))
+ aurl = xml_escape(entity.build_url('embed', url=url))
return u'<a href="%s">%s</a>' % (aurl, url)
--- a/wsgi/request.py Tue Aug 04 11:43:03 2009 +0200
+++ b/wsgi/request.py Tue Aug 04 15:06:09 2009 +0200
@@ -34,7 +34,7 @@
self._headers = dict([(normalize_header(k[5:]), v) for k, v in self.environ.items()
if k.startswith('HTTP_')])
https = environ.get("HTTPS") in ('yes', 'on', '1')
- self._base_url = base_url or self.application_uri()
+ self._base_url = base_url or self.instance_uri()
post, files = self.get_posted_data()
super(CubicWebWsgiRequest, self).__init__(vreg, https, post)
if files is not None:
@@ -63,7 +63,7 @@
def relative_path(self, includeparams=True):
"""return the normalized path of the request (ie at least relative
- to the application's root, but some other normalization may be needed
+ to the instance's root, but some other normalization may be needed
so that the returned path may be used to compare to generated urls
:param includeparams:
@@ -105,10 +105,10 @@
## wsgi request helpers ###################################################
- def application_uri(self):
- """Return the application's base URI (no PATH_INFO or QUERY_STRING)
+ def instance_uri(self):
+ """Return the instance's base URI (no PATH_INFO or QUERY_STRING)
- see python2.5's wsgiref.util.application_uri code
+ see python2.5's wsgiref.util.instance_uri code
"""
environ = self.environ
url = environ['wsgi.url_scheme'] + '://'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xy.py Tue Aug 04 15:06:09 2009 +0200
@@ -0,0 +1,20 @@
+"""map standard cubicweb schema to xml vocabularies
+
+: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
+"""
+
+from yams import xy
+
+xy.register_prefix('http://purl.org/dc/elements/1.1/', 'dc')
+xy.register_prefix('http://xmlns.com/foaf/0.1/', 'foaf')
+xy.register_prefix('http://usefulinc.com/ns/doap#', 'doap')
+
+xy.add_equivalence('creation_date', 'dc:date')
+xy.add_equivalence('created_by', 'dc:creator')
+xy.add_equivalence('description', 'dc:description')
+xy.add_equivalence('CWUser', 'foaf:Person')
+xy.add_equivalence('CWUser login', 'dc:title')
+xy.add_equivalence('CWUser surname', 'foaf:Person foaf:name')