def set_state(self, version_id, state, principal=None): """Set state of given version""" if str(version_id) not in self: return # update version state version = self[str(version_id)] wf_state = IWorkflowState(version) wf_state.version_id = version_id wf_state.state = state if principal is not None: wf_state.state_principal = principal # update versions/states mapping if state is None: state = NONE_STATE old_state = self.state_by_version[version_id] versions = self.versions_by_state[old_state] if version_id in versions: versions.remove(version_id) if versions: self.versions_by_state[old_state] = versions else: del self.versions_by_state[old_state] self.state_by_version[version_id] = state versions = self.versions_by_state.get(state, []) versions.append(version_id) self.versions_by_state[state] = versions
def handle_workflow_version_transition(event): """Handle workflow version transition""" principal = event.principal factory = get_object_factory(IWorkflowStateHistoryItem) if factory is not None: item = factory(date=gmtime(datetime.utcnow()), source_version=IWorkflowState( event.old_object).version_id, source_state=event.source, target_state=event.destination, transition_id=event.transition.transition_id, principal=principal.id if IPrincipalInfo.providedBy(principal) else principal, comment=event.comment) IWorkflowState(event.object).history.append(item) # pylint: disable=no-member
def label(self): """Label getter""" translate = self.request.localizer.translate state = IWorkflowState(self.context) return translate(_("Version {id} - {state}")).format( id=state.version_id, state=translate(self.workflow.get_state_label(state.state)))
def create_and_add(self, data): data = data.get(self, {}) info = IWorkflowInfo(self.context) result = info.fire_transition(self.transition.transition_id, comment=data.get('comment')) info.fire_automatic() IWorkflowState(self.context).state_urgency = data.get('urgent_request') or False self.request.registry.notify(ObjectModifiedEvent(self.context)) return result or info
def delete_action(wf, context): # pylint: disable=invalid-name,unused-argument """Delete draft version, and parent if single version""" versions = IWorkflowVersions(context) versions.remove_version(IWorkflowState(context).version_id) if not versions.get_last_versions(): document = get_parent(versions, IDocument) folder = get_parent(document, IDocumentFolder) del folder[document.__name__]
def fire_transition(self, transition_id, comment=None, side_effect=None, check_security=True, principal=None, request=None): # pylint: disable=too-many-arguments """Fire transition with given ID""" versions = IWorkflowVersions(self.parent) state = IWorkflowState(self.context) if request is None: request = check_request() # this raises InvalidTransitionError if id is invalid for current state transition = self.wf.get_transition(state.state, transition_id) # check whether we may execute this workflow transition if check_security and transition.permission: if not request.has_permission(transition.permission, context=self.context): raise HTTPUnauthorized() # now make sure transition can still work in this context if not transition.condition(self, self.context): raise ConditionFailedError() # perform action, return any result as new version if principal is None: principal = request.principal result = transition.action(self, self.context) if result is not None: # stamp it with version versions.add_version(result, transition.destination, principal) # execute any side effect: if side_effect is not None: side_effect(result) event = WorkflowVersionTransitionEvent(result, self.wf, principal, self.context, transition.source, transition.destination, transition, comment) else: versions.set_state(state.version_id, transition.destination, principal) # execute any side effect if side_effect is not None: side_effect(self.context) event = WorkflowTransitionEvent(self.context, self.wf, principal, transition.source, transition.destination, transition, comment) # change state of context or new object registry = request.registry registry.notify(event) # send modified event for original or new object if result is None: registry.notify(ObjectModifiedEvent(self.context)) else: registry.notify(ObjectModifiedEvent(result)) return result
def _get_transitions(self, trigger): """Retrieve all possible transitions from workflow utility""" state = IWorkflowState(self.context) transitions = self.wf.get_transitions(state.state) # now filter these transitions to retrieve all possible # transitions in this context, and return their ids return [ transition for transition in transitions if transition.trigger == trigger ]
def remove_version(self, version_id, state='deleted', comment=None, principal=None, request=None): # pylint: disable=too-many-arguments """Remove version with given ID""" if str(version_id) not in self: return # update version state version = self[str(version_id)] wf_state = IWorkflowState(version) if comment: if request is None: request = check_request() translate = request.localizer.translate workflow = get_utility( IWorkflow, name=get_parent(self, IWorkflowManagedContent).workflow_name) item = WorkflowHistoryItem( date=datetime.utcnow(), source_version=wf_state.version_id, source_state=translate( workflow.states.getTerm(wf_state.state).title), target_state=translate(workflow.states.getTerm(state).title), principal=request.principal.id, comment=comment) wf_state.history.append(item) # pylint: disable=no-member wf_state.state = state if principal is not None: wf_state.state_principal = principal # remove given version state = self.state_by_version[version_id] versions = self.versions_by_state[state] versions.remove(version_id) if versions: self.versions_by_state[state] = versions else: del self.versions_by_state[state] del self.state_by_version[version_id] self.deleted[version_id] = self[str(version_id)] del self[str(version_id)]
def edit_permission(self): """Document edit permission getter""" document = self.context request = self.request if request.has_permission(MANAGE_APPLICATION_PERMISSION, context=self): return MANAGE_APPLICATION_PERMISSION state = IWorkflowState(document) if state in ZFILES_WORKFLOW.visible_states: return FORBIDDEN_PERMISSION return MANAGE_DOCUMENT_PERMISSION
def set_file_properties(request, oid, properties, version=None): """Set document properties""" container = get_utility(IDocumentContainer) document = container.update_document(oid, version, properties=properties, request=request) if document is None: return None state = IWorkflowState(document) return {'version': state.version_id}
def handle_cloned_object(event): """Add comment when an object is cloned""" request = check_request() translate = request.localizer.translate source_state = IWorkflowState(event.source) factory = get_object_factory(IWorkflowStateHistoryItem) if factory is not None: item = factory( date=datetime.utcnow(), principal=request.principal.id, comment=translate( _("Clone created from version {source} (in « {state} » " "state)")).format(source=source_state.version_id, state=translate( IWorkflow( event.source).get_state_label( source_state.state)))) target_state = IWorkflowState(event.object) target_state.history.clear() # pylint: disable=no-member target_state.history.append(item) # pylint: disable=no-member
def add_version(self, content, state, principal=None): """Add new version to versions list""" self.last_version_id += 1 version_id = self.last_version_id # init version state alsoProvides(content, IWorkflowVersion) wf_state = IWorkflowState(content) wf_state.version_id = version_id wf_state.state = state if principal is not None: wf_state.state_principal = principal # store new version if state is None: state = NONE_STATE self[str(version_id)] = content self.state_by_version[version_id] = state versions = self.versions_by_state.get(state, []) versions.append(version_id) self.versions_by_state[state] = versions return version_id
def visible_publication_date(self): """Visible publication date getter""" displayed_date = self.displayed_publication_date if displayed_date == DISPLAY_FIRST_VERSION: state = IWorkflowState(self.__parent__, None) if (state is not None) and (state.version_id > 1): versions = IWorkflowVersions(self.__parent__, None) if versions is not None: version = versions.get_version(1) # pylint: disable=assignment-from-no-return return IWorkflowPublicationInfo( version).publication_effective_date return self.publication_effective_date
def set_file_data(request, oid, data, properties=None, version=None): """Set document data""" container = get_utility(IDocumentContainer) if isinstance(data, Binary): data = data.data else: data = base64.b64decode(data) document = container.update_document(oid, version, data, properties, request) if document is None: return None state = IWorkflowState(document) return {'version': state.version_id}
def _get_viewlets(self): translate = self.request.localizer.translate for version in IWorkflowVersions( self.context).get_last_versions(count=0): state = IWorkflowState(version) item = MenuItem(version, self.request, self.view, self) item.label = translate(_("Version {id} - {state}")).format( id=state.version_id, state=translate(self.workflow.get_state_label(state.state))) item.icon_class = 'fas fa-arrow-right' if version is self.context: item.css_class = 'bg-primary text-white' item.href = absolute_url(version, self.request, 'admin#{}'.format(self.request.view_name)) yield 'version_{}'.format(state.version_id), item
def get_label(content, request=None, format=True): # pylint: disable=redefined-builtin """Workflow state label getter""" if request is None: request = check_request() translate = request.localizer.translate state = IWorkflowState(content) header = STATES_HEADERS.get(state.state) if header is not None: state_label = translate(header) if format: state_label = translate(_('{state} {date}')).format( state=state_label, date=format_datetime(state.state_date)) else: state_label = translate(_("Unknown state")) return state_label
def publish_action(wf, context): # pylint: disable=invalid-name,unused-argument """Publish version""" request = check_request() translate = request.localizer.translate publication_info = IWorkflowPublicationInfo(context) publication_info.publication_date = datetime.utcnow() publication_info.publisher = request.principal.id version_id = IWorkflowState(context).version_id for version in IWorkflowVersions(context).get_versions( (PUBLISHED_STATE, )): if version is not context: IWorkflowInfo(version).fire_transition_toward( ARCHIVED_STATE, comment=translate( _("Published version {0}")).format(version_id))
def update_document(self, oid, version=None, data=None, properties=None, request=None, check_permission=True): # pylint: disable=too-many-arguments """Update document data or properties""" if not oid: return None document = self.get_document(oid, version) if document is None: raise HTTPNotFound() if request is None: request = check_request() if check_permission and \ not request.has_permission(MANAGE_DOCUMENT_PERMISSION, context=document): raise HTTPForbidden() if properties is None: properties = {} if data is not None: document_hash = get_hash(data) if document_hash == document.hash: # unmodified file content data = None _filename = properties.pop('filename', None) else: # modified file content, check state and create new version if required if request is None: request = check_request() state = IWorkflowState(document) if state.state != DRAFT_STATE: translate = request.localizer.translate workflow_info = IWorkflowInfo(document) document = workflow_info.fire_transition_toward( # pylint: disable=assignment-from-no-return DRAFT_STATE, comment=translate(_("Document content update")), request=request) request.response.status = HTTPCreated.code state = document.update(data, properties) request.registry.notify(ObjectModifiedEvent(document)) if state.state == DELETED_STATE: return None return document
def update(self, data, properties, request=None): """Document data setter""" if properties: if request is None: request = check_request() self.updater = request.principal.id if data is not None: if 'filename' in properties: data = (properties.pop('filename'), data) self.data = data if (not self.title) or ('title' in properties): self.title = properties.pop('title', '<UNDEFINED>') if (not self.application_name) or ('application_name' in properties): self.application_name = properties.pop('application_name', '<UNDEFINED>') self.update_roles(properties, request) self.update_security_policy(properties, request) self.update_status(properties, request) self.update_properties(properties, request) return IWorkflowState(self)
def fire_transition_toward(self, state, comment=None, side_effect=None, check_security=True, principal=None, request=None): # pylint: disable=too-many-arguments """Fire transition(s) to given state""" current_state = IWorkflowState(self.context) if state == current_state.state: # unchanged state return None transition_ids = self.get_fireable_transition_ids_toward( state, check_security) if not transition_ids: raise NoTransitionAvailableError(current_state.state, state) if len(transition_ids) != 1: raise AmbiguousTransitionError(current_state.state, state) return self.fire_transition(transition_ids[0], comment, side_effect, check_security, principal, request)
def get_label(content, request=None, format=True): # pylint: disable=redefined-builtin """Workflow state label getter""" if request is None: request = check_request() translate = request.localizer.translate state = IWorkflowState(content) if len(state.history) <= 2: header = STATES_HEADERS.get(state.state) if header is not None: if state.version_id == 1: state_label = translate(header) else: state_label = translate(_("new version created")) else: state_label = translate(_("Unknown state")) else: state_label = translate(_('publication refused')) if format: state_label = translate(_('{state} {date}')).format( state=state_label, date=format_datetime(state.state_date)) return state_label
def title(self): """Title getter""" translate = self.request.localizer.translate # pylint: disable=no-member return translate(_("Version {version} history")).format( version=IWorkflowState(self.context).version_id) # pylint: disable=no-member
def get_value(self, obj): state = IWorkflowState(obj) return self.request.localizer.translate(STATE_LABELS.get(state.state))
def to_json(self, fields=None, request=None): """Get document properties in JSON format""" if request is None: request = check_request() dc = IZopeDublinCore(self) # pylint: disable=invalid-name state = IWorkflowState(self) roles = IDocumentRoles(self) result = { 'api': absolute_url(request.root, request, 'api/zfiles/rest/{}'.format(self.oid)), 'oid': self.oid, 'title': self.title, 'application_name': self.application_name, 'filename': self.data.filename, 'filesize': self.data.get_size(), 'content_type': self.data.content_type, 'href': absolute_url(self.data, request), 'hash': self.hash, 'properties': self.properties, 'tags': list(self.tags or ()), 'version': state.version_id, 'status': state.state, 'creator': list(roles.creator)[0], 'created_time': dc.created.isoformat() if dc.created else None, # pylint: disable=no-member 'owner': list(roles.owner)[0], 'updater': self.updater, 'updated_time': dc.modified.isoformat() if dc.modified else None, # pylint: disable=no-member 'status_updater': state.state_principal, 'status_update_time': state.state_date.isoformat(), # pylint: disable=no-member 'access_mode': ACCESS_MODE_IDS[self.access_mode], 'readers': list(roles.readers or ()), 'update_mode': ACCESS_MODE_IDS[self.update_mode], 'managers': list(roles.managers or ()) } if fields: for key in tuple(result.keys()): if key not in fields: del result[key] return result
def values(self): """History table getter""" yield from IWorkflowState(self.context).history # pylint: disable=not-an-iterable
def get_value(self, obj): state = IWorkflowState(obj) return state.version_id