merge stable
authorAlexandre Fayolle <alexandre.fayolle@logilab.fr>
Thu, 04 Mar 2010 17:56:45 +0100
branchstable
changeset 5204 d175ce5c2e85
parent 5165 e5efbcaa3011 (current diff)
parent 4803 f531742e85f4 (diff)
child 5205 8b48add93b7e
merge
--- a/cwctl.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/cwctl.py	Thu Mar 04 17:56:45 2010 +0100
@@ -204,7 +204,7 @@
             # simplify constraints
             if versions:
                 for constraint in versions:
-                    op, ver = constraint.split()
+                    op, ver = constraint
                     if oper is None:
                         oper = op
                         version = ver
@@ -238,7 +238,12 @@
             for name, constraint in use.items():
                 self.constraints.setdefault(name,set())
                 if constraint:
-                    self.constraints[name].add(constraint)
+                    try:
+                        oper, version = constraint.split()
+                        self.constraints[name].add( (oper, version) )
+                    except:
+                        self.warnings.append('cube %s depends on %s but constraint badly formatted: %s'
+                                             % (cube, name, constraint))
                 self.reverse_constraints.setdefault(name, set()).add(cube)
 
 class ListCommand(Command):
--- a/devtools/__init__.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/devtools/__init__.py	Thu Mar 04 17:56:45 2010 +0100
@@ -30,8 +30,8 @@
 
 SYSTEM_RELATIONS = schema.META_RTYPES | set((
     # workflow related
-    'workflow_of', 'state_of', 'transition_of', 'initial_state', 'allowed_transition',
-    'destination_state', 'from_state', 'to_state',
+    'workflow_of', 'state_of', 'transition_of', 'initial_state', 'default_workflow',
+    'allowed_transition', 'destination_state', 'from_state', 'to_state',
     'condition', 'subworkflow', 'subworkflow_state', 'subworkflow_exit',
     'custom_workflow', 'in_state', 'wf_info_for',
     # cwproperty
--- a/doc/book/en/admin/additional-tips.rst	Thu Mar 04 17:26:43 2010 +0100
+++ b/doc/book/en/admin/additional-tips.rst	Thu Mar 04 17:56:45 2010 +0100
@@ -28,7 +28,7 @@
 
 Simply use the pg_dump in a cron ::
 
-    pg_dump -Fc --username=cubicweb --no-owner --file=/var/lib/cubicweb/backup/<instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
+    su -c "pg_dump -Fc --username=cubicweb --no-owner" postgres > <your-instance>-$(date '+%Y-%m-%d_%H:%M:%S').dump
 
 **CubicWeb way**
 
--- a/entity.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/entity.py	Thu Mar 04 17:56:45 2010 +0100
@@ -797,7 +797,7 @@
             del self.__unique
         except AttributeError:
             pass
-    
+
     # raw edition utilities ###################################################
 
     def set_attributes(self, _cw_unsafe=False, **kwargs):
--- a/schema.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/schema.py	Thu Mar 04 17:56:45 2010 +0100
@@ -50,8 +50,9 @@
     ))
 
 WORKFLOW_TYPES = set(('Transition', 'State', 'TrInfo', 'Workflow',
-                         'WorkflowTransition', 'BaseTransition',
-                         'SubWorkflowExitPoint'))
+                      'WorkflowTransition', 'BaseTransition',
+                      'SubWorkflowExitPoint'))
+
 INTERNAL_TYPES = set(('CWProperty', 'CWPermission', 'CWCache', 'ExternalUri'))
 
 
@@ -63,6 +64,31 @@
 ybo.RDEF_PROPERTIES += ('eid',)
 
 
+PUB_SYSTEM_ENTITY_PERMS = {
+    'read':   ('managers', 'users', 'guests',),
+    'add':    ('managers',),
+    'delete': ('managers',),
+    'update': ('managers',),
+    }
+PUB_SYSTEM_REL_PERMS = {
+    'read':   ('managers', 'users', 'guests',),
+    'add':    ('managers',),
+    'delete': ('managers',),
+    }
+PUB_SYSTEM_ATTR_PERMS = {
+    'read':   ('managers', 'users', 'guests',),
+    'update':    ('managers',),
+    }
+RO_REL_PERMS = {
+    'read':   ('managers', 'users', 'guests',),
+    'add':    (),
+    'delete': (),
+    }
+RO_ATTR_PERMS = {
+    'read':   ('managers', 'users', 'guests',),
+    'update': (),
+    }
+
 # XXX same algorithm as in reorder_cubes and probably other place,
 # may probably extract a generic function
 def order_eschemas(eschemas):
@@ -369,7 +395,8 @@
         if need_has_text is None:
             need_has_text = may_need_has_text
         if need_has_text and not has_has_text and not deletion:
-            rdef = ybo.RelationDefinition(self.type, 'has_text', 'String')
+            rdef = ybo.RelationDefinition(self.type, 'has_text', 'String',
+                                          __permissions__=RO_ATTR_PERMS)
             self.schema.add_relation_def(rdef)
         elif not need_has_text and has_has_text:
             self.schema.del_relation_def(self.type, 'has_text', 'String')
@@ -491,9 +518,11 @@
         if not eschema.final:
             # automatically add the eid relation to non final entity types
             rdef = ybo.RelationDefinition(eschema.type, 'eid', 'Int',
-                                          cardinality='11', uid=True)
+                                          cardinality='11', uid=True,
+                                          __permissions__=RO_ATTR_PERMS)
             self.add_relation_def(rdef)
-            rdef = ybo.RelationDefinition(eschema.type, 'identity', eschema.type)
+            rdef = ybo.RelationDefinition(eschema.type, 'identity', eschema.type,
+                                          __permissions__=RO_REL_PERMS)
             self.add_relation_def(rdef)
         self._eid_index[eschema.eid] = eschema
         return eschema
@@ -1054,8 +1083,16 @@
         cw = entity._cw
     elif form is not None:
         cw = form._cw
-    if cw is not None and cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT):
-        return self.regular_formats + tuple(NEED_PERM_FORMATS)
+    if cw is not None:
+        if hasattr(cw, 'is_super_session'):
+            # cw is a server session
+            hasperm = cw.is_super_session or \
+                      not cw.vreg.config.is_hook_category_activated('integrity') or \
+                      cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
+        else:
+            hasperm = cw.user.has_permission(PERM_USE_TEMPLATE_FORMAT)
+        if hasperm:
+            return self.regular_formats + tuple(NEED_PERM_FORMATS)
     return self.regular_formats
 
 # XXX monkey patch PyFileReader.import_erschema until bw_normalize_etype is
--- a/schemas/__init__.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/schemas/__init__.py	Thu Mar 04 17:56:45 2010 +0100
@@ -1,38 +1,25 @@
 """some utilities to define schema permissions
 
 :organization: Logilab
-:copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2008-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
 
 from rql.utils import quote
-from cubicweb.schema import ERQLExpression, RRQLExpression
+from cubicweb.schema import RO_REL_PERMS, RO_ATTR_PERMS, \
+     PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, \
+     ERQLExpression, RRQLExpression
 
 # permissions for "meta" entity type (readable by anyone, can only be
 # added/deleted by managers)
-META_ETYPE_PERMS = {
-    'read':   ('managers', 'users', 'guests',),
-    'add':    ('managers',),
-    'delete': ('managers',),
-    'update': ('managers', 'owners',),
-    }
-
+META_ETYPE_PERMS = PUB_SYSTEM_ENTITY_PERMS # XXX deprecates
 # permissions for "meta" relation type (readable by anyone, can only be
 # added/deleted by managers)
-META_RTYPE_PERMS = {
-    'read':   ('managers', 'users', 'guests',),
-    'add':    ('managers',),
-    'delete': ('managers',),
-    }
-
+META_RTYPE_PERMS = PUB_SYSTEM_REL_PERMS # XXX deprecates
 # permissions for relation type that should only set by hooks using unsafe
 # execute, readable by anyone
-HOOKS_RTYPE_PERMS = {
-    'read':   ('managers', 'users', 'guests',),
-    'add':    (),
-    'delete': (),
-    }
+HOOKS_RTYPE_PERMS = RO_REL_PERMS # XXX deprecates
 
 def _perm(names):
     if isinstance(names, (list, tuple)):
--- a/schemas/base.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/schemas/base.py	Thu Mar 04 17:56:45 2010 +0100
@@ -10,9 +10,9 @@
 
 from yams.buildobjs import (EntityType, RelationType, SubjectRelation,
                             String, Datetime, Password)
-from cubicweb.schema import (RQLConstraint, WorkflowableEntityType,
-                             ERQLExpression, RRQLExpression)
-from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
+from cubicweb.schema import (
+    RQLConstraint, WorkflowableEntityType, ERQLExpression, RRQLExpression,
+    PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS)
 
 class CWUser(WorkflowableEntityType):
     """define a CubicWeb user"""
@@ -85,7 +85,7 @@
 
 class in_group(RelationType):
     """core relation indicating a user's groups"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
 
 class owned_by(RelationType):
     """core relation indicating owners of an entity. This relation
@@ -118,18 +118,21 @@
 
 class creation_date(RelationType):
     """creation time of an entity"""
+    __permissions__ = PUB_SYSTEM_ATTR_PERMS
     cardinality = '11'
     subject = '*'
     object = 'Datetime'
 
 class modification_date(RelationType):
     """latest modification time of an entity"""
+    __permissions__ = PUB_SYSTEM_ATTR_PERMS
     cardinality = '11'
     subject = '*'
     object = 'Datetime'
 
 class cwuri(RelationType):
     """internal entity uri"""
+    __permissions__ = PUB_SYSTEM_ATTR_PERMS
     cardinality = '11'
     subject = '*'
     object = 'String'
@@ -155,7 +158,7 @@
 class CWPermission(EntityType):
     """entity type that may be used to construct some advanced security configuration
     """
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
 
     name = String(required=True, indexed=True, internationalizable=True, maxsize=100,
                   description=_('name or identifier of the permission'))
@@ -170,11 +173,11 @@
     """link a permission to the entity. This permission should be used in the
     security definition of the entity's type to be useful.
     """
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
 
 class require_group(RelationType):
     """used to grant a permission to a group"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
 
 
 class ExternalUri(EntityType):
@@ -209,6 +212,8 @@
 
     Also, checkout the AppObject.get_cache() method.
     """
+    # XXX only handle by hooks, shouldn't be readable/editable at all through
+    # the ui and so no permissions should be granted, no?
     __permissions__ = {
         'read':   ('managers', 'users', 'guests'),
         'add':    ('managers',),
--- a/schemas/bootstrap.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/schemas/bootstrap.py	Thu Mar 04 17:56:45 2010 +0100
@@ -10,14 +10,16 @@
 
 from yams.buildobjs import (EntityType, RelationType, RelationDefinition,
                             SubjectRelation, RichString, String, Boolean, Int)
-from cubicweb.schema import RQLConstraint
-from cubicweb.schemas import META_ETYPE_PERMS, META_RTYPE_PERMS
+from cubicweb.schema import (
+    RQLConstraint,
+    PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, PUB_SYSTEM_ATTR_PERMS
+    )
 
 # not restricted since as "is" is handled as other relations, guests need
 # access to this
 class CWEType(EntityType):
     """define an entity type, used to build the instance schema"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
     description = RichString(internationalizable=True,
@@ -28,7 +30,7 @@
 
 class CWRType(EntityType):
     """define a relation type, used to build the instance schema"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
     description = RichString(internationalizable=True,
@@ -48,7 +50,7 @@
 
     used to build the instance schema
     """
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final TRUE')],
                                     composite='object')
@@ -85,7 +87,7 @@
 
     used to build the instance schema
     """
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     relation_type = SubjectRelation('CWRType', cardinality='1*',
                                     constraints=[RQLConstraint('O final FALSE')],
                                     composite='object')
@@ -116,7 +118,7 @@
 # not restricted since it has to be read when checking allowed transitions
 class RQLExpression(EntityType):
     """define a rql expression used to define permissions"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     exprtype = String(required=True, vocabulary=['ERQLExpression', 'RRQLExpression'])
     mainvars = String(maxsize=8,
                       description=_('name of the main variables which should be '
@@ -134,14 +136,14 @@
 
 class CWConstraint(EntityType):
     """define a schema constraint"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     cstrtype = SubjectRelation('CWConstraintType', cardinality='1*')
     value = String(description=_('depends on the constraint type'))
 
 
 class CWConstraintType(EntityType):
     """define a schema constraint type"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
@@ -149,7 +151,7 @@
 # not restricted since it has to be read when checking allowed transitions
 class CWGroup(EntityType):
     """define a CubicWeb users group"""
-    __permissions__ = META_ETYPE_PERMS
+    __permissions__ = PUB_SYSTEM_ENTITY_PERMS
     name = String(required=True, indexed=True, internationalizable=True,
                   unique=True, maxsize=64)
 
@@ -173,32 +175,32 @@
 
 class relation_type(RelationType):
     """link a relation definition to its relation type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     inlined = True
 
 class from_entity(RelationType):
     """link a relation definition to its subject entity type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     inlined = True
 
 class to_entity(RelationType):
     """link a relation definition to its object entity type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     inlined = True
 
 class constrained_by(RelationType):
     """constraints applying on this relation"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
 
 class cstrtype(RelationType):
     """constraint factory"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     inlined = True
 
 
 class read_permission_cwgroup(RelationDefinition):
     """groups allowed to read entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'read_permission'
     subject = ('CWEType', 'CWAttribute', 'CWRelation')
     object = 'CWGroup'
@@ -206,7 +208,7 @@
 
 class add_permission_cwgroup(RelationDefinition):
     """groups allowed to add entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'add_permission'
     subject = ('CWEType', 'CWRelation')
     object = 'CWGroup'
@@ -214,7 +216,7 @@
 
 class delete_permission_cwgroup(RelationDefinition):
     """groups allowed to delete entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'delete_permission'
     subject = ('CWEType', 'CWRelation')
     object = 'CWGroup'
@@ -222,7 +224,7 @@
 
 class update_permission_cwgroup(RelationDefinition):
     """groups allowed to update entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'update_permission'
     subject = ('CWEType', 'CWAttribute')
     object = 'CWGroup'
@@ -230,7 +232,7 @@
 
 class read_permission_rqlexpr(RelationDefinition):
     """rql expression allowing to read entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'read_permission'
     subject = ('CWEType', 'CWAttribute', 'CWRelation')
     object = 'RQLExpression'
@@ -239,7 +241,7 @@
 
 class add_permission_rqlexpr(RelationDefinition):
     """rql expression allowing to add entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'add_permission'
     subject = ('CWEType', 'CWRelation')
     object = 'RQLExpression'
@@ -248,7 +250,7 @@
 
 class delete_permission_rqlexpr(RelationDefinition):
     """rql expression allowing to delete entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'delete_permission'
     subject = ('CWEType', 'CWRelation')
     object = 'RQLExpression'
@@ -257,7 +259,7 @@
 
 class update_permission_rqlexpr(RelationDefinition):
     """rql expression allowing to update entities/relations of this type"""
-    __permissions__ = META_RTYPE_PERMS
+    __permissions__ = PUB_SYSTEM_REL_PERMS
     name = 'update_permission'
     subject = ('CWEType', 'CWAttribute')
     object = 'RQLExpression'
@@ -305,3 +307,13 @@
     cardinality = '?*'
     subject = 'CWEType'
     object = 'CWEType'
+
+def post_build_callback(schema):
+    """set attributes permissions for schema/workflow entities"""
+    from cubicweb.schema import SCHEMA_TYPES, WORKFLOW_TYPES, META_RTYPES
+    for eschema in schema.entities():
+        if eschema in SCHEMA_TYPES or eschema in WORKFLOW_TYPES:
+            for rschema in eschema.subject_relations():
+                if rschema.final and not rschema in META_RTYPES:
+                    rdef = eschema.rdef(rschema)
+                    rdef.permissions = PUB_SYSTEM_ATTR_PERMS
--- a/server/__init__.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/__init__.py	Thu Mar 04 17:56:45 2010 +0100
@@ -186,6 +186,7 @@
     handler.install_custom_sql_scripts(join(CW_SOFTWARE_ROOT, 'schemas'), driver)
     for directory in reversed(config.cubes_path()):
         handler.install_custom_sql_scripts(join(directory, 'schema'), driver)
+    # serialize the schema
     initialize_schema(config, schema, handler)
     # yoo !
     cnx.commit()
--- a/server/querier.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/querier.py	Thu Mar 04 17:56:45 2010 +0100
@@ -358,6 +358,7 @@
         self.preprocess(rqlst, security=False)
         return rqlst
 
+
 class InsertPlan(ExecutionPlan):
     """an execution model specific to the INSERT rql query
     """
--- a/server/repository.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/repository.py	Thu Mar 04 17:56:45 2010 +0100
@@ -105,7 +105,8 @@
     #     skip that for super session (though we can still skip it for internal
     #     sessions). Also we should imo rely on the orm to first fetch existing
     #     entity if any then delete it.
-    if session.is_internal_session:
+    if session.is_internal_session \
+           or not session.vreg.config.is_hook_category_activated('integrity'):
         return
     card = session.schema_rproperty(rtype, eidfrom, eidto, 'cardinality')
     # one may be tented to check for neweids but this may cause more than one
--- a/server/schemaserial.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/schemaserial.py	Thu Mar 04 17:56:45 2010 +0100
@@ -51,9 +51,6 @@
     return res
 
 # schema / perms deserialization ##############################################
-OLD_SCHEMA_TYPES = frozenset(('EFRDef', 'ENFRDef', 'ERType', 'EEType',
-                              'EConstraintType', 'EConstraint', 'EGroup',
-                              'EUser', 'ECache', 'EPermission', 'EProperty'))
 
 def deserialize_schema(schema, session):
     """return a schema according to information stored in an rql database
@@ -90,12 +87,6 @@
                                {'x': eid, 'n': netype})
             session.system_sql('UPDATE entities SET type=%(n)s WHERE type=%(x)s',
                                {'x': etype, 'n': netype})
-            # XXX should be donne as well on sqlite based sources
-            if not etype in OLD_SCHEMA_TYPES and \
-               (getattr(dbhelper, 'case_sensitive', False)
-                or etype.lower() != netype.lower()):
-                session.system_sql('ALTER TABLE %s%s RENAME TO %s%s' % (
-                    sqlutils.SQL_PREFIX, etype, sqlutils.SQL_PREFIX, netype))
             session.commit(False)
             try:
                 session.system_sql('UPDATE deleted_entities SET type=%(n)s WHERE type=%(x)s',
@@ -182,6 +173,17 @@
             res.setdefault(eid, {}).setdefault(action, []).append( (expr, mainvars, expreid) )
     return res
 
+def deserialize_rdef_constraints(session):
+    """return the list of relation definition's constraints as instances"""
+    res = {}
+    for rdefeid, ceid, ct, val in session.execute(
+        'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, '
+        'X cstrtype T, T name TN, X value V', build_descr=False):
+        cstr = CONSTRAINTS[ct].deserialize(val)
+        cstr.eid = ceid
+        res.setdefault(rdefeid, []).append(cstr)
+    return res
+
 def set_perms(erschema, permsdict):
     """set permissions on the given erschema according to the permission
     definition dictionary as built by deserialize_ertype_permissions for a
@@ -202,21 +204,9 @@
             for p in somethings)
 
 
-def deserialize_rdef_constraints(session):
-    """return the list of relation definition's constraints as instances"""
-    res = {}
-    for rdefeid, ceid, ct, val in session.execute(
-        'Any E, X,TN,V WHERE E constrained_by X, X is CWConstraint, '
-        'X cstrtype T, T name TN, X value V', build_descr=False):
-        cstr = CONSTRAINTS[ct].deserialize(val)
-        cstr.eid = ceid
-        res.setdefault(rdefeid, []).append(cstr)
-    return res
-
-
 # schema / perms serialization ################################################
 
-def serialize_schema(cursor, schema, verbose=False):
+def serialize_schema(cursor, schema):
     """synchronize schema and permissions in the database according to
     current schema
     """
@@ -224,37 +214,43 @@
     if not quiet:
         _title = '-> storing the schema in the database '
         print _title,
-    execute = cursor.execute
+    execute = cursor.unsafe_execute
     eschemas = schema.entities()
-    aller = eschemas + schema.relations()
-    if not verbose and not quiet:
-        pb_size = len(aller) + len(CONSTRAINTS) + len([x for x in eschemas if x.specializes()])
+    if not quiet:
+        pb_size = (len(eschemas + schema.relations())
+                   + len(CONSTRAINTS)
+                   + len([x for x in eschemas if x.specializes()]))
         pb = ProgressBar(pb_size, title=_title)
     else:
         pb = None
+    groupmap = group_mapping(cursor, interactive=False)
+    # serialize all entity types, assuring CWEType is serialized first for proper
+    # is / is_instance_of insertion
+    eschemas.remove(schema.eschema('CWEType'))
+    eschemas.insert(0, schema.eschema('CWEType'))
+    for eschema in eschemas:
+        for rql, kwargs in eschema2rql(eschema, groupmap):
+            execute(rql, kwargs, build_descr=False)
+        if pb is not None:
+            pb.update()
+    # serialize constraint types
     rql = 'INSERT CWConstraintType X: X name %(ct)s'
     for cstrtype in CONSTRAINTS:
-        if verbose:
-            print rql
         execute(rql, {'ct': unicode(cstrtype)}, build_descr=False)
         if pb is not None:
             pb.update()
-    groupmap = group_mapping(cursor, interactive=False)
-    for ertype in aller:
-        # skip eid and has_text relations
-        if ertype in VIRTUAL_RTYPES:
+    # serialize relations
+    for rschema in schema.relations():
+        # skip virtual relations such as eid, has_text and identity
+        if rschema in VIRTUAL_RTYPES:
             if pb is not None:
                 pb.update()
             continue
-        for rql, kwargs in erschema2rql(schema[ertype], groupmap):
-            if verbose:
-                print rql % kwargs
+        for rql, kwargs in rschema2rql(rschema, groupmap=groupmap):
             execute(rql, kwargs, build_descr=False)
         if pb is not None:
             pb.update()
     for rql, kwargs in specialize2rql(schema):
-        if verbose:
-            print rql % kwargs
         execute(rql, kwargs, build_descr=False)
         if pb is not None:
             pb.update()
--- a/server/serverconfig.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/serverconfig.py	Thu Mar 04 17:56:45 2010 +0100
@@ -185,6 +185,8 @@
     # check user's state at login time
     consider_user_state = True
 
+    # XXX hooks control stuff should probably be on the session, not on the config
+
     # hooks activation configuration
     # all hooks should be activated during normal execution
     disabled_hooks_categories = set()
@@ -232,9 +234,13 @@
 
     @classmethod
     def is_hook_activated(cls, hook):
+        return cls.is_hook_category_activated(hook.category)
+
+    @classmethod
+    def is_hook_category_activated(cls, category):
         if cls.hooks_mode is cls.DENY_ALL:
-            return hook.category in cls.enabled_hooks_categories
-        return hook.category not in cls.disabled_hooks_categories
+            return category in cls.enabled_hooks_categories
+        return category not in cls.disabled_hooks_categories
 
     # should some hooks be deactivated during [pre|post]create script execution
     free_wheel = False
--- a/server/session.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/session.py	Thu Mar 04 17:56:45 2010 +0100
@@ -499,7 +499,7 @@
                         operation.handle_event('revert%s_event' % trstate)
                     # XXX use slice notation since self.pending_operations is a
                     # read-only property.
-                    self.pending_operations[:] = processed + self.pending_operations 
+                    self.pending_operations[:] = processed + self.pending_operations
                     self.rollback(reset_pool)
                     raise
             self.pool.commit()
--- a/server/sources/rql2sql.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/sources/rql2sql.py	Thu Mar 04 17:56:45 2010 +0100
@@ -41,6 +41,8 @@
 from cubicweb.server.sqlutils import SQL_PREFIX
 from cubicweb.server.utils import cleanup_solutions
 
+ColumnAlias._q_invariant = False # avoid to check for ColumnAlias / Variable
+
 def _new_var(select, varname):
     newvar = select.get_variable(varname)
     if not 'relations' in newvar.stinfo:
@@ -711,7 +713,7 @@
         return '%s=%s' % (lhssql, rhsvar.accept(self))
 
     def _process_relation_term(self, relation, rid, termvar, termconst, relfield):
-        if termconst or isinstance(termvar, ColumnAlias) or not termvar._q_invariant:
+        if termconst or not termvar._q_invariant:
             termsql = termconst and termconst.accept(self) or termvar.accept(self)
             yield '%s.%s=%s' % (rid, relfield, termsql)
         elif termvar._q_invariant:
--- a/server/test/unittest_querier.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/test/unittest_querier.py	Thu Mar 04 17:56:45 2010 +0100
@@ -257,7 +257,7 @@
         result, descr = rset.rows, rset.description
         self.assertEquals(descr[0][0], 'String')
         self.assertEquals(descr[0][1], 'Int')
-        self.assertEquals(result[0][0], 'RQLExpression') # XXX may change as schema evolve
+        self.assertEquals(result[0][0], 'CWRelation') # XXX may change as schema evolve
 
     def test_select_groupby_orderby(self):
         rset = self.execute('Any N GROUPBY N ORDERBY N WHERE X is CWGroup, X name N')
@@ -862,6 +862,14 @@
         self.assert_(rset.rows)
         self.assertEquals(rset.description, [('Personne', 'Societe',)])
 
+    def test_insert_5bis(self):
+        peid = self.execute("INSERT Personne X: X nom 'bidule'")[0][0]
+        self.execute("INSERT Societe Y: Y nom 'toto', X travaille Y WHERE X eid %(x)s",
+                     {'x': peid}, 'x')
+        rset = self.execute('Any X, Y WHERE X nom "bidule", Y nom "toto", X travaille Y')
+        self.assert_(rset.rows)
+        self.assertEquals(rset.description, [('Personne', 'Societe',)])
+
     def test_insert_6(self):
         self.execute("INSERT Personne X, Societe Y: X nom 'bidule', Y nom 'toto', X travaille Y")
         rset = self.execute('Any X, Y WHERE X nom "bidule", Y nom "toto", X travaille Y')
--- a/server/test/unittest_schemaserial.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/test/unittest_schemaserial.py	Thu Mar 04 17:56:45 2010 +0100
@@ -149,7 +149,6 @@
                                ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
                                ('SET X add_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
                                ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 3}),
                                ('SET X delete_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
                                ])
 
@@ -168,11 +167,6 @@
                                ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 1}),
                                ('SET X read_permission Y WHERE Y eid %(g)s, ', {'g': 2}),
                                ('SET X update_permission Y WHERE Y eid %(g)s, ', {'g': 0}),
-                               ('INSERT RQLExpression E: '
-                                'E expression %(e)s, E exprtype %(t)s, E mainvars %(v)s, '
-                                'X update_permission E '
-                                'WHERE ', # completed by the outer function
-                                {'e': u'U has_update_permission X', 't': u'ERQLExpression', 'v': u'X'}),
                                ])
 
     #def test_perms2rql(self):
--- a/server/test/unittest_security.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/server/test/unittest_security.py	Thu Mar 04 17:56:45 2010 +0100
@@ -257,6 +257,26 @@
         self.assertEquals(rset.rows, [[aff2]])
         rset = cu.execute('Affaire X WHERE NOT X eid %(x)s', {'x': aff2}, 'x')
         self.assertEquals(rset.rows, [])
+        # test can't update an attribute of an entity that can't be readen
+        self.assertRaises(Unauthorized, cu.execute, 'SET X sujet "hacked" WHERE X eid %(x)s', {'x': eid}, 'x')
+
+
+    def test_entity_created_in_transaction(self):
+        affschema = self.schema['Affaire']
+        origperms = affschema.permissions['read']
+        affschema.set_action_permissions('read', affschema.permissions['add'])
+        try:
+            cnx = self.login('iaminusersgrouponly')
+            cu = cnx.cursor()
+            aff2 = cu.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
+            # entity created in transaction are readable *by eid*
+            self.failUnless(cu.execute('Any X WHERE X eid %(x)s', {'x':aff2}, 'x'))
+            # XXX would be nice if it worked
+            rset = cu.execute("Affaire X WHERE X sujet 'cool'")
+            self.assertEquals(len(rset), 0)
+        finally:
+            affschema.set_action_permissions('read', origperms)
+            cnx.close()
 
     def test_read_erqlexpr_has_text1(self):
         aff1 = self.execute("INSERT Affaire X: X sujet 'cool'")[0][0]
--- a/web/data/jquery.corner.js	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/data/jquery.corner.js	Thu Mar 04 17:56:45 2010 +0100
@@ -140,7 +140,7 @@
                         this.style.position = 'relative';
                     ds.position = 'absolute';
                     ds.bottom = ds.left = ds.padding = ds.margin = '0';
-                    if ($.browser.msie)
+                    if (($.browser.msie) && ($.browser.version < 8.0))
                         ds.setExpression('width', 'this.parentNode.offsetWidth');
                     else
                         ds.width = '100%';
--- a/web/form.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/form.py	Thu Mar 04 17:56:45 2010 +0100
@@ -186,6 +186,11 @@
         # deleting validation errors here breaks form reloading (errors are
         # no more available), they have to be deleted by application's publish
         # method on successful commit
+        if hasattr(self, '_form_previous_values'):
+            # XXX behaviour changed in 3.6.1, warn
+            warn('[3.6.1] restore_previous_post already called, remove this call',
+                 DeprecationWarning, stacklevel=2)
+            return
         forminfo = self._cw.get_session_data(sessionkey, pop=True)
         if forminfo:
             self._form_previous_values = forminfo['values']
--- a/web/htmlwidgets.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/htmlwidgets.py	Thu Mar 04 17:56:45 2010 +0100
@@ -336,88 +336,3 @@
             yield column, self.model.sortvalue(rowindex, column.rset_sortcol)
 
 
-class ProgressBarWidget(HTMLWidget):
-    """display a progress bar widget"""
-    precision = 0.1
-    red_threshold = 1.1
-    orange_threshold = 1.05
-    yellow_threshold = 1
-
-    def __init__(self, done, todo, total):
-        self.done = done
-        self.todo = todo
-        self.budget = total
-
-    @property
-    def overrun(self):
-        """overrun = done + todo - """
-        if self.done + self.todo > self.budget:
-            overrun = self.done + self.todo - self.budget
-        else:
-            overrun = 0
-        if overrun < self.precision:
-            overrun = 0
-        return overrun
-
-    @property
-    def overrun_percentage(self):
-        """pourcentage overrun = overrun / budget"""
-        if self.budget == 0:
-            return 0
-        else:
-            return self.overrun * 100. / self.budget
-
-    def _render(self):
-        done = self.done
-        todo = self.todo
-        budget = self.budget
-        if budget == 0:
-            pourcent = 100
-        else:
-            pourcent = done*100./budget
-        if pourcent > 100.1:
-            color = 'red'
-        elif todo+done > self.red_threshold*budget:
-            color = 'red'
-        elif todo+done > self.orange_threshold*budget:
-            color = 'orange'
-        elif todo+done > self.yellow_threshold*budget:
-            color = 'yellow'
-        else:
-            color = 'green'
-        if pourcent < 0:
-            pourcent = 0
-
-        if floor(done) == done or done>100:
-            done_str = '%i' % done
-        else:
-            done_str = '%.1f' % done
-        if floor(budget) == budget or budget>100:
-            budget_str = '%i' % budget
-        else:
-            budget_str = '%.1f' % budget
-
-        title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent)
-        short_title = title
-        if self.overrun_percentage:
-            title += u' overrun +%sj (+%i%%)' % (self.overrun,
-                                                 self.overrun_percentage)
-            overrun = self.overrun
-            if floor(overrun) == overrun or overrun>100:
-                overrun_str = '%i' % overrun
-            else:
-                overrun_str = '%.1f' % overrun
-            short_title += u' +%s' % overrun_str
-        # write bars
-        maxi = max(done+todo, budget)
-        if maxi == 0:
-            maxi = 1
-
-        cid = random.randint(0, 100000)
-        self.w(u'%s<br/>'
-               u'<canvas class="progressbar" id="canvas%i" width="100" height="10"></canvas>'
-               u'<script type="application/x-javascript">'
-               u'draw_progressbar("canvas%i", %i, %i, %i, "%s");</script>'
-               % (short_title.replace(' ','&nbsp;'), cid, cid,
-                  int(100.*done/maxi), int(100.*(done+todo)/maxi),
-                  int(100.*budget/maxi), color))
--- a/web/test/unittest_views_editforms.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/test/unittest_views_editforms.py	Thu Mar 04 17:56:45 2010 +0100
@@ -43,13 +43,11 @@
                            ('firstname', 'subject'),
                            ('surname', 'subject'),
                            ('in_group', 'subject'),
-                           ('eid', 'subject'),
                            ])
         self.assertListEquals(rbc(e, 'muledit', 'attributes'),
                               [('login', 'subject'),
                                ('upassword', 'subject'),
                                ('in_group', 'subject'),
-                               ('eid', 'subject'),
                                ])
         self.assertListEquals(rbc(e, 'main', 'metadata'),
                               [('last_login_time', 'subject'),
@@ -76,13 +74,10 @@
         # owned_by is defined both as subject and object relations on CWUser
         self.assertListEquals(sorted(x for x in rbc(e, 'main', 'hidden')
                                      if x != ('tags', 'object')),
-                              sorted([('has_text', 'subject'),
-                                      ('identity', 'subject'),
-                                      ('for_user', 'object'),
+                              sorted([('for_user', 'object'),
                                       ('created_by', 'object'),
                                       ('wf_info_for', 'object'),
                                       ('owned_by', 'object'),
-                                      ('identity', 'object'),
                                       ]))
 
     def test_inlined_view(self):
@@ -106,11 +101,9 @@
                                ('test', 'subject'),
                                ('description', 'subject'),
                                ('salary', 'subject'),
-                               ('eid', 'subject')
                                ])
         self.assertListEquals(rbc(e, 'muledit', 'attributes'),
                               [('nom', 'subject'),
-                               ('eid', 'subject')
                                ])
         self.assertListEquals(rbc(e, 'main', 'metadata'),
                               [('creation_date', 'subject'),
@@ -124,10 +117,7 @@
                                ('connait', 'object')
                                ])
         self.assertListEquals(rbc(e, 'main', 'hidden'),
-                              [('has_text', 'subject'),
-                               ('identity', 'subject'),
-                               ('identity', 'object'),
-                               ])
+                              [])
 
     def test_edition_form(self):
         rset = self.execute('CWUser X LIMIT 1')
--- a/web/uicfg.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/uicfg.py	Thu Mar 04 17:56:45 2010 +0100
@@ -455,7 +455,7 @@
     """XXX for < 3.6 bw compat"""
     def tag_relation(self, key, tag):
         warn('autoform_is_inlined is deprecated, use autoform_section '
-             'with formtype="inlined", section="attributes" or section="hidden"',
+             'with formtype="main", section="inlined"',
              DeprecationWarning, stacklevel=3)
         section = tag and 'inlined' or 'hidden'
         autoform_section.tag_relation(key, 'main', section)
--- a/web/views/autoform.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/autoform.py	Thu Mar 04 17:56:45 2010 +0100
@@ -661,7 +661,7 @@
             return []
         # XXX we should simply put eid in the generated section, no?
         return [(rtype, role) for rtype, _, role in self._relations_by_section(
-            'attributes', 'update', strict) if rtype != 'eid']
+            'attributes', 'update', strict)]
 
     def editable_relations(self):
         """return a sorted list of (relation's label, relation'schema, role) for
--- a/web/views/basetemplates.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/basetemplates.py	Thu Mar 04 17:56:45 2010 +0100
@@ -391,8 +391,9 @@
         self.w(u'<td id="lastcolumn">')
         self.w(u'</td>\n')
         self.w(u'</tr></table>\n')
-        self.wview('logform', rset=self.cw_rset, id='popupLoginBox', klass='hidden',
-                   title=False, showmessage=False)
+        if self._cw.cnx.anonymous_connection:
+            self.wview('logform', rset=self.cw_rset, id='popupLoginBox',
+                       klass='hidden', title=False, showmessage=False)
 
     def state_header(self):
         state = self._cw.search_state
--- a/web/views/formrenderers.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/formrenderers.py	Thu Mar 04 17:56:45 2010 +0100
@@ -132,9 +132,9 @@
             errors = form.remaining_errors()
             if errors:
                 if len(errors) > 1:
-                    templstr = '<li>%s</li>\n'
+                    templstr = u'<li>%s</li>\n'
                 else:
-                    templstr = '&#160;%s\n'
+                    templstr = u'&#160;%s\n'
                 for field, err in errors:
                     if field is None:
                         errormsg += templstr % err
--- a/web/views/ibreadcrumbs.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/ibreadcrumbs.py	Thu Mar 04 17:56:45 2010 +0100
@@ -104,7 +104,7 @@
 class BreadCrumbView(EntityView):
     __regid__ = 'breadcrumbs'
 
-    def cell_call(self, row, col):
+    def cell_call(self, row, col, **kwargs):
         entity = self.cw_rset.get_entity(row, col)
         desc = xml_escape(uilib.cut(entity.dc_description(), 50))
         # XXX remember camember : tags.a autoescapes !
@@ -115,7 +115,7 @@
 class BreadCrumbTextView(EntityView):
     __regid__ = 'breadcrumbtext'
 
-    def cell_call(self, row, col):
+    def cell_call(self, row, col, **kwargs):
         entity = self.cw_rset.get_entity(row, col)
         textsize = self._cw.property_value('navigation.short-line-size')
         self.w(uilib.cut(entity.dc_title(), textsize))
--- a/web/views/iprogress.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/iprogress.py	Thu Mar 04 17:56:45 2010 +0100
@@ -8,13 +8,15 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+from math import floor
+
 from logilab.mtconverter import xml_escape
 
+from cubicweb.utils import make_uid
 from cubicweb.selectors import implements
 from cubicweb.interfaces import IProgress, IMileStone
 from cubicweb.schema import display_name
 from cubicweb.view import EntityView
-from cubicweb.web.htmlwidgets import ProgressBarWidget
 
 
 class ProgressTableView(EntityView):
@@ -182,11 +184,85 @@
     title = _('progress bar')
     __select__ = implements(IProgress)
 
+    precision = 0.1
+    red_threshold = 1.1
+    orange_threshold = 1.05
+    yellow_threshold = 1
+
+    @classmethod
+    def overrun(cls, entity):
+        """overrun = done + todo - """
+        if entity.done + entity.todo > entity.revised_cost:
+            overrun = entity.done + entity.todo - entity.revised_cost
+        else:
+            overrun = 0
+        if overrun < cls.precision:
+            overrun = 0
+        return overrun
+
+    @classmethod
+    def overrun_percentage(cls, entity):
+        """pourcentage overrun = overrun / budget"""
+        if entity.revised_cost == 0:
+            return 0
+        else:
+            return cls.overrun(entity) * 100. / entity.revised_cost
+
     def cell_call(self, row, col):
         self._cw.add_css('cubicweb.iprogress.css')
         self._cw.add_js('cubicweb.iprogress.js')
         entity = self.cw_rset.get_entity(row, col)
-        widget = ProgressBarWidget(entity.done, entity.todo,
-                                   entity.revised_cost)
-        self.w(widget.render())
+        done = entity.done
+        todo = entity.todo
+        budget = entity.revised_cost
+        if budget == 0:
+            pourcent = 100
+        else:
+            pourcent = done*100./budget
+        if pourcent > 100.1:
+            color = 'red'
+        elif todo+done > self.red_threshold*budget:
+            color = 'red'
+        elif todo+done > self.orange_threshold*budget:
+            color = 'orange'
+        elif todo+done > self.yellow_threshold*budget:
+            color = 'yellow'
+        else:
+            color = 'green'
+        if pourcent < 0:
+            pourcent = 0
 
+        if floor(done) == done or done>100:
+            done_str = '%i' % done
+        else:
+            done_str = '%.1f' % done
+        if floor(budget) == budget or budget>100:
+            budget_str = '%i' % budget
+        else:
+            budget_str = '%.1f' % budget
+
+        title = u'%s/%s = %i%%' % (done_str, budget_str, pourcent)
+        short_title = title
+        if self.overrun_percentage(entity):
+            title += u' overrun +%sj (+%i%%)' % (self.overrun(entity),
+                                                 self.overrun_percentage(entity))
+            overrun = self.overrun(entity)
+            if floor(overrun) == overrun or overrun>100:
+                overrun_str = '%i' % overrun
+            else:
+                overrun_str = '%.1f' % overrun
+            short_title += u' +%s' % overrun_str
+        # write bars
+        maxi = max(done+todo, budget)
+        if maxi == 0:
+            maxi = 1
+
+        cid = make_uid('progress_bar')
+        self._cw.html_headers.add_onload('draw_progressbar("canvas%s", %i, %i, %i, "%s");' %
+                                         (cid,
+                                          int(100.*done/maxi), int(100.*(done+todo)/maxi),
+                                          int(100.*budget/maxi), color),
+                                         jsoncall=self._cw.json_request)
+        self.w(u'%s<br/>'
+               u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>'
+               % (short_title.replace(' ','&nbsp;'), cid))
--- a/web/views/workflow.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/workflow.py	Thu Mar 04 17:56:45 2010 +0100
@@ -67,7 +67,7 @@
         form = self.get_form(entity, transition)
         self.w(u'<h4>%s %s</h4>\n' % (self._cw._(transition.name),
                                       entity.view('oneline')))
-        msg = self.req._('status will change from %(st1)s to %(st2)s') % {
+        msg = self._cw._('status will change from %(st1)s to %(st2)s') % {
             'st1': entity.printable_state,
             'st2': self._cw._(transition.destination(entity).name)}
         self.w(u'<p>%s</p>\n' % msg)
--- a/web/views/xmlrss.py	Thu Mar 04 17:26:43 2010 +0100
+++ b/web/views/xmlrss.py	Thu Mar 04 17:56:45 2010 +0100
@@ -188,11 +188,14 @@
         self.w(u'<guid isPermaLink="true">%s</guid>\n'
                % xml_escape(entity.absolute_url()))
         self.render_title_link(entity)
-        self._marker('description', entity.dc_description(format='text/html'))
+        self.render_description(entity)
         self._marker('dc:date', entity.dc_date(self.date_format))
         self.render_entity_creator(entity)
         self.w(u'</item>\n')
 
+    def render_description(self, entity):
+        self._marker('description', entity.dc_description(format='text/html'))
+
     def render_title_link(self, entity):
         self._marker('title', entity.dc_long_title())
         self._marker('link', entity.absolute_url())