Updated internal references management
authorThierry Florac <thierry.florac@onf.fr>
Thu, 12 Jul 2018 16:00:06 +0200
changeset 54 3f9dda94c354
parent 53 2342203445f1
child 55 198e62d68d1f
Updated internal references management
src/pyams_sequence/interfaces/__init__.py
src/pyams_sequence/reference.py
src/pyams_sequence/rpc/json/__init__.py
src/pyams_sequence/schema.py
src/pyams_sequence/utility.py
src/pyams_sequence/widget/__init__.py
--- a/src/pyams_sequence/interfaces/__init__.py	Mon Jul 09 11:42:45 2018 +0200
+++ b/src/pyams_sequence/interfaces/__init__.py	Thu Jul 12 16:00:06 2018 +0200
@@ -16,11 +16,11 @@
 # import standard library
 
 # import interfaces
-from pyams_sequence.schema import InternalReference, InternalReferencesList
+from pyams_sequence.schema import InternalReferenceField, InternalReferencesListField
 from zope.annotation.interfaces import IAttributeAnnotatable
 
 # import packages
-from zope.interface import Interface
+from zope.interface import Interface, Attribute
 from zope.schema import TextLine, Int
 
 from pyams_sequence import _
@@ -99,16 +99,24 @@
 class IInternalReference(Interface):
     """Internal link interface"""
 
-    reference = InternalReference(title=_("Internal reference"),
-                                  description=_("Internal link target reference. You can search a reference using "
+    reference = InternalReferenceField(title=_("Internal reference"),
+                                       description=_("Internal link target reference. You can search a reference using "
                                                 "'+' followed by internal number, of by entering text matching "
                                                 "content title."),
-                                  required=True)
+                                       required=True)
+
+    target = Attribute("Internal reference target")
+
+    def get_target(self, state=None):
+        """Get target from internal reference"""
 
 
 class IInternalReferencesList(Interface):
     """Internal references list"""
 
-    references = InternalReferencesList(title=_("Internal references"),
-                                        description=_("List of internal references"),
-                                        required=False)
\ No newline at end of file
+    references = InternalReferencesListField(title=_("Internal references"),
+                                             description=_("List of internal references"),
+                                             required=False)
+
+    def get_targets(self, state=None):
+        """Get iterator over targets from internal references"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_sequence/reference.py	Thu Jul 12 16:00:06 2018 +0200
@@ -0,0 +1,146 @@
+#
+# Copyright (c) 2008-2018 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from hypatia.interfaces import ICatalog
+from pyams_i18n.interfaces import II18n
+from pyams_sequence.interfaces import IInternalReference, ISequentialIdInfo, ISequentialIntIds
+from pyams_skin.layer import IPyAMSUserLayer
+from zope.lifecycleevent.interfaces import IObjectModifiedEvent
+
+try:
+    from pyams_workflow.interfaces import IWorkflow, IWorkflowVersions, IWorkflowVersion, \
+        IWorkflowState, IWorkflowManagedContent, IWorkflowPublicationInfo, IWorkflowTransitionEvent
+except ImportError:
+    handle_workflow = False
+else:
+    handle_workflow = True
+
+# import packages
+from hypatia.catalog import CatalogQuery
+from hypatia.query import Eq
+from pyams_catalog.query import CatalogResultSet
+from pyams_utils.registry import get_utility
+from pyams_utils.request import check_request
+from pyramid.events import subscriber
+
+
+@subscriber(IObjectModifiedEvent, context_selector=IInternalReference)
+def handle_modified_reference(event):
+    """Handle modified reference"""
+    for description in event.descriptions:
+        for attribute in description.attributes:
+            if attribute == 'reference':
+                del event.object.target
+                return
+
+
+if handle_workflow:
+    # Target is handled as a volatile property to keep references in memory
+    # Reference must be updated when a content is published or retired while being in version
+    # greater than 1
+    @subscriber(IWorkflowTransitionEvent)
+    def handle_workflow_transition(event):
+        """Handle workflow transition to update internal references"""
+        workflow_state = IWorkflowState(event.object, None)
+        if (workflow_state is None) or (workflow_state.version_id == 1):
+            return  # don't update references on first version
+        sequence_info = ISequentialIdInfo(event.object, None)
+        if sequence_info is not None:
+            catalog = get_utility(ICatalog)
+            params = Eq(catalog['link_reference'], sequence_info.hex_oid)
+            for result in CatalogResultSet(CatalogQuery(catalog).query(params)):
+                del result.target
+
+
+def get_last_version(content):
+    """Check for last available version"""
+    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
+        content = IWorkflowVersions(content).get_last_versions()[0]
+    if ISequentialIdInfo(content, None) is not None:
+        return content
+    else:
+        return None
+
+
+def get_visible_version(content):
+    """Check for visible version"""
+    if handle_workflow:
+        if IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content):
+            workflow = IWorkflow(content)
+            versions = IWorkflowVersions(content).get_versions(workflow.published_states, sort=True)
+            if versions:
+                return versions[-1]
+        publication_info = IWorkflowPublicationInfo(content, None)
+        if publication_info is not None:
+            if publication_info.is_visible():
+                return content
+            else:
+                return None
+    return content
+
+
+def get_version_in_state(content, state):
+    """Check for versions in given status"""
+    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
+        versions = IWorkflowVersions(content).get_versions(state)
+        if versions:
+            content = versions[0]
+    if ISequentialIdInfo(content, None) is not None:
+        return content
+    else:
+        return None
+
+
+def get_sequence_dict(version, attribute='title', request=None):
+    """Get OID and label matching given version"""
+    sequence = get_utility(ISequentialIntIds)
+    info = ISequentialIdInfo(version)
+    return {'id': info.hex_oid,
+            'text': '{0} ({1})'.format(II18n(version).query_attribute(attribute, request=request),
+                                       sequence.get_short_oid(info.oid))}
+
+
+def get_sequence_target(oid, state):
+    """Get content matching given OID"""
+    sequence = get_utility(ISequentialIntIds)
+    content = sequence.query_object_from_oid(oid)
+    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
+        versions = IWorkflowVersions(content).get_versions(state, sort=True)
+        if versions:
+            content = versions[0]
+            return content
+
+
+def get_reference_target(reference, state=None, request=None):
+    """Get target of given reference OID"""
+    catalog = get_utility(ICatalog)
+    params = Eq(catalog['oid'], reference)
+    results = list(CatalogResultSet(CatalogQuery(catalog).query(params)))
+    if results:
+        if state:
+            results = list(filter(lambda x: get_version_in_state(x, state), results))
+        else:
+            if request is None:
+                request = check_request()
+            if IPyAMSUserLayer.providedBy(request):
+                getter = get_visible_version
+            else:
+                getter = get_last_version
+            results = list(map(getter, results))
+        if results:
+            return results[0]
--- a/src/pyams_sequence/rpc/json/__init__.py	Mon Jul 09 11:42:45 2018 +0200
+++ b/src/pyams_sequence/rpc/json/__init__.py	Thu Jul 12 16:00:06 2018 +0200
@@ -24,7 +24,7 @@
 from hypatia.catalog import CatalogQuery
 from hypatia.query import Eq, Contains
 from pyams_catalog.query import CatalogResultSet
-from pyams_sequence.utility import get_last_version, get_sequence_dict
+from pyams_sequence.reference import get_last_version, get_sequence_dict
 from pyams_utils.list import unique
 from pyams_utils.registry import get_utility
 from pyramid_rpc.jsonrpc import jsonrpc_method
--- a/src/pyams_sequence/schema.py	Mon Jul 09 11:42:45 2018 +0200
+++ b/src/pyams_sequence/schema.py	Thu Jul 12 16:00:06 2018 +0200
@@ -23,33 +23,33 @@
 from zope.schema import TextLine, List
 
 
-class IInternalReference(ITextLine):
+class IInternalReferenceField(ITextLine):
     """Internal reference field interface"""
 
     content_type = Attribute("Requested target content type")
 
 
-@implementer(IInternalReference)
-class InternalReference(TextLine):
+@implementer(IInternalReferenceField)
+class InternalReferenceField(TextLine):
     """Internal reference field"""
 
     def __init__(self, content_type=None, *args, **kwargs):
-        super(InternalReference, self).__init__(*args, **kwargs)
+        super(InternalReferenceField, self).__init__(*args, **kwargs)
         self.content_type = content_type
 
 
-class IInternalReferencesList(IList):
+class IInternalReferencesListField(IList):
     """Internal references list field interface"""
 
     content_type = Attribute("Requested target content type")
 
 
-@implementer(IInternalReferencesList)
-class InternalReferencesList(List):
+@implementer(IInternalReferencesListField)
+class InternalReferencesListField(List):
     """Internal references list field"""
 
     def __init__(self, content_type=None, value_type=None, unique=False, *args, **kwargs):
-        super(InternalReferencesList, self).__init__(value_type=TextLine(),
-                                                     unique=True,
-                                                     *args, **kwargs)
+        super(InternalReferencesListField, self).__init__(value_type=TextLine(),
+                                                          unique=True,
+                                                          *args, **kwargs)
         self.content_type = content_type
--- a/src/pyams_sequence/utility.py	Mon Jul 09 11:42:45 2018 +0200
+++ b/src/pyams_sequence/utility.py	Thu Jul 12 16:00:06 2018 +0200
@@ -16,27 +16,11 @@
 # import standard library
 
 # import interfaces
-from hypatia.interfaces import ICatalog
-from pyams_i18n.interfaces import II18n
 from pyams_sequence.interfaces import ISequentialIntIds, ISequentialIdTarget, ISequentialIdInfo
-from pyams_skin.layer import IPyAMSUserLayer
-
-try:
-    from pyams_workflow.interfaces import IWorkflowVersions, IWorkflowVersion, IWorkflowManagedContent, IWorkflow, \
-        IWorkflowPublicationInfo
-except ImportError:
-    handle_workflow = False
-else:
-    handle_workflow = True
-
 from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectRemovedEvent
 
 # import packages
-from hypatia.catalog import CatalogQuery
-from hypatia.query import Eq, Any
-from pyams_catalog.query import CatalogResultSet
-from pyams_utils.registry import query_utility, get_utility
-from pyams_utils.request import check_request
+from pyams_utils.registry import query_utility
 from pyramid.events import subscriber
 from zope.interface import implementer, Invalid
 from zope.intid import IntIds
@@ -45,85 +29,6 @@
 from pyams_sequence import _
 
 
-def get_last_version(content):
-    """Check for last available version"""
-    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
-        content = IWorkflowVersions(content).get_last_versions()[0]
-    if ISequentialIdInfo(content, None) is not None:
-        return content
-    else:
-        return None
-
-
-def get_visible_version(content):
-    """Check for visible version"""
-    if handle_workflow:
-        if IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content):
-            workflow = IWorkflow(content)
-            versions = IWorkflowVersions(content).get_versions(workflow.published_states, sort=True)
-            if versions:
-                return versions[-1]
-        publication_info = IWorkflowPublicationInfo(content, None)
-        if publication_info is not None:
-            if publication_info.is_visible():
-                return content
-            else:
-                return None
-    return content
-
-
-def get_version_in_state(content, state):
-    """Check for versions in given status"""
-    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
-        versions = IWorkflowVersions(content).get_versions(state)
-        if versions:
-            content = versions[0]
-    if ISequentialIdInfo(content, None) is not None:
-        return content
-    else:
-        return None
-
-
-def get_sequence_dict(version, attribute='title', request=None):
-    """Get OID and label matching given version"""
-    sequence = get_utility(ISequentialIntIds)
-    info = ISequentialIdInfo(version)
-    return {'id': info.hex_oid,
-            'text': '{0} ({1})'.format(II18n(version).query_attribute(attribute, request=request),
-                                       sequence.get_short_oid(info.oid))}
-
-
-def get_sequence_target(oid, state):
-    """Get content matching given OID"""
-    sequence = get_utility(ISequentialIntIds)
-    content = sequence.query_object_from_oid(oid)
-    if handle_workflow and (IWorkflowVersion.providedBy(content) or IWorkflowManagedContent.providedBy(content)):
-        versions = IWorkflowVersions(content).get_versions(state, sort=True)
-        if versions:
-            content = versions[0]
-            return content
-
-
-def get_reference_target(reference, state=None, request=None):
-    """Get target of given reference OID"""
-    catalog = get_utility(ICatalog)
-    params = Eq(catalog['oid'], reference)
-    results = list(CatalogResultSet(CatalogQuery(catalog).query(params)))
-    if results:
-        if state:
-            results = list(filter(lambda x: get_version_in_state(x, state), results))
-        else:
-            if request is None:
-                request = check_request()
-            if IPyAMSUserLayer.providedBy(request):
-                getter = get_visible_version
-            else:
-                getter = get_last_version
-            results = list(map(getter, results))
-        if results:
-            return results[0]
-
-
 @implementer(ISequentialIntIds)
 class SequentialIntIds(IntIds):
     """Sequential IntIds utility"""
--- a/src/pyams_sequence/widget/__init__.py	Mon Jul 09 11:42:45 2018 +0200
+++ b/src/pyams_sequence/widget/__init__.py	Thu Jul 12 16:00:06 2018 +0200
@@ -21,7 +21,7 @@
 from pyams_form.interfaces.form import IFormLayer
 from pyams_i18n.interfaces import II18n
 from pyams_sequence.interfaces import ISequentialIntIds, ISequentialIdInfo
-from pyams_sequence.schema import IInternalReference, IInternalReferencesList
+from pyams_sequence.schema import IInternalReferenceField, IInternalReferencesListField
 from pyams_sequence.widget.interfaces import IInternalReferenceWidget, IInternalReferencesListWidget
 
 try:
@@ -38,7 +38,7 @@
 from hypatia.query import Eq, Any
 from pyams_catalog.query import CatalogResultSet
 from pyams_form.widget import widgettemplate_config
-from pyams_sequence.utility import get_last_version
+from pyams_sequence.reference import get_last_version
 from pyams_utils.adapter import adapter_config
 from pyams_utils.registry import get_utility
 from z3c.form.browser.widget import HTMLInputWidget
@@ -80,7 +80,7 @@
             return json.dumps({self.value: translate(_("missing reference: {0}")).format(self.value)})
 
 
-@adapter_config(context=(IInternalReference, IFormLayer), provides=IFieldWidget)
+@adapter_config(context=(IInternalReferenceField, IFormLayer), provides=IFieldWidget)
 def InternalReferenceFieldWidget(field, request):
     """Internal reference field widget factory"""
     return FieldWidget(field, InternalReferenceWidget(request))
@@ -90,7 +90,7 @@
 # Internal references list widget
 #
 
-@adapter_config(context=(IInternalReferencesList, IInternalReferencesListWidget), provides=IDataConverter)
+@adapter_config(context=(IInternalReferencesListField, IInternalReferencesListWidget), provides=IDataConverter)
 class InternalReferencesListDataConverter(BaseDataConverter):
     """Internal references list data converter"""
 
@@ -135,7 +135,7 @@
         return json.dumps(results)
 
 
-@adapter_config(context=(IInternalReferencesList, IFormLayer), provides=IFieldWidget)
+@adapter_config(context=(IInternalReferencesListField, IFormLayer), provides=IFieldWidget)
 def InternalReferencesListFieldWidget(field, request):
     """Internal references list field widget factory"""
     return FieldWidget(field, InternalReferencesListWidget(request))