예제 #1
0
        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
예제 #2
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)
        """
        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]
예제 #3
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]
예제 #4
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()
예제 #5
0
    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"
            )
예제 #8
0
    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
예제 #9
0
    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
예제 #10
0
    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
예제 #11
0
    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))
예제 #12
0
        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
예제 #13
0
    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
예제 #14
0
    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))
예제 #15
0
    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))