Example #1
0
        def perform_transition(self, action, user, request=None, **kwargs):
            transition = self.get_transition(action)
            if not transition:
                raise PermissionDenied(f'Invalid "{ action }" transition')
            if not can_proceed(transition):
                action = self.phase.transitions[action]
                raise PermissionDenied(f'You do not have permission to "{ action }"')

            transition(by=user, request=request, **kwargs)
            self.save(update_fields=['status'])

            self.progress_stage_when_possible(user, request, **kwargs)
Example #2
0
    def func_wrapper(transition_method):
        original_wrapped_transition_method = transition(
            field, source, target, on_error, conditions or [], permission,
            custom or {})(transition_method)

        @wraps(transition_method)
        def transition_wrapper(instance, *args, **kwargs):
            with transaction.atomic():
                locked_instance = instance.__class__.objects\
                    .filter(id=instance.id)\
                    .select_for_update()\
                    .first()

                field_name = field if isinstance(field, str) else field.name
                field_value = getattr(instance, field_name)
                saved_field_value = getattr(locked_instance, field_name)

                strict_fields = [
                    strict_field
                    if isinstance(strict_field, str) else strict_field.name
                    for strict_field in getattr(instance, 'strict_fields', [])
                ]

                for strict_field in strict_fields:
                    if getattr(locked_instance, strict_field) != getattr(
                            instance, strict_field):
                        raise TransitionNotAllowed(
                            f"{instance.__class__}'s {strict_field} value has concurrently changed."
                        )

                if source != '*':
                    if isinstance(source, (list, tuple, set)):
                        if saved_field_value not in source:
                            raise TransitionNotAllowed(
                                f"Saved object's {field} is not a valid source for this transition."
                            )
                    elif field_value != saved_field_value:
                        raise TransitionNotAllowed(
                            f"Object {field} is {field_value}, "
                            f"while the saved field is {saved_field_value}.")

                # post transition signal receivers are also executed inside this atomic transaction
                result = original_wrapped_transition_method(
                    instance, *args, **kwargs)

                setattr(instance, f'{field_name}_recently_transitioned_to',
                        target)

                if save:
                    instance.save()

                return result

        return transition_wrapper
Example #3
0
    def func_wrapper(transition_method):
        original_wrapped_transition_method = transition(
            field, source, target, on_error, conditions, permission, custom
        )(transition_method)

        @wraps(transition_method)
        def transition_wrapper(instance, *args, **kwargs):
            with transaction.atomic():
                # post transition signal receivers are also executed inside this atomic transaction
                return original_wrapped_transition_method(instance, *args, **kwargs)

        return transition_wrapper
Example #4
0
    def func_wrapper(transition_method):
        original_wrapped_transition_method = transition(
            field, source, target, on_error, conditions or [], permission,
            custom or {})(transition_method)

        @wraps(transition_method)
        def transition_wrapper(instance, *args, **kwargs):
            with transaction.atomic():
                field_name = field if isinstance(field, str) else field.name

                # post transition signal receivers are also executed inside this atomic transaction
                result = original_wrapped_transition_method(
                    instance, *args, **kwargs)

                setattr(instance, f'{field_name}_recently_transitioned_to',
                        target)

                if save:
                    instance.save()

                return result

        return transition_wrapper
Example #5
0
    def __new__(cls, name, bases, attrs, **kwargs):
        for workflow in WORKFLOWS.values():
            for phase, data in workflow.items():
                for transition_name, action in data.transitions.items():
                    method_name = transition_id(transition_name, data)
                    permission_name = method_name + '_permission'
                    permission_func = make_permission_check(
                        action['permissions'])

                    # Get the method defined on the parent or default to a NOOP
                    transition_state = wrap_method(
                        attrs.get(action.get('method'),
                                  lambda *args, **kwargs: None))
                    # Provide a neat name for graph viz display
                    transition_state.__name__ = slugify(action['display'])

                    conditions = [
                        attrs[condition]
                        for condition in action.get('conditions', [])
                    ]
                    # Wrap with transition decorator
                    transition_func = transition(
                        attrs['status'],
                        source=phase,
                        target=transition_name,
                        permission=permission_func,
                        conditions=conditions,
                    )(transition_state)

                    # Attach to new class
                    attrs[method_name] = transition_func
                    attrs[permission_name] = permission_func

        def get_transition(self, transition):
            try:
                return getattr(self, transition_id(transition, self.phase))
            except TypeError:
                # Defined on the class
                return None
            except AttributeError:
                # For the other workflow
                return None

        attrs['get_transition'] = get_transition

        def get_actions_for_user(self, user):
            transitions = self.get_available_user_status_transitions(user)
            actions = [(transition.target,
                        self.phase.transitions[transition.target]['display'])
                       for transition in transitions
                       if self.get_transition(transition.target)]
            yield from actions

        attrs['get_actions_for_user'] = get_actions_for_user

        def perform_transition(self, action, user, request=None, **kwargs):
            transition = self.get_transition(action)
            if not transition:
                raise PermissionDenied(f'Invalid "{ action }" transition')
            if not can_proceed(transition):
                action = self.phase.transitions[action]
                raise PermissionDenied(
                    f'You do not have permission to "{ action }"')

            transition(by=user, request=request, **kwargs)
            self.save(update_fields=['status'])

            self.progress_stage_when_possible(user, request, **kwargs)

        attrs['perform_transition'] = perform_transition

        def progress_stage_when_possible(self,
                                         user,
                                         request,
                                         notify=None,
                                         **kwargs):
            # Check to see if we can progress to a new stage from the current status
            for stage_transition in STAGE_CHANGE_ACTIONS:
                try:
                    self.perform_transition(stage_transition,
                                            user,
                                            request=request,
                                            notify=False,
                                            **kwargs)
                except PermissionDenied:
                    pass

        attrs['progress_stage_when_possible'] = progress_stage_when_possible

        return super().__new__(cls, name, bases, attrs, **kwargs)