def _objects_are_valid(self): try: _check_instances_for_transition(instances=self.objects, transition=self.transition, requester=self.request.user) except TransitionNotAllowedError as e: return False, e return True, None
def test_return_report_when_requester_is_not_assets_owner(self): _, transition, _ = self._create_transition( model=self.bo_asset, name='test', source=[BackOfficeAssetStatus.new.id], target=BackOfficeAssetStatus.used.id, actions=['must_be_owner_of_asset']) with self.assertRaises(TransitionNotAllowedError): _check_instances_for_transition(instances=[self.bo_asset], transition=transition, requester=self.user_pl)
def _check_instances(self): try: _check_instances_for_transition( instances=self.objects, transition=self.transition, requester=self.request.user, ) except TransitionNotAllowedError as e: raise DRFValidationError({ api_settings.NON_FIELD_ERRORS_KEY: list(itertools.chain(*e.errors.values())) })
def _perform_async_transition(transition_job): transition = transition_job.transition obj = transition_job.obj requester = transition_job.user _check_instances_for_transition( instances=[obj], transition=transition, check_async_job=False, requester=requester ) _check_action_with_instances([obj], transition) # check if this job isn't already finished if not transition_job.is_running: logger.warning( 'Running previously ended transition job: %s', transition_job ) return # make sure that none of previous actions has failed executed_actions = list(transition_job.transition_job_actions.all()) try: _check_previous_actions(transition_job, executed_actions) except AsyncTransitionError: return completed_actions_names = set([ tja.action_name for tja in executed_actions if tja.status != TransitionJobActionStatus.STARTED ]) attachments = [] # TODO: move this to transition (sth like # `for action in transition.get_actions(obj)`) for action in _order_actions_by_requirements(transition.actions.all(), obj): transition_job.refresh_from_db() if transition_job.is_killed: logger.info('Transition job: {} is killed'.format(transition_job)) return if action.name in completed_actions_names: logger.debug('Action {} already performed - skipping'.format( action.name )) continue logger.info('Performing action {} in transition {} (job: {})'.format( action, transition, transition_job )) func = getattr(obj, action.name) # TODO: disable save object ? # data should be in transition_job.params dict defaults = _prepare_action_data( action=action, **transition_job.params ) tja = TransitionJobAction.objects.get_or_create( transition_job=transition_job, action_name=action.name, defaults=dict( status=TransitionJobActionStatus.STARTED, ) )[0] freeze = False try: # we shouldn't run whole transition atomically since it could be # spreaded to multiple processes (multiple tasks) - run single # action in transaction instead with transaction.atomic(): try: result = func( instances=[obj], requester=requester, tja=tja, **defaults ) except RescheduleAsyncTransitionActionLater as e: # action is not ready - reschedule this job later and # continue when you left off transition_job.reschedule() return except FreezeAsyncTransition: # if action raise this exception, we're assumming that it's # finished freeze = True else: if isinstance(result, Attachment): attachments.append(result) except Exception as e: logger.exception(e) tja.status = TransitionJobActionStatus.FAILED raise FailedActionError('Action {} has failed'.format(action.name)) from e # noqa else: tja.status = TransitionJobActionStatus.FINISHED finally: tja.save() completed_actions_names.add(action.name) # freeze whole transition if demanded by some action if freeze: transition_job.freeze() return # save obj and history _post_transition_instance_processing( obj, transition, transition_job.params['data'], history_kwargs=transition_job.params['history_kwargs'], requester=requester, attachments=attachments, ) transition_job.success()