def process(workflow_object, user, action, next_state=None, god_mod=False): proceedings = ProceedingService.get_available_proceedings( workflow_object, [workflow_object.get_state()], user=user, god_mod=god_mod) c = proceedings.count() if c == 0: raise RiverException(ErrorCode.NO_AVAILABLE_NEXT_STATE_FOR_USER, "There is no available state for destination for the user.") if c > 1: if next_state: proceedings = proceedings.filter(meta__transition__destination_state=next_state) if proceedings.count() == 0: available_states = StateService.get_available_states(workflow_object, user) raise RiverException(ErrorCode.INVALID_NEXT_STATE_FOR_USER, "Invalid state is given(%s). Valid states is(are) %s" % ( next_state.__str__(), ','.join([ast.__str__() for ast in available_states]))) else: raise RiverException(ErrorCode.NEXT_STATE_IS_REQUIRED, "State must be given when there are multiple states for destination") proceeding = proceedings[0] proceeding.status = action proceeding.transactioner = user proceeding.transaction_date = timezone.now() if workflow_object.proceeding: proceeding.previous = workflow_object.proceeding proceeding.save() return proceeding
def get_initial_state(content_type, field): """ A state which is not a destination of a transition but can be source of a transition OR not (a destination of a transition and this transition direction is FORWARD) """ initial_state_candidates = State.objects.filter( Q( transitions_as_source__isnull=False, transitions_as_source__content_type=content_type, transitions_as_source__field=field, transitions_as_destination__isnull=True, ) & ~Q(transitions_as_destination__isnull=False, transitions_as_destination__direction=FORWARD, transitions_as_destination__content_type=content_type, transitions_as_destination__field=field)).distinct() c = initial_state_candidates.count() if c == 0: raise RiverException( ErrorCode.NO_AVAILABLE_INITIAL_STATE, 'There is no available initial state for the content type %s. ' % content_type) elif c > 1: raise RiverException( ErrorCode.MULTIPLE_INITIAL_STATE, 'There are multiple initial state for the content type %s. Have only one initial state' % content_type) return initial_state_candidates[0]
def get_initial_state(content_type, field): """ A state which is not a destination of a transition but can be source of a transition OR not (a destination of a transition and this transition direction is FORWARD) """ states = State.objects.filter(pk__in=ProceedingService.get_initial_proceedings(content_type, field).values_list('meta__transition__source_state', flat=True)) if states.count() == 0: raise RiverException(ErrorCode.NO_AVAILABLE_INITIAL_STATE, 'There is no available initial state for the content type %s. ' % content_type) elif states.count() > 1: raise RiverException(ErrorCode.MULTIPLE_INITIAL_STATE, 'There are multiple initial state for the content type %s. Have only one initial state' % content_type) return states[0]
def approve(self, as_user, next_state=None): available_approvals = self.get_available_approvals(as_user=as_user) number_of_available_approvals = available_approvals.count() if number_of_available_approvals == 0: raise RiverException( ErrorCode.NO_AVAILABLE_NEXT_STATE_FOR_USER, "There is no available approval for the user.") elif next_state: available_approvals = available_approvals.filter( transition__destination_state=next_state) if available_approvals.count() == 0: available_states = self.get_available_states(as_user) raise RiverException( ErrorCode.INVALID_NEXT_STATE_FOR_USER, "Invalid state is given(%s). Valid states is(are) %s" % (next_state.__str__(), ','.join( [ast.__str__() for ast in available_states]))) elif number_of_available_approvals > 1 and not next_state: raise RiverException( ErrorCode.NEXT_STATE_IS_REQUIRED, "State must be given when there are multiple states for destination" ) approval = available_approvals.first() approval.status = APPROVED approval.transactioner = as_user approval.transaction_date = timezone.now() approval.previous = self.recent_approval approval.save() if next_state: self.cancel_impossible_future(approval) has_transit = False if approval.peers.filter(status=PENDING).count() == 0: approval.transition.status = DONE approval.transition.save() previous_state = self.get_state() self.set_state(approval.transition.destination_state) has_transit = True LOGGER.debug( "Workflow object %s is proceeded for next transition. Transition: %s -> %s" % (self.workflow_object, previous_state, self.get_state())) if next_state: self.initialize_approvals(approval.transition.iteration + 1) with self._approve_signal(approval), self._transition_signal( has_transit, approval), self._on_complete_signal(): self.workflow_object.save()
def get_only_field(workflow_class): fields = ObjectService.get_the_fields(workflow_class) if not len(fields): raise RiverException( ErrorCode.NO_STATE_FIELD, "There is no state field in model class of given instance") elif len(fields) > 1: raise RiverException( ErrorCode.MULTIPLE_STATE_FIELDS, "There are multiple state field in the instance model class. Please send which field will be used" ) return fields[0]
def initial_state(self): initial_states = State.objects.filter( pk__in=TransitionApprovalMeta.objects.filter( content_type=self._content_type, field_name=self.name, parents__isnull=True ).values_list("source_state", flat=True) ) if initial_states.count() == 0: raise RiverException(ErrorCode.NO_AVAILABLE_INITIAL_STATE, 'There is no available initial state for the content type %s. ' % self._content_type) elif initial_states.count() > 1: raise RiverException(ErrorCode.MULTIPLE_INITIAL_STATE, 'There are multiple initial state for the content type %s. Have only one initial state' % self._content_type) return initial_states[0]
def jump_to(self, state): def _transitions_before(iteration): return Transition.objects.filter( workflow=self.workflow, workflow_object=self.workflow_object, iteration__lte=iteration) try: recent_iteration = self.recent_approval.transition.iteration if self.recent_approval else 0 jumped_transition = getattr( self.workflow_object, self.field_name + "_transitions").filter( iteration__gte=recent_iteration, destination_state=state, status=PENDING).earliest("iteration") jumped_transitions = _transitions_before( jumped_transition.iteration).filter(status=PENDING) for approval in TransitionApproval.objects.filter( pk__in=jumped_transitions.values_list( "transition_approvals__pk", flat=True)): approval.status = JUMPED approval.save() jumped_transitions.update(status=JUMPED) self.set_state(state) self.workflow_object.save() except Transition.DoesNotExist: raise RiverException( ErrorCode.STATE_IS_NOT_AVAILABLE_TO_BE_JUMPED, "This state is not available to be jumped in the future of this object" )
def on_final_state(self): if self.class_workflow.final_states.count() == 0: raise RiverException( ErrorCode.NO_AVAILABLE_FINAL_STATE, 'There is no available final state for the content type %s.' % self._content_type) return self.get_state() in self.class_workflow.final_states
def process(workflow_object, field, user, action, next_state=None, god_mod=False): current_state = getattr(workflow_object, field) approvements = ApprovementService.get_approvements_object_waiting_for_approval( workflow_object, field, [current_state], user=user, god_mod=god_mod) c = approvements.count() if c == 0: raise RiverException( ErrorCode.NO_AVAILABLE_NEXT_STATE_FOR_USER, "There is no available state for destination for the user.") if c > 1: if next_state: approvements = approvements.filter( meta__transition__destination_state=next_state) if approvements.count() == 0: available_states = StateService.get_available_states( workflow_object, field, user) raise RiverException( ErrorCode.INVALID_NEXT_STATE_FOR_USER, "Invalid state is given(%s). Valid states is(are) %s" % (next_state.__unicode__(), ','.join( [ast.__unicode__() for ast in available_states]))) else: raise RiverException( ErrorCode.NEXT_STATE_IS_REQUIRED, "State must be given when there are multiple states for destination" ) approvement = approvements[0] approvement.status = action approvement.transactioner = user approvement.transaction_date = datetime.now() approvement.save() c = False track = workflow_object.approvement_track while not c: track, c = approvement.tracks.get_or_create(previous_track=track) return approvement, track
def get_field(workflow_class): from river.models.fields.state import StateField field = next((f for f in workflow_class._meta.fields if type(f) is StateField), None) if not field: raise RiverException(ErrorCode.NO_STATE_FIELD, "There is no state field in model class of given instance") return field
def get_final_states(content_type): """ A state which is not a source of a transition but can be destination of a transition OR not (a source of a transition and this transition direction is FORWARD) """ proceedings = ProceedingService.get_final_proceedings(content_type) if proceedings.count() == 0: raise RiverException(ErrorCode.NO_AVAILABLE_FINAL_STATE, 'There is no available final state for the content type %s.' % content_type) return State.objects.filter(pk__in=proceedings.values_list('meta__transition__destination_state', flat=True))
def process(action, next_state=None, god_mod=False): available_transition_approvals = self.get_available_approvals( as_user=as_user, god_mod=god_mod) c = available_transition_approvals.count() if c == 0: raise RiverException( ErrorCode.NO_AVAILABLE_NEXT_STATE_FOR_USER, "There is no available state for destination for the user." ) if c > 1: if next_state: available_transition_approvals = available_transition_approvals.filter( destination_state=next_state) if available_transition_approvals.count() == 0: available_states = self.get_available_states( as_user=as_user) raise RiverException( ErrorCode.INVALID_NEXT_STATE_FOR_USER, "Invalid state is given(%s). Valid states is(are) %s" % (next_state.__str__(), ','.join( [ast.__str__() for ast in available_states]))) else: raise RiverException( ErrorCode.NEXT_STATE_IS_REQUIRED, "State must be given when there are multiple states for destination" ) available_transition_approval = available_transition_approvals[0] available_transition_approval.status = action available_transition_approval.transactioner = as_user available_transition_approval.transaction_date = timezone.now() if self.recent_approval: available_transition_approval.previous = self.recent_approval available_transition_approval.save() return available_transition_approval
def get_final_states(content_type, field): """ A state which is not a source of a transition but can be destination of a transition OR not (a source of a transition and this transition direction is FORWARD) """ final_states = State.objects.filter( Q(transitions_as_source__isnull=True, transitions_as_destination__isnull=False, transitions_as_destination__content_type=content_type, transitions_as_destination__field=field) & ~Q(transitions_as_source__isnull=False, transitions_as_source__direction=FORWARD, transitions_as_source__content_type=content_type, transitions_as_source__field=field)).distinct() c = final_states.count() if c == 0: raise RiverException( ErrorCode.NO_AVAILABLE_FINAL_STATE, 'There is no available final state for the content type %s.' % content_type) return final_states
def contribute_to_class(self, cls, name, virtual_only=False): def is_workflow_completed(workflow_object): return ObjectService.is_workflow_completed(workflow_object, name) def proceed(self, user, *args, **kwargs): TransitionService.proceed(self, name, user, *args, **kwargs) @property def on_initial_state(self): from river.services.state import StateService return StateService.get_initial_state( ContentType.objects.get_for_model(self), name) == getattr(self, name) @property def on_final_state(self): from river.services.state import StateService return getattr(self, name) in StateService.get_final_states( ContentType.objects.get_for_model(self), name) def get_initial_state(self): from river.services.state import StateService return StateService.get_initial_state( ContentType.objects.get_for_model(self), name) def get_available_proceedings(self, *args, **kwargs): from river.services.proceeding import ProceedingService return ProceedingService.get_available_proceedings( self, name, [getattr(self, name)], *args, **kwargs) @property def initial_proceedings(self): from river.services.proceeding import ProceedingService return getattr(self, name) in ProceedingService.get_initial_proceedings( ContentType.objects.get_for_model(self), name) @property def final_proceedings(self): from river.services.proceeding import ProceedingService return getattr(self, name) in ProceedingService.get_final_proceedings( ContentType.objects.get_for_model(self), name) @property def next_proceedings(self): from river.services.proceeding import ProceedingService return getattr(self, name) in ProceedingService.get_next_proceedings( ContentType.objects.get_for_model(self), name) @property def proceeding(self): try: return self.proceedings.filter( transaction_date__isnull=False).latest('transaction_date') except Proceeding.DoesNotExist: return None self.model = cls if id(cls) in classes: raise RiverException( ErrorCode.MULTIPLE_STATE_FIELDS, "There can be only one state field in a model class.") classes.append(id(cls)) self.__add_to_class( cls, "proceedings", GenericRelation( '%s.%s' % (Proceeding._meta.app_label, Proceeding._meta.object_name))) self.__add_to_class(cls, "proceeding", proceeding) self.__add_to_class(cls, "workflow", self.object_manager(name)) self.__add_to_class(cls, "is_workflow_completed", is_workflow_completed) self.__add_to_class(cls, "proceed", proceed) self.__add_to_class(cls, "on_initial_state", on_initial_state) self.__add_to_class(cls, "on_final_state", on_final_state) self.__add_to_class(cls, "get_initial_state", get_initial_state) self.__add_to_class(cls, "get_available_proceedings", get_available_proceedings) self.__add_to_class(cls, "initial_proceedings", initial_proceedings) self.__add_to_class(cls, "final_proceedings", final_proceedings) self.__add_to_class(cls, "next_proceedings", next_proceedings) super(StateField, self).contribute_to_class(cls, name, virtual_only=virtual_only) post_save.connect(_post_save, self.model, False, dispatch_uid='%s_%s_riverstatefield_post' % (self.model, self.name))
def contribute_to_class(self, cls, name, private_only=False): def is_workflow_completed(workflow_object): return ObjectService.is_workflow_completed(workflow_object) def proceed(self, user, *args, **kwargs): TransitionService.proceed(self, user, *args, **kwargs) @property def on_initial_state(self): from river.services.state import StateService return StateService.get_initial_state( ContentType.objects.get_for_model(self)) == self.get_state() @property def on_final_state(self): from river.services.state import StateService return self.get_state() in StateService.get_final_states( ContentType.objects.get_for_model(self)) def get_initial_state(self): from river.services.state import StateService return StateService.get_initial_state( ContentType.objects.get_for_model(self)) def get_available_proceedings(self, *args, **kwargs): from river.services.proceeding import ProceedingService return ProceedingService.get_available_proceedings( self, [self.get_state()], *args, **kwargs) @property def initial_proceedings(self): from river.services.proceeding import ProceedingService return self.get_state( ) in ProceedingService.get_initial_proceedings( ContentType.objects.get_for_model(self)) @property def final_proceedings(self): from river.services.proceeding import ProceedingService return self.get_state() in ProceedingService.get_final_proceedings( ContentType.objects.get_for_model(self)) @property def next_proceedings(self): from river.services.proceeding import ProceedingService return self.get_state() in ProceedingService.get_next_proceedings( ContentType.objects.get_for_model(self)) @property def proceeding(self): try: return self.proceedings.filter( transaction_date__isnull=False).latest('transaction_date') except Proceeding.DoesNotExist: return None def _get_state(self): return getattr(self, name) def _set_state(self, state): setattr(self, name, state) field_identifiers = [ _get_identifier(c, f) for c, f in class_field_rl.items() ] cls_identifier = _get_cls_identifier(cls) field_identifier = _get_identifier(cls_identifier, name) if field_identifier not in field_identifiers: if cls_identifier in class_field_rl.keys(): raise RiverException( ErrorCode.MULTIPLE_STATE_FIELDS, "There can be only one state field in a model class. Class:%s - Field:%s." % (cls.__name__, name)) class_field_rl[cls_identifier] = name self.model = cls self.__add_to_class( cls, "proceedings", GenericRelation( '%s.%s' % (Proceeding._meta.app_label, Proceeding._meta.object_name))) self.__add_to_class(cls, "proceeding", proceeding) self.__add_to_class(cls, "is_workflow_completed", is_workflow_completed) self.__add_to_class(cls, "proceed", proceed) self.__add_to_class(cls, "on_initial_state", on_initial_state) self.__add_to_class(cls, "on_final_state", on_final_state) self.__add_to_class(cls, "get_initial_state", get_initial_state) self.__add_to_class(cls, "get_available_proceedings", get_available_proceedings) self.__add_to_class(cls, "initial_proceedings", initial_proceedings) self.__add_to_class(cls, "final_proceedings", final_proceedings) self.__add_to_class(cls, "next_proceedings", next_proceedings) self.__add_to_class(cls, "get_state", _get_state) self.__add_to_class(cls, "set_state", _set_state) super(StateField, self).contribute_to_class(cls, name, private_only=private_only) post_save.connect(_post_save, self.model, False, dispatch_uid='%s_%s_riverstatefield_post' % (self.model, name))