src/pyams_content/component/paragraph/milestone.py
changeset 456 07646760c1b5
child 517 542aebe1bb4a
equal deleted inserted replaced
455:95582493a5ac 456:07646760c1b5
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 from pyramid.events import subscriber
       
    13 
       
    14 __docformat__ = 'restructuredtext'
       
    15 
       
    16 
       
    17 # import standard library
       
    18 from persistent import Persistent
       
    19 
       
    20 # import interfaces
       
    21 from pyams_content.component.paragraph.interfaces import IParagraphContainerTarget, IParagraphContainer, \
       
    22     IParagraphFactory
       
    23 from pyams_content.component.paragraph.interfaces.milestone import IMilestone, IMilestoneContainer, \
       
    24     IMilestoneContainerTarget, MILESTONE_CONTAINER_KEY, IMilestoneParagraph, MILESTONE_PARAGRAPH_TYPE, \
       
    25     MILESTONE_PARAGRAPH_RENDERERS
       
    26 from pyams_content.features.checker.interfaces import IContentChecker, MISSING_VALUE, MISSING_LANG_VALUE, ERROR_VALUE
       
    27 from pyams_form.interfaces.form import IFormContextPermissionChecker
       
    28 from pyams_i18n.interfaces import II18n, II18nManager, INegotiator
       
    29 from zope.annotation import IAnnotations
       
    30 from zope.location.interfaces import ISublocations
       
    31 from zope.traversing.interfaces import ITraversable
       
    32 
       
    33 # import packages
       
    34 from pyams_catalog.utils import index_object
       
    35 from pyams_content.component.paragraph import BaseParagraph, BaseParagraphFactory, BaseParagraphContentChecker
       
    36 from pyams_content.features.checker import BaseContentChecker
       
    37 from pyams_content.features.renderer import RenderedContentMixin, IContentRenderer
       
    38 from pyams_utils.adapter import adapter_config, ContextAdapter
       
    39 from pyams_utils.registry import get_current_registry, get_utility, utility_config
       
    40 from pyams_utils.request import check_request
       
    41 from pyams_utils.traversing import get_parent
       
    42 from pyams_utils.vocabulary import vocabulary_config
       
    43 from zope.container.contained import Contained
       
    44 from zope.container.ordered import OrderedContainer
       
    45 from zope.lifecycleevent import ObjectCreatedEvent, IObjectAddedEvent, ObjectModifiedEvent, IObjectModifiedEvent, \
       
    46     IObjectRemovedEvent
       
    47 from zope.location import locate
       
    48 from zope.interface import implementer
       
    49 from zope.schema.fieldproperty import FieldProperty
       
    50 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
       
    51 
       
    52 from pyams_content import _
       
    53 
       
    54 
       
    55 #
       
    56 # Milestone class and adapters
       
    57 #
       
    58 
       
    59 @implementer(IMilestone)
       
    60 class Milestone(Persistent, Contained):
       
    61     """Milestone persistent class"""
       
    62 
       
    63     visible = FieldProperty(IMilestone['visible'])
       
    64     title = FieldProperty(IMilestone['title'])
       
    65     label = FieldProperty(IMilestone['label'])
       
    66     anchor = FieldProperty(IMilestone['anchor'])
       
    67 
       
    68 
       
    69 @adapter_config(context=IMilestone, provides=IFormContextPermissionChecker)
       
    70 class MilestonePermissionChecker(ContextAdapter):
       
    71     """Milestone permission checker"""
       
    72 
       
    73     @property
       
    74     def edit_permission(self):
       
    75         content = get_parent(self.context, IMilestoneContainerTarget)
       
    76         return IFormContextPermissionChecker(content).edit_permission
       
    77 
       
    78 
       
    79 @subscriber(IObjectAddedEvent, context_selector=IMilestone)
       
    80 def handle_added_milestone(event):
       
    81     """Handle added milestone"""
       
    82     content = get_parent(event.object, IMilestoneContainerTarget)
       
    83     if content is not None:
       
    84         get_current_registry().notify(ObjectModifiedEvent(content))
       
    85 
       
    86 
       
    87 @subscriber(IObjectModifiedEvent, context_selector=IMilestone)
       
    88 def handle_modified_milestone(event):
       
    89     """Handle modified milestone"""
       
    90     content = get_parent(event.object, IMilestoneContainerTarget)
       
    91     if content is not None:
       
    92         get_current_registry().notify(ObjectModifiedEvent(content))
       
    93 
       
    94 
       
    95 @subscriber(IObjectRemovedEvent, context_selector=IMilestone)
       
    96 def handle_removed_milestone(event):
       
    97     """Handle removed milestone"""
       
    98     content = get_parent(event.object, IMilestoneContainerTarget)
       
    99     if content is not None:
       
   100         get_current_registry().notify(ObjectModifiedEvent(content))
       
   101 
       
   102 
       
   103 @adapter_config(context=IMilestone, provides=IContentChecker)
       
   104 class MilestoneContentChecker(BaseContentChecker):
       
   105     """Milestone content checker"""
       
   106 
       
   107     @property
       
   108     def label(self):
       
   109         request = check_request()
       
   110         return II18n(self.context).query_attribute('title', request=request)
       
   111 
       
   112     def inner_check(self, request):
       
   113         output = []
       
   114         translate = request.localizer.translate
       
   115         manager = get_parent(self.context, II18nManager)
       
   116         if manager is not None:
       
   117             langs = manager.get_languages()
       
   118         else:
       
   119             negotiator = get_utility(INegotiator)
       
   120             langs = (negotiator.server_language, )
       
   121         i18n = II18n(self.context)
       
   122         for lang in langs:
       
   123             for attr in ('title', 'label'):
       
   124                 value = i18n.get_attribute(attr, lang, request)
       
   125                 if not value:
       
   126                     field_title = translate(IMilestone[attr].title)
       
   127                     if len(langs) == 1:
       
   128                         output.append(translate(MISSING_VALUE).format(field=field_title))
       
   129                     else:
       
   130                         output.append(translate(MISSING_LANG_VALUE).format(field=field_title, lang=lang))
       
   131         field_title = translate(IMilestone['anchor'].title)
       
   132         if not self.context.anchor:
       
   133             output.append(translate(MISSING_VALUE).format(field=field_title))
       
   134         else:
       
   135             target = get_parent(self.context, IParagraphContainerTarget)
       
   136             if target is not None:
       
   137                 container = IParagraphContainer(target)
       
   138                 paragraph = container.get(self.context.anchor)
       
   139                 if paragraph is None:
       
   140                     output.append(translate(ERROR_VALUE).format(field=field_title,
       
   141                                                                 message=translate(_("Selected paragraph is missing"))))
       
   142                 elif not paragraph.visible:
       
   143                     output.append(translate(ERROR_VALUE).format(field=field_title,
       
   144                                                                 message=translate(_("Selected paragraph is not "
       
   145                                                                                     "visible"))))
       
   146         return output
       
   147 
       
   148 
       
   149 #
       
   150 # Milestones container classes and adapters
       
   151 #
       
   152 
       
   153 @implementer(IMilestoneContainer)
       
   154 class MilestoneContainer(OrderedContainer):
       
   155     """Milestones container"""
       
   156 
       
   157     last_id = 1
       
   158 
       
   159     def append(self, value, notify=True):
       
   160         key = str(self.last_id)
       
   161         if not notify:
       
   162             # pre-locate milestone item to avoid multiple notifications
       
   163             locate(value, self, key)
       
   164         self[key] = value
       
   165         self.last_id += 1
       
   166         if not notify:
       
   167             # make sure that milestone item is correctly indexed
       
   168             index_object(value)
       
   169 
       
   170     def get_visible_items(self):
       
   171         return filter(lambda x: IMilestone(x).visible, self.values())
       
   172 
       
   173 
       
   174 @adapter_config(context=IMilestoneContainerTarget, provides=IMilestoneContainer)
       
   175 def milestone_container_factory(target):
       
   176     """Milestone container factory"""
       
   177     annotations = IAnnotations(target)
       
   178     container = annotations.get(MILESTONE_CONTAINER_KEY)
       
   179     if container is None:
       
   180         container = annotations[MILESTONE_CONTAINER_KEY] = MilestoneContainer()
       
   181         get_current_registry().notify(ObjectCreatedEvent(container))
       
   182         locate(container, target, '++milestones++')
       
   183     return container
       
   184 
       
   185 
       
   186 @adapter_config(name='milestones', context=IMilestoneContainerTarget, provides=ITraversable)
       
   187 class MilestoneContainerNamespace(ContextAdapter):
       
   188     """Milestones container ++milestones++ namespace"""
       
   189 
       
   190     def traverse(self, name, furtherpaath=None):
       
   191         return IMilestoneContainer(self.context)
       
   192 
       
   193 
       
   194 @adapter_config(name='milestones', context=IMilestoneContainerTarget, provides=ISublocations)
       
   195 class MilestoneContainerSublocations(ContextAdapter):
       
   196     """Milestones container sub-locations adapter"""
       
   197 
       
   198     def sublocations(self):
       
   199         return IMilestoneContainer(self.context).values()
       
   200 
       
   201 
       
   202 @adapter_config(name='milestones', context=IMilestoneContainerTarget, provides=IContentChecker)
       
   203 class MilestoneContainerContentChecker(BaseContentChecker):
       
   204     """Milestones container content checker"""
       
   205 
       
   206     label = _("Milestones")
       
   207     sep = '\n'
       
   208     weight = 200
       
   209 
       
   210     def inner_check(self, request):
       
   211         output = []
       
   212         registry = request.registry
       
   213         for milestone in IMilestoneContainer(self.context).values():
       
   214             if not milestone.visible:
       
   215                 continue
       
   216             for name, checker in sorted(registry.getAdapters((milestone, ), IContentChecker),
       
   217                                         key=lambda x: x[1].weight):
       
   218                 output.append('- {0} :'.format(II18n(milestone).query_attribute('title', request=request)))
       
   219                 output.append(checker.get_check_output(request))
       
   220         return output
       
   221 
       
   222 
       
   223 @implementer(IMilestoneParagraph)
       
   224 class MilestoneParagraph(RenderedContentMixin, BaseParagraph):
       
   225     """Milestones paragraph"""
       
   226 
       
   227     icon_class = 'fa-arrows-h'
       
   228     icon_hint = _("Milestones")
       
   229 
       
   230     renderer = FieldProperty(IMilestoneParagraph['renderer'])
       
   231 
       
   232 
       
   233 @utility_config(name=MILESTONE_PARAGRAPH_TYPE, provides=IParagraphFactory)
       
   234 class MilestoneParagraphFactory(BaseParagraphFactory):
       
   235     """Milestones paragraph factory"""
       
   236 
       
   237     name = _("Milestones paragraph")
       
   238     content_type = MilestoneParagraph
       
   239 
       
   240 
       
   241 @adapter_config(context=IMilestoneParagraph, provides=IContentChecker)
       
   242 class MilestoneParagraphContentChecker(BaseParagraphContentChecker):
       
   243     """Milestones paragraph content checker"""
       
   244 
       
   245     @property
       
   246     def label(self):
       
   247         request = check_request()
       
   248         translate = request.localizer.translate
       
   249         return II18n(self.context).query_attribute('title', request) or \
       
   250             '({0})'.format(translate(self.context.icon_hint).lower())
       
   251 
       
   252     def inner_check(self, request):
       
   253         output = []
       
   254         translate = request.localizer.translate
       
   255         manager = get_parent(self.context, II18nManager)
       
   256         if manager is not None:
       
   257             langs = manager.get_languages()
       
   258         else:
       
   259             negotiator = get_utility(INegotiator)
       
   260             langs = (negotiator.server_language, )
       
   261         i18n = II18n(self.context)
       
   262         for lang in langs:
       
   263             value = i18n.get_attribute('title', lang, request)
       
   264             if not value:
       
   265                 field_title = translate(IMilestoneParagraph['title'].title)
       
   266                 if len(langs) == 1:
       
   267                     output.append(translate(MISSING_VALUE).format(field=field_title))
       
   268                 else:
       
   269                     output.append(translate(MISSING_LANG_VALUE).format(field=field_title, lang=lang))
       
   270         return output
       
   271 
       
   272 
       
   273 @vocabulary_config(name=MILESTONE_PARAGRAPH_RENDERERS)
       
   274 class MilestoneParagraphRendererVocabulary(SimpleVocabulary):
       
   275     """Milestones paragraph renderers vocabulary"""
       
   276 
       
   277     def __init__(self, context=None):
       
   278         request = check_request()
       
   279         translate = request.localizer.translate
       
   280         registry = request.registry
       
   281         if not IMilestoneParagraph.providedBy(context):
       
   282             context = MilestoneParagraph()
       
   283         terms = [SimpleTerm(name, title=translate(adapter.label))
       
   284                  for name, adapter in sorted(registry.getAdapters((context, request), IContentRenderer),
       
   285                                              key=lambda x: x[1].weight)]
       
   286         super(MilestoneParagraphRendererVocabulary, self).__init__(terms)