--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore Thu Sep 10 08:13:22 2009 +0200
@@ -0,0 +1,10 @@
+\.svn
+^build$
+^dist$
+\.pyc$
+\.pyo$
+\.bak$
+\.old$
+\~$
+\#.*?\#$
+\.swp$
--- a/.hgtags Wed Sep 02 16:42:07 2009 +0200
+++ b/.hgtags Thu Sep 10 08:13:22 2009 +0200
@@ -62,3 +62,5 @@
7fd294cbf6ff3cf34475cc50e972f650a34ae6e8 cubicweb-debian-version-3.4.5-1
921fdbf8b3038dc27a2ec5398a0fbcbc5b9ba4be cubicweb-version-3.4.6
52dba800ca4d4b82c47f3befb824bd91ef015368 cubicweb-debian-version-3.4.6-1
+0e549b299f0b357837ea620c561aa843f46de17a cubicweb-version-3.4.7
+ebb92e62eb040a070deb1f2d2434734cfac3af01 cubicweb-debian-version-3.4.7-1
--- a/__init__.py Wed Sep 02 16:42:07 2009 +0200
+++ b/__init__.py Thu Sep 10 08:13:22 2009 +0200
@@ -55,8 +55,7 @@
"Binary objects must use raw strings, not %s" % data.__class__
StringIO.write(self, data)
-
-# XXX 2.45 is allowing nicer entity type names, use this map for bw compat
+# use this dictionary for renaming of entity types while keeping bw compath
ETYPE_NAME_MAP = {# 3.2 migration
'ECache': 'CWCache',
'EUser': 'CWUser',
@@ -69,31 +68,19 @@
'EConstraintType': 'CWConstraintType',
'EConstraint': 'CWConstraint',
'EPermission': 'CWPermission',
- # 2.45 migration
- 'Eetype': 'CWEType',
- 'Ertype': 'CWRType',
- 'Efrdef': 'CWAttribute',
- 'Enfrdef': 'CWRelation',
- 'Econstraint': 'CWConstraint',
- 'Econstrainttype': 'CWConstraintType',
- 'Epermission': 'CWPermission',
- 'Egroup': 'CWGroup',
- 'Euser': 'CWUser',
- 'Eproperty': 'CWProperty',
- 'Emailaddress': 'EmailAddress',
- 'Rqlexpression': 'RQLExpression',
- 'Trinfo': 'TrInfo',
}
# XXX cubic web cube migration map
CW_MIGRATION_MAP = {'erudi': 'cubicweb',
-
'eaddressbook': 'addressbook',
'ebasket': 'basket',
'eblog': 'blog',
'ebook': 'book',
+ 'eclassschemes': 'keyword',
+ 'eclassfolders': 'folder',
+ 'eclasstags': 'tag',
'ecomment': 'comment',
'ecompany': 'company',
'econference': 'conference',
@@ -113,20 +100,6 @@
'ezone': 'zone',
'i18ncontent': 'i18ncontent',
'svnfile': 'vcsfile',
-
- 'eclassschemes': 'keyword',
- 'eclassfolders': 'folder',
- 'eclasstags': 'tag',
-
- 'jpl': 'jpl',
- 'jplintra': 'jplintra',
- 'jplextra': 'jplextra',
- 'jplorg': 'jplorg',
- 'jplrecia': 'jplrecia',
- 'crm': 'crm',
- 'agueol': 'agueol',
- 'docaster': 'docaster',
- 'asteretud': 'asteretud',
}
def neg_role(role):
--- a/_exceptions.py Wed Sep 02 16:42:07 2009 +0200
+++ b/_exceptions.py Thu Sep 10 08:13:22 2009 +0200
@@ -117,7 +117,7 @@
class NoSelectableObject(RegistryException):
"""some views with the given vid have been found but no
- one is applyable to the result set
+ one is applicable to the result set
"""
class UnknownProperty(RegistryException):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/cubicweb-ctl.bat Thu Sep 10 08:13:22 2009 +0200
@@ -0,0 +1,18 @@
+@echo off
+rem = """-*-Python-*- script
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x "%~f0" %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+from cubicweb.cwctl import run
+import sys
+run(sys.argv[1:])
+
+DosExitLabel = """
+:exit
+rem """
+
+
--- a/common/i18n.py Wed Sep 02 16:42:07 2009 +0200
+++ b/common/i18n.py Thu Sep 10 08:13:22 2009 +0200
@@ -9,6 +9,7 @@
import re
import os
+import sys
from os.path import join, basename, splitext, exists
from glob import glob
@@ -44,9 +45,13 @@
status != 0
"""
print cmd.replace(os.getcwd() + os.sep, '')
- status = os.system(cmd)
+ if sys.platform == 'win32':
+ from subprocess import call
+ else:
+ call = os.system
+ status = call(cmd)
if status != 0:
- raise Exception()
+ raise Exception('status = %s' % status)
def available_catalogs(i18ndir=None):
@@ -74,15 +79,15 @@
mergedpo = join(destdir, '%s_merged.po' % lang)
try:
# merge instance/cubes messages catalogs with the stdlib's one
- execute('msgcat --use-first --sort-output --strict %s > %s'
- % (' '.join(pofiles), mergedpo))
- # make sure the .mo file is writeable and compile with *msgfmt*
+ execute('msgcat --use-first --sort-output --strict -o "%s" %s'
+ % (mergedpo, ' '.join('"%s"' % f for f in pofiles)))
+ # make sure the .mo file is writeable and compiles with *msgfmt*
applmo = join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
try:
ensure_fs_mode(applmo)
except OSError:
pass # suppose not exists
- execute('msgfmt %s -o %s' % (mergedpo, applmo))
+ execute('msgfmt "%s" -o "%s"' % (mergedpo, applmo))
except Exception, ex:
errors.append('while handling language %s: %s' % (lang, ex))
try:
--- a/common/mail.py Wed Sep 02 16:42:07 2009 +0200
+++ b/common/mail.py Thu Sep 10 08:13:22 2009 +0200
@@ -21,6 +21,7 @@
return 'XXX'
from cubicweb.view import EntityView
+from cubicweb.entity import Entity
def header(ustring):
return Header(ustring.encode('UTF-8'), 'UTF-8')
@@ -176,28 +177,38 @@
else:
refs = ()
msgid = None
- userdata = self.req.user_data()
- origlang = self.req.lang
- for emailaddr, lang in recipients:
- self.req.set_language(lang)
+ req = self.req
+ self.user_data = req.user_data()
+ origlang = req.lang
+ for something in recipients:
+ if isinstance(something, Entity):
+ # hi-jack self.req to get a session for the returned user
+ self.req = self.req.hijack_user(something)
+ emailaddr = something.get_email()
+ else:
+ emailaddr, lang = something
+ self.req.set_language(lang)
# since the same view (eg self) may be called multiple time and we
# need a fresh stream at each iteration, reset it explicitly
self.w = None
# XXX call render before subject to set .row/.col attributes on the
# view
- content = self.render(row=0, col=0, **kwargs)
- subject = self.subject()
- msg = format_mail(userdata, [emailaddr], content, subject,
+ try:
+ content = self.render(row=0, col=0, **kwargs)
+ subject = self.subject()
+ except SkipEmail:
+ continue
+ msg = format_mail(self.user_data, [emailaddr], content, subject,
config=self.config, msgid=msgid, references=refs)
yield [emailaddr], msg
# restore language
- self.req.set_language(origlang)
+ req.set_language(origlang)
# recipients / email sending ###############################################
def recipients(self):
- """return a list of 2-uple (email, language) to who this email should be
- sent
+ """return a list of either 2-uple (email, language) or user entity to
+ who this email should be sent
"""
finder = self.vreg['components'].select('recipients_finder', self.req,
rset=self.rset,
@@ -230,7 +241,7 @@
subject = self.req._(self.message)
etype = entity.dc_type()
eid = entity.eid
- login = self.user_login()
+ login = self.user_data['login']
return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals()
def context(self, **kwargs):
@@ -238,17 +249,13 @@
for key, val in kwargs.iteritems():
if val and isinstance(val, unicode) and val.strip():
kwargs[key] = self.req._(val)
- kwargs.update({'user': self.user_login(),
+ kwargs.update({'user': self.user_data['login'],
'eid': entity.eid,
'etype': entity.dc_type(),
'url': entity.absolute_url(),
'title': entity.dc_long_title(),})
return kwargs
- def user_login(self):
- try:
- # if req is actually a session (we are on the server side), and we
- # have to prevent nested internal session
- return self.req.actual_session().user.login
- except AttributeError:
- return self.req.user.login
+
+class SkipEmail(Exception):
+ """raise this if you decide to skip an email during its generation"""
--- a/common/migration.py Wed Sep 02 16:42:07 2009 +0200
+++ b/common/migration.py Thu Sep 10 08:13:22 2009 +0200
@@ -338,7 +338,7 @@
configfile = self.config.main_config_file()
if self._option_changes:
read_old_config(self.config, self._option_changes, configfile)
- _, newconfig = tempfile.mkstemp()
+ fd, newconfig = tempfile.mkstemp()
for optdescr in self._option_changes:
if optdescr[0] == 'added':
optdict = self.config.get_option_def(optdescr[1])
@@ -346,6 +346,7 @@
self.config.input_option(optdescr[1], optdict)
self.config.generate_config(open(newconfig, 'w'))
show_diffs(configfile, newconfig)
+ os.close(fd)
if exists(newconfig):
os.unlink(newconfig)
--- a/common/test/unittest_mail.py Wed Sep 02 16:42:07 2009 +0200
+++ b/common/test/unittest_mail.py Thu Sep 10 08:13:22 2009 +0200
@@ -8,7 +8,7 @@
"""
import os
-import pwd
+import sys
from logilab.common.testlib import unittest_main
from logilab.common.umessage import message_from_string
@@ -22,7 +22,11 @@
(man 3 getlogin)
Another solution would be to use $LOGNAME, $USER or $USERNAME
"""
- return pwd.getpwuid(os.getuid())[0]
+ if sys.platform != 'win32':
+ import pwd
+ return pwd.getpwuid(os.getuid())[0]
+ else:
+ return os.environ.get('USERNAME')
class EmailTC(CubicWebTC):
--- a/cwconfig.py Wed Sep 02 16:42:07 2009 +0200
+++ b/cwconfig.py Thu Sep 10 08:13:22 2009 +0200
@@ -20,6 +20,7 @@
from smtplib import SMTP
from threading import Lock
from os.path import exists, join, expanduser, abspath, normpath, basename, isdir
+import tempfile
from logilab.common.decorators import cached
from logilab.common.deprecation import deprecated
@@ -526,13 +527,13 @@
if CubicWebNoAppConfiguration.mode == 'test':
root = os.environ['APYCOT_ROOT']
REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
- RUNTIME_DIR = '/tmp/'
+ RUNTIME_DIR = tempfile.gettempdir()
MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
if not exists(REGISTRY_DIR):
os.makedirs(REGISTRY_DIR)
elif CubicWebNoAppConfiguration.mode == 'dev':
REGISTRY_DIR = expanduser('~/etc/cubicweb.d/')
- RUNTIME_DIR = '/tmp/'
+ RUNTIME_DIR = tempfile.gettempdir()
MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration')
else: #mode = 'installed'
REGISTRY_DIR = '/etc/cubicweb.d/'
@@ -651,7 +652,7 @@
def default_log_file(self):
"""return default path to the log file of the instance'server"""
if self.mode == 'dev':
- basepath = '/tmp/%s-%s' % (basename(self.appid), self.name)
+ basepath = join(tempfile.gettempdir(), '%s-%s' % (basename(self.appid), self.name))
path = basepath + '.log'
i = 1
while exists(path) and i < 100: # arbitrary limit to avoid infinite loop
@@ -794,8 +795,8 @@
from glob import glob
yield 'en' # ensure 'en' is yielded even if no .mo found
for path in glob(join(self.apphome, 'i18n',
- '*', 'LC_MESSAGES', 'cubicweb.mo')):
- lang = path.split(os.sep)[-3]
+ '*', 'LC_MESSAGES')):
+ lang = path.split(os.sep)[-2]
if lang != 'en':
yield lang
--- a/cwctl.py Wed Sep 02 16:42:07 2009 +0200
+++ b/cwctl.py Thu Sep 10 08:13:22 2009 +0200
@@ -5,7 +5,13 @@
%s"""
import sys
-from os import remove, listdir, system, kill, getpgid, pathsep
+from os import remove, listdir, system, pathsep
+try:
+ from os import kill, getpgid
+except ImportError:
+ def kill(*args): pass
+ def getpgid(): pass
+
from os.path import exists, join, isfile, isdir
from logilab.common.clcommands import register_commands, pop_arg
@@ -24,7 +30,7 @@
while nbtry < maxtry:
try:
kill(pid, signal.SIGUSR1)
- except OSError:
+ except (OSError, AttributeError): # XXX win32
break
nbtry += 1
sleep(waittime)
--- a/cwvreg.py Wed Sep 02 16:42:07 2009 +0200
+++ b/cwvreg.py Thu Sep 10 08:13:22 2009 +0200
@@ -146,7 +146,12 @@
try:
objects = self[btype]
assert len(objects) == 1, objects
- cls = objects[0]
+ if btype == etype:
+ cls = objects[0]
+ else:
+ # recurse to ensure issubclass(etype_class('Child'),
+ # etype_class('Parent'))
+ cls = self.etype_class(btype)
break
except ObjectNotFound:
pass
--- a/dbapi.py Wed Sep 02 16:42:07 2009 +0200
+++ b/dbapi.py Thu Sep 10 08:13:22 2009 +0200
@@ -285,6 +285,12 @@
# server session compat layer #############################################
+ def hijack_user(self, user):
+ """return a fake request/session using specified user"""
+ req = DBAPIRequest(self.vreg)
+ req.set_connection(self.cnx, user)
+ return req
+
@property
def user(self):
if self._user is None and self.cnx:
--- a/debian/changelog Wed Sep 02 16:42:07 2009 +0200
+++ b/debian/changelog Thu Sep 10 08:13:22 2009 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.4.7-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 09 Sep 2009 14:08:41 +0200
+
cubicweb (3.4.6-1) unstable; urgency=low
* new upstream release
--- a/devtools/devctl.py Wed Sep 02 16:42:07 2009 +0200
+++ b/devtools/devctl.py Thu Sep 10 08:13:22 2009 +0200
@@ -10,7 +10,7 @@
import sys
from datetime import datetime
-from os import mkdir, chdir
+from os import mkdir, chdir, getcwd
from os.path import join, exists, abspath, basename, normpath, split, isdir
from warnings import warn
@@ -285,20 +285,20 @@
if lang is not None:
cmd += ' -L %s' % lang
potfile = join(tempdir, '%s.pot' % id)
- execute(cmd % (potfile, ' '.join(files)))
+ execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
if exists(potfile):
potfiles.append(potfile)
else:
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))
+ execute('msgcat -o %s %s' % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
print '-> merging main pot file with existing translations.'
chdir(I18NDIR)
toedit = []
for lang in LANGS:
target = '%s.po' % lang
- execute('msgmerge -N --sort-output %s %s > %snew' % (target, cubicwebpot, target))
+ execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"' % (target, target, cubicwebpot))
ensure_fs_mode(target)
shutil.move('%snew' % target, target)
toedit.append(abspath(target))
@@ -392,12 +392,13 @@
cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
cubefiles.append(tali18nfile)
execute('xgettext --no-location --omit-header -k_ -o %s %s'
- % (tmppotfile, ' '.join(cubefiles)))
+ % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
if exists(tmppotfile): # doesn't exists of no translation string found
potfiles.append(tmppotfile)
potfile = join(tempdir, 'cube.pot')
print '-> merging %i .pot files:' % len(potfiles)
- execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
+ execute('msgcat -o %s %s' % (potfile,
+ ' '.join('"%s"' % f for f in potfiles)))
print '-> merging main pot file with existing translations:'
chdir('i18n')
for lang in LANGS:
@@ -406,7 +407,7 @@
if not exists(cubepo):
shutil.copy(potfile, cubepo)
else:
- execute('msgmerge -N -s %s %s > %snew' % (cubepo, potfile, cubepo))
+ execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
ensure_fs_mode(cubepo)
shutil.move('%snew' % cubepo, cubepo)
toedit.append(abspath(cubepo))
--- a/devtools/htmlparser.py Wed Sep 02 16:42:07 2009 +0200
+++ b/devtools/htmlparser.py Thu Sep 10 08:13:22 2009 +0200
@@ -36,7 +36,11 @@
class DTDValidator(Validator):
def __init__(self):
Validator.__init__(self)
- self.parser = etree.XMLParser(dtd_validation=True)
+ # XXX understand what's happening under windows
+ validate = True
+ if sys.platform == 'win32':
+ validate = False
+ self.parser = etree.XMLParser(dtd_validation=validate)
def preprocess_data(self, data):
"""used to fix potential blockquote mess generated by docutils"""
--- a/devtools/testlib.py Wed Sep 02 16:42:07 2009 +0200
+++ b/devtools/testlib.py Thu Sep 10 08:13:22 2009 +0200
@@ -180,14 +180,16 @@
cls.admlogin = unicode(source['db-user'])
cls.admpassword = source['db-password']
# uncomment the line below if you want rql queries to be logged
- #config.global_set_option('query-log-file', '/tmp/test_rql_log.' + `os.getpid()`)
+ #config.global_set_option('query-log-file',
+ # '/tmp/test_rql_log.' + `os.getpid()`)
config.global_set_option('log-file', None)
# set default-dest-addrs to a dumb email address to avoid mailbox or
# mail queue pollution
config.global_set_option('default-dest-addrs', ['whatever'])
try:
send_to = '%s@logilab.fr' % os.getlogin()
- except OSError:
+ # AttributeError since getlogin not available under all platforms
+ except (OSError, AttributeError):
send_to = '%s@logilab.fr' % (os.environ.get('USER')
or os.environ.get('USERNAME')
or os.environ.get('LOGNAME'))
--- a/doc/book/en/B0015-define-permissions.en.txt Wed Sep 02 16:42:07 2009 +0200
+++ b/doc/book/en/B0015-define-permissions.en.txt Thu Sep 10 08:13:22 2009 +0200
@@ -114,7 +114,7 @@
require_group = SubjectRelation('EGroup', cardinality='+*',
description=_('groups to which the permission is granted'))
require_state = SubjectRelation('State',
- description=_("entity'state in which the permission is applyable"))
+ description=_("entity'state in which the permission is applicable"))
# can be used on any entity
require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
description=_("link a permission to the entity. This "
--- a/doc/book/en/admin/setup.rst Wed Sep 02 16:42:07 2009 +0200
+++ b/doc/book/en/admin/setup.rst Thu Sep 10 08:13:22 2009 +0200
@@ -77,6 +77,150 @@
In both cases, make sure you have installed the dependencies (see appendixes for
the list).
+Windows installation
+````````````````````
+
+Base elements
+_____________
+
+Setting up a windows development environment is not too complicated
+but requires a series of small steps. What is proposed there is only
+an example of what can be done. We assume everything goes into C:\ in
+this document. Adjusting the installation drive should be
+straightforward.
+
+You should start by downloading and installing the Python(x,y)
+distribution. It contains python 2.5 plus numerous useful third-party
+modules and applications::
+
+ http://www.pythonxy.com/download_fr.php
+
+At the time of this writting, one gets version 2.1.15. Among the many
+things provided, one finds Eclipse + pydev (an arguably good IDE for
+python under windows).
+
+Then you must grab Twisted. There is a windows installer directly
+available from this page::
+
+ http://twistedmatrix.com/trac/
+
+A windows installer for lxml will be found there::
+
+ http://pypi.python.org/pypi/lxml/2.2.1
+
+Check out the lxml-2.2.1-win32-py2.5.exe file. More recent bugfix
+releases should probably work, too.
+
+You should find postgresql 8.4 there::
+
+ http://www.enterprisedb.com/products/pgdownload.do#windows
+
+The python drivers for posgtresql are to be found there::
+
+ http://www.stickpeople.com/projects/python/win-psycopg/#Version2
+
+Please be careful to select the right python (2.5) and postgres (8.4)
+versions.
+
+Pyro enable remote access to cubicweb repository instances. Get it
+there::
+
+ http://sourceforge.net/projects/pyro/files/
+
+To access LDAP/Active directory directories, we need the python-ldap
+package. Windows binaries are available from::
+
+ http://www.osuch.org/python-ldap
+
+Check out the latest release.
+
+Having graphviz will allow schema drawings, which is quite recommended
+(albeit not mandatory). You should get an msi installer there::
+
+ http://www.graphviz.org/Download_windows.php
+
+Simplejson will be provided within the forest, but a win32 compiled
+version will run much faster::
+
+ http://www.osuch.org/python-simplejson%3Awin32
+
+Tools
+_____
+
+Get mercurial + its standard windows GUI (TortoiseHG) there (the
+latest is the greatest)::
+
+ http://bitbucket.org/tortoisehg/stable/wiki/download
+
+If you need to peruse mercurial over ssh, it can be helpful to get an
+ssh client like Putty::
+
+ http://www.putty.org/
+
+Integration of mercurial and Eclipse is convenient enough that we want
+it. Instructions are set there, in the `Download & Install` section::
+
+ http://www.vectrace.com/mercurialeclipse/
+
+Setting up the sources
+______________________
+
+You need to enable the mercurial forest extension. To do this, edit
+the file::
+
+ C:\Program Files\TortoiseHg\Mercurial.ini
+
+In the [extensions] section, add the following line::
+
+ forest=C:\Program Files\TortoiseHg\ext\forest\forest.py
+
+Now, you need to clone the cubicweb repository. We assume that you use
+Eclipse. From the IDE, choose File -> Import. In the box, select
+`Mercurial/Clone repository using MercurialEclipse`.
+
+In the import main panel you just have to:
+
+* fill the URL field with http://www.logilab.org/hg/forests/cubicwin32
+
+* check the 'Repository is a forest' box.
+
+Then, click on 'Finish'. It might take some time to get it all. Note
+that the `cubicwin32` forest contains additional python packages such
+as yapps, vobject, simplejson and twisted-web2 which are not provided
+with Python(x,y). This is provided for convenience, as we do not
+ensure the up-to-dateness of these packages, especially with respect
+to security fixes.
+
+Environment variables
+_____________________
+
+You will need some convenience environment variables once all is set
+up. These variables are settable through the GUI by getting at the
+'System properties' window (by righ-clicking on 'My Computer' ->
+properties).
+
+In the 'advanced' tab, there is an 'Environment variables'
+button. Click on it. That opens a small window allowing edition of
+user-related and system-wide variables.
+
+We will consider only user variables. First, the PATH variable. You
+should ensure it contains, separated by semi-colons, and assuming you
+are logged in as user Jane::
+
+ C:\Documents and Settings\Jane\My Documents\Python\cubicweb\cubicweb\bin
+ C:\Program Files\Graphviz2.24\bin
+
+The PYTHONPATH variable should also contain::
+
+ C:\Documents and Settings\Jane\My Documents\Python\cubicweb\
+
+From now, on a fresh `cmd` shell, you should be able to type::
+
+ cubicweb-ctl list
+
+... and get a meaningful output.
+
+
PostgreSQL installation
```````````````````````
@@ -135,8 +279,6 @@
Databases configuration
-----------------------
-
-
.. _ConfigurationPostgresql:
PostgreSQL configuration
--- a/doc/book/en/development/datamodel/definition.rst Wed Sep 02 16:42:07 2009 +0200
+++ b/doc/book/en/development/datamodel/definition.rst Thu Sep 10 08:13:22 2009 +0200
@@ -390,7 +390,7 @@
require_group = SubjectRelation('CWGroup', cardinality='+*',
description=_('groups to which the permission is granted'))
require_state = SubjectRelation('State',
- description=_("entity'state in which the permission is applyable"))
+ description=_("entity's state in which the permission is applicable"))
# can be used on any entity
require_permission = ObjectRelation('**', cardinality='*1', composite='subject',
description=_("link a permission to the entity. This "
--- a/entities/test/unittest_base.py Wed Sep 02 16:42:07 2009 +0200
+++ b/entities/test/unittest_base.py Thu Sep 10 08:13:22 2009 +0200
@@ -16,7 +16,6 @@
from cubicweb import ValidationError
from cubicweb.interfaces import IMileStone, IWorkflowable
from cubicweb.entities import AnyEntity
-from cubicweb.entities.authobjs import CWUser
class BaseEntityTC(CubicWebTC):
@@ -101,6 +100,7 @@
class InterfaceTC(CubicWebTC):
def test_nonregr_subclasses_and_mixins_interfaces(self):
+ CWUser = self.vreg['etypes'].etype_class('CWUser')
self.failUnless(implements(CWUser, IWorkflowable))
class MyUser(CWUser):
__implements__ = (IMileStone,)
@@ -108,7 +108,8 @@
self.vreg.register_appobject_class(MyUser)
self.vreg['etypes'].initialization_completed()
MyUser_ = self.vreg['etypes'].etype_class('CWUser')
- self.failUnless(MyUser is MyUser_)
+ self.failIf(MyUser is MyUser_.__bases__)
+ self.failUnless(MyUser in MyUser_.__bases__)
self.failUnless(implements(MyUser_, IMileStone))
self.failUnless(implements(MyUser_, IWorkflowable))
@@ -133,11 +134,12 @@
id = etype
self.vreg.register_appobject_class(Foo)
eclass = self.select_eclass('SubDivision')
+ self.failUnless(eclass.__autogenerated__)
+ self.failIf(eclass is Foo)
if etype == 'SubDivision':
- self.failUnless(eclass is Foo)
+ self.assertEquals(eclass.__bases__, (Foo,))
else:
- self.failUnless(eclass.__autogenerated__)
- self.assertEquals(eclass.__bases__, (Foo,))
+ self.assertEquals(eclass.__bases__[0].__bases__, (Foo,))
# check Division eclass is still selected for plain Division entities
eclass = self.select_eclass('Division')
self.assertEquals(eclass.id, 'Division')
--- a/entity.py Wed Sep 02 16:42:07 2009 +0200
+++ b/entity.py Thu Sep 10 08:13:22 2009 +0200
@@ -576,14 +576,16 @@
self.set_related_cache(rtype, role, rset)
return self.related(rtype, role, limit, entities)
- def related_rql(self, rtype, role='subject'):
+ def related_rql(self, rtype, role='subject', targettypes=None):
rschema = self.req.vreg.schema[rtype]
if role == 'subject':
- targettypes = rschema.objects(self.e_schema)
+ if targettypes is None:
+ targettypes = rschema.objects(self.e_schema)
restriction = 'E eid %%(x)s, E %s X' % rtype
card = greater_card(rschema, (self.e_schema,), targettypes, 0)
else:
- targettypes = rschema.subjects(self.e_schema)
+ if targettypes is None:
+ targettypes = rschema.subjects(self.e_schema)
restriction = 'E eid %%(x)s, X %s E' % rtype
card = greater_card(rschema, targettypes, (self.e_schema,), 1)
if len(targettypes) > 1:
--- a/etwist/server.py Wed Sep 02 16:42:07 2009 +0200
+++ b/etwist/server.py Thu Sep 10 08:13:22 2009 +0200
@@ -15,8 +15,13 @@
from urlparse import urlsplit, urlunsplit
import hotshot
-from twisted.application import service, strports
-from twisted.scripts._twistd_unix import daemonize
+from twisted.application import strports
+try:
+ from twisted.scripts._twistd_unix import daemonize
+except ImportError:
+ def daemonize():
+ raise NotImplementedError('not yet for win32')
+
from twisted.internet import reactor, task, threads
from twisted.internet.defer import maybeDeferred
from twisted.web2 import channel, http, server, iweb
--- a/ext/xhtml2fo.py Wed Sep 02 16:42:07 2009 +0200
+++ b/ext/xhtml2fo.py Thu Sep 10 08:13:22 2009 +0200
@@ -1,6 +1,3 @@
-from cubicweb.utils import can_do_pdf_conversion
-assert can_do_pdf_conversion()
-
from xml.etree.ElementTree import QName, fromstring
from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer
from pysixt.utils.xslfo.standard import cm
--- a/hooks/notification.py Wed Sep 02 16:42:07 2009 +0200
+++ b/hooks/notification.py Thu Sep 10 08:13:22 2009 +0200
@@ -98,6 +98,9 @@
'after_add_entity', 'before_update_entity')
def __call__(self):
+ # XXX use proper selectors
+ if self._cw.is_super_session or self._cw.repo.config.repairing:
+ return # ignore changes triggered by hooks or maintainance shell
dest = self._cw.vreg.config['supervising-addrs']
if not dest: # no supervisors, don't do this for nothing...
return
--- a/i18n/en.po Wed Sep 02 16:42:07 2009 +0200
+++ b/i18n/en.po Thu Sep 10 08:13:22 2009 +0200
@@ -608,7 +608,9 @@
msgid "You can use any of the following substitutions in your text"
msgstr ""
-msgid "You have no access to this view or it's not applyable to current data"
+msgid ""
+"You have no access to this view or it can not be used to display the current "
+"data."
msgstr ""
msgid ""
@@ -1646,12 +1648,13 @@
msgid "download"
msgstr ""
+#, python-format
+msgid "download %s"
+msgstr ""
+
msgid "download icon"
msgstr ""
-msgid "download image"
-msgstr ""
-
msgid "download schema as owl"
msgstr ""
@@ -2052,6 +2055,12 @@
msgid "invalid date"
msgstr ""
+msgid "invalid float value"
+msgstr ""
+
+msgid "invalid integer value"
+msgstr ""
+
msgid "is"
msgstr ""
--- a/i18n/es.po Wed Sep 02 16:42:07 2009 +0200
+++ b/i18n/es.po Thu Sep 10 08:13:22 2009 +0200
@@ -625,8 +625,10 @@
"Puede realizar cualquiera de las siguientes sustituciones en el contenido de "
"su email."
-msgid "You have no access to this view or it's not applyable to current data"
-msgstr "No tiene acceso a esta vista o No es aplicable a los datos actuales"
+msgid ""
+"You have no access to this view or it can not be used to display the current "
+"data."
+msgstr "No tiene acceso a esta vista o No se puede utilizare para los datos actuales."
msgid ""
"You're not authorized to access this page. If you think you should, please "
@@ -1708,12 +1710,13 @@
msgid "download"
msgstr "Descargar"
+#, python-format
+msgid "download %s"
+msgstr ""
+
msgid "download icon"
msgstr "Ãcono de descarga"
-msgid "download image"
-msgstr ""
-
msgid "download schema as owl"
msgstr "Descargar esquema en OWL"
@@ -2128,6 +2131,12 @@
msgid "invalid date"
msgstr "Esta fecha no es válida"
+msgid "invalid float value"
+msgstr ""
+
+msgid "invalid integer value"
+msgstr ""
+
msgid "is"
msgstr "es"
@@ -3183,3 +3192,7 @@
msgid "you should probably delete that property"
msgstr "deberia probablamente suprimir esta propriedad"
+
+#~ msgid ""
+#~ "You have no access to this view or it's not applyable to current data"
+#~ msgstr "No tiene acceso a esta vista o No es aplicable a los datos actuales"
--- a/i18n/fr.po Wed Sep 02 16:42:07 2009 +0200
+++ b/i18n/fr.po Thu Sep 10 08:13:22 2009 +0200
@@ -624,9 +624,10 @@
"Vous pouvez utiliser n'importe quelle substitution parmi la liste suivante "
"dans le contenu de votre courriel."
-msgid "You have no access to this view or it's not applyable to current data"
-msgstr ""
-"Vous n'avez pas accès à cette vue ou elle ne s'applique pas aux données"
+msgid ""
+"You have no access to this view or it can not be used to display the current "
+"data."
+msgstr "Vous n'avez pas accès à cette vue ou elle ne peut pas afficher ces données."
msgid ""
"You're not authorized to access this page. If you think you should, please "
@@ -1721,12 +1722,13 @@
msgid "download"
msgstr "télécharger"
+#, python-format
+msgid "download %s"
+msgstr "télécharger %s"
+
msgid "download icon"
msgstr "icône de téléchargement"
-msgid "download image"
-msgstr "image de téléchargement"
-
msgid "download schema as owl"
msgstr "télécharger le schéma OWL"
@@ -2141,6 +2143,12 @@
msgid "invalid date"
msgstr "cette date n'est pas valide"
+msgid "invalid float value"
+msgstr "nombre flottant non valide"
+
+msgid "invalid integer value"
+msgstr "nombre entier non valide"
+
msgid "is"
msgstr "de type"
@@ -3199,3 +3207,11 @@
msgid "you should probably delete that property"
msgstr "vous devriez probablement supprimer cette propriété"
+
+#~ msgid ""
+#~ "You have no access to this view or it's not applyable to current data"
+#~ msgstr ""
+#~ "Vous n'avez pas accès à cette vue ou elle ne s'applique pas aux données"
+
+#~ msgid "download image"
+#~ msgstr "image de téléchargement"
--- a/md5crypt.py Wed Sep 02 16:42:07 2009 +0200
+++ b/md5crypt.py Thu Sep 10 08:13:22 2009 +0200
@@ -1,10 +1,8 @@
#########################################################
"""
-
+XXX clarify this header
: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
"""
# md5crypt.py
#
@@ -58,8 +56,9 @@
v = v >> 6
return ret
-
def crypt(pw, salt, magic=None):
+ if isinstance(pw, unicode):
+ pw = pw.encode('utf-8')
if magic is None:
magic = MAGIC
# Take care of the magic string if present
--- a/req.py Wed Sep 02 16:42:07 2009 +0200
+++ b/req.py Thu Sep 10 08:13:22 2009 +0200
@@ -234,18 +234,9 @@
userinfo['email'] = ""
return userinfo
user = self.actual_session().user
- rql = "Any F,S,A where U eid %(x)s, U firstname F, U surname S, U primary_email E, E address A"
- try:
- firstname, lastname, email = self.execute(rql, {'x': user.eid}, 'x')[0]
- if firstname is None and lastname is None:
- userinfo['name'] = ''
- else:
- userinfo['name'] = ("%s %s" % (firstname, lastname))
- userinfo['email'] = email
- except IndexError:
- userinfo['name'] = None
- userinfo['email'] = None
userinfo['login'] = user.login
+ userinfo['name'] = user.name()
+ userinfo['email'] = user.get_email()
return userinfo
def is_internal_session(self):
--- a/server/migractions.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/migractions.py Thu Sep 10 08:13:22 2009 +0200
@@ -166,14 +166,20 @@
% (self.config.appid, backupfile)):
return
# unpack backup
- bkup = tarfile.open(backupfile, 'r|gz')
- for name in bkup.getnames():
- if name[0] in '/.':
- raise Exception('Security check failed, path starts with "/" or "."')
- bkup.close() # XXX seek error if not close+open !?!
- bkup = tarfile.open(backupfile, 'r|gz')
tmpdir = tempfile.mkdtemp()
- bkup.extractall(path=tmpdir)
+ try:
+ bkup = tarfile.open(backupfile, 'r|gz')
+ except tarfile.ReadError:
+ # assume restoring old backup
+ shutil.copy(backupfile, osp.join(tmpdir, 'system'))
+ else:
+ for name in bkup.getnames():
+ if name[0] in '/.':
+ raise Exception('Security check failed, path starts with "/" or "."')
+ bkup.close() # XXX seek error if not close+open !?!
+ bkup = tarfile.open(backupfile, 'r|gz')
+ bkup.extractall(path=tmpdir)
+ bkup.close()
self.config.open_connections_pools = False
repo = self.repo_connect()
@@ -186,7 +192,6 @@
print '-> error trying to restore [%s]' % exc
if not self.confirm('Continue anyway?', default='n'):
raise SystemExit(1)
- bkup.close()
shutil.rmtree(tmpdir)
# call hooks
repo.open_connections_pools()
--- a/server/querier.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/querier.py Thu Sep 10 08:13:22 2009 +0200
@@ -608,6 +608,8 @@
# return an empty result instead of raising UnknownEid
return empty_rset(session, rql, args)
cachekey.append(etype)
+ # ensure eid is correctly typed in args
+ args[key] = typed_eid(args[key])
cachekey = tuple(cachekey)
else:
cachekey = rql
--- a/server/serverctl.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/serverctl.py Thu Sep 10 08:13:22 2009 +0200
@@ -312,7 +312,8 @@
# postgres specific stuff
if driver == 'postgres':
# install plpythonu/plpgsql language if not installed by the cube
- for extlang in ('plpythonu', 'plpgsql'):
+ langs = ('plpgsql',) if sys.platform == 'win32' else ('plpythonu', 'plpgsql')
+ for extlang in langs:
helper.create_language(cursor, extlang)
cursor.close()
cnx.commit()
@@ -676,7 +677,8 @@
import tempfile
srcappid = pop_arg(args, 1, msg='No source instance specified !')
destappid = pop_arg(args, msg='No destination instance specified !')
- output = tempfile.mkstemp(dir='/tmp/')[1]
+ fd, output = tempfile.mkstemp()
+ os.close(fd)
if ':' in srcappid:
host, srcappid = srcappid.split(':')
_remote_dump(host, srcappid, output, self.config.sudo)
--- a/server/session.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/session.py Thu Sep 10 08:13:22 2009 +0200
@@ -75,6 +75,12 @@
return '<%ssession %s (%s 0x%x)>' % (self.cnxtype, self.user.login,
self.id, id(self))
+ def hijack_user(self, user):
+ """return a fake request/session using specified user"""
+ session = Session(user, self.repo)
+ session._threaddata = self.actual_session()._threaddata
+ return session
+
def _change_relation(self, cb, fromeid, rtype, toeid):
if self.is_super_session:
cb(self, fromeid, rtype, toeid)
@@ -112,6 +118,8 @@
self._change_relation(self.repo.glob_delete_relation,
fromeid, rtype, toeid)
+ # relations cache handling #################################################
+
def update_rel_cache_add(self, subject, rtype, object, symetric=False):
self._update_entity_rel_cache_add(subject, rtype, 'subject', object)
if symetric:
--- a/server/test/data/migratedapp/schema.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/test/data/migratedapp/schema.py Thu Sep 10 08:13:22 2009 +0200
@@ -50,9 +50,9 @@
'PE require_permission P, P name "add_note", '
'P require_group G'),)}
+ whatever = Int() # keep it before `date` for unittest_migraction.test_add_attribute_int
date = Datetime()
type = String(maxsize=1)
- whatever = Int()
mydate = Date(default='TODAY')
shortpara = String(maxsize=64)
ecrit_par = SubjectRelation('Personne', constraints=[RQLConstraint('S concerne A, O concerne A')])
--- a/server/test/unittest_extlite.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/test/unittest_extlite.py Thu Sep 10 08:13:22 2009 +0200
@@ -24,35 +24,34 @@
self._cleanup()
def test(self):
- lock = threading.Lock()
-
+ lock1 = threading.Lock()
+ lock2 = threading.Lock()
+
def run_thread():
cnx2 = get_connection('sqlite', database=self.sqlite_file)
- lock.acquire()
+ lock1.acquire()
cu = cnx2.cursor()
cu.execute('SELECT name FROM toto')
self.failIf(cu.fetchall())
cnx2.commit()
- lock.release()
- time.sleep(0.1)
- lock.acquire()
+ lock1.release()
+ lock2.acquire()
cu.execute('SELECT name FROM toto')
self.failUnless(cu.fetchall())
- lock.release()
+ lock2.release()
cnx1 = get_connection('sqlite', database=self.sqlite_file)
- lock.acquire()
+ lock1.acquire()
+ lock2.acquire()
thread = threading.Thread(target=run_thread)
thread.start()
cu = cnx1.cursor()
cu.execute('SELECT name FROM toto')
- lock.release()
- time.sleep(0.1)
+ lock1.release()
cnx1.commit()
- lock.acquire()
cu.execute("INSERT INTO toto(name) VALUES ('toto')")
cnx1.commit()
- lock.release()
+ lock2.release()
if __name__ == '__main__':
unittest_main()
--- a/server/test/unittest_migractions.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/test/unittest_migractions.py Thu Sep 10 08:13:22 2009 +0200
@@ -54,17 +54,24 @@
def test_add_attribute_int(self):
self.failIf('whatever' in self.schema)
- paraordernum = self.mh.rqlexec('Any O WHERE X name "Note", RT name "para", RDEF from_entity X, RDEF relation_type RT, RDEF ordernum O')[0][0]
+ orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, '
+ 'RDEF relation_type RT, RDEF ordernum O, RT name RTN'))
self.mh.cmd_add_attribute('Note', 'whatever')
self.failUnless('whatever' in self.schema)
self.assertEquals(self.schema['whatever'].subjects(), ('Note',))
self.assertEquals(self.schema['whatever'].objects(), ('Int',))
- paraordernum2 = self.mh.rqlexec('Any O WHERE X name "Note", RT name "para", RDEF from_entity X, RDEF relation_type RT, RDEF ordernum O')[0][0]
- self.assertEquals(paraordernum2, paraordernum+1)
+ orderdict2 = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, '
+ 'RDEF relation_type RT, RDEF ordernum O, RT name RTN'))
+ whateverorder = migrschema['whatever'].rproperty('Note', 'Int', 'order')
+ for k, v in orderdict.iteritems():
+ if v >= whateverorder:
+ orderdict[k] = v+1
+ orderdict['whatever'] = whateverorder
+ self.assertDictEquals(orderdict, orderdict2)
#self.assertEquals([r.type for r in self.schema['Note'].ordered_relations()],
# ['modification_date', 'creation_date', 'owned_by',
# 'eid', 'ecrit_par', 'inline1', 'date', 'type',
- # 'whatever', 'para', 'in_basket'])
+ # 'whatever', 'date', 'in_basket'])
# NB: commit instead of rollback make following test fail with py2.5
# this sounds like a pysqlite/2.5 bug (the same eid is affected to
# two different entities)
@@ -373,7 +380,9 @@
self.mh.cmd_remove_cube('email', removedeps=True)
# file was there because it's an email dependancy, should have been removed
self.failIf('email' in self.config.cubes())
+ self.failIf(self.config.cube_dir('email') in self.config.cubes_path())
self.failIf('file' in self.config.cubes())
+ self.failIf(self.config.cube_dir('file') in self.config.cubes_path())
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failIf(ertype in schema, ertype)
@@ -394,7 +403,9 @@
finally:
self.mh.cmd_add_cube('email')
self.failUnless('email' in self.config.cubes())
+ self.failUnless(self.config.cube_dir('email') in self.config.cubes_path())
self.failUnless('file' in self.config.cubes())
+ self.failUnless(self.config.cube_dir('file') in self.config.cubes_path())
for ertype in ('Email', 'EmailThread', 'EmailPart', 'File', 'Image',
'sender', 'in_thread', 'reply_to', 'data_format'):
self.failUnless(ertype in schema, ertype)
--- a/server/test/unittest_querier.py Wed Sep 02 16:42:07 2009 +0200
+++ b/server/test/unittest_querier.py Thu Sep 10 08:13:22 2009 +0200
@@ -213,6 +213,11 @@
# should return an empty result set
self.failIf(self.execute('Any X WHERE X eid 99999999'))
+ def test_typed_eid(self):
+ # should return an empty result set
+ rset = self.execute('Any X WHERE X eid %(x)s', {'x': '1'}, 'x')
+ self.assertIsInstance(rset[0][0], (int, long))
+
def test_bytes_storage(self):
feid = self.execute('INSERT File X: X name "foo.pdf", X data_format "text/plain", X data %(data)s',
{'data': Binary("xxx")})[0][0]
--- a/sobjects/notification.py Wed Sep 02 16:42:07 2009 +0200
+++ b/sobjects/notification.py Thu Sep 10 08:13:22 2009 +0200
@@ -115,7 +115,7 @@
def subject(self):
entity = self.rset.get_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())
+ entity.eid, self.user_data['login'])
from logilab.common.deprecation import class_renamed, class_moved, deprecated
--- a/test/unittest_entity.py Wed Sep 02 16:42:07 2009 +0200
+++ b/test/unittest_entity.py Thu Sep 10 08:13:22 2009 +0200
@@ -182,15 +182,16 @@
from cubicweb.entities import fetch_config
Personne = self.vreg['etypes'].etype_class('Personne')
Note = self.vreg['etypes'].etype_class('Note')
+ self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note))
Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type'))
Note.fetch_attrs, Note.fetch_order = fetch_config(('type',))
- aff = self.add_entity('Personne', nom=u'pouet')
- self.assertEquals(aff.related_rql('evaluee'),
+ p = self.add_entity('Personne', nom=u'pouet')
+ self.assertEquals(p.related_rql('evaluee'),
'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, '
'X type AA, X modification_date AB')
Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', ))
# XXX
- self.assertEquals(aff.related_rql('evaluee'),
+ self.assertEquals(p.related_rql('evaluee'),
'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA')
def test_entity_unrelated(self):
--- a/test/unittest_utils.py Wed Sep 02 16:42:07 2009 +0200
+++ b/test/unittest_utils.py Thu Sep 10 08:13:22 2009 +0200
@@ -17,12 +17,12 @@
self.assertNotEquals(make_uid('xyz'), make_uid('xyz'))
def test_2(self):
- d = {}
+ d = set()
while len(d)<10000:
uid = make_uid('xyz')
- if d.has_key(uid):
+ if uid in d:
self.fail(len(d))
- d[uid] = 1
+ d.add(uid)
class UStringIOTC(TestCase):
--- a/toolsutils.py Wed Sep 02 16:42:07 2009 +0200
+++ b/toolsutils.py Thu Sep 10 08:13:22 2009 +0200
@@ -10,9 +10,15 @@
# XXX move most of this in logilab.common (shellutils ?)
import os, sys
-from os import listdir, makedirs, symlink, environ, chmod, walk, remove
+from os import listdir, makedirs, environ, chmod, walk, remove
from os.path import exists, join, abspath, normpath
+try:
+ from os import symlink
+except ImportError:
+ def symlink(*args):
+ raise NotImplementedError
+
from logilab.common.clcommands import Command as BaseCommand, \
main_run as base_main_run
from logilab.common.compat import any
--- a/utils.py Wed Sep 02 16:42:07 2009 +0200
+++ b/utils.py Thu Sep 10 08:13:22 2009 +0200
@@ -7,8 +7,11 @@
"""
__docformat__ = "restructuredtext en"
+from logilab.mtconverter import xml_escape
+
import locale
from md5 import md5
+import sys
from datetime import datetime, timedelta, date
from time import time, mktime
from random import randint, seed
@@ -101,11 +104,17 @@
encoding = locale.getpreferredencoding(do_setlocale=False) or 'UTF-8'
return unicode(date.strftime(str(fmt)), encoding)
-def make_uid(key):
- """forge a unique identifier"""
- msg = str(key) + "%.10f" % time() + str(randint(0, 1000000))
- return md5(msg).hexdigest()
+if sys.version_info[:2] < (2, 5):
+ def make_uid(key):
+ """forge a unique identifier
+ not that unique on win32"""
+ msg = str(key) + "%.10f" % time() + str(randint(0, 1000000))
+ return md5(msg).hexdigest()
+else:
+ from uuid import uuid4
+ def make_uid(key):
+ return str(key) + str(uuid4())
def dump_class(cls, clsname):
"""create copy of a class by creating an empty class inheriting
@@ -262,17 +271,18 @@
# 2/ css files
for cssfile, media in self.cssfiles:
w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
+ (media, xml_escape(cssfile)))
# 3/ ie css if necessary
if self.ie_cssfiles:
w(u'<!--[if lt IE 8]>\n')
for cssfile, media in self.ie_cssfiles:
w(u'<link rel="stylesheet" type="text/css" media="%s" href="%s"/>\n' %
- (media, cssfile))
+ (media, xml_escape(cssfile)))
w(u'<![endif]--> \n')
# 4/ js files
for jsfile in self.jsfiles:
- w(u'<script type="text/javascript" src="%s"></script>\n' % jsfile)
+ w(u'<script type="text/javascript" src="%s"></script>\n' %
+ xml_escape(jsfile))
# 5/ post inlined scripts (i.e. scripts depending on other JS files)
if self.post_inlined_scripts:
w(u'<script type="text/javascript">\n')
@@ -305,7 +315,8 @@
self.htmltag = u'<html xmlns="http://www.w3.org/1999/xhtml" ' \
'xmlns:cubicweb="http://www.logilab.org/2008/cubicweb" ' \
'xml:lang="%s" lang="%s">' % (req.lang, req.lang)
-
+ # keep main_stream's reference on req for easier text/html demoting
+ req.main_stream = self
def write(self, data):
"""StringIO interface: this method will be assigned to self.w
--- a/web/data/cubicweb.ajax.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.ajax.js Thu Sep 10 08:13:22 2009 +0200
@@ -392,7 +392,9 @@
return stripped;
}
-/* convenience function that returns a DOM node based on req's result. */
+/* convenience function that returns a DOM node based on req's result.
+ * XXX clarify the need to clone
+ * */
function getDomFromResponse(response) {
if (typeof(response) == 'string') {
var doc = html2dom(response);
@@ -402,15 +404,18 @@
var children = doc.childNodes;
if (!children.length) {
// no child (error cases) => return the whole document
- return doc.cloneNode(true);
+ return jQuery(doc).clone().context;
}
children = stripEmptyTextNodes(children);
if (children.length == 1) {
// only one child => return it
- return children[0].cloneNode(true);
+ return jQuery(children[0]).clone().context;
}
// several children => wrap them in a single node and return the wrap
- return DIV(null, map(methodcaller('cloneNode', true), children));
+ return DIV(null, map(function(node) {
+ return jQuery(node).clone().context;
+ },
+ children));
}
function postJSON(url, data, callback) {
--- a/web/data/cubicweb.calendar.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.calendar.js Thu Sep 10 08:13:22 2009 +0200
@@ -112,7 +112,8 @@
this.domtable = TABLE({'class': this.cssclass},
THEAD(null, TR(null,
TH(null, A({'href' : prevlink}, "<<")),
- TH({'colspan' : 5, 'style' : "text-align: center;"}, monthname),
+ // IE 6/7 requires colSpan instead of colspan
+ TH({'colSpan': 5, 'colspan':5, 'style' : "text-align: center;"}, monthname),
TH(null, A({'href' : nextlink}, ">>")))),
TBODY(null,
this._headdisplay(),
--- a/web/data/cubicweb.calendar_popup.css Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.calendar_popup.css Thu Sep 10 08:13:22 2009 +0200
@@ -24,17 +24,16 @@
table.popupCalendar {
text-align: center;
border: 1px solid #ccc;
- z-index: 100;
+ z-index: 400;
}
-
table.popupCalendar th {
+ border:1px solid #ccc;
background : #d9d9c1;
color: black;
padding: 2px 3px;
}
-
table.popupCalendar th.calTitle,
table.popupCalendar th.prev,
table.popupCalendar th.next {
@@ -51,12 +50,12 @@
padding: 2px 0px;
}
-
table.popupCalendar td {
width: 2em;
height: 2em;
background : #f6f5e1;
font-size: 85%;
+ border:1px solid #ccc;
}
table.popupCalendar td.today {
--- a/web/data/cubicweb.compat.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.compat.js Thu Sep 10 08:13:22 2009 +0200
@@ -172,7 +172,7 @@
}
node[key] = value;
} else { // normal node attribute
- node.setAttribute(key, params[key]);
+ jQuery(node).attr(key, params[key]);
}
}
if (children) {
--- a/web/data/cubicweb.edition.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.edition.js Thu Sep 10 08:13:22 2009 +0200
@@ -60,7 +60,7 @@
if (!divNode.length) {
var args = {vid: 'unrelateddivs', relation: selectedValue,
rql: rql_for_eid(eid), '__notemplate': 1,
- callback: function() {_showMatchingSelect(eid, jQuery('#' + divId))}};
+ callback: function() {_showMatchingSelect(eid, jQuery('#' + divId));}};
jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append');
} else {
_showMatchingSelect(eid, divNode);
@@ -255,6 +255,11 @@
updateInlinedEntitiesCounters(rtype);
reorderTabindex();
form.trigger('inlinedform-added');
+ // if the inlined form contains a file input, we must force
+ // the form enctype to multipart/form-data
+ if (form.find('input:file').length) {
+ form.closest('form').attr('enctype', 'multipart/form-data');
+ }
postAjaxLoad(dom);
});
d.addErrback(function (xxx) {
@@ -353,7 +358,7 @@
} else {
document.location.href = result[1];
}
- return;
+ return true;
}
unfreezeFormButtons(formid);
// Failures
@@ -363,15 +368,15 @@
if ( !isArrayLike(descr) || descr.length != 2 ) {
log('got strange error :', descr);
updateMessage(descr);
- return;
+ return false;
}
_displayValidationerrors(formid, descr[0], descr[1]);
- updateMessage(_("please correct errors below"));
+ updateMessage(_('please correct errors below'));
document.location.hash = '#header';
- if (onfailure){
+ if (onfailure) {
onfailure(formid);
}
- return;
+ return false;
}
@@ -487,8 +492,6 @@
// switch inline form off only if no error
if (result[0]) {
// hide global error messages
- jQuery('div.errorMessage').remove();
- jQuery('#appMsg').hide();
hideInlineEdit(eid, rtype, divid);
}
}
@@ -506,15 +509,13 @@
var zipped = formContents(form);
var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]);
} catch (ex) {
- log('got exception', ex);
return false;
}
d.addCallback(function (result, req) {
- handleFormValidationResponse(divid+'-form', noop, noop, result);
- if (reload) {
- document.location.href = result[1];
- } else {
- if (result[0]) {
+ if (handleFormValidationResponse(divid+'-form', noop, noop, result)) {
+ if (reload) {
+ document.location.href = result[1].split('?')[0];
+ } else {
var d = asyncRemoteExec('reledit_form', eid, rtype, role, default_value, lzone);
d.addCallback(function (result) {
// XXX brittle ... replace with loadxhtml
@@ -535,6 +536,8 @@
}
function hideInlineEdit(eid, rtype, divid) {
+ jQuery('#appMsg').hide();
+ jQuery('div.errorMessage').remove();
jQuery('#' + divid).show();
jQuery('#' + divid+'-form').hide();
}
--- a/web/data/cubicweb.facets.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.facets.js Thu Sep 10 08:13:22 2009 +0200
@@ -66,6 +66,7 @@
extraparams['divid'] = divid;
copyParam(zipped, extraparams, 'divid');
copyParam(zipped, extraparams, 'subvid');
+ copyParam(zipped, extraparams, 'fromformfilter');
// paginate used to know if the filter box is acting, in which case we
// want to reload action box to match current selection (we don't want
// this from a table filter)
@@ -130,9 +131,10 @@
});
facet.find('div.facetCheckBox').click(function () {
var $this = jQuery(this);
- if ($this.hasClass('facetValueDisabled')){
- return
- }
+ // NOTE : add test on the facet operator (i.e. OR, AND)
+ // if ($this.hasClass('facetValueDisabled')){
+ // return
+ // }
if ($this.hasClass('facetValueSelected')) {
$this.removeClass('facetValueSelected');
$this.find('img').each(function (i){
--- a/web/data/cubicweb.python.js Wed Sep 02 16:42:07 2009 +0200
+++ b/web/data/cubicweb.python.js Thu Sep 10 08:13:22 2009 +0200
@@ -83,10 +83,10 @@
var skip0 = new RegExp('^0*[0-9]+');
var parsed = {};
for (var i1=0,i2=0;i1<format.length;i1++,i2++) {
- var c1 = format[i1];
- var c2 = datestring[i2];
+ var c1 = format.charAt(i1);
+ var c2 = datestring.charAt(i2);
if (c1 == '%') {
- c1 = format[++i1];
+ c1 = format.charAt(++i1);
var data = _DATE_FORMAT_REGXES[c1].exec(datestring.substring(i2));
if (!data.length) {
return null;
--- a/web/formwidgets.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/formwidgets.py Thu Sep 10 08:13:22 2009 +0200
@@ -430,8 +430,9 @@
# XXX entity form specific
entity = form.edited_entity
attrs['cubicweb:etype_to'] = entity.e_schema
- etype_from = entity.e_schema.subject_relation(self.name).objects(entity.e_schema)[0]
+ etype_from = entity.e_schema.subject_relation(field.name).objects(entity.e_schema)[0]
attrs['cubicweb:etype_from'] = etype_from
+ return name, values, attrs
def render(self, form, field, renderer):
return super(AddComboBoxWidget, self).render(form, field, renderer) + u'''
--- a/web/request.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/request.py Thu Sep 10 08:13:22 2009 +0200
@@ -27,7 +27,7 @@
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.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
RequestError, StatusResponse)
@@ -689,6 +689,16 @@
"""
raise NotImplementedError()
+ def demote_to_html(self):
+ """helper method to dynamically set request content type to text/html
+
+ The global doctype and xmldec must also be changed otherwise the browser
+ will display '<[' at the beginning of the page
+ """
+ self.set_content_type('text/html')
+ self.main_stream.doctype = TRANSITIONAL_DOCTYPE_NOEXT
+ self.main_stream.xmldecl = u''
+
# page data management ####################################################
def get_page_data(self, key, default=None):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_uicfg.py Thu Sep 10 08:13:22 2009 +0200
@@ -0,0 +1,14 @@
+from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.web import uicfg
+
+class UICFGTC(EnvBasedTC):
+
+ def test_autoform_section_inlined(self):
+ self.assertEquals(uicfg.autoform_is_inlined.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'),
+ True)
+ self.assertEquals(uicfg.autoform_section.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'),
+ 'generated')
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
--- a/web/uicfg.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/uicfg.py Thu Sep 10 08:13:22 2009 +0200
@@ -179,23 +179,28 @@
def init_autoform_section(rtag, sschema, rschema, oschema, role):
if rtag.get(sschema, rschema, oschema, role) is None:
- if role == 'subject':
- card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
- composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
- else:
- card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
- composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
- if sschema.is_metadata(rschema):
+ if autoform_is_inlined.get(sschema, rschema, oschema, role):
+ section = 'generated'
+ elif sschema.is_metadata(rschema):
section = 'metadata'
- elif card in '1+':
- if not rschema.is_final() and composed:
- section = 'generated'
+ else:
+ if role == 'subject':
+ card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
+ composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
else:
- section = 'primary'
- elif rschema.is_final():
- section = 'secondary'
- else:
- section = 'generic'
+ card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
+ composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
+ if card in '1+':
+ if not rschema.is_final() and composed:
+ # XXX why? probably because we want it unlined, though this
+ # is not the case by default
+ section = 'generated'
+ else:
+ section = 'primary'
+ elif rschema.is_final():
+ section = 'secondary'
+ else:
+ section = 'generic'
rtag.tag_relation((sschema, rschema, oschema, role), section)
autoform_section = RelationTags('autoform_section', init_autoform_section,
--- a/web/views/__init__.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/__init__.py Thu Sep 10 08:13:22 2009 +0200
@@ -8,8 +8,8 @@
__docformat__ = "restructuredtext en"
import os
+import sys
import tempfile
-
from rql import nodes
@@ -110,13 +110,9 @@
self.cell_call()
def cell_call(self, row=0, col=0):
- self.row, self.col = row, col # in case one need it
- _, tmpfile = tempfile.mkstemp('.png')
- try:
- self._generate(tmpfile)
- self.w(open(tmpfile).read())
- finally:
- try:
- os.unlink(tmpfile)
- except Exception, ex:
- self.warning('cant delete %s: %s', tmpfile, ex)
+ self.row, self.col = row, col # in case one needs it
+ fd, tmpfile = tempfile.mkstemp('.png')
+ os.close(fd)
+ self._generate(tmpfile)
+ self.w(open(tmpfile, 'rb').read())
+ os.unlink(tmpfile)
--- a/web/views/autoform.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/autoform.py Thu Sep 10 08:13:22 2009 +0200
@@ -348,7 +348,6 @@
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'),
--- a/web/views/basecontrollers.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/basecontrollers.py Thu Sep 10 08:13:22 2009 +0200
@@ -129,7 +129,8 @@
if rset:
req.set_message(req._("The view %s can not be applied to this query") % vid)
else:
- req.set_message(req._("You have no access to this view or it's not applyable to current data"))
+ req.set_message(req._("You have no access to this view or it can not "
+ "be used to display the current data."))
self.warning("the view %s can not be applied to this query", vid)
vid = vid_from_rset(req, rset, self.schema)
view = self.vreg['views'].select(vid, req, rset=rset)
--- a/web/views/editcontroller.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/editcontroller.py Thu Sep 10 08:13:22 2009 +0200
@@ -191,7 +191,22 @@
return
attrtype = rschema.objects(entity.e_schema)[0].type
# on checkbox or selection, the field may not be in params
- if attrtype == 'Boolean':
+ # NOTE: raising ValidationError here is not a good solution because
+ # we can't gather all errors at once. Hopefully, the new 3.6.x
+ # form handling will fix that
+ if value and attrtype == 'Int':
+ try:
+ value = int(value)
+ except ValueError:
+ raise ValidationError(entity.eid,
+ {attr: self.req._("invalid integer value")})
+ elif value and attrtype == 'Float':
+ try:
+ value = float(value)
+ except ValueError:
+ raise ValidationError(entity.eid,
+ {attr: self.req._("invalid float value")})
+ elif attrtype == 'Boolean':
value = bool(value)
elif attrtype == 'Decimal':
value = Decimal(value)
--- a/web/views/igeocodable.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/igeocodable.py Thu Sep 10 08:13:22 2009 +0200
@@ -74,6 +74,7 @@
need_navigation = False
def call(self, gmap_key, width=400, height=400, uselabel=True, urlparams=None):
+ self.req.demote_to_html()
# remove entities that don't define latitude and longitude
self.rset = self.rset.filtered_rset(lambda e: e.latitude and e.longitude)
self.req.add_js('http://maps.google.com/maps?sensor=false&file=api&v=2&key=%s' % gmap_key,
--- a/web/views/plots.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/plots.py Thu Sep 10 08:13:22 2009 +0200
@@ -103,7 +103,7 @@
def _render(self, req, width=500, height=400):
# XXX IE requires excanvas.js
- req.add_js( ('jquery.flot.js', 'cubicweb.flot.js') )
+ req.add_js( ('jquery.flot.js', 'cubicweb.flot.js', 'excanvas.js') )
figid = u'figure%s' % make_uid('foo')
plotdefs = []
plotdata = []
--- a/web/views/tableview.py Wed Sep 02 16:42:07 2009 +0200
+++ b/web/views/tableview.py Thu Sep 10 08:13:22 2009 +0200
@@ -51,6 +51,7 @@
"""display a form to filter table's content. This should only
occurs when a context eid is given
"""
+ self.req.add_css('cubicweb.facets.css')
self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.facets.js'))
# drop False / None values from vidargs
vidargs = dict((k, v) for k, v in vidargs.iteritems() if v)
@@ -58,7 +59,9 @@
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)
+ self.w(u'<input type="hidden" name="fromformfilter" value="1" />')
+ filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets),
+ baserql=baserql)
self.w(u'<table class="filter">\n')
self.w(u'<tr>\n')
for wdg in fwidgets:
@@ -138,7 +141,6 @@
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: