Exemple #1
0
 def objectStateChanged(self, object, event):
     """
     object: origin domain workflowed object 
     event: bungeni.core.workflow.states.WorkflowTransitionEvent
         .object # origin domain workflowed object 
         .source # souirce state
         .destination # destination state
         .transition # transition
         .comment #
     """
     change_data = self._get_change_data()
     # if note, attach it on object (if object supports such an attribute)
     if change_data["note"]:
         if hasattr(object, "note"):
             object.note = change_data["note"]
     # update object's workflow status date (if supported by object)
     if hasattr(object, "status_date"):
         object.status_date = change_data["date_active"] or datetime.now()
     # as a "base" description, use human readable workflow state title
     wf = IWorkflow(object) # !+ adapters.get_workflow(object)
     description = wf.get_state(event.destination).title
     # extras, that may be used e.g. to elaborate description at runtime
     extras = {
         "source": event.source, 
         "destination": event.destination,
         "transition": event.transition.id,
         "comment": change_data["note"]
     }
     return self._objectChanged("workflow", object, 
                     description=description,
                     extras=extras,
                     date_active=change_data["date_active"])
Exemple #2
0
    def change_data_items(self):
        """Get change data items, reverse-sorted by date (most recent first).
        """
        interaction = getInteraction()  # slight performance optimization
        changes = []

        def append_visible_changes_on_item(item):
            for c in domain.get_changes(item, *self.include_change_actions):
                if interaction.checkPermission("zope.View", c):
                    changes.append(c)

        # !+ align checkPermission zope.View with listings of sub item types...

        # changes direct on head item
        if "head" in self.include_change_types:
            append_visible_changes_on_item(self.head)

        # changes on sub-items -- only Parliamentary Content may have sub-items
        if interfaces.IBungeniParliamentaryContent.providedBy(self.head):
            hwf = IWorkflow(self.head)

            # changes on item signatories
            if "signatory" in self.include_change_types:
                signatories = [
                    s for s in self.head.item_signatories
                    if interaction.checkPermission("zope.View", s)
                ]
                for s in signatories:
                    append_visible_changes_on_item(s)

            # changes on item attachments
            if ("attachment" in self.include_change_types
                    and hwf.has_feature("attachment")  #!+IFeatureAttachment?
                ):
                attachments = [
                    f for f in self.head.attachments
                    if interaction.checkPermission("zope.View", f)
                ]
                for f in attachments:
                    append_visible_changes_on_item(f)

            # changes on item events
            if ("event" in self.include_change_types
                    # and workflow.has_feature("event"): #!+IEventable?
                    # at least no recursion, on events on events...
                    and not interfaces.IEvent.providedBy(self.head)):
                events = [
                    e for e in self.head.sa_events
                    if interaction.checkPermission("zope.View", e)
                ]
                for e in events:
                    append_visible_changes_on_item(e)

        # sort aggregated changes by date_active
        changes = [
            dc[1]
            for dc in reversed(sorted([(c.date_active, c) for c in changes]))
        ]
        return changes
        '''
Exemple #3
0
def schedule_sitting_items(context):

    # !+fireTransitionToward(mr, dec-2010) sequence of fireTransitionToward
    # calls was introduced in r5818, 28-jan-2010 -- here the code is reworked
    # to be somewhat more sane, and added logging of both SUCCESS and of
    # FAILURE of each call to fireTransitionToward().
    #
    # The check/logging should be removed once it is understood whether
    # NoTransitionAvailableError is *always* raised (i.e. fireTransitionToward is
    # broken) or it is indeed raised correctly when it should be.

    def fireTransitionScheduled(item, check_security=False):
        try:
            IWorkflowController(item).fireTransitionToward(
                "scheduled", check_security=False)
            raise RuntimeWarning(
                """It has WORKED !!! fireTransitionToward("scheduled")""")
        except (NoTransitionAvailableError, RuntimeWarning):
            debug.log_exc_info(sys.exc_info(), log.error)

    for schedule in context.item_schedule:
        wf = IWorkflow(schedule.item, None)
        if wf is None:
            continue
        try:
            if wf.get_state("scheduled"):
                fireTransitionScheduled(schedule.item)
        except InvalidStateError:
            pass
Exemple #4
0
 def objectStateChanged(self, object, event):
     """
     object: origin domain workflowed object 
     event: bungeni.core.workflow.states.WorkflowTransitionEvent
         .object # origin domain workflowed object 
         .source # souirce state
         .destination # destination state
         .transition # transition
         .comment #
     """
     change_data = self._get_change_data()
     # if note, attach it on object (if object supports such an attribute)
     if change_data["note"]:
         if hasattr(object, "note"):
             object.note = change_data["note"]
     # update object's workflow status date (if supported by object)
     if hasattr(object, "status_date"):
         object.status_date = change_data["date_active"] or datetime.now()
     # as a "base" description, use human readable workflow state title
     wf = IWorkflow(object)  # !+ adapters.get_workflow(object)
     description = wf.get_state(event.destination).title
     # extras, that may be used e.g. to elaborate description at runtime
     extras = {
         "source": event.source,
         "destination": event.destination,
         "transition": event.transition.id,
         "comment": change_data["note"]
     }
     return self._objectChanged("workflow",
                                object,
                                description=description,
                                extras=extras,
                                date_active=change_data["date_active"])
Exemple #5
0
def schedule_sitting_items(context):
    
    # !+fireTransitionToward(mr, dec-2010) sequence of fireTransitionToward 
    # calls was introduced in r5818, 28-jan-2010 -- here the code is reworked
    # to be somewhat more sane, and added logging of both SUCCESS and of 
    # FAILURE of each call to fireTransitionToward().
    #
    # The check/logging should be removed once it is understood whether
    # NoTransitionAvailableError is *always* raised (i.e. fireTransitionToward is
    # broken) or it is indeed raised correctly when it should be.
    
    def fireTransitionScheduled(item, check_security=False):
        try:
            IWorkflowController(item).fireTransitionToward("scheduled", 
                    check_security=False)
            raise RuntimeWarning(
                """It has WORKED !!! fireTransitionToward("scheduled")""")
        except (NoTransitionAvailableError, RuntimeWarning):
            debug.log_exc_info(sys.exc_info(), log.error)
    
    for schedule in context.item_schedule:
        wf = IWorkflow(schedule.item, None)
        if wf is None: 
            continue
        try:
            if wf.get_state("scheduled"):
                fireTransitionScheduled(schedule.item)
        except InvalidStateError:
            pass
Exemple #6
0
def get_sitting_items(sitting, request, include_actions=False):
    items = []

    if (sitting.status in IWorkflow(sitting).get_state_ids(
            keys=["draft_agenda", "published_agenda"])):
        order = "planned_order"
    else:
        order = "real_order"

    schedulings = map(removeSecurityProxy,
                      sitting.items.batch(order_by=order, limit=None))
    for scheduling in schedulings:
        item = ProxyFactory(location_wrapped(scheduling.item, sitting))

        props = IDCDescriptiveProperties.providedBy(item) and item or \
                IDCDescriptiveProperties(item)

        discussions = tuple(scheduling.discussions.values())
        discussion = discussions and discussions[0] or None
        truncated_discussion = None
        if ((discussion is not None) and (discussion.body is not None)):
            #truncate discussion to first hundred characters
            t_discussion = discussion.body[0:100]
            try:
                #truncate discussion to first two lines
                index = t_discussion.index("<br>")
                index2 = t_discussion.index("<br>", index + 4)
                truncated_discussion = t_discussion[0:index2] + "..."
            except ValueError:
                truncated_discussion = t_discussion + "..."
        state_title = IWorkflow(item).get_state(item.status).title
        item = removeSecurityProxy(item)
        record = {
            "title": props.title,
            "description": props.description,
            "name": stringKey(scheduling),
            "status": item.status,
            "type": item.type.capitalize,
            "state_title": state_title,
            "heading": True if item.type == "heading" else False,
            #"category_id": scheduling.category_id,
            #"category": scheduling.category,
            "discussion": discussion,
            "truncated_discussion": truncated_discussion,
            "delete_url": "%s/delete" % url.absoluteURL(scheduling, request),
            "url": url.absoluteURL(item, request),
        }

        if include_actions:
            record["actions"] = get_scheduling_actions(scheduling, request)
            record["workflow"] = get_workflow_actions(item, request)

            discussion_actions = get_discussion_actions(discussion, request)
            if discussion_actions:
                assert len(discussion_actions) == 1
                record["discussion_action"] = discussion_actions[0]
            else:
                record["discussion_action"] = None
        items.append(record)
    return items
 def documentInDraft(self):
     """Assume destinations of transitions with no sources are draft
     """
     wf = IWorkflow(self.pi_instance, None)
     if wf:
         return (self.pi_instance.status in 
             [tr.destination for tr in wf.get_transitions_from(None)]
         )
     return False
 def documentInDraft(self):
     """Assume destinations of transitions with no sources are draft
     """
     wf = IWorkflow(self.pi_instance, None)
     if wf:
         return (self.pi_instance.status in [
             tr.destination for tr in wf.get_transitions_from(None)
         ])
     return False
Exemple #9
0
    def change_data_items(self):
        """Get change data items, reverse-sorted by date (most recent first).
        """
        interaction = getInteraction()
        changes = []

        def append_visible_changes_on_item(item):
            permission = view_permission(item)
            for c in domain.get_changes(item, *self.include_change_actions):
                if checkPermission(permission, c):
                    changes.append(c)
    
        # changes direct on head item
        if "head" in self.include_change_types:
            append_visible_changes_on_item(self.head)
        
        # changes on sub-items -- only Parliamentary Content may have sub-items
        if interfaces.IBungeniParliamentaryContent.providedBy(self.head):
            hwf = IWorkflow(self.head)
            
            # changes on item signatories
            if "signatory" in self.include_change_types:
                signatories = [ s for s in self.head.item_signatories
                    if interaction.checkPermission("bungeni.signatory.View", s)
                ]
                for s in signatories:
                    append_visible_changes_on_item(s)
            
            # changes on item attachments
            if ("attachment" in self.include_change_types 
                    and hwf.has_feature("attachment") #!+IFeatureAttachment?
                ):
                attachments = [ f for f in self.head.attachments
                    if interaction.checkPermission("bungeni.attachment.View", f)
                ]
                for f in attachments:
                    append_visible_changes_on_item(f)
            
            # changes on item events
            if ("event" in self.include_change_types 
                    # and workflow.has_feature("event"): #!+IEventable?
                    # at least no recursion, on events on events...
                    and not interfaces.IEvent.providedBy(self.head)
                ):
                events = [ e for e in self.head.sa_events
                    if interaction.checkPermission("bungeni.event.View", e)
                ]
                for e in events:
                    append_visible_changes_on_item(e)
        
        # sort aggregated changes by date_active
        changes = [ dc[1] for dc in 
            reversed(sorted([ (c.date_active, c) for c in changes ])) ]
        return changes
        '''
 def generate_preview(self):
     sitting = removeSecurityProxy(self.context)
     wf = IWorkflow(sitting)
     sittings = [data.ExpandedSitting(sitting)]
     generator = generators.ReportGeneratorXHTML(self.get_template())
     if sitting.status in wf.get_state_ids(tagged=["publishedminutes"]):
         title = generator.title = _(u"Sitting Votes and Proceedings")
     else:
         title = generator.title = _(u"Sitting Agenda")
     generator.context = data.ReportContext(sittings=sittings, title=title)
     return generator.generateReport()
Exemple #11
0
    def getMenuItems(self, context, request):
        """Return menu item entries in a TAL-friendly form."""
        if (not interfaces.IWorkspaceOrAdminSectionLayer.providedBy(request) or
            interfaces.IFormEditLayer.providedBy(request) or
            IVersion.providedBy(context)
        ):
            return ()
        #!+wfc.workflow
        wf = IWorkflow(context, None)
        if wf is None:
            return ()
        #state = IWorkflowController(context).state_controller.get_status()
        wfc = IWorkflowController(context)
        wf = wfc.workflow  # !+wfc.workflow
        tids = wfc.getManualTransitionIds()

        parliament_id = getCurrentParliamentId()
        _url = url.absoluteURL(context, request)
        site_url2 = url.absoluteURL(getSite(), request)
        results = []
        for tid in tids:
            state_transition = wf.get_transition(tid)
            #Compares the current url to homepage to determine whether
            #we are on workspace or not.
            #Fix for bug 319
            #Someone should probably review this.
            if _url == site_url2:
                transition_url = site_url2 + \
                             "/archive/browse/parliaments/obj-" + \
                             str(parliament_id) + \
                             "/change_workflow_state?" + \
                             "transition=%s&next_url=..." % tid
            else:
                transition_url = _url + \
                             "/change_workflow_state?"\
                             "transition=%s&next_url=..." % tid
            extra = {"id": "workflow-transition-%s" % tid,
                     "separator": None,
                     "class": ""}
            state_title = translate(str(state_transition.title),
                    domain="bungeni",
                    context=request)
            results.append(
                dict(title=state_title,
                     description="",
                     action=transition_url,
                     selected=False,
                     transition_id=tid,
                     icon=None,
                     extra=extra,
                     submenu=None))

        return results
Exemple #12
0
 def as_json(self):
     is_text = IScheduleText.implementedBy(self.domain_class)
     date_formatter = date.getLocaleFormatter(common.get_request(), "date",
                                              "medium")
     items = [
         dict(
             item_type=self.item_type,
             item_id=orm.object_mapper(item).primary_key_from_instance(
                 item)[0],
             item_title=IDCDescriptiveProperties(item).title,
             status=(IWorkflow(item).get_state(item.status).title
                     if not is_text else None),
             status_date=(date_formatter.format(item.submission_date) if
                          (hasattr(item, "submission_date")
                           and getattr(item, "submission_date")) else None),
             registry_number=(item.registry_number if hasattr(
                 item, "registry_number") else None),
             item_mover=(IDCDescriptiveProperties(item.owner).title
                         if hasattr(item, "owner") else None),
             item_uri="%s-%d" %
             (self.item_type,
              orm.object_mapper(item).primary_key_from_instance(item)[0]))
         for item in self.query()
     ]
     items = sorted(items,
                    key=lambda item: item.get("status_date"),
                    reverse=True)
     return json.dumps(dict(items=items))
Exemple #13
0
def _get(discriminator):
    """Get the TypeInfo instance for discriminator, that may be any of:
            type_key: str (the lowercase underscore-separated of domain cls name)
            workflow: an instance of Workflow, provides IWorkflow
            interface: provides IInterface
            domain model: provides IBungeniContent
            domain model instance: type provides IBungeniContent
            descriptor: provides IModelDescriptor
    
    Raise KeyError if no entry matched.
    
    Usage: capi.get_type_info(discriminator)
    """
    if discriminator is None:
        m = "type_info._get discriminator is None"
        log.error(m)
        raise ValueError(m)
    discri = removeSecurityProxy(discriminator)
    getter = None

    # !+IALCHEMISTCONTENT normalize trickier discriminator cases to type_key
    if IIModelInterface.providedBy(discri):
        discri = naming.type_key("table_schema_interface_name",
                                 discri.__name__)
    elif IInterface.providedBy(discri):
        discri = naming.type_key("model_interface_name", discri.__name__)
    elif type(discri) is type and issubclass(discri, domain.Entity):
        discri = naming.polymorphic_identity(discri)
    elif isinstance(discri, domain.Entity):
        discri = naming.polymorphic_identity(type(discri))

    if isinstance(discri, basestring):
        getter = _get_by_type_key
    #elif IInterface.providedBy(discri):
    #    getter = _get_by_interface
    #!+elif interfaces.IBungeniContent.implementedBy(discri):
    #elif issubclass(discri, domain.Entity):
    #    getter = _get_by_model
    #!+elif interfaces.IBungeniContent.providedBy(discri):
    #elif isinstance(discri, domain.Entity):
    #    getter = _get_by_instance
    elif IWorkflow.providedBy(discri):
        getter = _get_by_workflow
    elif IModelDescriptor.implementedBy(discri):
        getter = _get_by_descriptor_model

    if getter is not None:
        ti = getter(discri)
        if ti is not None:
            return ti
        else:
            m = "No type registered for discriminator: %r" % (discriminator)
    else:
        m = "Invalid type info lookup discriminator: %r" % (discriminator)
    from bungeni.utils import probing
    log.debug(probing.interfaces(discriminator))
    log.debug(m)
    raise KeyError(m)
Exemple #14
0
def _get(discriminator):
    """Get the TypeInfo instance for discriminator, that may be any of:
            type_key: str (the lowercase underscore-separated of domain cls name)
            workflow: an instance of Workflow, provides IWorkflow
            interface: provides IInterface
            domain model: provides IBungeniContent
            domain model instance: type provides IBungeniContent
            descriptor: provides IModelDescriptor
    
    Raise KeyError if no entry matched.
    
    Usage: capi.get_type_info(discriminator)
    """
    if discriminator is None:
        m = "type_info._get discriminator is None"
        log.error(m)
        raise ValueError(m)
    discri = removeSecurityProxy(discriminator)
    getter = None
    
    # !+IALCHEMISTCONTENT normalize trickier discriminator cases to type_key
    if IIModelInterface.providedBy(discri):
        discri = naming.type_key("table_schema_interface_name", discri.__name__)
    elif IInterface.providedBy(discri):
        discri = naming.type_key("model_interface_name", discri.__name__)
    elif type(discri) is type and issubclass(discri, domain.Entity):
        discri = naming.polymorphic_identity(discri)
    elif isinstance(discri, domain.Entity):
        discri = naming.polymorphic_identity(type(discri))
    
    if isinstance(discri, basestring):
        getter = _get_by_type_key
    #elif IInterface.providedBy(discri):
    #    getter = _get_by_interface
    #!+elif interfaces.IBungeniContent.implementedBy(discri):
    #elif issubclass(discri, domain.Entity):
    #    getter = _get_by_model
    #!+elif interfaces.IBungeniContent.providedBy(discri):
    #elif isinstance(discri, domain.Entity):
    #    getter = _get_by_instance
    elif IWorkflow.providedBy(discri):
        getter = _get_by_workflow
    elif IModelDescriptor.implementedBy(discri):
        getter = _get_by_descriptor_model
    
    if getter is not None:
        ti = getter(discri)
        if ti is not None:
            return ti
        else:
            m = "No type registered for discriminator: %r" % (discriminator)
    else: 
        m = "Invalid type info lookup discriminator: %r" % (discriminator)
    from bungeni.ui.utils import debug
    log.debug(debug.interfaces(discriminator))
    log.debug(m)
    raise KeyError(m)
Exemple #15
0
 def __new__(cls, context, request):
     # this is currently the only way to make sure this menu only
     # "adapts" to a workflowed context; the idea is that the
     # component lookup will fail, which will propagate back to the
     # original lookup request
     workflow = IWorkflow(context, None)
     if workflow is None:
         return
     return object.__new__(cls, context, request)
Exemple #16
0
def default_reports(sitting, event):
    wf = IWorkflow(sitting)
    if sitting.status in wf.get_state_ids(tagged=["published"]):
        sitting = removeSecurityProxy(sitting)
        sittings = [ExpandedSitting(sitting)]
        report_context = ReportContext(sittings=sittings)
        report = domain.Report()
        session = Session()
        # !+GROUP_AS_OWNER(mr, apr-2012) we assume for now that the "owner" of
        # the report is the currently logged in user.
        report.owner_id = get_db_user_id()
        report.created_date = datetime.datetime.now()
        report.group_id = sitting.group_id
        
        # generate using html template in bungeni_custom
        vocabulary = component.queryUtility(
            schema.interfaces.IVocabularyFactory, 
            "bungeni.vocabulary.ReportXHTMLTemplates"
        )
        preview_template = filter(
            lambda t: t.title=="Sitting Agenda", vocabulary.terms
        )[0]
        doc_template = preview_template.value
        generator = generators.ReportGeneratorXHTML(doc_template)
        generator.context = report_context
        report.language = generator.language
        
        if sitting.status in wf.get_state_ids(tagged=["publishedminutes"]):
            report.short_title = generator.title = _(u"Sitting Votes and "
                u" Proceedings"
            )
        else:
            report.short_title = generator.title = _(u"Sitting Agenda")
    
        report.body = generator.generateReport()
        session.add(report)
        session.flush()
        notify(ObjectCreatedEvent(report))
        sr = domain.SittingReport()
        sr.report = report
        sr.sitting = sitting
        session.add(sr)
        session.flush()
        notify(ObjectCreatedEvent(sr))
Exemple #17
0
 def get_status(self, item_type):
     workspace_config = component.getUtility(IWorkspaceTabsUtility)
     roles = get_workspace_roles() + OBJECT_ROLES
     domain_class = workspace_config.get_domain(item_type)
     results = set()
     for role in roles:
         status = workspace_config.get_status(role, domain_class,
                                              self.context.__name__)
         if status:
             for s in status:
                 results.add(s)
     translated = dict()
     for result in results:
         workflow = IWorkflow(domain_class())
         status_title = translate(str(workflow.get_state(result).title),
                                  domain="bungeni",
                                  context=self.request)
         translated[result] = status_title
     return translated
Exemple #18
0
def default_reports(sitting, event):
    wf = IWorkflow(sitting)
    if sitting.status in wf.get_state_ids(tagged=["published"]):
        sitting = removeSecurityProxy(sitting)
        report_type = "sitting_agenda"
        report_title = _("report_title_sitting_agenda", 
            default=u"Sitting Agenda")
        if sitting.status in wf.get_state_ids(tagged=["publishedminutes"]):
            report_type = "sitting_minutes"
            report_title =  _("report_title_votes_and_proceedings", 
                default=u"Sitting Votes and Proceedings")
        sittings = [ExpandedSitting(sitting)]
        report = domain.Report()
        session = Session()
        # !+GROUP_AS_OWNER(mr, apr-2012) we assume for now that the "owner" of
        # the report is the currently logged in user.
        report.owner_id = get_db_user_id()
        report.created_date = datetime.datetime.now()
        report.group_id = sitting.group_id
        # generate using html template in bungeni_custom
        vocab = vocabulary.report_xhtml_template_factory
        term = vocab.getTermByFileName(report_type)
        doc_template = term and term.value or vocab.terms[0].value
        generator = generators.ReportGeneratorXHTML(doc_template)
        generator.title = report_title
        report_title_i18n = translate(report_title, 
            target_language=generator.language)
        report_context = ReportContext(sittings=sittings, 
            title=report_title_i18n)
        generator.context = report_context
        report.title = report_title_i18n
        report.language = generator.language
        report.body = generator.generateReport()
        session.add(report)
        session.flush()
        notify(ObjectCreatedEvent(report))
        sr = domain.SittingReport()
        sr.report = report
        sr.sitting = sitting
        session.add(sr)
        session.flush()
        notify(ObjectCreatedEvent(sr))
Exemple #19
0
    def getMenuItems(self, context, request):
        """Return menu item entries in a TAL-friendly form.
        !+TAL-friendly(mr, sep-2011) means what?
        """
        if (not interfaces.IWorkspaceOrAdminSectionLayer.providedBy(request)
                or interfaces.IFormEditLayer.providedBy(request)
                or IVersion.providedBy(context)):
            return ()
        #!+wfc.workflow
        wf = IWorkflow(context, None)
        if wf is None:
            return ()
        #state = IWorkflowController(context).state_controller.get_status()
        wfc = IWorkflowController(context)
        wf = wfc.workflow
        tids = wfc.getManualTransitionIds()

        _url = url.absoluteURL(context, request)
        results = []
        for tid in tids:
            transit_url = \
                "%s/change_workflow_state?transition_id=%s&next_url=..." % (
                    _url, tid)
            extra = {
                "id": "workflow-transition-%s" % tid,
                "separator": None,
                "class": ""
            }
            state_title = translate(str(wf.get_transition(tid).title),
                                    domain="bungeni",
                                    context=request)
            results.append(
                dict(title=state_title,
                     description="",
                     action=transit_url,
                     selected=False,
                     transition_id=tid,
                     icon=None,
                     extra=extra,
                     submenu=None))
        return results
 def get_status(self, item_type):
     workspace_config = component.getUtility(IWorkspaceTabsUtility)
     roles = get_workspace_roles() + OBJECT_ROLES
     domain_class = workspace_config.get_domain(item_type)
     results = set()
     for role in roles:
         status = workspace_config.get_status(
             role, domain_class, self.context.__name__)
         if status:
             for s in status:
                 results.add(s)
     translated = dict()
     for result in results:
         workflow = IWorkflow(domain_class())
         status_title = translate(
             str(workflow.get_state(result).title),
             domain="bungeni",
             context=self.request
             )
         translated[result] = status_title
     return translated
Exemple #21
0
def get_filter_config(tag="tobescheduled"):
    return dict([(item_type, {
        "label":
        _(u"choose status"),
        "menu": [{
            "text":
            IWorkflow(
                domain.DOMAIN_CLASSES[item_type]()).get_state(status).title,
            "value":
            status
        } for status in get_workflow(item_type).get_state_ids(tagged=[tag])]
    }) for item_type in get_schedulable_types().keys()])
Exemple #22
0
 def getMenuItems(self, context, request):
     """Return menu item entries in a TAL-friendly form.
     !+TAL-friendly(mr, sep-2011) means what?
     """
     if (not interfaces.IWorkspaceOrAdminSectionLayer.providedBy(request) or
             interfaces.IFormEditLayer.providedBy(request) or
             IVersion.providedBy(context)
         ):
         return ()
     #!+wfc.workflow
     wf = IWorkflow(context, None)
     if wf is None:
         return ()
     #state = IWorkflowController(context).state_controller.get_status()
     wfc = IWorkflowController(context)
     wf = wfc.workflow
     tids = wfc.getManualTransitionIds()
     
     _url = url.absoluteURL(context, request)
     results = []
     for tid in tids:
         transit_url = ("%s/change_workflow_state?transition_id=%s&"
             "next_url=./workflow-redirect" % (_url, tid)
         )
         extra = {"id": "workflow-transition-%s" % tid,
                  "separator": None,
                  "class": ""}
         state_title = translate(wf.get_transition(tid).title,
             domain="bungeni",
             context=request)
         results.append(
             dict(title=state_title,
                  description="",
                  action=transit_url,
                  selected=False,
                  transition_id=tid,
                  icon=None,
                  extra=extra,
                  submenu=None))
     return results
Exemple #23
0
 def extra(self):
     wf = IWorkflow(self.context, None)
     if wf is None:
         return {"id": self.id}
     status = self.context.status
     state_title = translate(misc.get_wf_state(self.context),
                             domain="bungeni",
                             context=self.request)
     return {
         "id": self.id,
         "class": "state-%s" % status,
         "state": status,
         "stateTitle": state_title
     }
Exemple #24
0
 def change_data_items(self):
     """Get change data items, reverse-sorted by date (most recent first).
     """
     interaction = getInteraction()
     head_wf = IWorkflow(self.head)
     changes = []
     
     def append_visible_changes_on_item(item):
         permission = view_permission(item)
         for c in domain.get_changes(item, *self.param_audit_actions):
             if checkPermission(permission, c):
                 changes.append(c)
     
     def include_feature_changes(feature_name, SUPPORTED_FEATURE):
         # !+ interfaces.IFeatureX.providedBy(self.head)
         return (
             (not SUPPORTED_FEATURE or head_wf.has_feature(feature_name)) and 
             feature_name in self.param_include_subtypes)
     
     def append_visible_changes_on_sub_items(sub_type_key, items_attr,
             SUPPORTED_FEATURE=True # !+
         ):
         if include_feature_changes(sub_type_key, SUPPORTED_FEATURE):
             pid = "bungeni.%s.View" % (sub_type_key)
             items = [ item for item in getattr(self.head, items_attr)
                     if interaction.checkPermission(pid, item) ]
             for item in items:
                 append_visible_changes_on_item(item)
     
     # changes direct on head item
     append_visible_changes_on_item(self.head)
     # changes on sub-items
     append_visible_changes_on_sub_items("signatory", "sa_signatories")
     append_visible_changes_on_sub_items("attachment", "attachments")
     append_visible_changes_on_sub_items("event", "sa_events")
     if interfaces.IDoc.providedBy(self.head):
         append_visible_changes_on_sub_items("group_assignment", "sa_group_assignments")
     elif interfaces.IGroup.providedBy(self.head):
         append_visible_changes_on_sub_items("group_assignment", "sa_group_assignments",
             SUPPORTED_FEATURE=False) # !+ "group_assignment" is not a "group" feature
         append_visible_changes_on_sub_items("member", "group_members",
             SUPPORTED_FEATURE=False) # !+ "member" is not a "group" feature)
     
     # sort aggregated changes by date_active
     changes = [ dc[1] for dc in 
         reversed(sorted([ (c.date_active, c) for c in changes ])) ]
     return changes
     '''
Exemple #25
0
    def update(self):
        need("yui-dragdrop")
        need("yui-container")

        sitting = self._parent._parent.context
        scheduled_item_ids = [item.item_id for item in sitting.item_schedule]

        # add location to items
        gsm = component.getSiteManager()
        adapter = gsm.adapters.lookup(
            (interface.implementedBy(self.model), interface.providedBy(self)),
            ILocation)

        date_formatter = self.get_date_formatter("date", "medium")
        items = [adapter(item, None) for item in self._query_items()]
        # for each item, format dictionary for use in template
        self.items = [
            {
                "title":
                properties.title,
                "name":
                item.__class__.__name__,
                "description":
                properties.description,
                #"date": _(u"$F", mapping={"F":
                #       datetimedict.fromdatetime(item.changes[-1].date)}),
                #"date":item.changes[-1].date,
                # not every item has a auditlog (headings)
                # use last status change instead.
                "date":
                self._item_date(item)
                and date_formatter.format(self._item_date(item)),
                "state":
                IWorkflow(item).get_state(item.status).title,
                "id":
                self._get_item_key(item),
                "class": ((self._get_item_key(item) in scheduled_item_ids
                           and "dd-disable") or ""),
                "url":
                self._item_url(item),
                "type":
                item.type
            } for item, properties in [(
                item, (IDCDescriptiveProperties.providedBy(item) and item
                       or IDCDescriptiveProperties(item))) for item in items]
        ]
Exemple #26
0
 def as_json(self):
     date_formatter = date.getLocaleFormatter(common.get_request(), "date",
                                              "medium")
     items_json = dict(items=[
         dict(item_type=self.item_type,
              item_id=orm.object_mapper(item).primary_key_from_instance(
                  item)[0],
              item_title=IDCDescriptiveProperties(item).title,
              status=IWorkflow(item).get_state(item.status).title,
              status_date=(date_formatter.format(item.submission_date)
                           if hasattr(item, "submission_date") else None),
              registry_number=(item.registry_number if hasattr(
                  item, "registry_number") else None),
              item_mover=(IDCDescriptiveProperties(item.owner).
                          title if hasattr(item, "owner") else None),
              item_uri=IDCDescriptiveProperties(item).uri)
         for item in self.query()
     ])
     return json.dumps(items_json)
Exemple #27
0
def publish_to_xml(context):
    """Generates XML for object and saves it to the file. If object contains
    attachments - XML is saved in zip archive with all attached files. 
    """
    include = []

    context = removeSecurityProxy(context)

    if IVersionable.implementedBy(context.__class__):
        include.append("versions")
    if IAuditable.implementedBy(context.__class__):
        include.append("event")

    data = obj2dict(context,
                    1,
                    parent=None,
                    include=include,
                    exclude=[
                        "file_data", "image", "logo_data", "event",
                        "attached_files", "changes"
                    ])

    type = IWorkflow(context).name

    tags = IStateController(context).get_state().tags
    if tags:
        data["tags"] = tags

    permissions = get_object_state_rpm(context).permissions
    data["permissions"] = get_permissions_dict(permissions)

    data["changes"] = []
    for change in getattr(context, "changes", []):
        change_dict = obj2dict(change, 0, parent=context)
        change_permissions = get_head_object_state_rpm(change).permissions
        change_dict["permissions"] = get_permissions_dict(change_permissions)
        data["changes"].append(change_dict)

    # list of files to zip
    files = []
    # setup path to save serialized data
    path = os.path.join(setupStorageDirectory(), type)
    if not os.path.exists(path):
        os.makedirs(path)

    # xml file path
    file_path = os.path.join(path, stringKey(context))

    has_attachments = False
    if IAttachmentable.implementedBy(context.__class__):
        attached_files = getattr(context, "attached_files", None)
        if attached_files:
            has_attachments = True
            # add xml file to list of files to zip
            files.append("%s.xml" % (file_path))
            data["attached_files"] = []
            for attachment in attached_files:
                # serializing attachment
                attachment_dict = obj2dict(
                    attachment,
                    1,
                    parent=context,
                    exclude=["file_data", "event", "versions", "changes"])
                permissions = get_object_state_rpm(attachment).permissions
                attachment_dict["permissions"] = \
                    get_permissions_dict(permissions)
                # saving attachment to tmp
                with tmp(delete=False) as f:
                    f.write(attachment.file_data)
                    files.append(f.name)
                    attachment_dict["saved_file"] = \
                        os.path.split(f.name)[-1]
                data["attached_files"].append(attachment_dict)

    # saving xml file
    with open("%s.xml" % (file_path), "w") as file:
        file.write(serialize(data, name=type))

    # zipping xml and attached files
    # unzipped files are removed
    if has_attachments:
        zip = ZipFile("%s.zip" % (file_path), "w")
        for f in files:
            zip.write(f, os.path.split(f)[-1])
            os.remove(f)
        zip.close()
Exemple #28
0
def publish_to_xml(context):
    """Generates XML for object and saves it to the file. If object contains
    attachments - XML is saved in zip archive with all attached files. 
    """

    #create a fake interaction to ensure items requiring a participation
    #are serialized
    #!+SERIALIZATION(mb, Jan-2013) review this approach
    try:
        zope.security.management.getInteraction()
    except zope.security.interfaces.NoInteraction:
        principal = zope.security.testing.Principal('user', 'manager', ())
        zope.security.management.newInteraction(
            create_participation(principal))

    include = []
    # list of files to zip
    files = []
    # data dict to be published
    data = {}

    context = zope.security.proxy.removeSecurityProxy(context)

    if interfaces.IFeatureVersion.providedBy(context):
        include.append("versions")
    if interfaces.IFeatureAudit.providedBy(context):
        include.append("event")

    exclude = ["data", "event", "attachments", "changes"]

    # include binary fields and include them in the zip of files for this object
    for column in class_mapper(context.__class__).columns:
        if column.type.__class__ == Binary:
            exclude.append(column.key)
            content = getattr(context, column.key, None)
            if content:
                bfile = tmp(delete=False)
                bfile.write(content)
                files.append(bfile.name)
                data[column.key] = dict(
                    saved_file=os.path.basename(bfile.name))
                bfile.close()
    data.update(
        obj2dict(context, 1, parent=None, include=include, exclude=exclude))
    obj_type = IWorkflow(context).name
    tags = IStateController(context).get_state().tags
    if tags:
        data["tags"] = tags
    permissions = get_object_state_rpm(context).permissions
    data["permissions"] = get_permissions_dict(permissions)
    data["changes"] = []
    for change in getattr(context, "changes", []):
        change_dict = obj2dict(change, 0, parent=context)
        change_permissions = get_head_object_state_rpm(change).permissions
        change_dict["permissions"] = get_permissions_dict(change_permissions)
        data["changes"].append(change_dict)

    # setup path to save serialized data
    path = os.path.join(setupStorageDirectory(), obj_type)
    if not os.path.exists(path):
        os.makedirs(path)

    # xml file path
    file_path = os.path.join(path, stringKey(context))

    if interfaces.IFeatureAttachment.providedBy(context):
        attachments = getattr(context, "attachments", None)
        if attachments:
            data["attachments"] = []
            for attachment in attachments:
                # serializing attachment
                attachment_dict = obj2dict(
                    attachment,
                    1,
                    parent=context,
                    exclude=["data", "event", "versions"])
                permissions = get_object_state_rpm(attachment).permissions
                attachment_dict["permissions"] = \
                    get_permissions_dict(permissions)
                # saving attachment to tmp
                attached_file = tmp(delete=False)
                attached_file.write(attachment.data)
                attached_file.flush()
                attached_file.close()
                files.append(attached_file.name)
                attachment_dict["saved_file"] = os.path.basename(
                    attached_file.name)
                data["attachments"].append(attachment_dict)

    # zipping xml, attached files plus any binary fields
    # also remove the temporary files
    if files:
        #generate temporary xml file
        temp_xml = tmp(delete=False)
        temp_xml.write(serialize(data, name=obj_type))
        temp_xml.close()
        #write attachments/binary fields to zip
        zip_file = ZipFile("%s.zip" % (file_path), "w")
        for f in files:
            zip_file.write(f, os.path.basename(f))
            os.remove(f)
        #write the xml
        zip_file.write(temp_xml.name, "%s.xml" % os.path.basename(file_path))
        zip_file.close()
        #placed remove after zip_file.close !+ZIP_FILE_CRC_FAILURE
        os.remove(temp_xml.name)

    else:
        # save serialized xml to file
        with open("%s.xml" % (file_path), "w") as xml_file:
            xml_file.write(serialize(data, name=obj_type))
            xml_file.close()

    #publish to rabbitmq outputs queue
    connection = get_mq_connection()
    if not connection:
        return
    channel = connection.channel()
    publish_file_path = "%s.%s" % (file_path, ("zip" if files else "xml"))
    channel.basic_publish(exchange=SERIALIZE_OUTPUT_EXCHANGE,
                          routing_key=SERIALIZE_OUTPUT_ROUTING_KEY,
                          body=simplejson.dumps({
                              "type": "file",
                              "location": publish_file_path
                          }),
                          properties=pika.BasicProperties(
                              content_type="text/plain", delivery_mode=2))

    #clean up - remove any files if zip was created
    if files:
        prev_xml_file = "%s.%s" % (file_path, "xml")
        if os.path.exists(prev_xml_file):
            os.remove(prev_xml_file)
Exemple #29
0
def publish_to_xml(context):
    """Generates XML for object and saves it to the file. If object contains
    attachments - XML is saved in zip archive with all attached files. 
    """

    context = zope.security.proxy.removeSecurityProxy(context)
    obj_type = IWorkflow(context).name

    #locking
    lock_name = "%s-%s" % (obj_type, stringKey(context))
    with LockStore.get_lock(lock_name):
        #root key (used to cache files to zip)
        root_key = make_key()

        #create a fake interaction to ensure items requiring a participation
        #are serialized
        #!+SERIALIZATION(mb, Jan-2013) review this approach
        try:
            zope.security.management.getInteraction()
        except zope.security.interfaces.NoInteraction:
            principal = zope.security.testing.Principal('user', 'manager', ())
            zope.security.management.newInteraction(
                create_participation(principal))
        include = []
        # data dict to be published
        data = {}

        if interfaces.IFeatureVersion.providedBy(context):
            include.append("versions")
        if interfaces.IFeatureAudit.providedBy(context):
            include.append("event")

        exclude = ["data", "event", "attachments"]

        data.update(
            obj2dict(context,
                     1,
                     parent=None,
                     include=include,
                     exclude=exclude,
                     root_key=root_key))
        tags = IStateController(context).get_state().tags
        if tags:
            data["tags"] = tags
        permissions = get_object_state_rpm(context).permissions
        data["permissions"] = get_permissions_dict(permissions)

        # setup path to save serialized data
        path = os.path.join(setupStorageDirectory(), obj_type)
        if not os.path.exists(path):
            os.makedirs(path)

        # xml file path
        file_path = os.path.join(path, stringKey(context))

        #files to zip
        files = []

        if interfaces.IFeatureAttachment.providedBy(context):
            attachments = getattr(context, "attachments", None)
            if attachments:
                data["attachments"] = []
                for attachment in attachments:
                    # serializing attachment
                    attachment_dict = obj2dict(
                        attachment,
                        1,
                        parent=context,
                        exclude=["data", "event", "versions"])
                    # saving attachment to tmp
                    attached_file = tmp(delete=False)
                    attached_file.write(attachment.data)
                    attached_file.flush()
                    attached_file.close()
                    files.append(attached_file.name)
                    attachment_dict["saved_file"] = os.path.basename(
                        attached_file.name)
                    data["attachments"].append(attachment_dict)

        #add explicit origin chamber for this object (used to partition data in
        #if more than one parliament exists)
        data["origin_parliament"] = get_origin_parliament(context)

        #add any additional files to file list
        files = files + PersistFiles.get_files(root_key)
        # zipping xml, attached files plus any binary fields
        # also remove the temporary files
        if files:
            #generate temporary xml file
            temp_xml = tmp(delete=False)
            temp_xml.write(serialize(data, name=obj_type))
            temp_xml.close()
            #write attachments/binary fields to zip
            with ZipFile("%s.zip" % (file_path), "w") as zip_file:
                for f in files:
                    zip_file.write(f, os.path.basename(f))
                # write the xml
                zip_file.write(temp_xml.name,
                               "%s.xml" % os.path.basename(file_path))
            files.append(temp_xml.name)

        else:
            # save serialized xml to file
            with open("%s.xml" % (file_path), "w") as xml_file:
                xml_file.write(serialize(data, name=obj_type))
                xml_file.close()

        # publish to rabbitmq outputs queue
        connection = bungeni.core.notifications.get_mq_connection()
        if not connection:
            return
        channel = connection.channel()
        publish_file_path = "%s.%s" % (file_path, ("zip" if files else "xml"))
        channel.basic_publish(exchange=SERIALIZE_OUTPUT_EXCHANGE,
                              routing_key=SERIALIZE_OUTPUT_ROUTING_KEY,
                              body=simplejson.dumps({
                                  "type":
                                  "file",
                                  "location":
                                  publish_file_path
                              }),
                              properties=pika.BasicProperties(
                                  content_type="text/plain", delivery_mode=2))

        #clean up - remove any files if zip was/was not created
        if files:
            files.append("%s.%s" % (file_path, "xml"))
        else:
            files.append("%s.%s" % (file_path, "zip"))
        remove_files(files)

        #clear the cache
        PersistFiles.clear_files(root_key)
Exemple #30
0
def publish_to_xml(context):
    """Generates XML for object and saves it to the file. If object contains
    attachments - XML is saved in zip archive with all attached files. 
    """
    context = zope.security.proxy.removeSecurityProxy(context)
    obj_type = IWorkflow(context).name
    #locking
    random_name_sfx = generate_random_filename()
    context_file_name = "%s-%s" % (stringKey(context), random_name_sfx)
    #lock_name = "%s-%s" %(obj_type, context_file_name)
    #!+LOCKING(AH, 25-01-2014) disabling file locking
    #! locking was reqiured when the serializer used ta constant file name
    #! for an object. Now serialized file names are unique, and non repeated
    #with LockStore.get_lock(lock_name):
    #    
    #root key (used to cache files to zip)
    root_key = make_key()
    # create a fake interaction to ensure items requiring a participation
    # are serialized 
    #!+SERIALIZATION(mb, Jan-2013) review this approach
    try:
        zope.security.management.getInteraction()
    except zope.security.interfaces.NoInteraction:
        principal = zope.security.testing.Principal("user", "manager", ())
        zope.security.management.newInteraction(create_participation(principal))
    include = []
    # data dict to be published
    data = {}
    if IFeatureVersion.providedBy(context):
        include.append("versions")
    if IFeatureEvent.providedBy(context):
        include.append("event")
    
    exclude = ["data", "event", "attachments"]
    updated_dict = obj2dict(context, 1, 
	    parent=None,
	    include=include,
	    exclude=exclude,
	    root_key=root_key
        )
    data.update(
        updated_dict
    )

    tags = IStateController(context).get_state().tags
    if tags:
        data["tags"] = tags
    permissions = get_object_state_rpm(context).permissions
    data["permissions"] = get_permissions_dict(permissions)

    # setup path to save serialized data
    path = os.path.join(setupStorageDirectory(), obj_type)
    log.info("Setting up path to write to : %s", path)
    if not os.path.exists(path):
        #
        # !+THREADSAFE(AH, 2014-09-24) making makedirs threadsafe, 
        # sometimes between checking for existence and execution 
        # of makedirs() the folder has already been created by 
        # another thread
        try:
            os.makedirs(path)
        except OSError as exc:
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                log.info("Error Folder : %s already exists, ignoring exception ", path)
            else:
                raise

    # xml file path
    file_path = os.path.join(path, context_file_name) 
    # files to zip
    files = []

    if IFeatureAttachment.providedBy(context):
        attachments = getattr(context, "attachments", None)
        if attachments:
	    data["attachments"] = []
	    for attachment in attachments:
	        # serializing attachment
	        attachment_dict = obj2dict(attachment, 1,
	            parent=context,
	            exclude=["data", "event", "versions"])
	        # saving attachment to tmp
	        attached_file = tmp(delete=False)
	        attached_file.write(attachment.data)
	        attached_file.flush()
	        attached_file.close()
	        files.append(attached_file.name)
	        attachment_dict["saved_file"] = os.path.basename(
	            attached_file.name
	        )
	        data["attachments"].append(attachment_dict)

    # add explicit origin chamber for this object (used to partition data
    # in if more than one chamber exists)
    
    if obj_type == "Legislature":
        data["origin_chamber"] = None
    else:
        data["origin_chamber"] = get_origin_chamber(context)

    # add any additional files to file list
    files = files + PersistFiles.get_files(root_key)
    # zipping xml, attached files plus any binary fields
    # also remove the temporary files
    if files:
        # generate temporary xml file
        temp_xml = tmp(delete=False)
        temp_xml.write(serialize(data, name=obj_type))
        temp_xml.close()
        # write attachments/binary fields to zip
        with  ZipFile("%s.zip" % (file_path), "w") as zip_file:
	    for f in files:
	        zip_file.write(f, os.path.basename(f))
	    # write the xml
	    zip_file.write(temp_xml.name, "%s.xml" % os.path.basename(file_path))
        files.append(temp_xml.name)
    else:
        # save serialized xml to file
        with open("%s.xml" % (file_path), "w") as xml_file:
	    xml_file.write(serialize(data, name=obj_type))
	    xml_file.close()
    # publish to rabbitmq outputs queue
    connection = bungeni.core.notifications.get_mq_connection()
    if not connection:
        return
    channel = connection.channel()
    #channel.confirm_delivery()
    publish_file_path = "%s.%s" %(file_path, ("zip" if files else "xml"))
    #channel_delivery = 
    channel.basic_publish(
        exchange=SERIALIZE_OUTPUT_EXCHANGE,
        routing_key=SERIALIZE_OUTPUT_ROUTING_KEY,
        body=simplejson.dumps({"type": "file", "location": publish_file_path }),
        properties=pika.BasicProperties(content_type="text/plain",
            delivery_mode=2
        )
    )
    #if channel_delivery:
    #    log.info("Message published to exchange %s with key %s for %s" % 
    #        (SERIALIZE_OUTPUT_EXCHANGE, SERIALIZE_OUTPUT_ROUTING_KEY, publish_file_path))
    #else:
    #    log.error("Message publication failed for %r", publish_file_path)
        

    #clean up - remove any files if zip was/was not created
    if files:
        files.append("%s.%s" %(file_path, "xml"))
    else:
        files.append("%s.%s" %(file_path, "zip"))
    remove_files(files)

    # clear the cache
    PersistFiles.clear_files(root_key)
Exemple #31
0
class ChangeDataProvider(object):
    
    def __init__(self, context):
        self.head = removeSecurityProxy(context)
        self.head_workflow = IWorkflow(self.head)
        self.head_audit_feature = self.head_workflow.get_feature("audit")
    
    @property
    def param_audit_actions(self):
        return self.head_audit_feature.get_param("audit_actions")
    @property
    def param_include_subtypes(self):
        return self.head_audit_feature.get_param("include_subtypes")
    @property
    def param_display_columns(self):
        return self.head_audit_feature.get_param("display_columns")
    
    
    def change_data_items(self):
        """Get change data items, reverse-sorted by date (most recent first).
        """
        interaction = getInteraction()
        head_wf = IWorkflow(self.head)
        changes = []
        
        def append_visible_changes_on_item(item):
            permission = view_permission(item)
            for c in domain.get_changes(item, *self.param_audit_actions):
                if checkPermission(permission, c):
                    changes.append(c)
        
        def include_feature_changes(feature_name, SUPPORTED_FEATURE):
            # !+ interfaces.IFeatureX.providedBy(self.head)
            return (
                (not SUPPORTED_FEATURE or head_wf.has_feature(feature_name)) and 
                feature_name in self.param_include_subtypes)
        
        def append_visible_changes_on_sub_items(sub_type_key, items_attr,
                SUPPORTED_FEATURE=True # !+
            ):
            if include_feature_changes(sub_type_key, SUPPORTED_FEATURE):
                pid = "bungeni.%s.View" % (sub_type_key)
                items = [ item for item in getattr(self.head, items_attr)
                        if interaction.checkPermission(pid, item) ]
                for item in items:
                    append_visible_changes_on_item(item)
        
        # changes direct on head item
        append_visible_changes_on_item(self.head)
        # changes on sub-items
        append_visible_changes_on_sub_items("signatory", "sa_signatories")
        append_visible_changes_on_sub_items("attachment", "attachments")
        append_visible_changes_on_sub_items("event", "sa_events")
        if interfaces.IDoc.providedBy(self.head):
            append_visible_changes_on_sub_items("group_assignment", "sa_group_assignments")
        elif interfaces.IGroup.providedBy(self.head):
            append_visible_changes_on_sub_items("group_assignment", "sa_group_assignments",
                SUPPORTED_FEATURE=False) # !+ "group_assignment" is not a "group" feature
            append_visible_changes_on_sub_items("member", "group_members",
                SUPPORTED_FEATURE=False) # !+ "member" is not a "group" feature)
        
        # sort aggregated changes by date_active
        changes = [ dc[1] for dc in 
            reversed(sorted([ (c.date_active, c) for c in changes ])) ]
        return changes
        '''
Exemple #32
0
 def __init__(self, context):
     self.head = removeSecurityProxy(context)
     self.head_workflow = IWorkflow(self.head)
     self.head_audit_feature = self.head_workflow.get_feature("audit")