def __call__(self, context):
     terms = []
     for type_key, info in capi.iter_type_info():
         if info.workflow and info.workflow.has_feature("workspace"):
             terms.append(
                 schema.vocabulary.SimpleTerm(
                     value=type_key, token=type_key, title=naming.split_camel(info.domain_model.__name__)
                 )
             )
     terms.sort(key=lambda item: item.value)
     all_types = ",".join([t.value for t in terms])
     terms.insert(
         0, schema.vocabulary.SimpleTerm(value=all_types, token=all_types, title=_(u"* all document types"))
     )
     return schema.vocabulary.SimpleVocabulary(terms)
Exemple #2
0
 def __call__(self, context):
     terms = []
     for type_key, info in capi.iter_type_info():
         if info.workflow and info.workflow.has_feature("workspace"):
             terms.append(
                 schema.vocabulary.SimpleTerm(
                     value=type_key,
                     token=type_key,
                     title=naming.split_camel(info.domain_model.__name__)))
     terms.sort(key=lambda item: item.value)
     all_types = ",".join([t.value for t in terms])
     terms.insert(
         0,
         schema.vocabulary.SimpleTerm(value=all_types,
                                      token=all_types,
                                      title=_(u"* all document types")))
     return schema.vocabulary.SimpleVocabulary(terms)
 def display_name(cls):
     cls_name = naming.model_name(
         naming.type_key("descriptor_class_name", cls.__name__))
     return naming.split_camel(cls_name) # !+unicode
Exemple #4
0
def _load(workflow_name, workflow):
    """ (workflow_name:str, workflow:etree_doc) -> Workflow
    """
    workflow_title = xas(workflow, "title")
    naming.MSGIDS.add(workflow_title)
    workflow_description = xas(workflow, "description")
    naming.MSGIDS.add(workflow_description)
    transitions = []
    states = []
    note = xas(workflow, "note")
    
    # initial_state, in XML indicated with a transition.@source=""
    initial_state = None
    
    ZCML_PROCESSED = bool(workflow_name in ZCML_WORKFLOWS_PROCESSED)
    if not ZCML_PROCESSED:
        ZCML_WORKFLOWS_PROCESSED.add(workflow_name)
        ZCML_LINES.append(ZCML_INDENT)
        ZCML_LINES.append(ZCML_INDENT)
        ZCML_LINES.append("%s<!-- %s -->" % (ZCML_INDENT, workflow_name))
    
    def validate_id(id, tag):
        """Ensure that ID values are unique within the same XML doc scope.
        """
        m = "Invalid element <%s> id=%r in workflow %r" % (tag, id, workflow_name)
        assert id not in validate_id.wuids, "%s -- id not unique in workflow document" % (m)
        validate_id.wuids.add(id)
    validate_id.wuids = set() # unique IDs in this XML workflow file
    
    def get_from_state(state_id):
        if state_id is None:
            return
        for state in states:
            if state.id == state_id:
                return state
        raise ValueError("Invalid value: permissions_from_state=%r" % (state_id))
    
    def check_not_global_grant(pid, role):
        # ensure global and local assignments are distinct
        # (global: global_pid_roles, workflow_name)
        global_proles = global_pid_roles.get(pid, "")
        assert role not in global_proles, ("Workflow [%s] may not mix "
            "global and local granting of a same permission [%s] to a "
            "same role [%s].") % (workflow_name, pid, role)
    
    # permission_actions -> permissions for this type
    for (key, permission_action) in capi.schema.qualified_permission_actions(
            workflow_name, xas(workflow, "permission_actions", "").split()
        ):
        pid = "bungeni.%s.%s" % (key, permission_action)
        title = "%s %s" % (
            permission_action, naming.split_camel(naming.model_name(key)))
        # !+ add to a Workflow.defines_permissions list
        ZCML_LINES.append(
            '%s<permission id="%s" title="%s" />' % (ZCML_INDENT, pid, title))
        provideUtility(Permission(pid), IPermission, pid)
    
    # global grants
    global_pid_roles = {} # {pid: [role]}
    global_grants = get_permissions_from_allows(workflow_name, workflow)
    for (setting, pid, role) in global_grants:
        # for each global permission, build list of roles it is set to
        global_pid_roles.setdefault(pid, []).append(role)
        assert setting and pid and role, \
            "Global grant must specify valid permission/role" #!+RNC
        # !+ add to a Workflow.global_grants list
        ZCML_LINES.append(
            '%s<grant permission="%s" role="%s" />' % (ZCML_INDENT, pid, role))
        # no real need to check that the permission and role of a global grant 
        # are properly registered in the system -- an error should be raised 
        # by the zcml if either is not defined.
        rpm.grantPermissionToRole(pid, role, check=False)

    for perm, roles in global_pid_roles.items():
        # assert roles mix limitations for state permissions
        assert_distinct_permission_scopes(perm, roles, workflow_name, "global grants")
    
    # all workflow features (enabled AND disabled)
    workflow_features = load_features(workflow_name, workflow)
    enabled_feature_names = [None] + [ 
        f.name for f in workflow_features if f.enabled ]
    # !+EVENT_FEATURE_TYPES add each as enabled_feature_names, or extend the 
    # facet quailifer by feature_name(sub_type_key).facet_ref?
    # workflow facets
    workflow_facets = load_facets(workflow_name, workflow)
    
    # states
    for s in workflow.iterchildren("state"):
        # @id
        state_id = xas(s, "id")
        assert state_id, "Workflow State must define @id" #!+RNC
        validate_id(state_id, "state")
        
        # @actions - transition-to-state actions
        state_actions = []
        for action_name in xas(s, "actions", "").split():
            state_actions.append(capi.get_workflow_action(action_name))
        
        # @permissions_from_state
        permissions = [] # [ tuple(bool:int, permission:str, role:str) ]
        # state.@permissions_from_state : to reduce repetition and enhance 
        # maintainibility of workflow XML files, a state may inherit ALL 
        # permissions defined by the specified state. NO other permissions 
        # may be specified by this state. 
        from_state = get_from_state(xas(s, "permissions_from_state"))
        parent_permissions = xab(s, "parent_permissions")
        if parent_permissions:
            pass # no own permission definitions allowed
        elif from_state:
            # assimilate (no more no less) the state's permissions !+tuple, use same?
            permissions[:] = from_state.permissions
        else:
            used_facets_fq = resolve_state_facets(
                workflow_name, workflow_facets, enabled_feature_names, s)
            # assimilate permissions from facets from None and all enabled features
            def add_facet_permissions(facet):
                for perm in facet.permissions:
                    check_add_assign_permission(workflow_name, permissions, perm)
                    check_not_global_grant(perm[1], perm[2])
            for (feature_name, qtk) in used_facets_fq:
                # debug check that feature is enabled
                assert feature_name in enabled_feature_names
                facet = used_facets_fq[(feature_name, qtk)]
                if facet is not None:
                    add_facet_permissions(facet)
        
        # states
        state_title = xas(s, "title")
        naming.MSGIDS.add(state_title)
        states.append(
            State(state_id, state_title,
                xas(s, "note"),
                state_actions, permissions,
                parent_permissions,
                xab(s, "obsolete"),
            )
        )
    
    STATE_IDS = [ s.id for s in states ]
    
    # transitions
    for t in workflow.iterchildren("transition"):
        title = xas(t, "title")
        naming.MSGIDS.add(title)
        # sources, empty string -> initial_state
        sources = t.get("source").split() or [initial_state]
        assert len(sources) == len(set(sources)), \
            "Transition contains duplicate sources [%s]" % (sources)
        for source in sources:
            if source is not initial_state:
                assert source in STATE_IDS, \
                    "Unknown transition source state [%s]" % (source)
        # destination must be a valid state
        destination = t.get("destination")
        assert destination in STATE_IDS, \
            "Unknown transition destination state [%s]" % (destination)
        
        # optionals -- only set on kw IFF explicitly defined
        kw = {}
        for i in TRANS_ATTRS_OPTIONALS:
            val = xas(t, i)
            if not val:
                # we let setting of defaults be handled downstream
                continue
            kw[i] = val
        
        # data up-typing
        #
        # trigger
        if "trigger" in kw:
            kw["trigger"] = trigger_value_map[kw["trigger"]]
        # roles -> permission - one-to-one per transition
        roles = capi.schema.qualified_roles(kw.pop("roles", ""))
        if not is_zcml_permissionable(t):
            assert not roles, "Workflow [%s] - non-permissionable transition " \
                "does not allow @roles [%s]." % (workflow_name, roles) #!+RNC
            kw["permission"] = None # None -> CheckerPublic
        # !+CAN_EDIT_AS_DEFAULT_TRANSITION_PERMISSION(mr, oct-2011) this feature
        # is functional (uncomment following elif clause) but as yet not enabled. 
        #
        # Advantage would be that it would be easier to keep transitions 
        # permissions in sync with object permissions (set in state) as the 
        # majority of transition require exactly this as privilege; for the 
        # occassional transition needing a different privilege, the current 
        # transition.@roles mechanism may be used to make this explicit. 
        #
        # Need to consider implications further; the zope_principal_role_map db 
        # table, that caches contextual roles for principals, should probably 
        # first be reworked to be db-less (as for zope_role_permission_map).
        #
        #elif not roles:
        #    # then as fallback transition permission use can modify object
        #    kw["permission"] = "bungeni.%s.Edit" % (workflow_name) # fallback permission
        else:
            # Dedicated permission for XML multi-source transition.
            # Given that, irrespective of how sources are grouped into 
            # multi-source XML <transition> elements, there may be only *one* 
            # path from any given *source* to any given *destination* state, 
            # it suffices to use only the first source element + the destination 
            # to guarantee a unique identifier for an XML transition element.
            #
            # Note: the "-" char is not allowed within a permission id 
            # (so we use "." also here).
            #
            tid = "%s.%s" % (sources[0] or "", destination)
            pid = "bungeni.%s.wf.%s" % (workflow_name, tid)
            if not ZCML_PROCESSED:
                zcml_transition_permission(pid, title, roles)
                # remember list of roles from xml
                kw["_roles"] = roles
            kw["permission"] = pid
        # python resolvables
        if "condition" in kw:
            kw["condition"] = capi.get_workflow_condition(kw["condition"])
        # numeric
        if "order" in kw:
            kw["order"] = float(kw["order"]) # ValueError if not numeric
        # bool
        if "require_confirmation" in kw:
            try:
                kw["require_confirmation"] = misc.as_bool(kw["require_confirmation"])
                assert kw["require_confirmation"] is not None #!+RNC
            except:
                raise ValueError("Invalid transition value "
                    '[require_confirmation="%s"]' % (
                        t.get("require_confirmation")))
        # multiple-source transitions are really multiple "transition paths"
        for source in sources:
            args = (title, source, destination)
            transitions.append(Transition(*args, **kw))
            log.debug("[%s] adding transition [%s-%s] [%s]" % (
                workflow_name, source or "", destination, kw))
    
    return Workflow(workflow_name,
        workflow_features, workflow_facets, states, transitions, global_grants,
        workflow_title, workflow_description, note)
Exemple #5
0
 def model_title(type_key):
     return naming.split_camel(naming.model_name(type_key))
 def model_title(type_key):
     return naming.split_camel(naming.model_name(type_key))
Exemple #7
0
def setup_customization_ui():
    """Called from ui.app.on_wsgi_application_created_event -- must be called
    late, at least as long as there other ui zcml directives (always executed 
    very late) that need to have been executed prior to this e.g. 
    creation of specific menus such as "context_actions".
    """
    
    def register_menu_item(type_key, privilege, title, for_, action,
            menu="context_actions", 
            order=10,
            layer="bungeni.ui.interfaces.IBungeniSkin"
        ):
        naming.MSGIDS.add(title) # for i18n extraction
        UI_ZC_DECLS.append(register_menu_item.TMPL.format(**locals()))
    register_menu_item.TMPL = """
            <browser:menuItem menu="{menu}"
                for="{for_}"
                action="{action}"
                title="{title}"
                order="{order}"
                permission="bungeni.{type_key}.{privilege}"
                layer="{layer}"
            />"""
    
    def register_form_view(type_key, privilege, name, for_, class_,
            layer="bungeni.ui.interfaces.IBungeniSkin"
        ):
        UI_ZC_DECLS.append(register_form_view.TMPL.format(**locals()))
    register_form_view.TMPL = """
            <browser:page name="{name}"
                for="{for_}"
                class="{class_}"
                permission="bungeni.{type_key}.{privilege}"
                layer="{layer}"
            />"""
    
    UI_ZC_DECLS[:] = []
    # we assume that non-custom types have already been set up as needed
    for type_key, ti in capi.iter_type_info(scope="custom"):
        UI_ZC_DECLS.append("""
            
            <!-- {type_key} -->""".format(type_key=type_key))
        
        type_title = naming.split_camel(naming.model_name(type_key))
        # model interface is defined, but container interface is not yet
        model_interface_qualname = naming.qualname(ti.interface)
        container_interface_qualname = "bungeni.models.interfaces.%s" % (
                naming.container_interface_name(type_key))
        
        # generic forms (independent of any feature)
        # add
        register_form_view(type_key, "Add", "add", container_interface_qualname,
            "bungeni.ui.forms.common.AddForm")
        # view
        register_form_view(type_key, "View", "view", model_interface_qualname,
            "bungeni.ui.forms.common.DisplayForm")
        # edit !+DiffEditForm prior to r10032, doc-archetyped types were being
        # *declared* to use bungeni.ui.forms.forms.DiffEditForm, but this
        # is not the edit view tht was actually being used!
        register_form_view(type_key, "Edit", "edit", model_interface_qualname,
            "bungeni.ui.forms.common.EditForm")
        # delete
        register_form_view(type_key, "Delete", "delete", model_interface_qualname,
            "bungeni.ui.forms.common.DeleteForm")
        
        # plone content menu (for custom types)
        # !+ doc-types were previously being layered on IWorkspaceOrAdminSectionLayer
        # !+ there was previously no reg for IReportConatiner and one of the member
        # containers, plus there was inconsistency in permission for 
        # IOfficeMemberContainer (was bungeni.office.Add).
        register_menu_item(type_key, "Add", "Add %s..." % (type_title), 
            container_interface_qualname,
            "./add",
            menu="plone_contentmenu",
            layer="bungeni.ui.interfaces.IAdminSectionLayer")
        
        # workspace
        if ti.workflow.has_feature("workspace"):
            log.debug("Setting up UI for feature %r for type %r", "workspace", type_key)
            
            # add menu item
            # !+workspace_feature_add(mr, oct-2012) note that an enabled
            # workspace feature also implies "add" functionality for the type
            first_tab = capi.workspace_tabs[0]
            action = "../../{first_tab}/add_{k}".format(first_tab=first_tab,
                k=type_key)
            register_menu_item(type_key, "Add", type_title, "*", action,
                menu="workspace_add_parliamentary_content", order=7)
            
            # add menu item -> for admin ?!
            # !+ why a duplicated (almost identical) menu item for admin?
            # !+ criteria here is having workspace enabled... but, cirterion 
            # should be simply that of being "parliamentary"? Do we need to 
            # formalize this distinction?
            # !+ need a formal "container attribute" naming convention!
            action = "{k}/add".format(k=naming.plural(type_key)) 
            register_menu_item(type_key, "Add", type_title, "*", action,
                menu="context_add_parliamentary_content", order=7)
            
            # edit menu item
            # !+ edit/delete used to be on layer="bungeni.ui.interfaces.IWorkspaceOrAdminSectionLayer"
            title = "Edit {t}".format(t=type_title)
            register_menu_item(type_key, "Edit", title, model_interface_qualname, "edit",
                menu="context_actions", order=10)
            
            # delete menu item
            title = "Delete {t}".format(t=type_title)
            register_menu_item(type_key, "Delete", title, model_interface_qualname, "delete",
                menu="context_actions", order=99)
            
            # add view
            name = "add_{type_key}".format(type_key=type_key)
            register_form_view(type_key, "Add", name,
                "bungeni.core.interfaces.IWorkspaceTab",
                "bungeni.ui.workspace.WorkspaceAddForm")
        
        # events
        if ti.workflow.has_feature("event"):
            log.debug("Setting up UI for feature %r for type %r", "event", type_key)
            title = "Add {t} Event".format(t=type_title)
            register_menu_item("event", "Add", title, model_interface_qualname, 
                "./events/add", menu="additems", order=21,
                layer="bungeni.ui.interfaces.IWorkspaceOrAdminSectionLayer")
        
        # address
        if ti.workflow.has_feature("address"):
            log.debug("Setting up UI for feature %r for type %r", "address", type_key)
            if issubclass(ti.domain_model, domain.Group):
                title = "Add {t} Address".format(t=type_title)
                #layer="bungeni.ui.interfaces.IWorkspaceOrAdminSectionLayer"
                # add address in the "add items..." menu
                register_menu_item("address", "Add", title, model_interface_qualname, 
                    "./addresses/add", menu="additems", order=80)
            elif issubclass(ti.domain_model, domain.User):
                # !+ User not a custom type (so should never pass here)
                assert False, "Type %s may not be a custom type" % (ti.domain_model)