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)
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
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
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
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)