backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 07 Jun 2010 13:22:24 +0200
changeset 5679 0f2ded880d01
parent 5676 aa04ccb8dd62 (current diff)
parent 5678 0a8bb754fe51 (diff)
child 5680 3a46fd84acc4
backport stable
etwist/server.py
hooks/security.py
i18n/fr.po
rset.py
server/migractions.py
server/test/unittest_querier.py
test/unittest_rset.py
view.py
web/data/cubicweb.edition.js
web/data/cubicweb.facets.js
web/formfields.py
web/views/autoform.py
web/views/editforms.py
web/views/tableview.py
--- a/.hgtags	Mon Jun 07 12:39:15 2010 +0200
+++ b/.hgtags	Mon Jun 07 13:22:24 2010 +0200
@@ -127,3 +127,5 @@
 eb972d125eefd0de2d0743e95c6e1f4e3e93e4c1 cubicweb-debian-version-3.8.1-1
 ef2e37d34013488a2018e73338fbbfbde5901c5c cubicweb-version-3.8.2
 2b962bb9eee8ee7156a12cf137428c292f8e3b35 cubicweb-debian-version-3.8.2-1
+7e6c6a2a272d0a95fd42248f3125e45185f0eef1 cubicweb-version-3.8.3
+1ccaa924786047be66b44f6dbc76e6631f56b04a cubicweb-debian-version-3.8.3-1
--- a/debian/changelog	Mon Jun 07 12:39:15 2010 +0200
+++ b/debian/changelog	Mon Jun 07 13:22:24 2010 +0200
@@ -1,3 +1,9 @@
+cubicweb (3.8.3-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Mon, 07 Jun 2010 09:19:50 +0200
+
 cubicweb (3.8.2-1) unstable; urgency=low
 
   * new upstream release
@@ -56,7 +62,7 @@
 
  -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 16 Mar 2010 17:55:37 +0100
 
- cubicweb (3.6.3-1) unstable; urgency=low
+cubicweb (3.6.3-1) unstable; urgency=low
 
   * remove postgresql-contrib from cubicweb dependency (using tsearch
     which is included with postgres >= 8.3)
--- a/etwist/server.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/etwist/server.py	Mon Jun 07 13:22:24 2010 +0200
@@ -42,7 +42,6 @@
 from cubicweb.web import dumps
 
 from logilab.common.decorators import monkeypatch
-from logilab.common.daemon import daemonize
 
 from cubicweb import AuthenticationError, ConfigurationError, CW_EVENT_MANAGER
 from cubicweb.web import Redirect, DirectResponse, StatusResponse, LogOut
@@ -398,12 +397,11 @@
     # serve it via standard HTTP on port set in the configuration
     port = config['port'] or 8080
     reactor.listenTCP(port, website)
-    logger = getLogger('cubicweb.twisted')
     if not config.debugmode:
         if sys.platform == 'win32':
             raise ConfigurationError("Under windows, you must use the service management "
                                      "commands (e.g : 'net start my_instance)'")
-        logger.info('instance started in the background on %s', root_resource.base_url)
+        LOGGER.info('instance started in the background on %s', root_resource.base_url)
         if daemonize(config['pid-file']):
             return # child process
     root_resource.init_publisher() # before changing uid
@@ -415,7 +413,7 @@
             uid = getpwnam(config['uid']).pw_uid
         os.setuid(uid)
     root_resource.start_service()
-    logger.info('instance started on %s', root_resource.base_url)
+    LOGGER.info('instance started on %s', root_resource.base_url)
     # avoid annoying warnign if not in Main Thread
     signals = threading.currentThread().getName() == 'MainThread'
     if config['profile']:
--- a/hooks/security.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/hooks/security.py	Mon Jun 07 13:22:24 2010 +0200
@@ -25,7 +25,7 @@
 from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
 
 
-def check_entity_attributes(session, entity, editedattrs=None):
+def check_entity_attributes(session, entity, editedattrs=None, creation=False):
     eid = entity.eid
     eschema = entity.e_schema
     # ._cw_skip_security_attributes is there to bypass security for attributes
@@ -42,6 +42,8 @@
         rdef = eschema.rdef(attr)
         if rdef.final: # non final relation are checked by other hooks
             # add/delete should be equivalent (XXX: unify them into 'update' ?)
+            if creation and not rdef.permissions.get('update'):
+                continue
             rdef.check_perm(session, 'update', eid=eid)
     # don't update dontcheck until everything went fine: see usage in
     # after_update_entity, where if we got an Unauthorized at hook time, we will
@@ -58,6 +60,7 @@
             action = values[1]
             entity.cw_check_perm(action)
             check_entity_attributes(session, entity, values[2:])
+                                    creation=self.creation)
 
     def commit_event(self):
         pass
@@ -94,7 +97,7 @@
     def __call__(self):
         hook.set_operation(self._cw, 'check_entity_perm_op',
                            (self.entity.eid, 'add') + tuple(self.entity.edited_attributes),
-                           _CheckEntityPermissionOp)
+                           _CheckEntityPermissionOp, creation=True)
 
 
 class AfterUpdateEntitySecurityHook(SecurityHook):
@@ -113,7 +116,7 @@
             # overwritten
             hook.set_operation(self._cw, 'check_entity_perm_op',
                                (self.entity.eid, 'update') + tuple(self.entity.edited_attributes),
-                               _CheckEntityPermissionOp)
+                               _CheckEntityPermissionOp, creation=False)
 
 
 class BeforeDelEntitySecurityHook(SecurityHook):
--- a/i18n/en.po	Mon Jun 07 12:39:15 2010 +0200
+++ b/i18n/en.po	Mon Jun 07 13:22:24 2010 +0200
@@ -31,7 +31,7 @@
 msgstr ""
 
 msgid " :"
-msgstr ""
+msgstr ":"
 
 #, python-format
 msgid "%(attr)s set to %(newvalue)s"
@@ -172,14 +172,15 @@
 msgstr "1 0..1"
 
 #, python-format
+msgid "<%s not specified>"
+msgstr ""
+
+#, python-format
 msgid ""
 "<div>This schema of the data model <em>excludes</em> the meta-data, but you "
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
 msgstr ""
 
-msgid "<no value>"
-msgstr ""
-
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -2599,6 +2600,9 @@
 msgid "log in"
 msgstr ""
 
+msgid "log out first"
+msgstr ""
+
 msgid "login"
 msgstr ""
 
@@ -3947,6 +3951,3 @@
 
 msgid "you should probably delete that property"
 msgstr ""
-
-#~ msgid "schema-image"
-#~ msgstr "image"
--- a/i18n/es.po	Mon Jun 07 12:39:15 2010 +0200
+++ b/i18n/es.po	Mon Jun 07 13:22:24 2010 +0200
@@ -177,6 +177,10 @@
 msgstr "1 0..1"
 
 #, python-format
+msgid "<%s not specified>"
+msgstr ""
+
+#, python-format
 msgid ""
 "<div>This schema of the data model <em>excludes</em> the meta-data, but you "
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
@@ -185,9 +189,6 @@
 "pero se puede ver a un <a href=\"%s\">modelo completo con meta-datos</a>.</"
 "div>"
 
-msgid "<no value>"
-msgstr ""
-
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -2667,6 +2668,9 @@
 msgid "log in"
 msgstr "Identificarse"
 
+msgid "log out first"
+msgstr ""
+
 msgid "login"
 msgstr "Clave de acesso"
 
--- a/i18n/fr.po	Mon Jun 07 12:39:15 2010 +0200
+++ b/i18n/fr.po	Mon Jun 07 13:22:24 2010 +0200
@@ -177,6 +177,10 @@
 msgstr "1 0..1"
 
 #, python-format
+msgid "<%s not specified>"
+msgstr "<%s non spécifié>"
+
+#, python-format
 msgid ""
 "<div>This schema of the data model <em>excludes</em> the meta-data, but you "
 "can also display a <a href=\"%s\">complete schema with meta-data</a>.</div>"
@@ -184,9 +188,6 @@
 "<div>Ce schéma du modèle de données <em>exclue</em> les méta-données, mais "
 "vous pouvez afficher un <a href=\"%s\">schéma complet</a>.</div>"
 
-msgid "<no value>"
-msgstr "<non spécifié>"
-
 msgid "?*"
 msgstr "0..1 0..n"
 
@@ -2705,6 +2706,9 @@
 msgid "log in"
 msgstr "s'identifier"
 
+msgid "log out first"
+msgstr "déconnecter vous d'abord"
+
 msgid "login"
 msgstr "identifiant"
 
@@ -4081,6 +4085,3 @@
 
 msgid "you should probably delete that property"
 msgstr "vous devriez probablement supprimer cette propriété"
-
-#~ msgid "schema-image"
-#~ msgstr "image"
--- a/rset.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/rset.py	Mon Jun 07 13:22:24 2010 +0200
@@ -15,9 +15,8 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""The `ResultSet` class which is returned as result of an rql query
+"""The `ResultSet` class which is returned as result of an rql query"""
 
-"""
 __docformat__ = "restructuredtext en"
 
 from logilab.common.decorators import cached, clear_cache, copy_cache
@@ -600,7 +599,11 @@
         if rel is not None:
             index = rel.children[0].root_selection_index()
             if index is not None and self.rows[row][index]:
-                return self.get_entity(row, index), rel.r_type
+                try:
+                    entity = self.get_entity(row, index)
+                    return entity, rel.r_type
+                except NotAnEntity, exc:
+                    return None, None
         return None, None
 
     @cached
--- a/server/migractions.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/server/migractions.py	Mon Jun 07 13:22:24 2010 +0200
@@ -416,7 +416,8 @@
                                   'vars': expression.mainvars, 'x': teid},
                                  ask_confirm=False)
 
-    def _synchronize_rschema(self, rtype, syncrdefs=True, syncperms=True, syncprops=True):
+    def _synchronize_rschema(self, rtype, syncrdefs=True,
+                             syncperms=True, syncprops=True):
         """synchronize properties of the persistent relation schema against its
         current definition:
 
@@ -448,7 +449,8 @@
                                               syncprops=syncprops,
                                               syncperms=syncperms)
 
-    def _synchronize_eschema(self, etype, syncperms=True):
+    def _synchronize_eschema(self, etype, syncrdefs=True,
+                             syncperms=True, syncprops=True):
         """synchronize properties of the persistent entity schema against
         its current definition:
 
@@ -465,40 +467,43 @@
         try:
             eschema = self.fs_schema.eschema(etype)
         except KeyError:
-            return
-        repospschema = repoeschema.specializes()
-        espschema = eschema.specializes()
-        if repospschema and not espschema:
-            self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
-                         {'x': str(repoeschema)}, ask_confirm=False)
-        elif not repospschema and espschema:
-            self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
-                         'Y is CWEType, Y name %(y)s',
-                         {'x': str(repoeschema), 'y': str(espschema)},
-                         ask_confirm=False)
-        self.rqlexecall(ss.updateeschema2rql(eschema, repoeschema.eid),
-                        ask_confirm=self.verbosity >= 2)
-        for rschema, targettypes, role in eschema.relation_definitions(True):
-            if rschema in VIRTUAL_RTYPES:
-                continue
-            if role == 'subject':
-                if not rschema in repoeschema.subject_relations():
-                    continue
-                subjtypes, objtypes = [etype], targettypes
-            else: # role == 'object'
-                if not rschema in repoeschema.object_relations():
-                    continue
-                subjtypes, objtypes = targettypes, [etype]
-            self._synchronize_rschema(rschema, syncperms=syncperms,
-                                      syncrdefs=False)
-            reporschema = self.repo.schema.rschema(rschema)
-            for subj in subjtypes:
-                for obj in objtypes:
-                    if (subj, obj) not in reporschema.rdefs:
-                        continue
-                    self._synchronize_rdef_schema(subj, rschema, obj)
+            return # XXX somewhat unexpected, no?...
+        if syncprops:
+            repospschema = repoeschema.specializes()
+            espschema = eschema.specializes()
+            if repospschema and not espschema:
+                self.rqlexec('DELETE X specializes Y WHERE X is CWEType, X name %(x)s',
+                             {'x': str(repoeschema)}, ask_confirm=False)
+            elif not repospschema and espschema:
+                self.rqlexec('SET X specializes Y WHERE X is CWEType, X name %(x)s, '
+                             'Y is CWEType, Y name %(y)s',
+                             {'x': str(repoeschema), 'y': str(espschema)},
+                             ask_confirm=False)
+            self.rqlexecall(ss.updateeschema2rql(eschema, repoeschema.eid),
+                            ask_confirm=self.verbosity >= 2)
         if syncperms:
             self._synchronize_permissions(eschema, repoeschema.eid)
+        if syncrdefs:
+            for rschema, targettypes, role in eschema.relation_definitions(True):
+                if rschema in VIRTUAL_RTYPES:
+                    continue
+                if role == 'subject':
+                    if not rschema in repoeschema.subject_relations():
+                        continue
+                    subjtypes, objtypes = [etype], targettypes
+                else: # role == 'object'
+                    if not rschema in repoeschema.object_relations():
+                        continue
+                    subjtypes, objtypes = targettypes, [etype]
+                self._synchronize_rschema(rschema, syncrdefs=False,
+                                          syncprops=syncprops, syncperms=syncperms)
+                reporschema = self.repo.schema.rschema(rschema)
+                for subj in subjtypes:
+                    for obj in objtypes:
+                        if (subj, obj) not in reporschema.rdefs:
+                            continue
+                        self._synchronize_rdef_schema(subj, rschema, obj,
+                                                      syncprops=syncprops, syncperms=syncperms)
 
     def _synchronize_rdef_schema(self, subjtype, rtype, objtype,
                                  syncperms=True, syncprops=True):
@@ -991,32 +996,23 @@
         if ertype is not None:
             if isinstance(ertype, (tuple, list)):
                 assert len(ertype) == 3, 'not a relation definition'
-                assert syncprops, 'can\'t update permission for a relation definition'
                 self._synchronize_rdef_schema(ertype[0], ertype[1], ertype[2],
                                               syncperms=syncperms,
                                               syncprops=syncprops)
             else:
                 erschema = self.repo.schema[ertype]
                 if isinstance(erschema, CubicWebRelationSchema):
-                    self._synchronize_rschema(erschema, syncperms=syncperms,
-                                              syncprops=syncprops,
-                                              syncrdefs=syncrdefs)
-                elif syncprops:
-                    self._synchronize_eschema(erschema, syncperms=syncperms)
+                    self._synchronize_rschema(erschema, syncrdefs=syncrdefs,
+                                              syncperms=syncperms,
+                                              syncprops=syncprops)
                 else:
-                    self._synchronize_permissions(self.fs_schema[ertype], erschema.eid)
+                    self._synchronize_eschema(erschema, syncrdefs=syncrdefs,
+                                              syncperms=syncperms,
+                                              syncprops=syncprops)
         else:
             for etype in self.repo.schema.entities():
-                if syncprops:
-                    self._synchronize_eschema(etype, syncperms=syncperms)
-                else:
-                    try:
-                        fseschema = self.fs_schema[etype]
-                    except KeyError:
-                        # entity type in the repository schema but not anymore
-                        # on the fs schema
-                        continue
-                    self._synchronize_permissions(fseschema, etype.eid)
+                self._synchronize_eschema(etype, syncrdefs=syncrdefs,
+                                          syncprops=syncprops, syncperms=syncperms)
         if commit:
             self.commit()
 
--- a/server/querier.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/server/querier.py	Mon Jun 07 13:22:24 2010 +0200
@@ -419,7 +419,7 @@
         # list of new or updated entities definition (utils.Entity)
         self.e_defs = [[]]
         # list of new relation definition (3-uple (from_eid, r_type, to_eid)
-        self.r_defs = []
+        self.r_defs = set()
         # indexes to track entity definitions bound to relation definitions
         self._r_subj_index = {}
         self._r_obj_index = {}
@@ -432,7 +432,7 @@
 
     def add_relation_def(self, rdef):
         """add an relation definition to build"""
-        self.r_defs.append(rdef)
+        self.r_defs.add(rdef)
         if not isinstance(rdef[0], int):
             self._r_subj_index.setdefault(rdef[0], []).append(rdef)
         if not isinstance(rdef[2], int):
@@ -458,9 +458,9 @@
         for i, row in enumerate(self.e_defs[:]):
             self.e_defs[i][colidx] = edefs[0]
             samplerow = self.e_defs[i]
-            for edef in edefs[1:]:
+            for edef_ in edefs[1:]:
                 row = samplerow[:]
-                row[colidx] = edef
+                row[colidx] = edef_
                 self.e_defs.append(row)
         # now, see if this entity def is referenced as subject in some relation
         # definition
@@ -469,8 +469,8 @@
                 expanded = self._expanded(rdef)
                 result = []
                 for exp_rdef in expanded:
-                    for edef in edefs:
-                        result.append( (edef, exp_rdef[1], exp_rdef[2]) )
+                    for edef_ in edefs:
+                        result.append( (edef_, exp_rdef[1], exp_rdef[2]) )
                 self._expanded_r_defs[rdef] = result
         # and finally, see if this entity def is referenced as object in some
         # relation definition
@@ -479,8 +479,8 @@
                 expanded = self._expanded(rdef)
                 result = []
                 for exp_rdef in expanded:
-                    for edef in edefs:
-                        result.append( (exp_rdef[0], exp_rdef[1], edef) )
+                    for edef_ in edefs:
+                        result.append( (exp_rdef[0], exp_rdef[1], edef_) )
                 self._expanded_r_defs[rdef] = result
 
     def _expanded(self, rdef):
--- a/server/sources/rql2sql.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/server/sources/rql2sql.py	Mon Jun 07 13:22:24 2010 +0200
@@ -556,8 +556,8 @@
                                   if not isinstance(vref, Constant))
             if having:
                 # filter out constants as for GROUP BY
-                having = ','.join(vref.accept(self) for vref in having
-                                  if not isinstance(vref, Constant))
+                having = ' AND '.join(term.accept(self) for term in having
+                                      if not isinstance(term, Constant))
             if needwrap:
                 sql = '%s FROM (%s) AS T1' % (self._selection_sql(outerselection, distinct,
                                                                   needalias),
--- a/server/test/unittest_querier.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/server/test/unittest_querier.py	Mon Jun 07 13:22:24 2010 +0200
@@ -915,12 +915,33 @@
         self.assert_(rset.rows)
         self.assertEquals(rset.description, [('Personne', 'Societe',)])
 
+    def test_insert_7_2(self):
+        self.execute("INSERT Personne X, Societe Y: X nom N, Y nom 'toto', X travaille Y WHERE U login N")
+        rset = self.execute('Any X, Y WHERE Y nom "toto", X travaille Y')
+        self.assertEquals(len(rset), 2)
+        self.assertEquals(rset.description, [('Personne', 'Societe',),
+                                             ('Personne', 'Societe',)])
+
     def test_insert_8(self):
         self.execute("INSERT Societe Y, Personne X: Y nom N, X nom 'toto', X travaille Y WHERE U login 'admin', U login N")
         rset = self.execute('Any X, Y WHERE X nom "toto", Y nom "admin", X travaille Y')
         self.assert_(rset.rows)
         self.assertEquals(rset.description, [('Personne', 'Societe',)])
 
+    def test_insert_9(self):
+        self.execute("INSERT Societe X: X nom  'Lo'")
+        self.execute("INSERT Societe X: X nom  'Gi'")
+        self.execute("INSERT SubDivision X: X nom  'Lab'")
+        rset = self.execute("INSERT Personne X: X nom N, X travaille Y, X travaille_subdivision Z WHERE Y is Societe, Z is SubDivision, Y nom N")
+        self.assertEquals(len(rset), 2)
+        self.assertEquals(rset.description, [('Personne',), ('Personne',)])
+        # self.assertSetEquals(set(x.nom for x in rset.entities()),
+        #                      ['Lo', 'Gi'])
+        # self.assertSetEquals(set(y.nom for x in rset.entities() for y in x.travaille),
+        #                      ['Lo', 'Gi'])
+        # self.assertEquals([y.nom for x in rset.entities() for y in x.travaille_subdivision],
+        #                      ['Lab', 'Lab'])
+
     def test_insert_query_error(self):
         self.assertRaises(Exception,
                           self.execute,
--- a/test/unittest_rset.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/test/unittest_rset.py	Mon Jun 07 13:22:24 2010 +0200
@@ -397,5 +397,9 @@
                           '(Any X,N WHERE X is CWGroup, X name N)'
                           ')')
 
+    def test_count_users_by_date(self):
+        rset = self.execute('Any D, COUNT(U) GROUPBY D WHERE U is CWUser, U creation_date D')
+        self.assertEquals(rset.related_entity(0,0), (None, None))
+
 if __name__ == '__main__':
     unittest_main()
--- a/view.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/view.py	Mon Jun 07 13:22:24 2010 +0200
@@ -51,7 +51,6 @@
  cubicweb:accesskey         CDATA   #IMPLIED
  cubicweb:actualrql         CDATA   #IMPLIED
  cubicweb:dataurl           CDATA   #IMPLIED
- cubicweb:displayactions    CDATA   #IMPLIED
  cubicweb:facetName         CDATA   #IMPLIED
  cubicweb:facetargs         CDATA   #IMPLIED
  cubicweb:fallbackvid       CDATA   #IMPLIED
--- a/web/data/cubicweb.edition.js	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/data/cubicweb.edition.js	Mon Jun 07 13:22:24 2010 +0200
@@ -471,13 +471,15 @@
     // Failures
     _clearPreviousErrors(formid);
     var descr = result[1];
+    var errmsg;
     // Unknown structure
-    if (!isArrayLike(descr) || descr.length != 2) {
-        updateMessage(descr);
-        return false;
+    if ( !isArrayLike(descr) || descr.length != 2 ) {
+	errmsg = descr;
+    } else {
+	_displayValidationerrors(formid, descr[0], descr[1]);
+	errmsg = _('please correct errors below');
     }
-    _displayValidationerrors(formid, descr[0], descr[1]);
-    updateMessage(_('please correct errors below'));
+    updateMessage(errmsg);
     // ensure the browser does not scroll down
     document.location.hash = '#header';
     return false;
--- a/web/data/cubicweb.facets.js	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/data/cubicweb.facets.js	Mon Jun 07 13:22:24 2010 +0200
@@ -56,13 +56,7 @@
         }
         var toupdate = result[1];
         var extraparams = vidargs;
-        var displayactions = jQuery('#' + divid).attr('cubicweb:displayactions');
-        if (displayactions) {
-            extraparams['displayactions'] = displayactions;
-        }
-        if (paginate) {
-            extraparams['paginate'] = '1';
-        }
+	if (paginate) { extraparams['paginate'] = '1'; } // XXX in vidargs
         // copy some parameters
         // XXX cleanup vid/divid mess
         // if vid argument is specified , the one specified in form params will
--- a/web/formfields.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/formfields.py	Mon Jun 07 13:22:24 2010 +0200
@@ -439,9 +439,11 @@
             # attribute or relation
             return True
         # if it's a non final relation, we need the eids
-        if isinstance(previous_value, tuple):
+        # XXX underlying regression: getattr(ent, 'foo') used to return
+        #     a tuple, now we get a list
+        if isinstance(previous_value, (list, tuple)):
             # widget should return a set of untyped eids
-            previous_value = set(unicode(e.eid) for e in previous_value)
+            previous_value = set(e.eid for e in previous_value)
         try:
             new_value = self.process_form_value(form)
         except ProcessFormError:
--- a/web/views/autoform.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/views/autoform.py	Mon Jun 07 13:22:24 2010 +0200
@@ -650,13 +650,13 @@
     # pre 3.8.3 compat
     def set_action(self, action):
         self._action = action
-    @deprecated('[3.9] use form.form_action()')
     def get_action(self):
         try:
             return self._action
         except AttributeError:
             return self._cw.build_url(self._default_form_action_path)
-    action = property(get_action, set_action)
+    action = property(deprecated('[3.9] use form.form_action()')(get_action),
+                      set_action)
 
     @iclassmethod
     def field_by_name(cls_or_self, name, role=None, eschema=None):
--- a/web/views/editforms.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/views/editforms.py	Mon Jun 07 13:22:24 2010 +0200
@@ -340,16 +340,13 @@
                            self._build_renderer(entity, rtype, role))
 
     def should_edit_attribute(self, entity, rschema, form):
-        rtype = str(rschema)
-        rdef = entity.e_schema.rdef(rtype)
-        afs = uicfg.autoform_section.etype_get(
-            entity.__regid__, rtype, 'subject', rdef.object)
-        if 'main_hidden' in afs or not entity.cw_has_perm('update'):
+        if not entity.has_perm('update'):
             return False
+        rdef = entity.e_schema.rdef(rschema)
         if not rdef.has_perm(self._cw, 'update', eid=entity.eid):
             return False
         try:
-            form.field_by_name(rtype, 'subject', entity.e_schema)
+            form.field_by_name(str(rschema), 'subject', entity.e_schema)
         except FieldNotFound:
             return False
         return True
@@ -439,15 +436,27 @@
     _onclick = (u"loadInlineEditionFormOptions(%(eid)s, '%(rtype)s', "
                 "'%(divid)s', %(options)s);")
 
+    def should_edit_attribute(self, entity, rschema, form):
+        rdef = entity.e_schema.rdef(rschema)
+        afs = uicfg.autoform_section.etype_get(
+            entity.__regid__, rschema, 'subject', rdef.object)
+        if 'main_hidden' in afs:
+            return False
+        return super(AutoClickAndEditFormView, self).should_edit_attribute(
+            entity, rschema, form)
+
     def should_edit_relation(self, entity, rschema, role, rvid):
         eschema = entity.e_schema
-        rtype = str(rschema)
-        # XXX check autoform_section. what if 'generic'?
-        dispctrl = _pvdc.etype_get(eschema, rtype, role)
+        dispctrl = _pvdc.etype_get(eschema, rschema, role)
         vid = dispctrl.get('vid', 'reledit')
         if vid != 'reledit': # reledit explicitly disabled
             return False
-        if eschema.rdef(rschema, role).composite == role:
+        rdef = eschema.rdef(rschema, role)
+        if rdef.composite == role:
+            return False
+        afs = uicfg.autoform_section.etype_get(
+            entity.__regid__, rschema, role, rdef.object)
+        if 'main_hidden' in afs:
             return False
         return super(AutoClickAndEditFormView, self).should_edit_relation(
             entity, rschema, role, rvid)
--- a/web/views/forms.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/views/forms.py	Mon Jun 07 13:22:24 2010 +0200
@@ -196,9 +196,13 @@
 
     _default_form_action_path = 'edit'
     def form_action(self):
-        if self.action is None:
+        try:
+            action = self.get_action() # avoid spurious warning w/ autoform bw compat property
+        except AttributeError:
+            action = self.action
+        if action is None:
             return self._cw.build_url(self._default_form_action_path)
-        return self.action
+        return action
 
     @deprecated('[3.6] use .add_hidden(name, value, **kwargs)')
     def form_add_hidden(self, name, value=None, **kwargs):
--- a/web/views/tableview.py	Mon Jun 07 12:39:15 2010 +0200
+++ b/web/views/tableview.py	Mon Jun 07 13:22:24 2010 +0200
@@ -46,7 +46,7 @@
     finalview = 'final'
 
     def form_filter(self, divid, displaycols, displayactions, displayfilter,
-                    hidden=True):
+                    paginate, hidden=True):
         rqlst = self.cw_rset.syntax_tree()
         # union not yet supported
         if len(rqlst.children) != 1:
@@ -60,7 +60,8 @@
         wdgs = [wdg for wdg in wdgs if wdg is not None]
         if wdgs:
             self._generate_form(divid, baserql, wdgs, hidden,
-                               vidargs={'displaycols': displaycols,
+                               vidargs={'paginate': paginate,
+                                        'displaycols': displaycols,
                                         'displayactions': displayactions,
                                         'displayfilter': displayfilter})
             return self.show_hide_actions(divid, not hidden)
@@ -162,19 +163,16 @@
                 self.w(u'<h2 class="tableTitle">%s</h2>\n' % title)
             if displayfilter:
                 actions += self.form_filter(divid, displaycols, displayfilter,
-                                            displayactions)
+                                            displayactions, paginate)
         elif displayfilter:
             actions += self.show_hide_actions(divid, True)
-        self.w(u'<div id="%s"' % divid)
+        self.w(u'<div id="%s">' % divid)
         if displayactions:
             actionsbycat = self._cw.vreg['actions'].possible_actions(req, self.cw_rset)
             for action in actionsbycat.get('mainactions', ()):
                 for action in action.actual_actions():
                     actions.append( (action.url(), req._(action.title),
                                      action.html_class(), None) )
-            self.w(u' cubicweb:displayactions="1">') # close <div tag
-        else:
-            self.w(u'>') # close <div tag
         # render actions menu
         if actions:
             self.render_actions(divid, actions)
@@ -327,7 +325,7 @@
     title = None
 
     def call(self, title=None, subvid=None, headers=None, divid=None,
-             displaycols=None, displayactions=None, mainindex=None):
+             paginate=False, displaycols=None, displayactions=None, mainindex=None):
         """Dumps a table displaying a composite query"""
         try:
             actrql = self._cw.form['actualrql']
@@ -349,7 +347,8 @@
         if mainindex is None:
             mainindex = self.main_var_index()
         if mainindex is not None:
-            actions = self.form_filter(divid, displaycols, displayactions, True)
+            actions = self.form_filter(divid, displaycols, displayactions,
+                                       paginate, True)
         else:
             actions = ()
         if not subvid and 'subvid' in self._cw.form: