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 __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
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)
def model_title(type_key): return naming.split_camel(naming.model_name(type_key))
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)