Move most content's tab to the archive unit
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 11 Jan 2017 14:14:32 +0100
changeset 2298 999e4cf6efe9
parent 2297 ff04117be4a2
child 2299 21132e4f9132
Move most content's tab to the archive unit The only one remaining being the default one. Along the way, rename some identifier and classes to be more consistent. The views.content module isn't modified yet, it will be dropped all at once when this work will be completed. Related to #16684017
i18n/en.po
i18n/fr.po
views/archiveunit.py
--- a/i18n/en.po	Wed Jan 11 22:03:06 2017 +0100
+++ b/i18n/en.po	Wed Jan 11 14:14:32 2017 +0100
@@ -4906,6 +4906,9 @@
 msgid "seda_addressee_to_object"
 msgstr ""
 
+msgid "seda_agents_tab"
+msgstr ""
+
 msgid "seda_algorithm"
 msgstr ""
 
@@ -5295,9 +5298,6 @@
 msgid "seda_au_data_objects_refs_tab"
 msgstr ""
 
-msgid "seda_au_indexation_tab"
-msgstr ""
-
 msgid "seda_authorized_agent_from"
 msgstr ""
 
@@ -5476,34 +5476,7 @@
 msgid "seda_compression_algorithm_code_list_version_to_object"
 msgstr ""
 
-msgid "seda_content_agent_tab"
-msgstr ""
-
-msgid "seda_content_coverage_tab"
-msgstr ""
-
-msgid "seda_content_date_tab"
-msgstr ""
-
-msgid "seda_content_event_tab"
-msgstr ""
-
-msgid "seda_content_gps_tab"
-msgstr ""
-
-msgid "seda_content_identification_tab"
-msgstr ""
-
-msgid "seda_content_indexation_tab"
-msgstr ""
-
-msgid "seda_content_relation_tab"
-msgstr ""
-
-msgid "seda_content_restriction_tab"
-msgstr ""
-
-msgid "seda_content_service_tab"
+msgid "seda_coverage_tab"
 msgstr ""
 
 msgid "seda_created_date"
@@ -5734,6 +5707,9 @@
 msgid "seda_date_created_by_application_object"
 msgstr ""
 
+msgid "seda_dates_tab"
+msgstr ""
+
 msgid "seda_depth"
 msgstr ""
 
@@ -6021,6 +5997,9 @@
 msgid "seda_event_type_to_object"
 msgstr ""
 
+msgid "seda_events_tab"
+msgstr ""
+
 msgid "seda_file_format_code_list_version"
 msgstr ""
 
@@ -6207,6 +6186,9 @@
 msgid "seda_gps_longitude_ref_object"
 msgstr ""
 
+msgid "seda_gps_tab"
+msgstr ""
+
 msgid "seda_gps_version_id"
 msgstr ""
 
@@ -6238,6 +6220,12 @@
 msgid "seda_history_tab"
 msgstr ""
 
+msgid "seda_identification_tab"
+msgstr ""
+
+msgid "seda_indexation_tab"
+msgstr ""
+
 msgid "seda_is_part_of"
 msgstr ""
 
@@ -6792,6 +6780,9 @@
 msgid "seda_related_transfer_reference_object"
 msgstr ""
 
+msgid "seda_relations_tab"
+msgstr ""
+
 msgid "seda_relationship"
 msgstr ""
 
@@ -6968,6 +6959,9 @@
 msgid "seda_restriction_rule_id_ref_object"
 msgstr ""
 
+msgid "seda_restriction_tab"
+msgstr ""
+
 msgid "seda_restriction_value"
 msgstr ""
 
@@ -7188,6 +7182,9 @@
 msgid "seda_service_level_object"
 msgstr ""
 
+msgid "seda_services_tab"
+msgstr ""
+
 msgid "seda_shape"
 msgstr ""
 
--- a/i18n/fr.po	Wed Jan 11 22:03:06 2017 +0100
+++ b/i18n/fr.po	Wed Jan 11 14:14:32 2017 +0100
@@ -4927,6 +4927,9 @@
 msgid "seda_addressee_to_object"
 msgstr ""
 
+msgid "seda_agents_tab"
+msgstr "agents"
+
 msgid "seda_algorithm"
 msgstr "algorithme"
 
@@ -5316,9 +5319,6 @@
 msgid "seda_au_data_objects_refs_tab"
 msgstr "objets-données"
 
-msgid "seda_au_indexation_tab"
-msgstr "indexation"
-
 msgid "seda_authorized_agent_from"
 msgstr ""
 
@@ -5497,36 +5497,9 @@
 msgid "seda_compression_algorithm_code_list_version_to_object"
 msgstr ""
 
-msgid "seda_content_agent_tab"
-msgstr "agents"
-
-msgid "seda_content_coverage_tab"
+msgid "seda_coverage_tab"
 msgstr "couverture"
 
-msgid "seda_content_date_tab"
-msgstr "dates"
-
-msgid "seda_content_event_tab"
-msgstr "événements"
-
-msgid "seda_content_gps_tab"
-msgstr "coordonnées GPS"
-
-msgid "seda_content_identification_tab"
-msgstr "identification"
-
-msgid "seda_content_indexation_tab"
-msgstr "indexation"
-
-msgid "seda_content_relation_tab"
-msgstr "objets liés"
-
-msgid "seda_content_restriction_tab"
-msgstr "restriction"
-
-msgid "seda_content_service_tab"
-msgstr "services"
-
 msgid "seda_created_date"
 msgstr ""
 
@@ -5755,6 +5728,9 @@
 msgid "seda_date_created_by_application_object"
 msgstr ""
 
+msgid "seda_dates_tab"
+msgstr "dates"
+
 msgid "seda_depth"
 msgstr ""
 
@@ -6042,6 +6018,9 @@
 msgid "seda_event_type_to_object"
 msgstr ""
 
+msgid "seda_events_tab"
+msgstr "événements"
+
 msgid "seda_file_format_code_list_version"
 msgstr "formats de fichier"
 
@@ -6228,6 +6207,9 @@
 msgid "seda_gps_longitude_ref_object"
 msgstr ""
 
+msgid "seda_gps_tab"
+msgstr "coordonnées GPS"
+
 msgid "seda_gps_version_id"
 msgstr ""
 
@@ -6259,6 +6241,12 @@
 msgid "seda_history_tab"
 msgstr "historique de conservation"
 
+msgid "seda_identification_tab"
+msgstr "identification"
+
+msgid "seda_indexation_tab"
+msgstr "indexation"
+
 msgid "seda_is_part_of"
 msgstr ""
 
@@ -6813,6 +6801,9 @@
 msgid "seda_related_transfer_reference_object"
 msgstr ""
 
+msgid "seda_relations_tab"
+msgstr "objets liés"
+
 msgid "seda_relationship"
 msgstr ""
 
@@ -6989,6 +6980,9 @@
 msgid "seda_restriction_rule_id_ref_object"
 msgstr ""
 
+msgid "seda_restriction_tab"
+msgstr "restriction"
+
 msgid "seda_restriction_value"
 msgstr ""
 
@@ -7209,6 +7203,9 @@
 msgid "seda_service_level_object"
 msgstr ""
 
+msgid "seda_services_tab"
+msgstr "services"
+
 msgid "seda_shape"
 msgstr ""
 
--- a/views/archiveunit.py	Wed Jan 11 22:03:06 2017 +0100
+++ b/views/archiveunit.py	Wed Jan 11 14:14:32 2017 +0100
@@ -17,6 +17,7 @@
 
 from six import text_type
 
+from logilab.mtconverter import xml_escape
 from logilab.common.registry import objectify_predicate
 
 from cubicweb import tags, _
@@ -27,10 +28,11 @@
 from cubes.compound import views as compound
 from cubes.relationwidget import views as rwdg
 
-from ..entities import simplified_profile, parent_and_container
+from ..xsd import un_camel_case
+from ..entities import full_seda2_profile, simplified_profile, parent_and_container
 from ..entities.itree import parent_archive_unit
 from . import (CONTENT_ETYPE, add_subobject_link, add_subobjects_button, dropdown_button,
-               rtags_from_rtype_role_targets, copy_rtag, has_rel_perm)
+               rtags_from_xsd_element, rtags_from_rtype_role_targets, copy_rtag, has_rel_perm)
 from . import clone, viewlib, widgets
 from . import uicfg as sedauicfg  # noqa - ensure those rules are defined first
 
@@ -200,7 +202,16 @@
         _('seda_archive_units_tab'),
         _('seda_au_data_objects_refs_tab'),
         # Content tabs
+        _('seda_identification_tab'),
+        _('seda_restriction_tab'),
+        _('seda_dates_tab'),
+        _('seda_gps_tab'),
+        _('seda_services_tab'),
+        _('seda_agents_tab'),
+        _('seda_coverage_tab'),
         _('seda_indexation_tab'),
+        _('seda_relations_tab'),
+        _('seda_events_tab'),
         _('seda_history_tab'),
 
     ]
@@ -299,6 +310,199 @@
     rsection, display_ctrl = rtags_from_rtype_role_targets(CONTENT_ETYPE, rtype_role_targets)
 
 
+# identification tab ###########################################################
+
+class ArchiveUnitIdentificationTab(ArchiveUnitContentTab):
+    """Display identification information about a full seda2 archive unit."""
+
+    __regid__ = 'seda_identification_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_identification'
+
+
+class ContentIdentificationView(viewlib.PrimaryTabWithoutBoxes):
+    """Display identification information about an archive unit content."""
+
+    __regid__ = 'seda_content_identification'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [
+        ('seda_source', 'object', None),
+        ('seda_file_plan_position', 'object', None),
+        ('seda_system_id', 'object', None),
+        ('seda_originating_system_id', 'object', None),
+        ('seda_archival_agency_archive_unit_identifier', 'object', None),
+        ('seda_originating_agency_archive_unit_identifier', 'object', None),
+        ('seda_transferring_agency_archive_unit_identifier', 'object', None),
+    ]
+    rsection, display_ctrl = rtags_from_rtype_role_targets(CONTENT_ETYPE, rtype_role_targets)
+
+
+# restriction tab ##############################################################
+
+class ArchiveUnitRestrictionTab(ArchiveUnitContentTab):
+    """Display restrictions on a full seda2 archive unit."""
+
+    __regid__ = 'seda_restriction_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_restriction'
+
+
+class ContentRestrictionView(viewlib.PrimaryTabWithoutBoxes):
+    """Display restrictions on an archive unit content."""
+
+    __regid__ = 'seda_content_restriction'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [
+        ('seda_restriction_rule_id_ref', 'object', None),
+        ('seda_restriction_value', 'object', None),
+        ('seda_restriction_end_date', 'object', None),
+    ]
+    rsection, display_ctrl = rtags_from_rtype_role_targets(CONTENT_ETYPE, rtype_role_targets)
+
+
+# dates tab ####################################################################
+
+class ArchiveUnitDatesTab(ArchiveUnitContentTab):
+    """Display dates information about a full seda2 archive unit."""
+
+    __regid__ = 'seda_dates_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_dates'
+
+
+class ContentDatesView(viewlib.PrimaryTabWithoutBoxes):
+    """Display dates information about an archive unit content."""
+
+    __regid__ = 'seda_content_dates'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [
+        ('seda_created_date', 'object', None),
+        ('seda_transacted_date', 'object', None),
+        ('seda_acquired_date', 'object', None),
+        ('seda_sent_date', 'object', None),
+        ('seda_received_date', 'object', None),
+        ('seda_registered_date', 'object', None),
+        ('seda_start_date', 'object', None),
+        ('seda_end_date', 'object', None),
+    ]
+    rsection, display_ctrl = rtags_from_rtype_role_targets(CONTENT_ETYPE, rtype_role_targets)
+
+
+# GPS tab ######################################################################
+
+class ArchiveUnitGPSTab(ArchiveUnitContentTab):
+    """Display GPS information about a full seda2 archive unit."""
+
+    __regid__ = 'seda_gps_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_gps'
+
+
+class ContentGPSView(viewlib.PrimaryTabWithoutBoxes):
+    """Display GPS information about an archive unit content."""
+
+    __regid__ = 'seda_content_gps'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rsection, display_ctrl = rtags_from_xsd_element(CONTENT_ETYPE, 'Gps')
+
+
+# services tab #################################################################
+
+class ArchiveUnitServicesTab(ArchiveUnitContentTab):
+    """Display services related to a full seda2 archive unit."""
+
+    __regid__ = 'seda_services_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_services'
+
+
+class ContentServicesView(viewlib.PrimaryTabWithoutBoxes):
+    """Display services related to an archive unit content."""
+
+    __regid__ = 'seda_content_services'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [
+        ('seda_originating_agency_from', 'object', None),
+        ('seda_submission_agency_from', 'object', None),
+        ('seda_authorized_agent_from', 'object', None),
+    ]
+    rsection, display_ctrl = rtags_from_rtype_role_targets(CONTENT_ETYPE, rtype_role_targets)
+
+
+# agents tab ###################################################################
+
+class ArchiveUnitAgentsTab(ArchiveUnitContentTab):
+    """Display agents related to a full seda2 archive unit."""
+
+    __regid__ = 'seda_agents_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_agents'
+
+
+class ContentAgentsView(viewlib.SubObjectsTab):
+    """Display agents related to an archive unit content."""
+
+    __regid__ = 'seda_content_agents'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [
+        ('seda_writer_from', 'object', None),
+        ('seda_addressee_from', 'object', None),
+        ('seda_recipient_from', 'object', None),
+    ]
+
+    _('creating SEDAWriter (SEDAWriter seda_writer_from '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDAAddressee (SEDAAddressee seda_addressee_from '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDARecipient (SEDARecipient seda_recipient_from '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linto)s)')
+
+
+# coverage tab #################################################################
+
+class ArchiveUnitCoverageTab(ArchiveUnitContentTab):
+    """Display coverage information about a full seda2 archive unit."""
+
+    __regid__ = 'seda_coverage_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_coverage'
+
+
+class ContentCoverageView(viewlib.SubObjectsTab):
+    """Display coverage information about an archive unit content."""
+
+    __regid__ = 'seda_content_coverage'
+    __select__ = is_instance(CONTENT_ETYPE)
+
+    rtype_role_targets = [('seda_spatial', 'object', None),
+                          ('seda_temporal', 'object', None),
+                          ('seda_juridictional', 'object', None)]
+
+    _('creating SEDASpatial (SEDASpatial seda_spatial '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDATemporal (SEDATemporal seda_temporal '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDAJuridictional (SEDAJuridictional seda_juridictional '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+
+
+class TypeListItemContentView(viewlib.ListItemContentView):
+    """Extended list item content view including the entity's type (for case where the list may
+    include entities of different types).
+    """
+    __select__ = is_instance('SEDAWriter', 'SEDAAddressee', 'SEDARecipient',
+                             'SEDASpatial', 'SEDATemporal', 'SEDAJuridictional')
+
+    def entity_call(self, entity):
+        entity.view('seda.type_meta', w=self.w)
+
+
 # indexation tab ###############################################################
 
 class ArchiveUnitIndexationTab(ArchiveUnitContentTab):
@@ -332,6 +536,178 @@
     rtype_role_targets = [('seda_keyword', 'object', 'SEDAKeyword')]
 
 
+class KeywordBusinessValueEntityView(viewlib.ListItemContentView):
+
+    __select__ = viewlib.ListItemContentView.__select__ & is_instance('SEDAKeyword')
+
+    def entity_call(self, entity):
+        if entity.seda_keyword_content[0].keyword_content:
+            content = entity.seda_keyword_content[0].keyword_content
+        else:
+            content = self._cw._('<no value specified>')
+        msg = xml_escape(self._cw._('keyword: {0}').format(content))
+        self.w(u'<span class="value">{0} {1}</span>'.format(msg, entity.view('seda.xsdmeta')))
+        if entity.reverse_seda_keyword_type_from:
+            kwt = entity.reverse_seda_keyword_type_from[0]
+            if kwt.seda_keyword_type_to:
+                kwt_value = kwt.seda_keyword_type_to[0].label()
+            else:
+                kwt_value = self._cw._('<no type specified>')
+            msg = xml_escape(self._cw._('keyword type: {0}').format(kwt_value))
+            self.w(u'<br/><span>{0} {1}</span>'.format(msg, kwt.view('seda.xsdmeta')))
+        if entity.reverse_seda_keyword_reference_from:
+            kwr = entity.reverse_seda_keyword_reference_from[0]
+            if kwr.concept:
+                kwr_value = kwr.concept.view('oneline')
+                msg = xml_escape(self._cw._('keyword reference: {0}')).format(kwr_value)
+            elif kwr.scheme:
+                kwr_value = kwr.scheme.view('oneline')
+                msg = xml_escape(self._cw._('keyword scheme: {0}')).format(kwr_value)
+            else:
+                msg = xml_escape(self._cw._('<no reference specified>'))
+            self.w(u'<br/><span>{0} {1}</span>'.format(msg, kwr.view('seda.xsdmeta')))
+
+
+afs.tag_subject_of(('SEDAKeywordReference', 'seda_keyword_reference_to_scheme', '*'),
+                   'main', 'attributes')
+affk.set_field_kwargs('SEDAKeywordReference', 'seda_keyword_reference_to',
+                      widget=widgets.ConceptAutoCompleteWidget(
+                          slave_name='seda_keyword_reference_to',
+                          master_name='seda_keyword_reference_to_scheme'))
+affk.set_fields_order('SEDAKeywordReference', ['user_cardinality',
+                                               'seda_keyword_reference_to_scheme',
+                                               'seda_keyword_reference_to'])
+
+
+# relations tab ################################################################
+
+class ArchiveUnitRelationsTab(ArchiveUnitContentTab):
+    """Display content's relations about a full seda2  archive unit."""
+
+    __regid__ = 'seda_relations_tab'
+    content_vid = 'seda_content_relations'
+
+
+class ContentRelationsView(ContentSubObjectsView):
+    """Display relation information about an archive unit content."""
+
+    __regid__ = 'seda_content_relations'
+    tabid = ArchiveUnitRelationsTab.__regid__
+
+    rtype_role_targets = [('seda_is_version_of', 'object', None),
+                          ('seda_replaces', 'object', None),
+                          ('seda_requires', 'object', None),
+                          ('seda_is_part_of', 'object', None),
+                          ('seda_references', 'object', None)]
+
+    _('creating SEDAIsVersionOf (SEDAIsVersionOf seda_is_version_of '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDAReplaces (SEDAReplaces seda_replaces '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDARequires (SEDARequires seda_requires '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDAIsPartOf (SEDAIsPartOf seda_is_part_of '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+    _('creating SEDAReferences (SEDAReferences seda_references '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+
+
+class ReferenceListItemContentView(viewlib.ListItemContentView):
+    __select__ = is_instance('SEDAIsVersionOf', 'SEDAReplaces', 'SEDARequires', 'SEDAIsPartOf',
+                             'SEDAReferences')
+
+    def entity_call(self, entity):
+        self.w(u'<b>{} '.format(entity.dc_type()))
+        entity.view('seda.xsdmeta')
+        self.w(u'</b> :')
+        alt_rtype = 'seda_alt_{}_archive_unit_ref_id'.format(un_camel_case(entity.cw_etype[4:]))
+        alt = entity.related(alt_rtype).one()
+        alternatives = viewlib.alternative_values(alt, alt_rtype)
+        self.w(u'<div class="value alternative">')
+        if alternatives:
+            self.w(alternatives)
+        else:
+            self.wdata(self._cw._('<no value specified>'))
+        self.w(u'</div>')
+        self.w(u'<div class="clearfix"/>')
+
+
+do_ref_afs = copy_rtag(afs, __name__,
+                       is_instance('SEDADataObjectReference') & is_typed_reference())
+do_ref_afs.tag_attribute(('SEDADataObjectReference', 'user_cardinality'), 'main', 'hidden')
+
+for etype in ('SEDAAltIsPartOfArchiveUnitRefId',
+              'SEDAAltIsVersionOfArchiveUnitRefId',
+              'SEDAAltReferencesArchiveUnitRefId',
+              'SEDAAltReplacesArchiveUnitRefId',
+              'SEDAAltRequiresArchiveUnitRefId'):
+    affk.set_fields_order(etype, [('seda_data_object_reference', 'object'),
+                                  ('seda_repository_object_pid', 'object'),
+                                  ('seda_archive_unit_ref_id_from', 'object'),
+                                  ('seda_repository_archive_unit_pid', 'object')])
+
+
+# event tab ####################################################################
+
+class ArchiveUnitEventsTab(ArchiveUnitContentTab):
+    """Display content's relations about a full seda2 archive unit."""
+
+    __regid__ = 'seda_events_tab'
+    __select__ = ArchiveUnitContentTab.__select__ & full_seda2_profile()
+    content_vid = 'seda_content_events'
+
+
+class ContentEventsView(ContentSubObjectsView):
+    """Display events about an archive unit content."""
+
+    __regid__ = 'seda_content_events'
+    tabid = ArchiveUnitEventsTab.__regid__
+
+    rtype_role_targets = [
+        ('seda_event', 'object', None),
+    ]
+
+    _('creating SEDAEvent (SEDAEvent seda_event '
+      'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
+
+
+class EventListItemContentView(viewlib.ListItemContentView):
+
+    __select__ = viewlib.ListItemContentView.__select__ & is_instance('SEDAEvent')
+
+    def entity_call(self, entity):
+        entity.view('seda.xsdmeta', w=self.w, skip_one_card=True, with_annotation=False)
+        attrs = []
+        for rtype in ['seda_event_type_from',
+                      'seda_event_identifier',
+                      'seda_event_detail']:
+            related = getattr(entity, 'reverse_' + rtype)
+            if related:
+                if related[0].user_cardinality == '1':
+                    card = self._cw._('mandatory')
+                else:
+                    card = self._cw._('optional')
+                value = ''
+                if rtype == 'seda_event_type_from':
+                    value = related[0].seda_event_type_to
+                    if value:
+                        value = value[0].label()
+                attrs.append(u'{rtype} {value} {card}'.format(rtype=self._cw.__(rtype + '_object'),
+                                                              value=value,
+                                                              card=card))
+        if attrs:
+            self.w(u' ({0})'.format(', '.join(attrs)))
+        description = getattr(entity, 'user_annotation', None)
+        if description:
+            self.w(u' <div class="description text-muted">%s</div>' % description)
+
+
+affk.set_fields_order('SEDAEvent', ['user_cardinality',
+                                    ('seda_event_type_from', 'object'),
+                                    ('seda_event_identifier', 'object'),
+                                    ('seda_event_detail', 'object')])
+
+
 # history tab ##################################################################
 
 class ArchiveUnitHistoryTab(ArchiveUnitContentTab):
@@ -353,6 +729,20 @@
       'SEDASeqAltArchiveUnitArchiveUnitRefIdManagement %(linkto)s)')
 
 
+class CustodialHistoryItemListItemContentView(viewlib.ListItemContentView):
+
+    __select__ = viewlib.ListItemContentView.__select__ & is_instance('SEDACustodialHistoryItem')
+
+    def entity_call(self, entity):
+        super(CustodialHistoryItemListItemContentView, self).entity_call(entity)
+        if entity.reverse_seda_when:
+            when = entity.reverse_seda_when[0]
+            if when.user_cardinality == '1':
+                self.w(self._cw._(' (mandatory timestamp)'))
+            else:
+                self.w(self._cw._(' (optional timestamp)'))
+
+
 # archive units tab ############################################################
 
 class ArchiveUnitArchiveUnitsTab(tabs.TabsMixin, EntityView):