Updated management interface
authorThierry Florac <thierry.florac@onf.fr>
Mon, 18 Jan 2016 17:42:32 +0100
changeset 14 234db8f05928
parent 13 35afd52c902b
child 15 d498aa030d6b
Updated management interface
src/pyams_content/shared/common/zmi/__init__.py
src/pyams_content/shared/common/zmi/dashboard.py
src/pyams_content/shared/common/zmi/header.py
src/pyams_content/shared/common/zmi/manager.py
src/pyams_content/shared/common/zmi/properties.py
src/pyams_content/shared/common/zmi/summary.py
src/pyams_content/shared/common/zmi/templates/header.pt
src/pyams_content/shared/common/zmi/templates/wf-operator-warning.pt
src/pyams_content/shared/common/zmi/templates/wf-owner-warning.pt
src/pyams_content/shared/common/zmi/templates/wf-transition-info.pt
src/pyams_content/shared/common/zmi/workflow.py
--- a/src/pyams_content/shared/common/zmi/__init__.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/__init__.py	Mon Jan 18 17:42:32 2016 +0100
@@ -26,7 +26,7 @@
 from pyams_sequence.interfaces import ISequentialIntIds, ISequentialIdInfo
 from pyams_skin.interfaces import IContentTitle
 from pyams_skin.interfaces.container import ITable, ITableElementEditor
-from pyams_skin.interfaces.viewlet import IContextActions, IMenuHeader
+from pyams_skin.interfaces.viewlet import IContextActions, IMenuHeader, IBreadcrumbItem
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import FORBIDDEN_PERMISSION
 from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowInfo, IWorkflowState, IWorkflowCommentInfo, IWorkflow
@@ -39,6 +39,7 @@
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.page import DefaultPageHeaderAdapter
 from pyams_skin.table import DefaultElementEditorAdapter
+from pyams_skin.viewlet.breadcrumb import BreadcrumbItem
 from pyams_skin.viewlet.toolbar import ToolbarMenuItem
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextAdapter, ContextRequestAdapter
@@ -74,6 +75,7 @@
 
     def updateWidgets(self, prefix=None):
         super(SharedContentAddForm, self).updateWidgets(prefix)
+        self.widgets['title'].description = _("This title can be modified afterwards")
         self.widgets['description'].label_css_class = 'textarea'
 
     def create(self, data):
@@ -119,6 +121,19 @@
 
 
 #
+# Shared tools common adapters
+#
+
+@adapter_config(context=(IWfSharedContent, IPyAMSLayer), provides=IBreadcrumbItem)
+class WfSharedContentBreadcrumbAdapter(BreadcrumbItem):
+    """Shared content breadcrumb adapter"""
+
+    @property
+    def label(self):
+        return II18n(self.context).query_attribute('short_name', request=self.request)
+
+
+#
 # Edit adapters and views
 #
 
@@ -171,7 +186,7 @@
 class WfSharedContentSiteManagementMenuHeader(ContextRequestAdapter):
     """Shared content site management menu header adapter"""
 
-    header = _("Manage this content")
+    header = ' '
 
 
 @adapter_config(context=(IWfSharedContent, IPyAMSLayer, Interface), provides=IContentTitle)
@@ -223,7 +238,7 @@
     """Shared content duplication form buttons"""
 
     close = CloseButton(name='close', title=_("Cancel"))
-    duplicate = button.Button(name='duplicate', title=_("Duplicate content"))
+    duplicate = button.Button(name='duplicate', title=_("Duplicate this content"))
 
 
 @pagelet_config(name='duplicate.html', context=IWfSharedContent, layer=IPyAMSLayer,
@@ -232,6 +247,8 @@
     """Shared content duplicate form"""
 
     legend = _("Duplicate content")
+    icon_css_class = 'fa fa-fw fa-files-o'
+
     fields = field.Fields(IWorkflowCommentInfo)
     buttons = button.Buttons(ISharedContentDuplicateButtons)
 
@@ -314,6 +331,7 @@
                 item_dc = IZopeDublinCore(item)
                 if item_dc.modified and (item_dc.modified > IZopeDublinCore(content).created):
                     translate = self.request.localizer.translate
-                    value += '<i class="fa fa-fw fa-circle txt-color-orange pull-right hint" title="{0}" ' \
-                             'data-ams-hint-gravity="e"></i>'.format(translate(_("Created or modified in this version")))
+                    value += '<i class="fa fa-fw fa-circle txt-color-orange hint align-middle padding-left-5" ' \
+                             'title="{0}" data-ams-hint-gravity="w"></i>'.format(
+                            translate(_("Created or modified in this version")))
         return value
--- a/src/pyams_content/shared/common/zmi/dashboard.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/dashboard.py	Mon Jan 18 17:42:32 2016 +0100
@@ -42,7 +42,7 @@
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.container import ContainerView
 from pyams_skin.page import DefaultPageHeaderAdapter
-from pyams_skin.table import BaseTable, I18nColumn, ActionColumn
+from pyams_skin.table import BaseTable, I18nColumn, NameColumn, JsActionColumn
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
@@ -69,14 +69,19 @@
 class BaseDashboardTable(BaseTable):
     """Base dashboard table"""
 
-    _title = '{0} contents'
+    _single_title = '{0} content'
+    _plural_title = '{0} contents'
 
     sortOn = None
     dt_sort_order = 'desc'
 
     @property
     def title(self):
-        return self.request.localizer.translate(self._title).format(len(self.values))
+        translate = self.request.localizer.translate
+        if len(self.values) < 2:
+            return translate(self._single_title).format(len(self.values))
+        else:
+            return translate(self._plural_title).format(len(self.values))
 
     @property
     def data_attributes(self):
@@ -92,13 +97,20 @@
         return tuple(super(BaseDashboardTable, self).values)
 
 
+@adapter_config(name='name', context=(Interface, IPyAMSLayer, ISharedToolDashboardTable), provides=IColumn)
+class SharedToolDashboardNameColumn(NameColumn):
+    """Shared tool dashboard name column"""
+
+    _header = _("Title")
+
+
 @adapter_config(context=(IWfSharedContent, IPyAMSLayer, ISharedToolDashboardTable), provides=ITableElementName)
 class SharedToolDashboardContentNameAdapter(ContextRequestViewAdapter):
     """Shared tool dashboard content name adapter"""
 
     @property
     def name(self):
-        return II18n(self.context).query_attribute('short_name', request=self.request)
+        return II18n(self.context).query_attribute('title', request=self.request)
 
 
 @adapter_config(name='sequence', context=(Interface, IPyAMSLayer, ISharedToolDashboardTable), provides=IColumn)
@@ -125,27 +137,6 @@
         return str(IWorkflowState(obj).version_id)
 
 
-@adapter_config(name='urgency', context=(Interface, IPyAMSLayer, ISharedToolDashboardTable), provides=IColumn)
-class SharedToolDashboardUrgencyColumn(ActionColumn):
-    """Shared tool dashboard urgency column"""
-
-    icon_class = 'fa fa-fw fa-exclamation-triangle txt-color-red'
-    icon_hint = _("Urgent request !")
-
-    url = '#'
-    weight = 19
-
-    def renderCell(self, item):
-        state = IWorkflowState(item)
-        if not state.state_urgency:
-            return ''
-        else:
-            return super(SharedToolDashboardUrgencyColumn, self).renderCell(item)
-
-    def get_url(self, item):
-        return self.url
-
-
 @adapter_config(name='status', context=(Interface, IPyAMSLayer, ISharedToolDashboardTable), provides=IColumn)
 class SharedToolDashboardStatusColumn(I18nColumn, GetAttrColumn):
     """Shared tool dashboard status column"""
@@ -156,7 +147,10 @@
     def getValue(self, obj):
         workflow = IWorkflow(obj)
         state = IWorkflowState(obj)
-        return self.request.localizer.translate(workflow.get_state_label(state.state))
+        result = self.request.localizer.translate(workflow.get_state_label(state.state))
+        if state.state_urgency:
+            result += ' <i class="fa fa-fw fa-exclamation-triangle txt-color-red"></i>'
+        return result
 
 
 @adapter_config(name='status_date', context=(Interface, IPyAMSLayer, ISharedToolDashboardTable), provides=IColumn)
@@ -263,7 +257,8 @@
 class SharedToolDashboardManagerWaitingTable(BaseDashboardTable):
     """Shared tool dashboard waiting table"""
 
-    _title = _("MANAGER - {0} content(s) waiting for your action")
+    _single_title = _("MANAGER - {0} content waiting for your action")
+    _plural_title = _("MANAGER - {0} contents waiting for your action")
 
     dt_sort_order = 'asc'
 
@@ -304,7 +299,8 @@
 class SharedToolDashboardOwnerWaitingTable(BaseDashboardTable):
     """Shared tool dashboard waiting owned contents table"""
 
-    _title = _("CONTRIBUTOR - Your {0} content(s) waiting for action")
+    _single_title = _("CONTRIBUTOR - {0} content waiting for action")
+    _plural_title = _("CONTRIBUTOR - {0} contents waiting for action")
 
     dt_sort_order = 'asc'
 
@@ -334,7 +330,14 @@
 class SharedToolDashboardOwnerModifiedTable(BaseDashboardTable):
     """Shared tool dashboard owned modified contents table"""
 
-    _title = _("CONTRIBUTOR - Your last modified contents (limited to {0})")
+    _single_title = _("CONTRIBUTOR - {0} modified content")
+
+    @property
+    def _plural_title(self):
+        if len(self.values) <= 50:
+            return _("CONTRIBUTOR - {0} modified contents")
+        else:
+            return _("CONTRIBUTOR - Last {0} modified contents")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolDashboardOwnerModifiedTable), provides=IValues)
@@ -350,7 +353,7 @@
         return unique(map(lambda x: sorted(IWorkflowVersions(x).get_versions(IWorkflowState(x).state),
                                            key=lambda y: IZopeDublinCore(y).modified, reverse=True)[0],
                           CatalogResultSet(CatalogQuery(catalog).query(params,
-                                                                       limit=IAdminProfile(self.request.principal).table_page_length,
+                                                                       limit=50,
                                                                        sort_index='modified_date',
                                                                        reverse=True))))
 
@@ -372,6 +375,102 @@
     
     
 #
+# My favorites
+# Dashboard of favorites contents
+#
+
+@viewlet_config(name='my-favorites.menu', context=ISharedTool, layer=IAdminLayer,
+                manager=IMyDashboardMenu, permission=VIEW_SYSTEM_PERMISSION, weight=3)
+class SharedToolFavoritesMenu(MenuItem):
+    """Shared tool favorites dashboard menu"""
+
+    label = _("My favorites")
+    icon_class = None
+    url = '#my-favorites.html'
+
+
+@implementer(ISharedToolDashboardTable)
+class SharedToolFavoritesTable(BaseDashboardTable):
+    """Shared tool favorites table"""
+
+    _single_title = _("CONTRIBUTOR - {0} favorite")
+    _plural_title = _("CONTRIBUTOR - {0} favorites")
+
+
+@adapter_config(name='show-hide', context=(Interface, IPyAMSLayer, SharedToolFavoritesTable),
+                provides=IColumn)
+class FavoritesColumnAddRemoveColumn(JsActionColumn):
+    """Favorites column add/remove column"""
+
+    cssClasses = {'th': 'action',
+                  'td': 'action switcher'}
+
+    icon_class = 'fa fa-fw fa-star'
+    icon_hint = _("Add/remove from favorites")
+
+    url = 'PyAMS_content.profile.switchFavorite'
+
+    weight = 1
+
+    def __init__(self, context, request, table):
+        super(FavoritesColumnAddRemoveColumn, self).__init__(context, request, table)
+        self.profile = IAdminProfile(request)
+
+    def get_icon(self, item):
+        oid = ISequentialIdInfo(item).hex_oid
+        icon_class = 'fa fa-fw fa-star{0}'.format('' if oid in (self.profile.favorites or ()) else '-o')
+        return '<i data-ams-plugins="pyams_content" ' \
+            'data-ams-plugin-pyams_content-src="/--static--/pyams_content/js/pyams_content{{MyAMS.devext}}.js" ' \
+            'data-ams-click-handler="PyAMS_content.profile.switchFavorite" data-ams-stop-propagation="true" ' \
+            'data-sequence-oid="{oid}" ' \
+            'class="{icon_class}"></i>'.format(
+                oid=oid,
+                title=self.request.localizer.translate(self.icon_hint),
+                icon_class=icon_class)
+
+    def get_url(self, item):
+        return ''
+
+
+@adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolFavoritesTable), provides=IValues)
+class SharedToolFavoritesValues(ContextRequestViewAdapter):
+    """Shared tool favorites values adapter"""
+
+    @property
+    def values(self):
+        catalog = get_utility(ICatalog)
+        profile = IAdminProfile(self.request.principal)
+        params = And(Eq(catalog['content_type'], self.context.shared_content_type),
+                     Any(catalog['oid'], profile.favorites or ()))
+        return unique(map(lambda x: IWorkflowVersions(x).get_last_versions()[0],
+                      CatalogResultSet(CatalogQuery(catalog).query(params,
+                                                                   sort_index='modified_date',
+                                                                   reverse=True))))
+
+
+@pagelet_config(name='my-favorites.html', context=ISharedTool, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@implementer(IInnerPage)
+class SharedToolFavoritesView(AdminView, ContainerView):
+    """Shared tool favorites view"""
+
+    table_class = SharedToolFavoritesTable
+
+
+@adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolFavoritesView), provides=IPageHeader)
+class SharedToolFavoritesHeaderAdapter(DefaultPageHeaderAdapter):
+    """Shared tool favorites header adapter"""
+
+    back_url = '#dashboard.html'
+    icon_class = 'fa fa-fw fa-user'
+
+    @property
+    def title(self):
+        return II18n(self.context).query_attribute('title', request=self.request)
+
+    subtitle = _("Your favorites")
+
+
+#
 # My preparations
 # Dashboard of owned and modified contents which can be updated
 #
@@ -379,7 +478,7 @@
 @viewlet_config(name='my-preparations.menu', context=ISharedTool, layer=IAdminLayer,
                 manager=IMyDashboardMenu, permission=VIEW_SYSTEM_PERMISSION, weight=5)
 class SharedToolPreparationsMenu(MenuItem):
-    """Site root preparations dashboard menu"""
+    """Shared tool preparations dashboard menu"""
 
     label = _("My preparations")
     icon_class = None
@@ -388,14 +487,15 @@
 
 @implementer(ISharedToolDashboardTable)
 class SharedToolPreparationsTable(BaseDashboardTable):
-    """Site root preparations table"""
+    """Shared tool preparations table"""
 
-    _title = _("CONTRIBUTOR - Your {0} prepared contents")
+    _single_title = _("CONTRIBUTOR - {0} prepared content")
+    _plural_title = _("CONTRIBUTOR - {0} prepared contents")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolPreparationsTable), provides=IValues)
 class SharedToolPreparationsValues(ContextRequestViewAdapter):
-    """Site root preparations values adapter"""
+    """Shared tool preparations values adapter"""
 
     @property
     def values(self):
@@ -404,7 +504,7 @@
         params = And(Eq(catalog['content_type'], self.context.shared_content_type),
                      Or(Eq(catalog['role:owner'], self.request.principal.id),
                         Eq(catalog['role:contributor'], self.request.principal.id)),
-                     Any(catalog['workflow_state'], workflow.update_states))
+                     Eq(catalog['workflow_state'], workflow.initial_state))
         return unique(CatalogResultSet(CatalogQuery(catalog).query(params,
                                                                    sort_index='modified_date',
                                                                    reverse=True)))
@@ -413,14 +513,14 @@
 @pagelet_config(name='my-preparations.html', context=ISharedTool, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @implementer(IInnerPage)
 class SharedToolPreparationsView(AdminView, ContainerView):
-    """Site root preparations view"""
+    """Shared tool preparations view"""
 
     table_class = SharedToolPreparationsTable
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolPreparationsView), provides=IPageHeader)
 class SharedToolPreparationsHeaderAdapter(DefaultPageHeaderAdapter):
-    """Site root preparations header adapter"""
+    """Shared tool preparations header adapter"""
 
     back_url = '#dashboard.html'
     icon_class = 'fa fa-fw fa-user'
@@ -433,6 +533,68 @@
 
 
 #
+# My submissions
+# Dashboard of owned and modified contents which are waiting for manager action
+#
+
+@viewlet_config(name='my-submissions.menu', context=ISharedTool, layer=IAdminLayer,
+                manager=IMyDashboardMenu, permission=VIEW_SYSTEM_PERMISSION, weight=7)
+class SharedToolSubmissionsMenu(MenuItem):
+    """Shared tool submissions dashboard menu"""
+
+    label = _("My submissions")
+    icon_class = None
+    url = '#my-submissions.html'
+
+
+@implementer(ISharedToolDashboardTable)
+class SharedToolSubmissionsTable(BaseDashboardTable):
+    """Shared tool submissions table"""
+
+    _single_title = _("CONTRIBUTOR - {0} submitted content")
+    _plural_title = _("CONTRIBUTOR - {0} submitted contents")
+
+
+@adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolSubmissionsTable), provides=IValues)
+class SharedToolSubmissionsValues(ContextRequestViewAdapter):
+    """Shared tool submissions values adapter"""
+
+    @property
+    def values(self):
+        catalog = get_utility(ICatalog)
+        workflow = get_utility(IWorkflow, name=self.context.shared_content_workflow)
+        params = And(Eq(catalog['content_type'], self.context.shared_content_type),
+                     Or(Eq(catalog['role:owner'], self.request.principal.id),
+                        Eq(catalog['role:contributor'], self.request.principal.id)),
+                     Any(catalog['workflow_state'], workflow.waiting_states))
+        return unique(CatalogResultSet(CatalogQuery(catalog).query(params,
+                                                                   sort_index='modified_date',
+                                                                   reverse=True)))
+
+
+@pagelet_config(name='my-submissions.html', context=ISharedTool, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
+@implementer(IInnerPage)
+class SharedToolSubmissionsView(AdminView, ContainerView):
+    """Shared tool submissions view"""
+
+    table_class = SharedToolSubmissionsTable
+
+
+@adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolSubmissionsView), provides=IPageHeader)
+class SharedToolSubmissionsHeaderAdapter(DefaultPageHeaderAdapter):
+    """Shared tool submissions header adapter"""
+
+    back_url = '#dashboard.html'
+    icon_class = 'fa fa-fw fa-user'
+
+    @property
+    def title(self):
+        return II18n(self.context).query_attribute('title', request=self.request)
+
+    subtitle = _("Your submitted contents")
+
+
+#
 # My publications
 # Dashboard of owned and modified contents which are published
 #
@@ -451,7 +613,8 @@
 class SharedToolPublicationsTable(BaseDashboardTable):
     """Shared tool publications table"""
 
-    _title = _("CONTRIBUTOR - Your {0} published contents")
+    _single_title = _("CONTRIBUTOR - {0} published content")
+    _plural_title = _("CONTRIBUTOR - {0} published contents")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolPublicationsTable), provides=IValues)
@@ -512,7 +675,8 @@
 class SharedToolRetiredContentsTable(BaseDashboardTable):
     """Shared tool retired contents table"""
 
-    _title = _("CONTRIBUTOR - Your {0} retired contents")
+    _single_title = _("CONTRIBUTOR - {0} retired content")
+    _plural_title = _("CONTRIBUTOR - {0} retired contents")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolRetiredContentsTable), provides=IValues)
@@ -574,7 +738,8 @@
 class SharedToolArchivedContentsTable(BaseDashboardTable):
     """Shared tool archived contents table"""
 
-    _title = _("CONTRIBUTOR - Your {0} archived contents")
+    _single_title = _("CONTRIBUTOR - {0} archived content")
+    _plural_title = _("CONTRIBUTOR - {0} archived contents")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolArchivedContentsTable), provides=IValues)
@@ -658,7 +823,14 @@
 class SharedToolAllPublicationsTable(BaseDashboardTable):
     """Shared tool published contents table"""
 
-    _title = _("CONTRIBUTORS - Last published contents (in the limit of 50)")
+    _single_title = _("CONTRIBUTORS - {0} published content")
+
+    @property
+    def _plural_title(self):
+        if len(self.values) <= 50:
+            return _("CONTRIBUTORS - Last {0} published contents")
+        else:
+            return _("CONTRIBUTORS - Last published contents (in the limit of 50)")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolAllPublicationsTable), provides=IValues)
@@ -719,7 +891,14 @@
 class SharedToolAllUpdatesTable(BaseDashboardTable):
     """Shared tool updated contents table"""
 
-    _title = _("CONTRIBUTORS - Last updated contents (in the limit of 50)")
+    _single_title = _("CONTRIBUTORS - {0} updated content")
+
+    @property
+    def _plural_title(self):
+        if len(self.values) <= 50:
+            return _("CONTRIBUTORS - Last {0} updated contents")
+        else:
+            return _("CONTRIBUTORS - Last updated contents (in the limit of 50)")
 
 
 @adapter_config(context=(ISharedTool, IPyAMSLayer, SharedToolAllUpdatesTable), provides=IValues)
--- a/src/pyams_content/shared/common/zmi/header.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/header.py	Mon Jan 18 17:42:32 2016 +0100
@@ -9,6 +9,7 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
+from pyams_content.profile.interfaces import IAdminProfile
 
 __docformat__ = 'restructuredtext'
 
@@ -31,6 +32,7 @@
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import contentprovider_config
+from pyramid.decorator import reify
 from zope.interface import Interface
 
 from pyams_content import _
@@ -61,27 +63,37 @@
         versions = IWorkflowVersions(context)
         state = IWorkflowState(context)
         self.version_id = state.version_id
+        # init state format
+        state_format = translate(_("{state} by {principal}"))
+        if state.state_urgency:
+            state_format = state_format.replace('{state}',
+                                                '{state} <i class="fa fa-fw fa-exclamation-triangle txt-color-red"></i>');
+        state_class = 'text-danger'  # if state.state in workflow.update_states else 'txt-color-text'
+        state_format = state_format.replace('{state}',
+                                            '<span class="{0}">{{state}}</span>'.format(state_class))
+        # init principal format
+        # principal_class = 'text-danger' if state.state_principal != owner else 'txt-color-text'
+        principal_class = 'text-danger' \
+            if state.state in (workflow.update_states | workflow.waiting_states) \
+            else 'txt-color-text'
+        state_format = state_format.replace('{principal}',
+                                            '<span class="{0}">{{principal}}</span>'.format(principal_class))
+        # get state label
         state_label = request.registry.queryAdapter(workflow, IWorkflowStateLabel, name=state.state)
         if state_label is None:
             state_label = request.registry.queryAdapter(workflow, IWorkflowStateLabel)
         if state_label is None:
-            self.state = translate(_("{state} by {{principal}}")).format(
-                state=translate(workflow.get_state_label(state.state)))
+            self.state = state_format.format(state=translate(workflow.get_state_label(state.state)),
+                                             principal=security.get_principal(state.state_principal).title)
         else:
-            self.state = state_label.get_label(context, request, format=False)
-        principal_class = 'text-danger' if state.state_principal != owner else 'txt-color-text'
-        self.state = self.state.replace('{principal}',
-                                        '<span class="{0}">{{principal}}</span>'.format(principal_class))
-        state_class = 'text-danger' if state.state in workflow.update_states else None
-        if state_class:
-            self.state = '<span class="{state_class}">{state}</span>'.format(state_class=state_class,
-                                                                             state=self.state)
-        self.state = self.state.format(principal=security.get_principal(state.state_principal).title)
+            self.state = state_format.format(state=state_label.get_label(context, request, format=False),
+                                             principal=security.get_principal(state.state_principal).title)
         self.state_date = translate(_("since {date}")).format(date=format_datetime(state.state_date, request=request))
-        if state.state not in workflow.update_states and versions.has_version(workflow.update_states):
+        has_update_state = versions.has_version(workflow.update_states)
+        if (state.state not in workflow.update_states and has_update_state) or \
+           (state.state in workflow.retired_states and has_update_state):
             target = sorted(versions.get_versions(workflow.update_states),
-                            key=lambda x: IWorkflowState(x).version_id,
-                            reverse=True)[-1]
+                            key=lambda x: IWorkflowState(x).version_id)[-1]
             self.version_link = {
                 'css_class': 'text-danger',
                 'href': absolute_url(target, request, 'admin.html'),
@@ -92,7 +104,7 @@
                             key=lambda x: IWorkflowState(x).version_id,
                             reverse=True)[-1]
             self.version_link = {
-                'css_class': 'txt-color-text',
+                'css_class': 'text-danger',
                 'href': absolute_url(target, request, 'admin.html'),
                 'title': translate(_("access published version"))
             }
@@ -104,6 +116,15 @@
         tool = get_parent(self.context, ISharedTool)
         return II18n(tool).query_attribute('title', request=self.request)
 
+    @reify
+    def hex_oid(self):
+        return ISequentialIdInfo(self.context).hex_oid
+
+    @property
+    def favorite_content(self):
+        profile = IAdminProfile(self.request.principal)
+        return self.hex_oid in (profile.favorites or '')
+
 
 @contentprovider_config(name='content_header', context=IWfSharedContent, view=IInnerTabForm, layer=IPyAMSLayer)
 class SharedContentInnerPageHeaderContentProvider(object):
--- a/src/pyams_content/shared/common/zmi/manager.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/manager.py	Mon Jan 18 17:42:32 2016 +0100
@@ -21,7 +21,7 @@
 from pyams_form.interfaces.form import IWidgetForm, IFormHelp
 from pyams_i18n.interfaces import II18n, II18nManager
 from pyams_skin.interfaces import IInnerPage, IPageHeader, IContentTitle
-from pyams_skin.interfaces.viewlet import IMenuHeader
+from pyams_skin.interfaces.viewlet import IMenuHeader, IBreadcrumbItem
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 from pyams_zmi.interfaces.menu import IPropertiesMenu, ISiteManagementMenu
@@ -32,6 +32,7 @@
 from pyams_form.help import FormHelp
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.page import DefaultPageHeaderAdapter
+from pyams_skin.viewlet.breadcrumb import BreadcrumbItem
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter, ContextRequestAdapter
 from pyams_viewlet.manager import viewletmanager_config
@@ -48,6 +49,17 @@
 # Shared tools common adapters
 #
 
+@adapter_config(context=(ISharedTool, IPyAMSLayer), provides=IBreadcrumbItem)
+class SharedToolBreadcrumbAdapter(BreadcrumbItem):
+    """Shared tool breadcrumb adapter"""
+
+    @property
+    def label(self):
+        return II18n(self.context).query_attribute('short_name', request=self.request)
+
+    css_class = 'strong'
+
+
 @adapter_config(context=(ISharedTool, IPyAMSLayer, Interface), provides=IContentTitle)
 class SharedToolTitleAdapter(ContextRequestViewAdapter):
     """Shared tool title adapter"""
--- a/src/pyams_content/shared/common/zmi/properties.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/properties.py	Mon Jan 18 17:42:32 2016 +0100
@@ -18,7 +18,7 @@
 # import interfaces
 from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common.interfaces import IWfSharedContent
-from pyams_form.interfaces.form import IWidgetForm
+from pyams_form.interfaces.form import IWidgetForm, IInnerTabForm, IInnerSubForm
 from pyams_skin.interfaces import IInnerPage, IPageHeader
 from pyams_skin.layer import IPyAMSLayer
 from pyams_zmi.interfaces.menu import IContentManagementMenu, IPropertiesMenu
@@ -26,16 +26,17 @@
 
 # import packages
 from pyams_content.shared.common.zmi import WfSharedContentHeaderAdapter
+from pyams_content.shared.common.zmi.summary import SharedContentSummaryForm
 from pyams_form.form import AJAXEditForm
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config
 from pyams_viewlet.manager import viewletmanager_config
 from pyams_viewlet.viewlet import viewlet_config
-from pyams_zmi.form import AdminEditForm
+from pyams_zmi.form import AdminEditForm, InnerAdminDisplayForm
 from pyramid.view import view_config
 from z3c.form import field
-from zope.interface import implementer
+from zope.interface import implementer, Interface
 
 from pyams_content import _
 
@@ -53,7 +54,7 @@
 
     label = _("Composition")
     icon_class = 'fa-dropbox'
-    url = '#'
+    url = '#summary.html'
 
 
 @viewlet_config(name='properties.submenu', context=IWfSharedContent, layer=IAdminLayer,
@@ -74,7 +75,7 @@
 
     legend = _("Content properties")
 
-    fields = field.Fields(IWfSharedContent).omit('__parent__', '__name__', 'creator', 'first_owner', 'modifiers')
+    fields = field.Fields(IWfSharedContent).select('title', 'short_name', 'description', 'keywords', 'notepad')
     ajax_handler = 'properties.json'
 
     def updateWidgets(self, prefix=None):
@@ -96,3 +97,42 @@
     """Shared content properties header adapter"""
 
     icon_class = 'fa fa-fw fa-edit'
+
+
+#
+# Properties summary
+#
+
+@adapter_config(name='properties-summary',
+                context=(IWfSharedContent, IPyAMSLayer, SharedContentSummaryForm),
+                provides=IInnerTabForm)
+class SharedContentPropertiesSummary(InnerAdminDisplayForm):
+    """Shared content properties summary tab"""
+
+    tab_label = _("Properties")
+    tab_target = 'properties-summary.html'
+
+    fields = field.Fields(Interface)
+
+    weight = 10
+
+
+@pagelet_config(name='properties-summary.html', context=IWfSharedContent, layer=IPyAMSLayer)
+@implementer(IInnerPage, IInnerTabForm)
+class SharedContentPropertiesSummaryDisplayForm(InnerAdminDisplayForm):
+    """Shared content properties summary display form"""
+
+    fields = field.Fields(Interface)
+
+
+@adapter_config(name='properties-main-summary.html',
+                context=(IWfSharedContent, IPyAMSLayer, SharedContentPropertiesSummaryDisplayForm),
+                provides=IInnerSubForm)
+class SharedContentMainPropertiesSummaryDisplayForm(InnerAdminDisplayForm):
+    """Shared content main properties summary display form"""
+
+    legend = _("Content properties")
+
+    fields = field.Fields(IWfSharedContent).select('title', 'short_name', 'description', 'keywords', 'notepad')
+
+    weight = 1
--- a/src/pyams_content/shared/common/zmi/summary.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/summary.py	Mon Jan 18 17:42:32 2016 +0100
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_workflow.interfaces import IWorkflowState, IWorkflowVersions
 
 __docformat__ = 'restructuredtext'
 
@@ -19,46 +18,30 @@
 # import interfaces
 from pyams_content.interfaces import IBaseContentInfo
 from pyams_content.shared.common.interfaces import IWfSharedContent, IWfSharedContentRoles, ISharedTool
-from pyams_content.zmi.interfaces import ISummaryMenu
-from pyams_form.interfaces.form import IWidgetForm, IInnerTabForm
-from pyams_sequence.interfaces import ISequentialIdInfo
+from pyams_form.interfaces.form import IWidgetForm, IInnerTabForm, IInnerSubForm
+from pyams_sequence.interfaces import ISequentialIdInfo, ISequentialIntIds
 from pyams_skin.interfaces import IInnerPage
 from pyams_skin.layer import IPyAMSLayer
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-from pyams_zmi.interfaces.menu import IContentManagementMenu
-from pyams_zmi.layer import IAdminLayer
+from pyams_workflow.interfaces import IWorkflowState, IWorkflow, IWorkflowPublicationInfo
 
 # import packages
 from pyams_content.shared.common.zmi.header import SharedContentHeaderContentProvider
-from pyams_form.form import InnerDisplayForm
 from pyams_pagelet.pagelet import pagelet_config
-from pyams_skin.viewlet.menu import MenuItem
 from pyams_utils.adapter import adapter_config
 from pyams_utils.date import format_datetime
+from pyams_utils.registry import get_utility
 from pyams_utils.timezone import tztime
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
-from pyams_viewlet.manager import viewletmanager_config
-from pyams_viewlet.viewlet import viewlet_config, contentprovider_config
-from pyams_zmi.form import AdminDisplayForm
+from pyams_viewlet.viewlet import contentprovider_config
+from pyams_zmi.form import AdminDisplayForm, InnerAdminDisplayForm
 from z3c.form import field
 from zope.interface import implementer, Interface
 
 from pyams_content import _
 
 
-@viewlet_config(name='summary.menu', context=IWfSharedContent, layer=IAdminLayer,
-                manager=IContentManagementMenu, permission=VIEW_SYSTEM_PERMISSION, weight=1)
-@viewletmanager_config(name='summary.menu', layer=IAdminLayer, provides=ISummaryMenu)
-@implementer(ISummaryMenu)
-class SharedContentSummaryMenu(MenuItem):
-    """Shared content summary menu"""
-
-    label = _("Summary")
-    icon_class = 'fa-info-circle'
-    url = '#summary.html'
-
-
 @pagelet_config(name='summary.html', context=IWfSharedContent, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @implementer(IWidgetForm, IInnerPage)
 class SharedContentSummaryForm(AdminDisplayForm):
@@ -67,7 +50,7 @@
     legend = _("Display content summary")
     css_class = 'ams-form form-horizontal form-tight'
 
-    fields = field.Fields(IWfSharedContent).select('title', 'short_name', 'description', 'keywords')
+    fields = field.Fields(Interface)
 
 
 @contentprovider_config(name='content_header', context=IWfSharedContent, view=SharedContentSummaryForm,
@@ -84,27 +67,68 @@
 @adapter_config(name='dublincore-summary',
                 context=(IWfSharedContent, IPyAMSLayer, SharedContentSummaryForm),
                 provides=IInnerTabForm)
-class SharedContentDublinCoreSummary(InnerDisplayForm):
+class SharedContentDublinCoreSummary(InnerAdminDisplayForm):
     """Shared content DublinCore summary"""
 
     weight = 1
     tab_label = _("Identity card")
     css_class = 'form-tight'
 
+    fields = field.Fields(Interface)
+
+
+@adapter_config(name='dublincore-version-summary',
+                context=(IWfSharedContent, IPyAMSLayer, SharedContentDublinCoreSummary),
+                provides=IInnerSubForm)
+class SharedContentDublinCoreVersionSummary(InnerAdminDisplayForm):
+    """Shared content version DublinCore summary"""
+
+    legend = _("About this version")
+    weight = 1
+
     @property
     def fields(self):
         fields = field.Fields(ISequentialIdInfo).select('hex_oid') + \
+                 field.Fields(IWorkflowState).select('version_id') + \
                  field.Fields(IWfSharedContentRoles).select('owner') + \
-                 field.Fields(IWfSharedContent).select('creator', 'first_owner') + \
-                 field.Fields(IBaseContentInfo) + \
-                 field.Fields(IWfSharedContent).select('modifiers', 'notepad')
-        state = IWorkflowState(self.context)
-        if state.version_id == 1:
-            fields = fields.omit('first_owner')
+                 field.Fields(IWfSharedContent).select('creator') + \
+                 field.Fields(IBaseContentInfo).select('created_date') + \
+                 field.Fields(IWorkflowPublicationInfo).select('publication') + \
+                 field.Fields(IBaseContentInfo).select('modified_date') + \
+                 field.Fields(IWfSharedContent).select('last_modifier', 'modifiers')
+        workflow = IWorkflow(self.context)
+        if IWorkflowState(self.context).state not in workflow.published_states:
+            fields = fields.omit('publication')
         return fields
 
     def updateWidgets(self, prefix=None):
-        super(SharedContentDublinCoreSummary, self).updateWidgets(prefix)
+        super(SharedContentDublinCoreVersionSummary, self).updateWidgets(prefix)
+        sequence = get_utility(ISequentialIntIds)
+        self.widgets['hex_oid'].value = sequence.get_short_oid(ISequentialIdInfo(self.context).oid)
+        workflow = IWorkflow(self.context)
+        self.widgets['version_id'].label = _("Version")
+        self.widgets['version_id'].value = '{version_id} - {status}'.format(
+            version_id=IWorkflowState(self.context).version_id,
+            status=self.request.localizer.translate(workflow.get_state_label(IWorkflowState(self.context).state)))
         info = IBaseContentInfo(self.context)
         self.widgets['created_date'].value = format_datetime(tztime(info.created_date))
         self.widgets['modified_date'].value = format_datetime(tztime(info.modified_date))
+
+
+@adapter_config(name='dublincore-history-summary',
+                context=(IWfSharedContent, IPyAMSLayer, SharedContentDublinCoreSummary),
+                provides=IInnerSubForm)
+class SharedContentDublinCoreHistorySummary(InnerAdminDisplayForm):
+    """Shared content history DublinCore summary"""
+
+    legend = _("Content history")
+
+    fields = field.Fields(IWfSharedContent).select('first_owner') + \
+             field.Fields(IWorkflowPublicationInfo).select('first_publication_date')
+
+    weight = 2
+
+    def updateWidgets(self, prefix=None):
+        super(SharedContentDublinCoreHistorySummary, self).updateWidgets(prefix)
+        info = IWorkflowPublicationInfo(self.context)
+        self.widgets['first_publication_date'].value = format_datetime(tztime(info.first_publication_date))
--- a/src/pyams_content/shared/common/zmi/templates/header.pt	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/templates/header.pt	Mon Jan 18 17:42:32 2016 +0100
@@ -1,21 +1,29 @@
-<tal:block i18n:domain="pyams_content">
-	<h1 class="page-title margin-bottom-5" tal:define="back_url view.back_url">
-		<a class="font-xs hint" data-ams-target="#content"
-		   title="Back to previous page" i18n:attributes="title"
-		   tal:attributes="href back_url;
-						   data-ams-target view.back_target;">
-			<i class="fa fa-chevron-left padding-right-10"></i>
-		</a>
-		<tal:var define="config extension:back_configuration"
-				 condition="config.display_content_icon">
-			<i tal:attributes="class view.icon_class"></i>
-		</tal:var>
-		<tal:var content="view.title" />
-	</h1>
-	<div class="margin-left-10 margin-bottom-5 padding-left-20">
+<tal:block i18n:domain="pyams_content"
+		   define="config extension:back_configuration">
+	<tal:if condition="config.display_shared_tool_title">
+		<h1 class="page-title margin-bottom-5" tal:define="back_url view.back_url">
+			<a class="font-xs hint" data-ams-target="#content"
+			   title="Back to previous page" i18n:attributes="title"
+			   tal:attributes="href back_url;
+							   data-ams-target view.back_target;">
+				<i class="fa fa-chevron-left padding-right-10"></i>
+			</a>
+			<tal:if condition="config.display_content_icon">
+				<i tal:attributes="class view.icon_class"></i>
+			</tal:if>
+			<tal:var content="view.title" />
+		</h1>
+	</tal:if>
+	<div tal:attributes="class 'margin-left-10 margin-bottom-5 padding-left-20' if config.display_shared_tool_title else 'margin-bottom-5 padding-left-10'">
 		<span class="bold content-oid" tal:content="view.oid">OID</span> :
 		<span class="bold content-title" tal:content="i18n:context.title">Title</span> |
-		<span class="content-owner" i18n:translate="">by <i18n:var name="owner" tal:content="view.owner" /></span><br />
+		<span class="content-owner" i18n:translate="">by <i18n:var name="owner" tal:content="view.owner" /></span>
+		<i data-ams-plugins="pyams_content"
+		   data-ams-plugin-pyams_content-src="/--static--/pyams_content/js/pyams_content{MyAMS.devext}.js"
+		   data-ams-click-handler="PyAMS_content.profile.switchFavorite"
+		   data-ams-hint-gravity="w" title="Add/remove from favorites" i18n:attributes="title"
+		   tal:attributes="class 'fa fa-star{0} txt-color-blue hint opaque align-middle padding-left-10 padding-bottom-5'.format('' if view.favorite_content else '-o');
+						   data-sequence-oid view.hex_oid;"></i><br />
 		<span class="content-version" tal:content="string:V${view.version_id}">Version</span> =
 		<span class="content-state" tal:content="structure view.state">state</span> |
 		<span class="content-state-date" tal:content="view.state_date">state date</span>
--- a/src/pyams_content/shared/common/zmi/templates/wf-operator-warning.pt	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/templates/wf-operator-warning.pt	Mon Jan 18 17:42:32 2016 +0100
@@ -1,3 +1,3 @@
-<div class="alert alert-danger margin-bottom-5" i18n:domain="pyams_content" i18n:translate="">
+<div class="alert alert-danger padding-5 margin-bottom-5 persistent" i18n:domain="pyams_content" i18n:translate="">
 	WARNING: this request was made by a contributor which is not the owner of this content.
 </div>
--- a/src/pyams_content/shared/common/zmi/templates/wf-owner-warning.pt	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/templates/wf-owner-warning.pt	Mon Jan 18 17:42:32 2016 +0100
@@ -1,3 +1,3 @@
-<div class="alert alert-danger margin-bottom-5" i18n:domain="pyams_content" i18n:translate="">
+<div class="alert alert-danger margin-bottom-5 padding-5" i18n:domain="pyams_content" i18n:translate="">
 	RECALL: you are not the owner of the content on which you are intervening.
 </div>
--- a/src/pyams_content/shared/common/zmi/templates/wf-transition-info.pt	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/templates/wf-transition-info.pt	Mon Jan 18 17:42:32 2016 +0100
@@ -1,6 +1,10 @@
 <div class="alert alert-warning padding-5" i18n:domain="pyams_content">
 	<strong i18n:translate="">FOR YOUR INFORMATION</strong><br />
-	<u i18n:translate="">Previous step:</u> <tal:var content="view.previous_step" />
+	<tal:if define="next_step view.next_step"
+			condition="next_step">
+		<u i18n:translate="">Next step</u> : <tal:var content="next_step" /><br />
+	</tal:if>
+	<u i18n:translate="">Previous step</u> : <tal:var content="view.previous_step" />
 	<tal:var define="message view.previous_message">
 		<tal:if	condition="message"><br />
 			<span class="small" i18n:translate="">With this comment:</span>
@@ -8,8 +12,4 @@
 		</tal:if>
 		<br tal:condition="not:message" />
 	</tal:var>
-	<tal:if define="next_step view.next_step"
-			condition="next_step">
-		<u i18n:translate="">Next step:</u> <tal:var content="next_step" />
-	</tal:if>
 </div>
--- a/src/pyams_content/shared/common/zmi/workflow.py	Mon Jan 18 17:29:59 2016 +0100
+++ b/src/pyams_content/shared/common/zmi/workflow.py	Mon Jan 18 17:42:32 2016 +0100
@@ -9,7 +9,6 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 #
-from pyams_utils.text import text_to_html
 
 __docformat__ = 'restructuredtext'
 
@@ -18,6 +17,7 @@
 from datetime import datetime
 
 # import interfaces
+from pyams_content.interfaces import PUBLISH_CONTENT_PERMISSION, CREATE_CONTENT_PERMISSION, MANAGE_CONTENT_PERMISSION
 from pyams_content.shared.common.interfaces import IWfSharedContent, ISharedTool, ISharedContent
 from pyams_form.interfaces.form import IInnerTabForm, IFormPrefixViewletsManager, IWidgetsPrefixViewletsManager, \
     IFormSuffixViewletsManager
@@ -32,18 +32,20 @@
 # import packages
 from pyams_content.shared.common.zmi.summary import SharedContentSummaryForm
 from pyams_content.workflow import DRAFT, DELETED
-from pyams_form.form import AJAXAddForm, InnerDisplayForm
+from pyams_form.form import AJAXAddForm
 from pyams_form.schema import CloseButton
 from pyams_pagelet.pagelet import pagelet_config
 from pyams_template.template import template_config
 from pyams_utils.adapter import adapter_config
 from pyams_utils.date import format_datetime
 from pyams_utils.registry import get_utility
+from pyams_utils.text import text_to_html
 from pyams_utils.timezone import tztime
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
 from pyams_viewlet.viewlet import viewlet_config, Viewlet
 from pyams_workflow.zmi.transition import WorkflowContentTransitionForm, WorkflowContentTransitionAJAXForm
+from pyams_zmi.form import InnerAdminDisplayForm
 from pyramid.events import subscriber
 from pyramid.view import view_config
 from z3c.form import field, button
@@ -59,10 +61,10 @@
 @adapter_config(name='workflow-summary',
                 context=(IWfSharedContent, IPyAMSLayer, SharedContentSummaryForm),
                 provides=IInnerTabForm)
-class WfSharedContentWorkflowSummary(InnerDisplayForm):
+class WfSharedContentWorkflowSummary(InnerAdminDisplayForm):
     """Shared content workflow summary"""
 
-    weight = 10
+    weight = 990
     tab_label = _("Workflow")
     tab_target = 'workflow-summary.html'
 
@@ -71,9 +73,11 @@
 
 @pagelet_config(name='workflow-summary.html', context=IWfSharedContent, layer=IPyAMSLayer)
 @implementer(IInnerPage, IInnerTabForm)
-class WorkflowSummaryDisplayForm(InnerDisplayForm):
+class WorkflowSummaryDisplayForm(InnerAdminDisplayForm):
     """Workflow summary display form"""
 
+    legend = _("Workflow details")
+
     @property
     def fields(self):
         fields = field.Fields(IWorkflowState).omit('history') + \
@@ -116,19 +120,22 @@
         translate = self.request.localizer.translate
         workflow = IWorkflow(self.context)
         state = IWorkflowState(self.context)
-        adapter = self.request.registry.queryAdapter(workflow, IWorkflowStateLabel,
-                                                     name=state.state)
+        registry = self.request.registry
+        adapter = registry.queryMultiAdapter((workflow, self.request), IWorkflowStateLabel, name=state.state)
         if adapter is None:
-            adapter = self.request.registry.queryAdapter(workflow, IWorkflowStateLabel)
+            adapter = registry.queryAdapter(workflow, IWorkflowStateLabel, name=state.state)
+        if adapter is None:
+            adapter = registry.queryAdapter(workflow, IWorkflowStateLabel)
         if adapter is not None:
             state_label = adapter.get_label(self.context, request=self.request)
         else:
-            security = get_utility(ISecurityManager)
-            state_label = translate(_("{state} by {principal}")).format(
+            state_label = translate(_("{state} {date}")).format(
                 state=translate(workflow.get_state_label(state.state)),
-                principal=security.get_principal(state.state_principal).title)
-        return translate(_("{state} {date}")).format(state=state_label,
-                                                     date=format_datetime(state.state_date, request=self.request))
+                date=format_datetime(state.state_date, request=self.request))
+        security = get_utility(ISecurityManager)
+        return translate(_("{state} | by {principal}")).format(
+            state=state_label,
+            principal=security.get_principal(state.state_principal).title)
 
     @property
     def previous_message(self):
@@ -165,7 +172,8 @@
     action = button.Button(name='action', title=_("Request publication"))
 
 
-@pagelet_config(name='wf-propose.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.ManageContent')
+@pagelet_config(name='wf-propose.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request form"""
 
@@ -191,7 +199,7 @@
 
 
 @view_config(name='wf-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestForm):
     """Shared content publication request form, JSON renderer"""
 
@@ -206,8 +214,15 @@
         event.form.widgets.errors += (Invalid(_("A comment is required")), )
 
 
+@viewlet_config(name='wf-propose-message', context=IWfSharedContent, layer=IPyAMSLayer, view=PublicationRequestForm,
+                manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-propose-message.pt')
+class PublicationRequestFormMessage(Viewlet):
+    """Publication request form info message"""
+
+
 @viewlet_config(name='wf-propose-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationRequestForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationRequestFormWarning(Viewlet):
     """Publication request form warning message"""
@@ -218,13 +233,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-propose-message', context=IWfSharedContent, layer=IPyAMSLayer, view=PublicationRequestForm,
-                manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-propose-message.pt')
-class PublicationRequestFormMessage(Viewlet):
-    """Publication request form info message"""
-
-
 #
 # Cancel publication request form
 #
@@ -237,7 +245,7 @@
 
 
 @pagelet_config(name='wf-cancel-propose.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.ManageContent')
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationRequestCancelForm(WorkflowContentTransitionForm):
     """Shared content publication request cancel form"""
 
@@ -246,13 +254,20 @@
 
 
 @view_config(name='wf-cancel-propose.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRequestCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestCancelForm):
     """Shared content publication request cancel form, JSON renderer"""
 
 
+@viewlet_config(name='wf-cancel-propose-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRequestCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-propose-message.pt')
+class PublicationRequestCancelFormMessage(Viewlet):
+    """Publication request cancel form info message"""
+
+
 @viewlet_config(name='wf-cancel-propose-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRequestCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationRequestCancelForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationRequestCancelFormWarning(Viewlet):
     """Publication request cancel form warning message"""
@@ -263,13 +278,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-cancel-propose-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRequestCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-cancel-propose-message.pt')
-class PublicationRequestCancelFormMessage(Viewlet):
-    """Publication request cancel form info message"""
-
-
 #
 # Refuse publication form
 #
@@ -282,7 +290,7 @@
 
 
 @pagelet_config(name='wf-refuse.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.PublishContent')
+                permission=PUBLISH_CONTENT_PERMISSION)
 class PublicationRequestRefuseForm(WorkflowContentTransitionForm):
     """Shared content publication request refuse form"""
 
@@ -295,7 +303,7 @@
 
 
 @view_config(name='wf-refuse.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.PublishContent', renderer='json', xhr=True)
+             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRequestRefuseAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRequestRefuseForm):
     """Shared content publication request refuse form, JSON renderer"""
 
@@ -339,7 +347,8 @@
     action = button.Button(name='action', title=_("Publish"))
 
 
-@pagelet_config(name='wf-publish.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.PublishContent')
+@pagelet_config(name='wf-publish.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission=PUBLISH_CONTENT_PERMISSION)
 class PublicationForm(WorkflowContentTransitionForm):
     """Shared content publication form"""
 
@@ -369,7 +378,7 @@
 
 
 @view_config(name='wf-publish.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.PublishContent', renderer='json', xhr=True)
+             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationAJAXForm(WorkflowContentTransitionAJAXForm, PublicationForm):
     """Shared content publication form, JSON renderer"""
 
@@ -413,7 +422,7 @@
 
 
 @pagelet_config(name='wf-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.ManageContent')
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationRetireRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request refuse form"""
 
@@ -429,7 +438,7 @@
 
 
 @view_config(name='wf-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRetireRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireRequestForm):
     """Shared content publication retire request form, JSON renderer"""
 
@@ -442,8 +451,15 @@
         event.form.widgets.errors += (Invalid(_("A comment is required")), )
 
 
+@viewlet_config(name='wf-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-retiring-message.pt')
+class PublicationRetireRequestFormMessage(Viewlet):
+    """Publication retire request form info message"""
+
+
 @viewlet_config(name='wf-retiring-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRetireRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationRetireRequestForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationRetireRequestFormWarning(Viewlet):
     """Publication retire request form warning message"""
@@ -454,13 +470,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRetireRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-retiring-message.pt')
-class PublicationRetireRequestFormMessage(Viewlet):
-    """Publication retire request form info message"""
-
-
 #
 # Publication retire cancel form
 #
@@ -473,7 +482,7 @@
 
 
 @pagelet_config(name='wf-cancel-retiring.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.ManageContent')
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationRetireCancelForm(WorkflowContentTransitionForm):
     """Shared content publication retire request cancel form"""
 
@@ -482,13 +491,20 @@
 
 
 @view_config(name='wf-cancel-retiring.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRetireCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireCancelForm):
     """Shared content publication retire request cancel form, JSON renderer"""
 
 
+@viewlet_config(name='wf-cancel-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationRetireCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-retiring-message.pt')
+class PublicationRetireCancelFormMessage(Viewlet):
+    """Publication retire request form info message"""
+
+
 @viewlet_config(name='wf-cancel-retiring-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRetireCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationRetireCancelForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationRetireCancelFormWarning(Viewlet):
     """Publication retire request cancel form warning message"""
@@ -499,13 +515,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-cancel-retiring-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationRetireCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-cancel-retiring-message.pt')
-class PublicationRetireCancelFormMessage(Viewlet):
-    """Publication retire request form info message"""
-
-
 #
 # Publication retire form
 #
@@ -518,7 +527,7 @@
 
 
 @pagelet_config(name='wf-retire.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.PublishContent')
+                permission=PUBLISH_CONTENT_PERMISSION)
 class PublicationRetireForm(WorkflowContentTransitionForm):
     """Shared content publication retire form"""
 
@@ -527,7 +536,7 @@
 
 
 @view_config(name='wf-retire.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.PublishContent', renderer='json', xhr=True)
+             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationRetireAJAXForm(WorkflowContentTransitionAJAXForm, PublicationRetireForm):
     """Shared content publication retire form, JSON renderer"""
 
@@ -564,7 +573,7 @@
 
 
 @pagelet_config(name='wf-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.ManageContent')
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationArchiveRequestForm(WorkflowContentTransitionForm):
     """Shared content publication request archive form"""
 
@@ -580,7 +589,7 @@
 
 
 @view_config(name='wf-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationArchiveRequestAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveRequestForm):
     """Shared content publication archive request form, JSON renderer"""
 
@@ -593,8 +602,15 @@
         event.form.widgets.errors += (Invalid(_("A comment is required")), )
 
 
+@viewlet_config(name='wf-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-archiving-message.pt')
+class PublicationArchiveRequestFormMessage(Viewlet):
+    """Publication archive request form info message"""
+
+
 @viewlet_config(name='wf-archiving-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationArchiveRequestForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationArchiveRequestForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationArchiveRequestFormWarning(Viewlet):
     """Publication archive request form warning message"""
@@ -605,13 +621,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationArchiveRequestForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-archiving-message.pt')
-class PublicationArchiveRequestFormMessage(Viewlet):
-    """Publication archive request form info message"""
-
-
 #
 # Publication archive cancel form
 #
@@ -624,7 +633,7 @@
 
 
 @pagelet_config(name='wf-cancel-archiving.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.ManageContent')
+                permission=MANAGE_CONTENT_PERMISSION)
 class PublicationArchiveCancelForm(WorkflowContentTransitionForm):
     """Shared content publication archive request cancel form"""
 
@@ -633,13 +642,20 @@
 
 
 @view_config(name='wf-cancel-archiving.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationArchiveCancelAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveCancelForm):
     """Shared content publication archive request cancel form, JSON renderer"""
 
 
+@viewlet_config(name='wf-cancel-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=PublicationArchiveCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-cancel-archiving-message.pt')
+class PublicationArchiveCancelFormMessage(Viewlet):
+    """Publication archive cancel form info message"""
+
+
 @viewlet_config(name='wf-cancel-archiving-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationArchiveCancelForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=PublicationArchiveCancelForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class PublicationArchiveCancelFormWarning(Viewlet):
     """Publication archive cancel form warning message"""
@@ -650,13 +666,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-cancel-archiving-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=PublicationArchiveCancelForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-cancel-archiving-message.pt')
-class PublicationArchiveCancelFormMessage(Viewlet):
-    """Publication archive cancel form info message"""
-
-
 #
 # Publication archive form
 #
@@ -669,7 +678,7 @@
 
 
 @pagelet_config(name='wf-archive.html', context=IWfSharedContent, layer=IPyAMSLayer,
-                permission='pyams.PublishContent')
+                permission=PUBLISH_CONTENT_PERMISSION)
 class PublicationArchiveForm(WorkflowContentTransitionForm):
     """Shared content publication archive form"""
 
@@ -678,7 +687,7 @@
 
 
 @view_config(name='wf-archive.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.PublishContent', renderer='json', xhr=True)
+             permission=PUBLISH_CONTENT_PERMISSION, renderer='json', xhr=True)
 class PublicationArchiveAJAXForm(WorkflowContentTransitionAJAXForm, PublicationArchiveForm):
     """Shared content publication archive form, JSON renderer"""
 
@@ -714,7 +723,8 @@
     action = button.Button(name='action', title=_("Create new version"))
 
 
-@pagelet_config(name='wf-clone.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.CreateContent')
+@pagelet_config(name='wf-clone.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission=CREATE_CONTENT_PERMISSION)
 class SharedContentCloneForm(WorkflowContentTransitionForm):
     """Shared content clone form"""
 
@@ -727,7 +737,7 @@
 
 
 @view_config(name='wf-clone.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.CreateContent', renderer='json', xhr=True)
+             permission=CREATE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class SharedContentCloneAJAXForm(AJAXAddForm, SharedContentCloneForm):
     """Shared content clone form, JSON rendener"""
 
@@ -736,8 +746,15 @@
                 'location': absolute_url(changes, self.request, 'admin.html#properties.html')}
 
 
+@viewlet_config(name='wf-clone-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentCloneForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-clone-message.pt')
+class SharedContentCloneFormMessage(Viewlet):
+    """Shared content clone form info message"""
+
+
 @viewlet_config(name='wf-clone-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=SharedContentCloneForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=SharedContentCloneForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class SharedContentCloneFormWarning(Viewlet):
     """Shared content clone form warning message"""
@@ -748,13 +765,6 @@
         return Viewlet.__new__(cls)
 
 
-@viewlet_config(name='wf-clone-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=SharedContentCloneForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-clone-message.pt')
-class SharedContentCloneFormMessage(Viewlet):
-    """Shared content clone form info message"""
-
-
 #
 # Delete form
 #
@@ -766,13 +776,21 @@
     action = button.Button(name='action', title=_("Delete version"))
 
 
-@pagelet_config(name='wf-delete.html', context=IWfSharedContent, layer=IPyAMSLayer, permission='pyams.ManageContent')
+@pagelet_config(name='wf-delete.html', context=IWfSharedContent, layer=IPyAMSLayer,
+                permission=MANAGE_CONTENT_PERMISSION)
 class SharedContentDeleteForm(WorkflowContentTransitionForm):
     """Shared content delete form"""
 
     buttons = button.Buttons(ISharedContentDeleteButtons)
     ajax_handler = 'wf-delete.json'
 
+    def updateActions(self):
+        super(SharedContentDeleteForm, self).updateActions()
+        if 'action' in self.actions:
+            state = IWorkflowState(self.context)
+            if state.version_id == 1:  # remove the first and only version => remove all
+                self.actions['action'].title = _("Delete definitively")
+
     def createAndAdd(self, data):
         state = IWorkflowState(self.context)
         if state.version_id == 1:  # remove the first and only version => remove all
@@ -780,12 +798,13 @@
             self.__target = get_parent(content, ISharedTool)
             del content.__parent__[content.__name__]
         else:
-            self.__target = get_parent(self.context, ISharedTool)
-            IWorkflowVersions(self.context).remove_version(state.version_id, state=DELETED, comment=data.get('comment'))
+            versions = IWorkflowVersions(self.context)
+            versions.remove_version(state.version_id, state=DELETED, comment=data.get('comment'))
+            self.__target = versions.get_last_versions(count=1)[0]
 
 
 @view_config(name='wf-delete.json', context=IWfSharedContent, request_type=IPyAMSLayer,
-             permission='pyams.ManageContent', renderer='json', xhr=True)
+             permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
 class SharedContentDeleteAJAXForm(AJAXAddForm, SharedContentDeleteForm):
     """Shared content delete form, JSON rendener"""
 
@@ -794,8 +813,15 @@
                 'location': absolute_url(self._SharedContentDeleteForm__target, self.request, 'admin.html')}
 
 
+@viewlet_config(name='wf-delete-message', context=IWfSharedContent, layer=IPyAMSLayer,
+                view=SharedContentDeleteForm, manager=IWidgetsPrefixViewletsManager, weight=20)
+@template_config(template='templates/wf-delete-message.pt')
+class SharedContentDeleteFormMessage(Viewlet):
+    """Shared content delete form info message"""
+
+
 @viewlet_config(name='wf-delete-owner-warning', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=SharedContentDeleteForm, manager=IFormPrefixViewletsManager, weight=10)
+                view=SharedContentDeleteForm, manager=IWidgetsPrefixViewletsManager, weight=30)
 @template_config(template='templates/wf-owner-warning.pt')
 class SharedContentDeleteFormWarning(Viewlet):
     """Shared content delete form warning message"""
@@ -804,10 +830,3 @@
         if request.principal.id in context.owner:
             return None
         return Viewlet.__new__(cls)
-
-
-@viewlet_config(name='wf-delete-message', context=IWfSharedContent, layer=IPyAMSLayer,
-                view=SharedContentDeleteForm, manager=IWidgetsPrefixViewletsManager, weight=20)
-@template_config(template='templates/wf-delete-message.pt')
-class SharedContentDeleteFormMessage(Viewlet):
-    """Shared content delete form info message"""