backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 15 Feb 2010 18:44:47 +0100
changeset 4587 70d47389630c
parent 4567 bf3453789887 (current diff)
parent 4586 440e340c61fe (diff)
child 4588 36b700c00d38
backport stable
web/views/autoform.py
--- a/appobject.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/appobject.py	Mon Feb 15 18:44:47 2010 +0100
@@ -404,7 +404,7 @@
     def format_date(self, date, date_format=None, time=False):
         return self._cw.format_date(date, date_format, time)
 
-    @deprecated('[3.6] use self._cw.format_timoe')
+    @deprecated('[3.6] use self._cw.format_time')
     def format_time(self, time):
         return self._cw.format_time(time)
 
--- a/hooks/metadata.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/hooks/metadata.py	Mon Feb 15 18:44:47 2010 +0100
@@ -55,7 +55,13 @@
     events = ('before_update_entity',)
 
     def __call__(self):
-        self.entity.setdefault('modification_date', datetime.now())
+        # repairing is true during c-c upgrade/shell and similar commands. We
+        # usually don't want to update modification date in such cases.
+        #
+        # XXX to be really clean, we should turn off modification_date update
+        # explicitly on each command where we do not want that behaviour.
+        if not self._cw.vreg.config.repairing:
+            self.entity.setdefault('modification_date', datetime.now())
 
 
 class _SetCreatorOp(hook.Operation):
--- a/hooks/notification.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/hooks/notification.py	Mon Feb 15 18:44:47 2010 +0100
@@ -126,7 +126,7 @@
             rqlsel.append(var)
             rqlrestr.append('X %s %s' % (attr, var))
         rql = 'Any %s WHERE %s' % (','.join(rqlsel), ','.join(rqlrestr))
-        rset = session.execute(rql, {'x': self.entity.eid}, 'x')
+        rset = session.unsafe_execute(rql, {'x': self.entity.eid}, 'x')
         for i, attr in enumerate(attrs):
             oldvalue = rset[0][i]
             newvalue = self.entity[attr]
--- a/hooks/security.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/hooks/security.py	Mon Feb 15 18:44:47 2010 +0100
@@ -12,30 +12,31 @@
 from cubicweb.server import BEFORE_ADD_RELATIONS, ON_COMMIT_ADD_RELATIONS, hook
 
 
-def check_entity_attributes(session, entity):
+def check_entity_attributes(session, entity, editedattrs=None):
     eid = entity.eid
     eschema = entity.e_schema
     # ._default_set is only there on entity creation to indicate unspecified
     # attributes which has been set to a default value defined in the schema
     defaults = getattr(entity, '_default_set', ())
-    try:
-        editedattrs = entity.edited_attributes
-    except AttributeError:
-        editedattrs = entity
+    if editedattrs is None:
+        try:
+            editedattrs = entity.edited_attributes
+        except AttributeError:
+            editedattrs = entity
     for attr in editedattrs:
         if attr in defaults:
             continue
         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' ?)
-            rdef.check_perm(session, 'add', eid=eid)
+            rdef.check_perm(session, 'update', eid=eid)
 
 
 class _CheckEntityPermissionOp(hook.LateOperation):
     def precommit_event(self):
         #print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
         self.entity.check_perm(self.action)
-        check_entity_attributes(self.session, self.entity)
+        check_entity_attributes(self.session, self.entity, self.editedattrs)
 
     def commit_event(self):
         pass
@@ -63,7 +64,9 @@
     events = ('after_add_entity',)
 
     def __call__(self):
-        _CheckEntityPermissionOp(self._cw, entity=self.entity, action='add')
+        _CheckEntityPermissionOp(self._cw, entity=self.entity,
+                                 editedattrs=tuple(self.entity.edited_attributes),
+                                 action='add')
 
 
 class AfterUpdateEntitySecurityHook(SecurityHook):
@@ -77,7 +80,12 @@
             check_entity_attributes(self._cw, self.entity)
         except Unauthorized:
             self.entity.clear_local_perm_cache('update')
-            _CheckEntityPermissionOp(self._cw, entity=self.entity, action='update')
+            # save back editedattrs in case the entity is reedited later in the
+            # same transaction, which will lead to edited_attributes being
+            # overwritten
+            _CheckEntityPermissionOp(self._cw, entity=self.entity,
+                                     editedattrs=tuple(self.entity.edited_attributes),
+                                     action='update')
 
 
 class BeforeDelEntitySecurityHook(SecurityHook):
--- a/hooks/syncschema.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/hooks/syncschema.py	Mon Feb 15 18:44:47 2010 +0100
@@ -716,6 +716,9 @@
             return
         if isinstance(erschema, RelationSchema): # XXX 3.6 migration
             return
+        if isinstance(erschema, RelationDefinitionSchema) and \
+               self.action in ('delete', 'add'): # XXX 3.6.1 migration
+            return
         perms = list(erschema.action_permissions(self.action))
         if hasattr(self, 'group_eid'):
             perm = self.session.entity_from_eid(self.group_eid).name
--- a/hooks/syncsession.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/hooks/syncsession.py	Mon Feb 15 18:44:47 2010 +0100
@@ -153,7 +153,7 @@
             raise ValidationError(self.entity.eid,
                                   {'value': session._(str(ex))})
         if not session.user.matching_groups('managers'):
-            session.add_relation(entity.eid, 'for_user', session.user.eid)
+            session.add_relation(self.entity.eid, 'for_user', session.user.eid)
         else:
             _AddCWPropertyOp(session, cwprop=self.entity)
 
@@ -178,7 +178,7 @@
         if entity.for_user:
             for session_ in get_user_sessions(session.repo, entity.for_user[0].eid):
                 _ChangeCWPropertyOp(session, cwpropdict=session_.user.properties,
-                                  key=key, value=value)
+                                    key=key, value=value)
         else:
             # site wide properties
             _ChangeCWPropertyOp(session, cwpropdict=session.vreg['propertyvalues'],
--- a/mail.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/mail.py	Mon Feb 15 18:44:47 2010 +0100
@@ -200,7 +200,8 @@
                 continue
             except Exception, ex:
                 # shouldn't make the whole transaction fail because of rendering
-                # error (unauthorized or such)
+                # error (unauthorized or such) XXX check it doesn't actually
+                # occurs due to rollback on such error
                 self.exception(str(ex))
                 continue
             msg = format_mail(self.user_data, [emailaddr], content, subject,
--- a/migration.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/migration.py	Mon Feb 15 18:44:47 2010 +0100
@@ -70,8 +70,11 @@
     ability to show the script's content
     """
     while True:
-        answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y')
-        if answer == 'n':
+        answer = ASK.ask('Execute %r ?' % scriptpath,
+                         ('Y','n','show','abort'), 'Y')
+        if answer == 'abort':
+            raise SystemExit(1)
+        elif answer == 'n':
             return False
         elif answer == 'show':
             stream = open(scriptpath)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.6.1_Any.py	Mon Feb 15 18:44:47 2010 +0100
@@ -0,0 +1,1 @@
+sync_schema_props_perms(syncprops=False)
--- a/misc/migration/bootstrapmigration_repository.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Mon Feb 15 18:44:47 2010 +0100
@@ -10,24 +10,35 @@
 
 applcubicwebversion, cubicwebversion = versions_map['cubicweb']
 
-if applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
-    from cubicweb.server import schemaserial as ss
+from cubicweb.server import schemaserial as ss
+def _add_relation_definition_no_perms(subjtype, rtype, objtype):
+    rschema = fsschema.rschema(rtype)
+    for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
+        rql(query, args, ask_confirm=False)
+    commit(ask_confirm=False)
+
+if applcubicwebversion == (3, 6, 0) and cubicwebversion >= (3, 6, 0):
+    _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'CWGroup')
+    _add_relation_definition_no_perms('CWAttribute', 'update_permission', 'RQLExpression')
+    session.set_pool()
+    session.unsafe_execute('SET X update_permission Y WHERE X is CWAttribute, X add_permission Y')
+    drop_relation_definition('CWAttribute', 'add_permission', 'CWGroup')
+    drop_relation_definition('CWAttribute', 'add_permission', 'RQLExpression')
+    drop_relation_definition('CWAttribute', 'delete_permission', 'CWGroup')
+    drop_relation_definition('CWAttribute', 'delete_permission', 'RQLExpression')
+
+elif applcubicwebversion < (3, 6, 0) and cubicwebversion >= (3, 6, 0):
     session.set_pool()
     session.execute = session.unsafe_execute
     permsdict = ss.deserialize_ertype_permissions(session)
-    def _add_relation_definition_no_perms(subjtype, rtype, objtype):
-        rschema = fsschema.rschema(rtype)
-        for query, args in ss.rdef2rql(rschema, subjtype, objtype, groupmap=None):
-            rql(query, args, ask_confirm=False)
-        commit(ask_confirm=False)
 
     config.disabled_hooks_categories.add('integrity')
     for rschema in repo.schema.relations():
         rpermsdict = permsdict.get(rschema.eid, {})
         for rdef in rschema.rdefs.values():
-            for action in ('read', 'add', 'delete'):
+            for action in rdef.ACTIONS:
                 actperms = []
-                for something in rpermsdict.get(action, ()):
+                for something in rpermsdict.get(action == 'update' and 'add' or action, ()):
                     if isinstance(something, tuple):
                         actperms.append(rdef.rql_expression(*something))
                     else: # group name
@@ -36,18 +47,32 @@
     for action in ('read', 'add', 'delete'):
         _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'CWGroup')
         _add_relation_definition_no_perms('CWRelation', '%s_permission' % action, 'RQLExpression')
+    for action in ('read', 'update'):
         _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'CWGroup')
         _add_relation_definition_no_perms('CWAttribute', '%s_permission' % action, 'RQLExpression')
     for action in ('read', 'add', 'delete'):
-        rql('SET X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+        rql('SET X %s_permission Y WHERE X is CWRelation, '
             'RT %s_permission Y, X relation_type RT, Y is CWGroup' % (action, action))
         rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
-            'X %s_permission Y WHERE X is IN (CWAttribute, CWRelation), '
+            'X %s_permission Y WHERE X is CWRelation, '
             'X relation_type RT, RT %s_permission Y2, Y2 exprtype YET, '
             'Y2 mainvars YMV, Y2 expression YEX' % (action, action))
+    rql('SET X read_permission Y WHERE X is CWAttribute, '
+        'RT read_permission Y, X relation_type RT, Y is CWGroup')
+    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+        'X read_permission Y WHERE X is CWAttribute, '
+        'X relation_type RT, RT read_permission Y2, Y2 exprtype YET, '
+        'Y2 mainvars YMV, Y2 expression YEX')
+    rql('SET X update_permission Y WHERE X is CWAttribute, '
+        'RT add_permission Y, X relation_type RT, Y is CWGroup')
+    rql('INSERT RQLExpression Y: Y exprtype YET, Y mainvars YMV, Y expression YEX, '
+        'X update_permission Y WHERE X is CWAttribute, '
+        'X relation_type RT, RT add_permission Y2, Y2 exprtype YET, '
+        'Y2 mainvars YMV, Y2 expression YEX')
+    for action in ('read', 'add', 'delete'):
         drop_relation_definition('CWRType', '%s_permission' % action, 'CWGroup', commit=False)
         drop_relation_definition('CWRType', '%s_permission' % action, 'RQLExpression')
-    config.disabled_hooks_categories.add('integrity')
+    config.disabled_hooks_categories.remove('integrity')
 
 if applcubicwebversion < (3, 4, 0) and cubicwebversion >= (3, 4, 0):
 
--- a/schema.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/schema.py	Mon Feb 15 18:44:47 2010 +0100
@@ -283,22 +283,10 @@
                    isinstance(group_or_rqlexpr, RQLExpression):
                 msg = "can't use rql expression for read permission of %s"
                 raise BadSchemaDefinition(msg % self)
-            elif self.final and isinstance(group_or_rqlexpr, RRQLExpression):
-                if schema.reading_from_database:
-                    # we didn't have final relation earlier, so turn
-                    # RRQLExpression into ERQLExpression now
-                    rqlexpr = group_or_rqlexpr
-                    newrqlexprs = [x for x in self.get_rqlexprs(action)
-                                   if not x is rqlexpr]
-                    newrqlexprs.append(ERQLExpression(rqlexpr.expression,
-                                                      rqlexpr.mainvars,
-                                                      rqlexpr.eid))
-                    self.set_rqlexprs(action, newrqlexprs)
-                else:
-                    msg = "can't use RRQLExpression on %s, use an ERQLExpression"
-                    raise BadSchemaDefinition(msg % self)
-            elif not self.final and \
-                     isinstance(group_or_rqlexpr, ERQLExpression):
+            if self.final and isinstance(group_or_rqlexpr, RRQLExpression):
+                msg = "can't use RRQLExpression on %s, use an ERQLExpression"
+                raise BadSchemaDefinition(msg % self)
+            if not self.final and isinstance(group_or_rqlexpr, ERQLExpression):
                 msg = "can't use ERQLExpression on %s, use a RRQLExpression"
                 raise BadSchemaDefinition(msg % self)
 RelationDefinitionSchema.check_permission_definitions = check_permission_definitions
@@ -314,13 +302,14 @@
         if eid is None and edef is not None:
             eid = getattr(edef, 'eid', None)
         self.eid = eid
-        # take care: no _groups attribute when deep-copying
-        if getattr(self, 'permissions', None):
-            for groups in self.permissions.itervalues():
-                for group_or_rqlexpr in groups:
-                    if isinstance(group_or_rqlexpr, RRQLExpression):
-                        msg = "can't use RRQLExpression on an entity type, use an ERQLExpression (%s)"
-                        raise BadSchemaDefinition(msg % self.type)
+
+    def check_permission_definitions(self):
+        super(CubicWebEntitySchema, self).check_permission_definitions()
+        for groups in self.permissions.itervalues():
+            for group_or_rqlexpr in groups:
+                if isinstance(group_or_rqlexpr, RRQLExpression):
+                    msg = "can't use RRQLExpression on %s, use an ERQLExpression"
+                    raise BadSchemaDefinition(msg % self.type)
 
     def attribute_definitions(self):
         """return an iterator on attribute definitions
@@ -426,14 +415,24 @@
 
     def has_perm(self, session, action, **kwargs):
         """return true if the action is granted globaly or localy"""
-        if 'fromeid' in kwargs:
-            subjtype = session.describe(kwargs['fromeid'])[0]
+        if self.final:
+            assert not ('fromeid' in kwargs or 'toeid' in kwargs), kwargs
+            assert action in ('read', 'update')
+            if 'eid' in kwargs:
+                subjtype = session.describe(kwargs['eid'])[0]
+            else:
+                subjtype = objtype = None
         else:
-            subjtype = None
-        if 'toeid' in kwargs:
-            objtype = session.describe(kwargs['toeid'])[0]
-        else:
-            objtype = None
+            assert not 'eid' in kwargs, kwargs
+            assert action in ('read', 'add', 'delete')
+            if 'fromeid' in kwargs:
+                subjtype = session.describe(kwargs['fromeid'])[0]
+            else:
+                subjtype = None
+            if 'toeid' in kwargs:
+                objtype = session.describe(kwargs['toeid'])[0]
+            else:
+                objtype = None
         if objtype and subjtype:
             return self.rdef(subjtype, objtype).has_perm(session, action, **kwargs)
         elif subjtype:
@@ -919,6 +918,11 @@
             kwargs['o'] = toeid
         return self._check(session, **kwargs)
 
+# in yams, default 'update' perm for attributes granted to managers and owners.
+# Within cw, we want to default to users who may edit the entity holding the
+# attribute.
+ybo.DEFAULT_ATTRPERMS['update'] = (
+    'managers', ERQLExpression('U has_update_permission X'))
 
 # workflow extensions #########################################################
 
--- a/schemas/bootstrap.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/schemas/bootstrap.py	Mon Feb 15 18:44:47 2010 +0100
@@ -9,7 +9,7 @@
 _ = unicode
 
 from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
-                            ObjectRelation, RichString, String, Boolean, Int)
+                            RichString, String, Boolean, Int)
 from cubicweb.schema import RQLConstraint
 from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
 
@@ -131,15 +131,6 @@
                                       'relation\'subject, object and to '
                                       'the request user. '))
 
-    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                      description=_('rql expression allowing to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                     description=_('rql expression allowing to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='*?', composite='subject',
-                                        description=_('rql expression allowing to delete entities/relations of this type'))
-    update_permission = ObjectRelation('CWEType', cardinality='*?', composite='subject',
-                                        description=_('rql expression allowing to update entities of this type'))
-
 
 class CWConstraint(EntityType):
     """define a schema constraint"""
@@ -162,16 +153,6 @@
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
-    read_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'), cardinality='**',
-                                      description=_('groups allowed to read entities/relations of this type'))
-    add_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
-                                     description=_('groups allowed to add entities/relations of this type'))
-    delete_permission = ObjectRelation(('CWEType', 'CWAttribute', 'CWRelation'),
-                                        description=_('groups allowed to delete entities/relations of this type'))
-    update_permission = ObjectRelation('CWEType',
-                                        description=_('groups allowed to update entities of this type'))
-
-
 
 class CWProperty(EntityType):
     """used for cubicweb configuration. Once a property has been created you
@@ -215,27 +196,44 @@
     inlined = True
 
 class read_permission(RelationType):
-    """core relation giving to a group the permission to read an entity or
-    relation type
+    """grant permission to read entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWAttribute', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class add_permission(RelationType):
-    """core relation giving to a group the permission to add an entity or
-    relation type
+    """grant permission to add entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class delete_permission(RelationType):
-    """core relation giving to a group the permission to delete an entity or
-    relation type
+    """grant permission to delete entity or relation through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWRelation')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 class update_permission(RelationType):
-    """core relation giving to a group the permission to update an entity type
+    """grant permission to update entity or attribute through a group or rql
+    expression
     """
     __permissions__ = META_RTYPE_PERMS
+    subject = ('CWEType', 'CWAttribute')
+    object = ('CWGroup', 'RQLExpression')
+    cardinality = '*?'
+    composite = 'subject'
 
 
 class is_(RelationType):
--- a/server/migractions.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/server/migractions.py	Mon Feb 15 18:44:47 2010 +0100
@@ -1166,6 +1166,10 @@
         if not isinstance(rql, (tuple, list)):
             rql = ( (rql, kwargs), )
         res = None
+        try:
+            execute = self._cw.unsafe_execute
+        except AttributeError:
+            execute = self._cw.execute
         for rql, kwargs in rql:
             if kwargs:
                 msg = '%s (%s)' % (rql, kwargs)
@@ -1173,7 +1177,7 @@
                 msg = rql
             if not ask_confirm or self.confirm('execute rql: %s ?' % msg):
                 try:
-                    res = self._cw.execute(rql, kwargs, cachekey)
+                    res = execute(rql, kwargs, cachekey)
                 except Exception, ex:
                     if self.confirm('error: %s\nabort?' % ex):
                         raise
--- a/server/schemaserial.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/server/schemaserial.py	Mon Feb 15 18:44:47 2010 +0100
@@ -189,13 +189,17 @@
     definition dictionary as built by deserialize_ertype_permissions for a
     given erschema's eid
     """
+    # reset erschema permissions here to avoid getting yams default anyway
+    erschema.permissions = dict((action, ()) for action in erschema.ACTIONS)
     try:
         thispermsdict = permsdict[erschema.eid]
     except KeyError:
         return
-    permissions = erschema.permissions
     for action, somethings in thispermsdict.iteritems():
-        permissions[action] = tuple(
+        # XXX cw < 3.6.1 bw compat
+        if isinstance(erschema, schemamod.RelationDefinitionSchema) and erschema.final and action == 'add':
+            action = 'update'
+        erschema.permissions[action] = tuple(
             isinstance(p, tuple) and erschema.rql_expression(*p) or p
             for p in somethings)
 
--- a/server/sources/ldapuser.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/server/sources/ldapuser.py	Mon Feb 15 18:44:47 2010 +0100
@@ -78,7 +78,7 @@
         ('auth-realm',
          {'type' : 'string',
           'default': None,
-          'help': 'realm to use when using gssapp/kerberos authentication.',
+          'help': 'realm to use when using gssapi/kerberos authentication.',
           'group': 'ldap-source', 'inputlevel': 1,
           }),
 
--- a/server/test/data/schema.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/server/test/data/schema.py	Mon Feb 15 18:44:47 2010 +0100
@@ -137,14 +137,13 @@
 class para(RelationType):
     __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
-        'add':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
-        'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')),
+        'update':    ('managers', ERQLExpression('X in_state S, S name "todo"')),
         }
 
 class test(RelationType):
     __permissions__ = {'read': ('managers', 'users', 'guests'),
-                   'delete': ('managers',),
-                   'add': ('managers',)}
+                       'update': ('managers',),
+                       }
 
 class multisource_rel(RelationDefinition):
     subject = ('Card', 'Note')
--- a/toolsutils.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/toolsutils.py	Mon Feb 15 18:44:47 2010 +0100
@@ -10,6 +10,7 @@
 # XXX move most of this in logilab.common (shellutils ?)
 
 import os, sys
+import subprocess
 from os import listdir, makedirs, environ, chmod, walk, remove
 from os.path import exists, join, abspath, normpath
 
@@ -75,8 +76,8 @@
     user decision
     """
     import shutil
-    p_output = os.popen('diff -u %s %s' % (appl_file, ref_file), 'r')
-    diffs = p_output.read()
+    pipe = subprocess.Popen(['diff', '-u', appl_file, ref_file], stdout=subprocess.PIPE)
+    diffs = pipe.stdout.read()
     if diffs:
         if askconfirm:
             print
--- a/vregistry.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/vregistry.py	Mon Feb 15 18:44:47 2010 +0100
@@ -365,7 +365,6 @@
 
     def initialization_completed(self):
         for regname, reg in self.iteritems():
-            self.debug('available in registry %s: %s', regname, sorted(reg))
             reg.initialization_completed()
 
     def load_file(self, filepath, modname, force_reload=False):
@@ -406,7 +405,6 @@
                 if objname.startswith('_'):
                     continue
                 self._load_ancestors_then_object(module.__name__, obj)
-        self.debug('loaded %s', module)
 
     def _load_ancestors_then_object(self, modname, appobjectcls):
         """handle automatic appobject class registration:
--- a/web/formfields.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/formfields.py	Mon Feb 15 18:44:47 2010 +0100
@@ -816,6 +816,18 @@
     def format_single_value(self, req, value):
         return value
 
+    def process_form_value(self, form):
+        """process posted form and return correctly typed value"""
+        try:
+            return form.formvalues[self]
+        except KeyError:
+            value = self._process_form_value(form)
+            # if value is None, there are some remaining pending fields, we'll
+            # have to recompute this later -> don't cache in formvalues
+            if value is not None:
+                form.formvalues[self] = value
+            return value
+
     def _process_form_value(self, form):
         """process posted form and return correctly typed value"""
         widget = self.get_widget(form)
@@ -826,7 +838,6 @@
             values = (values,)
         eids = set()
         for eid in values:
-            # XXX 'not eid' for AutoCompletionWidget, deal with this in the widget
             if not eid or eid == INTERNAL_FIELD_VALUE:
                 continue
             typed_eid = form.actual_eid(eid)
--- a/web/formwidgets.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/formwidgets.py	Mon Feb 15 18:44:47 2010 +0100
@@ -17,6 +17,7 @@
 from cubicweb import tags, uilib
 from cubicweb.web import stdmsgs, INTERNAL_FIELD_VALUE, ProcessFormError
 
+
 class FieldWidget(object):
     """abstract widget class"""
     # javascript / css files required by the widget
--- a/web/test/unittest_views_editforms.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/test/unittest_views_editforms.py	Mon Feb 15 18:44:47 2010 +0100
@@ -16,7 +16,11 @@
 AFS = uicfg.autoform_section
 
 def rbc(entity, formtype, section):
-    return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section)]
+    if section in ('attributes', 'metadata', 'hidden'):
+        permission = 'update'
+    else:
+        permission = 'add'
+    return [(rschema.type, x) for rschema, tschemas, x in AFS.relations_by_section(entity, formtype, section, permission)]
 
 class AutomaticEntityFormTC(CubicWebTC):
 
@@ -69,19 +73,16 @@
                               [('use_email', 'subject'),
                                ])
         # owned_by is defined both as subject and object relations on CWUser
-        self.assertListEquals(rbc(e, 'main', 'hidden'),
-                              [('in_state', 'subject'),
-                               ('is', 'subject'),
-                               ('is_instance_of', 'subject'),
-                               ('has_text', 'subject'),
-                               ('identity', 'subject'),
-                               ('tags', 'object'),
-                               ('for_user', 'object'),
-                               ('created_by', 'object'),
-                               ('wf_info_for', 'object'),
-                               ('owned_by', 'object'),
-                               ('identity', 'object'),
-                               ])
+        self.assertListEquals(sorted(rbc(e, 'main', 'hidden')),
+                              sorted([('has_text', 'subject'),
+                                      ('identity', 'subject'),
+                                      ('tags', 'object'),
+                                      ('for_user', 'object'),
+                                      ('created_by', 'object'),
+                                      ('wf_info_for', 'object'),
+                                      ('owned_by', 'object'),
+                                      ('identity', 'object'),
+                                      ]))
 
     def test_inlined_view(self):
         self.failUnless('main_inlined' in AFS.etype_get('CWUser', 'use_email', 'subject', 'EmailAddress'))
@@ -122,10 +123,8 @@
                                ('connait', 'object')
                                ])
         self.assertListEquals(rbc(e, 'main', 'hidden'),
-                              [('is', 'subject'),
-                               ('has_text', 'subject'),
+                              [('has_text', 'subject'),
                                ('identity', 'subject'),
-                               ('is_instance_of', 'subject'),
                                ('identity', 'object'),
                                ])
 
--- a/web/uicfg.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/uicfg.py	Mon Feb 15 18:44:47 2010 +0100
@@ -312,6 +312,10 @@
             formsections.add('%s_%s' % (formtype, section))
 
     def tag_relation(self, key, formtype, section=None):
+        if isinstance(formtype, tuple):
+            for ftype in formtype:
+                self.tag_relation(key, ftype, section)
+            return
         if section is None:
             tag = formtype
             for formtype, section in self.bw_tag_map[tag].iteritems():
@@ -343,8 +347,8 @@
         # overriden to avoid recomputing done in parent classes
         return self._tagdefs.get(key, ())
 
-    def relations_by_section(self, entity, formtype, section,
-                             permission=None, strict=False):
+    def relations_by_section(self, entity, formtype, section, permission,
+                             strict=False):
         """return a list of (relation schema, target schemas, role) for the
         given entity matching categories and permission.
 
@@ -359,52 +363,60 @@
         else:
             eid = None
             strict = False
+        if permission == 'update':
+            assert section in ('attributes', 'metadata', 'hidden')
+            relpermission = 'add'
+        else:
+            assert section not in ('attributes', 'metadata', 'hidden')
+            relpermission = permission
         cw = entity._cw
         for rschema, targetschemas, role in eschema.relation_definitions(True):
-            # check category first, potentially lower cost than checking
-            # permission which may imply rql queries
             _targetschemas = []
             for tschema in targetschemas:
+                # check section's tag first, potentially lower cost than
+                # checking permission which may imply rql queries
                 if not tag in self.etype_get(eschema, rschema, role, tschema):
                     continue
                 rdef = rschema.role_rdef(eschema, tschema, role)
-                if permission is not None and \
-                       not ((not strict and rdef.has_local_role(permission)) or
-                            rdef.has_perm(cw, permission, fromeid=eid)):
-                    continue
+                if rschema.final:
+                    if not rdef.has_perm(cw, permission, eid=eid):
+                        continue
+                elif strict or not rdef.has_local_role(relpermission):
+                    if role == 'subject':
+                        if not rdef.has_perm(cw, relpermission, fromeid=eid):
+                            continue
+                    elif role == 'object':
+                        if not rdef.has_perm(cw, relpermission, toeid=eid):
+                            continue
                 _targetschemas.append(tschema)
             if not _targetschemas:
                 continue
             targetschemas = _targetschemas
-            if permission is not None:
-                rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
-                # tag allowing to hijack the permission machinery when
-                # permission is not verifiable until the entity is actually
-                # created...
-                if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
-                    yield (rschema, targetschemas, role)
+            rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0])
+            # XXX tag allowing to hijack the permission machinery when
+            # permission is not verifiable until the entity is actually
+            # created...
+            if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
+                yield (rschema, targetschemas, role)
+                continue
+            if not rschema.final and role == 'subject':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', fromeid=eid,
+                                          toeid=entity.related(rschema.type, role)[0][0])):
                     continue
-                if rschema.final:
-                    if not rdef.has_perm(cw, permission, fromeid=eid):
-                        continue
-                elif role == 'subject':
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rdef.role_cardinality(role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rdef.has_perm(cw, 'delete', fromeid=eid,
-                                              toeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-                elif role == 'object':
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rdef.role_cardinality(role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rdef.has_perm(cw, 'delete', toeid=eid,
-                                              fromeid=entity.related(rschema.type, role)[0][0])):
-                        continue
+            elif role == 'object':
+                # on relation with cardinality 1 or ?, we need delete perm as well
+                # if the relation is already set
+                if (relpermission == 'add'
+                    and rdef.role_cardinality(role) in '1?'
+                    and eid and entity.related(rschema.type, role)
+                    and not rdef.has_perm(cw, 'delete', toeid=eid,
+                                          fromeid=entity.related(rschema.type, role)[0][0])):
+                    continue
             yield (rschema, targetschemas, role)
 
 autoform_section = AutoformSectionRelationTags('autoform_section')
@@ -442,8 +454,8 @@
 class AutoformIsInlined(RelationTags):
     """XXX for < 3.6 bw compat"""
     def tag_relation(self, key, tag):
-        warn('autoform_is_inlined rtag is deprecated, use autoform_section '
-             'with inlined formtype and "attributes" or "hidden" section',
+        warn('autoform_is_inlined is deprecated, use autoform_section '
+             'with formtype="inlined", section="attributes" or section="hidden"',
              DeprecationWarning, stacklevel=3)
         section = tag and 'inlined' or 'hidden'
         autoform_section.tag_relation(key, 'main', section)
--- a/web/views/actions.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/actions.py	Mon Feb 15 18:44:47 2010 +0100
@@ -31,7 +31,7 @@
         # if user has no update right but it can modify some relation,
         # display action anyway
         form = entity._cw.vreg['forms'].select('edition', entity._cw,
-                                             entity=entity)
+                                               entity=entity)
         for dummy in form.editable_relations():
             return 1
         try:
--- a/web/views/autoform.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/autoform.py	Mon Feb 15 18:44:47 2010 +0100
@@ -85,7 +85,10 @@
 
     def __init__(self, *args, **kwargs):
         for attr in self._select_attrs:
-            setattr(self, attr, kwargs.pop(attr, None))
+            # don't pop attributes from kwargs, so the end-up in
+            # self.cw_extra_kwargs which is then passed to the edition form (see
+            # the .form method)
+            setattr(self, attr, kwargs.get(attr))
         super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
 
     def _entity(self):
@@ -384,7 +387,11 @@
             related = []
             if entity.has_eid():
                 rset = entity.related(rschema, role, limit=form.related_limit)
-                if rschema.has_perm(form._cw, 'delete'):
+                if role == 'subject':
+                    haspermkwargs = {'fromeid': entity.eid}
+                else:
+                    haspermkwargs = {'toeid': entity.eid}
+                if rschema.has_perm(form._cw, 'delete', **haspermkwargs):
                     toggleable_rel_link_func = toggleable_relation_link
                 else:
                     toggleable_rel_link_func = lambda x, y, z: u''
@@ -650,7 +657,7 @@
             return self.display_fields
         # XXX we should simply put eid in the generated section, no?
         return [(rtype, role) for rtype, _, role in self._relations_by_section(
-            'attributes', strict=strict) if rtype != 'eid']
+            'attributes', 'update', strict) if rtype != 'eid']
 
     def editable_relations(self):
         """return a sorted list of (relation's label, relation'schema, role) for
--- a/web/views/calendar.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/calendar.py	Mon Feb 15 18:44:47 2010 +0100
@@ -155,7 +155,9 @@
             last_day_of_month = date(year + 1, 1, 1) - timedelta(1)
         else:
             last_day_of_month = date(year, month + 1, 1) - timedelta(1)
-        lastday = last_day_of_month + timedelta(6 - last_day_of_month.weekday())
+        # date range exclude last day so we should at least add one day, hence
+        # the 7
+        lastday = last_day_of_month + timedelta(7 - last_day_of_month.weekday())
         month_dates = list(date_range(firstday, lastday))
         dates = {}
         task_max = 0
@@ -250,7 +252,6 @@
         # output header
         self.w(u'<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>' %
                tuple(self._cw._(day) for day in WEEKDAYS))
-
         # build calendar
         for mdate, task_rows in zip(month_dates, days):
             if mdate.weekday() == 0:
--- a/web/views/formrenderers.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/formrenderers.py	Mon Feb 15 18:44:47 2010 +0100
@@ -92,7 +92,7 @@
         self.render_fields(w, form, values)
         self.render_buttons(w, form)
         w(u'</fieldset>')
-        w(u'</form>')
+        w(self.close_form(form, values))
         errormsg = self.error_message(form)
         if errormsg:
             data.insert(0, errormsg)
@@ -171,6 +171,13 @@
             tag += ' cubicweb:target="%s"' % xml_escape(form.cwtarget)
         return tag + '>'
 
+    def close_form(self, form, values):
+        """seem dump but important for consistency w/ close form, and necessary
+        for form renderers overriding open_form to use something else or more than
+        and <form>
+        """
+        return '</form>'
+
     def render_fields(self, w, form, values):
         fields = self._render_hidden_fields(w, form)
         if fields:
--- a/web/views/forms.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/forms.py	Mon Feb 15 18:44:47 2010 +0100
@@ -266,6 +266,9 @@
             self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row,
             col=self.cw_col, entity=self.edited_entity)
 
+    def should_display_add_new_relation_link(self, rschema, existant, card):
+        return False
+
     # controller side method (eg POST reception handling)
 
     def actual_eid(self, eid):
@@ -284,9 +287,6 @@
     def editable_relations(self):
         return ()
 
-    def should_display_add_new_relation_link(self, rschema, existant, card):
-        return False
-
     @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function')
     def subject_relation_vocabulary(self, rtype, limit=None):
         """defaut vocabulary method for the given relation, looking for
--- a/web/views/primary.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/primary.py	Mon Feb 15 18:44:47 2010 +0100
@@ -46,8 +46,8 @@
         self.render_entity(entity)
 
     def render_entity(self, entity):
+        self.render_entity_toolbox(entity)
         self.render_entity_title(entity)
-        self.render_entity_toolbox(entity)
         # entity's attributes and relations, excluding meta data
         # if the entity isn't meta itself
         if self.is_primary():
@@ -161,7 +161,7 @@
                 try:
                     label, rset, vid, dispctrl  = box
                 except ValueError:
-                    warn('box views should now be defined as a 4-uple (label, rset, vid, dispctrl), '
+                    warn('[3.5] box views should now be defined as a 4-uple (label, rset, vid, dispctrl), '
                          'please update %s' % self.__class__.__name__,
                          DeprecationWarning)
                     label, rset, vid  = box
--- a/web/views/schema.py	Thu Feb 11 12:19:08 2010 +0100
+++ b/web/views/schema.py	Mon Feb 15 18:44:47 2010 +0100
@@ -380,7 +380,6 @@
 
     def _generate(self, tmpfile):
         """display global schema information"""
-        print 'skipedtypes', skip_types(self._cw)
         visitor = FullSchemaVisitor(self._cw, self._cw.vreg.schema,
                                     skiptypes=skip_types(self._cw))
         s2d.schema2dot(outputfile=tmpfile, visitor=visitor)