backport 3.5
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 10 Sep 2009 08:13:22 +0200
changeset 3163 edfe43ceaa35
parent 3090 8184bec7414d (current diff)
parent 3162 d6ae24439bee (diff)
child 3185 bd0126d17e83
backport 3.5
__init__.py
common/test/unittest_mail.py
cwctl.py
cwvreg.py
dbapi.py
devtools/__init__.py
devtools/devctl.py
devtools/htmlparser.py
devtools/testlib.py
entities/test/unittest_base.py
entity.py
etwist/server.py
hooks/notification.py
req.py
server/migractions.py
server/serverctl.py
server/session.py
server/test/unittest_migractions.py
server/test/unittest_querier.py
sobjects/notification.py
sobjects/supervising.py
test/unittest_entity.py
toolsutils.py
web/request.py
web/uicfg.py
web/views/basecontrollers.py
web/views/editcontroller.py
web/views/igeocodable.py
web/views/tableview.py
--- /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&amp;v=2&amp;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: