def test_run_json_report_action(self): """Test JSON action using the filter execution.""" OnTaskSharedState.json_outbox = None action = self.workflow.actions.get(name='Send JSON report') # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={'token': 'fake token'}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self.assertTrue(len(OnTaskSharedState.json_outbox) == 1) self.assertTrue( OnTaskSharedState.json_outbox[0]['auth'] == 'Bearer fake token') self.assertTrue(status.is_success(resp.status_code))
def test_run_action_email_no_filter(self): """Run sequence of request to send email list .""" action = self.workflow.actions.get(name='Send Email with report') # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={ 'email_to': '*****@*****.**', 'cc_email': '[email protected] [email protected]', 'bcc_email': '[email protected] [email protected]', 'subject': 'Email to instructor' }) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self.assertTrue(len(mail.outbox) == 1) self.assertTrue(status.is_success(resp.status_code))
def process_run_request_done( self, request: http.HttpRequest, workflow: models.Workflow, payload: SessionPayload, action: Optional[models.Action] = None, ): """Finish processing the request after item selection.""" # Get the information from the payload if not action: action = workflow.actions.filter(pk=payload['action_id']).first() if not action: return redirect('home') log_item = self._create_log_event(request.user, action, payload.get_store()) tasks.execute_operation.delay(self.log_event, user_id=request.user.id, log_id=log_item.id, workflow_id=workflow.id, action_id=action.id if action else None, payload=payload.get_store()) # Reset object to carry action info throughout dialogs SessionPayload.flush(request.session) # Successful processing. return render(request, 'action/run_done.html', { 'log_id': log_item.id, 'download': payload['export_wf'] })
def test_run_action_email_no_filter(self): """Run sequence of request to send email without filtering users.""" action = self.workflow.actions.get(name='Midterm comments') column = action.workflow.columns.get(name='email') # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={ 'cc_email': '[email protected] [email protected]', 'bcc_email': '[email protected] [email protected]', 'item_column': column.pk, 'subject': 'message subject' }) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self.assertTrue(len(mail.outbox) == action.get_rows_selected()) self._verify_content() self.assertTrue(status.is_success(resp.status_code))
def test_run_json_action_no_filter(self): """Test JSON action using the filter execution.""" OnTaskSharedState.json_outbox = None action = self.workflow.actions.get(name='Send JSON to remote server') column = action.workflow.columns.get(name='email') # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={ 'item_column': column.pk, 'token': 'fake token' }) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self.assertTrue( len(OnTaskSharedState.json_outbox) == action.get_rows_selected()) self._verify_content() self.assertTrue(status.is_success(resp.status_code))
def test_email_with_filter(self): """Run sequence of request to send email without filtering users.""" action = self.workflow.actions.get(name='Midterm comments') column = action.workflow.columns.get(name='email') exclude_values = ['*****@*****.**', '*****@*****.**'] # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={ 'item_column': column.pk, 'cc_email': '[email protected] [email protected]', 'bcc_email': '[email protected] [email protected]', 'confirm_items': True, 'subject': 'message subject' }) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload['confirm_items']) self.assertEqual(resp.status_code, status.HTTP_302_FOUND) # Load the Filter page with a GET resp = self.get_response('action:item_filter', session_payload=payload) self.assertTrue(status.is_success(resp.status_code)) # Emulate the filter page with a POST resp = self.get_response('action:item_filter', method='POST', req_params={'exclude_values': exclude_values}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload['exclude_values'] == exclude_values) self.assertEqual(resp.status_code, status.HTTP_302_FOUND) self.assertEqual(resp.url, payload['post_url']) # Emulate the redirection to run_done resp = self.get_response('action:run_done') payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self._verify_content() self.assertTrue( len(mail.outbox) == (action.get_rows_selected() - len(exclude_values))) self.assertTrue(status.is_success(resp.status_code))
def test_json_action_with_filter(self): """Test JSON action without using the filter execution.""" OnTaskSharedState.json_outbox = None action = self.workflow.actions.get(name='Send JSON to remote server') column = action.workflow.columns.get(name='email') exclude_values = ['*****@*****.**', '*****@*****.**'] # Step 1 invoke the form resp = self.get_response('action:run', url_params={'pk': action.id}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(action.id == payload['action_id']) self.assertTrue(payload['prev_url'] == reverse( 'action:run', kwargs={'pk': action.id})) self.assertTrue(payload['post_url'] == reverse('action:run_done')) self.assertTrue('post_url' in payload.keys()) self.assertTrue(status.is_success(resp.status_code)) # Step 2 send POST resp = self.get_response('action:run', url_params={'pk': action.id}, method='POST', req_params={ 'item_column': column.pk, 'confirm_items': True, 'token': 'fake token' }) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload['confirm_items']) self.assertEqual(resp.status_code, status.HTTP_302_FOUND) # Load the Filter page with a GET resp = self.get_response('action:item_filter', session_payload=payload) self.assertTrue(status.is_success(resp.status_code)) # Emulate the filter page with a POST resp = self.get_response('action:item_filter', method='POST', req_params={'exclude_values': exclude_values}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload['exclude_values'] == exclude_values) self.assertEqual(resp.status_code, status.HTTP_302_FOUND) self.assertEqual(resp.url, payload['post_url']) # Emulate the redirection to run_done resp = self.get_response('action:run_done') payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(payload == {}) self.assertTrue( len(OnTaskSharedState.json_outbox) == (action.get_rows_selected() - len(exclude_values))) self.assertTrue(status.is_success(resp.status_code))
def finish_scheduling( request: http.HttpRequest, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Finish the create/edit operation of a scheduled operation.""" del workflow payload = SessionPayload(request.session) if payload is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect action scheduling invocation.')) return redirect('action:index') return services.schedule_crud_factory.crud_finish( payload.get('operation_type'), request=request, payload=payload)
def create_and_send_zip( session: SessionBase, action: models.Action, item_column: models.Column, user_fname_column: Optional[models.Column], payload: SessionPayload, ) -> http.HttpResponse: """Process the list of tuples in files and create the ZIP BytesIO object. :param session: Session object while creating a zip (need it to flush it) :param action: Action being used for ZIP :param item_column: Column used to itemize the zip :param user_fname_column: Optional column to create file name :param payload: Dictionary with additional parameters to create the ZIP :return: HttpResponse to send back with the ZIP download header """ files = _create_eval_data_tuple(action, item_column, payload.get('exclude_values', []), user_fname_column) file_name_template = _create_filename_template(payload, user_fname_column) # Create the ZIP and return it for download sbuf = BytesIO() zip_file_obj = zipfile.ZipFile(sbuf, 'w') for user_fname, part_id, msg_body in files: if payload['zip_for_moodle']: # If a zip for Moodle, field is Participant [number]. Take the # number only part_id = part_id.split()[1] zip_file_obj.writestr( file_name_template.format(user_fname=user_fname, part_id=part_id), str(msg_body), ) zip_file_obj.close() SessionPayload.flush(session) suffix = datetime.now().strftime('%y%m%d_%H%M%S') # Attach the compressed value to the response and send compressed_content = sbuf.getvalue() response = http.HttpResponse(compressed_content) response['Content-Type'] = 'application/x-zip-compressed' response['Content-Transfer-Encoding'] = 'binary' response['Content-Disposition'] = ( 'attachment; filename="ontask_zip_action_{0}.zip"'.format(suffix)) response['Content-Length'] = str(len(compressed_content)) return response
def run_done( request: http.HttpRequest, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Finish the create/edit operation of a scheduled operation.""" payload = SessionPayload(request.session) if payload is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect action run invocation.')) return redirect('action:index') return services.ACTION_PROCESS_FACTORY.process_run_request_done( payload.get('operation_type'), request=request, workflow=workflow, payload=payload)
def process_run_request( self, operation_type: str, request: http.HttpRequest, action: models.Action, prev_url: str, ) -> http.HttpResponse: """Process a request (GET or POST).""" payload = SessionPayload(request.session, initial_values={ 'action_id': action.id, 'operation_type': operation_type, 'prev_url': prev_url, 'post_url': reverse('action:run_done') }) form = self.run_form_class( request.POST or None, columns=action.workflow.columns.filter(is_key=True), action=action, form_info=payload) if request.method == 'POST' and form.is_valid(): return self.process_run_post(request, action, payload) # Render the form return render( request, self.run_template, { 'action': action, 'num_msgs': action.get_rows_selected(), 'form': form, 'valuerange': range(2) })
def callback(request: WSGIRequest) -> http.HttpResponse: """Process the call received from the server. This is supposed to contain the token so it is saved to the database and then redirects to a page previously stored in the session object. :param request: Request object :return: Redirection to the stored page """ payload = SessionPayload(request.session) # If there is no payload, something went wrong. if payload is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect Canvas callback invocation.')) return redirect('action:index') # Check first if there has been some error error_string = request.GET.get('error') if error_string: messages.error( request, ugettext('Error in OAuth2 step 1 ({0})').format(error_string)) return redirect('action:index') status = services.process_callback(request, payload) if status: messages.error(request, status) return redirect('action:index') return redirect( request.session.get(services.return_url_key, reverse('action:index')))
def action_zip_export( request: http.HttpRequest, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Create a zip with the personalised text and return it as response. :param request: Request object with a Dictionary with all the required information :param workflow: Workflow being manipulated (set by the decorator) :return: Response (download) """ # Get the payload from the session if not given payload = SessionPayload(request.session) if not payload: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect ZIP action invocation.')) return redirect('action:index') # Payload has the right keys if any(key_name not in payload.keys() for key_name in [ 'action_id', 'zip_for_moodle', 'item_column', 'user_fname_column', 'file_suffix' ]): messages.error(request, _('Incorrect payload in ZIP action invocation')) return redirect('action:index') # Get the action action = workflow.actions.filter(pk=payload['action_id']).first() if not action: return redirect('home') item_column = workflow.columns.get(pk=payload['item_column']) if not item_column: messages.error(request, _('Incorrect payload in ZIP action invocation')) return redirect('action:index') user_fname_column = None if payload['user_fname_column']: user_fname_column = workflow.columns.get( pk=payload['user_fname_column']) # Create the ZIP with the eval data tuples and return it for download return services.create_and_send_zip(request.session, action, item_column, user_fname_column, payload)
def process_post( self, request: http.HttpRequest, schedule_item: models.ScheduledOperation, op_payload: SessionPayload, ) -> http.HttpResponse: """Process the valid form.""" if op_payload.get('confirm_items'): # Update information to carry to the filtering stage op_payload['button_label'] = ugettext('Schedule') op_payload['valuerange'] = 2 op_payload['step'] = 2 op_payload.store_in_session(request.session) return redirect('action:item_filter') # Go straight to the final step return self.finish(request, op_payload, schedule_item)
def _create_payload(self, **kwargs) -> SessionPayload: """Create a payload dictionary to store in the session. :param request: HTTP request :param operation_type: String denoting the type of s_item being processed :param s_item: Existing schedule item being processed (Optional) :param prev_url: String with the URL to use to "go back" :param action: Corresponding action for the schedule operation type, or if empty, it is contained in the scheduled_item (Optional) :return: Dictionary with pairs name: value """ s_item = kwargs.get('schedule_item') action = kwargs.get('action') if s_item: action = s_item.action exclude_values = s_item.payload.get('exclude_values', []) else: exclude_values = [] # Get the payload from the session, and if not, use the given one payload = SessionPayload(kwargs.get('request').session, initial_values={ 'action_id': action.id, 'exclude_values': exclude_values, 'operation_type': self.operation_type, 'valuerange': list(range(2)), 'prev_url': kwargs.get('prev_url'), 'post_url': reverse('scheduler:finish_scheduling'), 'page_title': ugettext('Schedule Action Execution'), }) if s_item: payload.update(s_item.payload) payload['schedule_id'] = s_item.id return payload
def process_callback( request: http.HttpRequest, payload: SessionPayload, ) -> Optional[str]: """Extract the token and store for future calls. :param request: Http Request received :param payload: Session payload with dictionary with additional info. :return: Error message or None if everything has gone correctly. """ # Correct response from a previous request. Obtain the access token, # the refresh token, and the expiration date. # Verify if the state is the one expected (stored in the session) if request.GET.get('state') != request.session[oauth_hash_key]: # This call back does not match the appropriate request. Something # went wrong. return _('Inconsistent OAuth response. Unable to authorize') oauth_instance = payload.get('target_url') if not oauth_instance: return _('Internal error. Empty OAuth Instance name') oauth_info = settings.CANVAS_INFO_DICT.get(oauth_instance) if not oauth_info: return _('Internal error. Invalid OAuth Dict element') domain = oauth_info['domain_port'] response = requests.post( oauth_info['access_token_url'].format(domain), { 'grant_type': 'authorization_code', 'client_id': oauth_info['client_id'], 'client_secret': oauth_info['client_secret'], 'redirect_uri': request.session[callback_url_key], 'code': request.GET.get('code')}) if response.status_code != status.HTTP_200_OK: return _('Unable to obtain access token from OAuth') # Response is correct. Parse and extract elements response_data = response.json() # Create the new token for the user utoken = models.OAuthUserToken( user=request.user, instance_name=oauth_instance, access_token=response_data['access_token'], refresh_token=response_data.get('refresh_token'), valid_until=timezone.now() + timedelta( seconds=response_data.get('expires_in', 0)), ) utoken.save() return None
def run_action_item_filter( request: http.HttpRequest, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Offer a select widget to tick items to exclude from selection. This is a generic Web function. It assumes that the session object has a dictionary with a field stating what objects need to be considered for selection. It creates the right web form and then updates in the session dictionary the result and proceeds to a URL given also as part of that dictionary. :param request: HTTP request (GET) with a session object and a dictionary with the right parameters. The selected values are stored in the field 'exclude_values'. :param workflow: Workflow object being processed. :return: HTTP response """ # Get the payload from the session, and if not, use the given one payload = SessionPayload(request.session) if payload is None: # Something is wrong with this execution. Return to the action table. messages.error(request, _('Incorrect item filter invocation.')) return redirect('action:index') # Get the information from the payload try: action = workflow.actions.get(pk=payload['action_id']) item_column = workflow.columns.get(pk=payload['item_column']) except Exception: # Something is wrong with this execution. Return to the action table. messages.error(request, _('Incorrect item filter invocation.')) return redirect('action:index') form = forms.ValueExcludeForm(request.POST or None, action=action, column_name=item_column.name, form_info=payload) context = { 'form': form, 'action': action, 'button_label': payload['button_label'], 'valuerange': range(payload['valuerange']), 'step': payload['step'], 'prev_step': payload['prev_url'] } # The post is correct if request.method == 'POST' and form.is_valid(): # Updating the payload in the session return redirect(payload['post_url']) return render(request, 'action/item_filter.html', context)
def action_index( request: http.HttpRequest, wid: Optional[int] = None, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Show all the actions attached to the workflow. :param request: HTTP Request :param wid: Primary key of the workflow object to use :param workflow: Workflow for the session. :return: HTTP response """ del wid # Reset object to carry action info throughout dialogs SessionPayload.flush(request.session) return render( request, 'action/index.html', { 'workflow': workflow, 'table': services.ActionTable(workflow.actions.all(), orderable=False) })
def process_run_post( self, request: http.HttpRequest, action: models.Action, payload: SessionPayload, ) -> http.HttpResponse: """Process the VALID POST request.""" if payload.get('confirm_items'): # Add information to the session object to execute the next pages payload['button_label'] = ugettext('Send') payload['valuerange'] = 2 payload['step'] = 2 payload.store_in_session(request.session) return redirect('action:item_filter') # Go straight to the final step. return self.process_run_request_done(request, workflow=action.workflow, payload=payload, action=action)
def process_run_post( self, request: http.HttpRequest, action: models.Action, payload: SessionPayload, ) -> http.HttpResponse: """Process the VALID POST request.""" if payload.get('confirm_items'): # Create a dictionary in the session to carry over all the # information to execute the next pages payload['button_label'] = ugettext('Send') payload['valuerange'] = 2 payload['step'] = 2 continue_url = 'action:item_filter' else: continue_url = 'action:run_done' payload.store_in_session(request.session) # Check for the CANVAS token and proceed to the continue_url return _canvas_get_or_set_oauth_token(request, payload['target_url'], continue_url)
def index( request: http.HttpRequest, workflow: Optional[models.Workflow] = None, ) -> http.HttpResponse: """Render the list of actions attached to a workflow. :param request: Request object :param workflow: Workflow currently used :return: HTTP response with the table. """ # Reset object to carry action info throughout dialogs SessionPayload.flush(request.session) return render( request, 'scheduler/index.html', { 'table': services.ScheduleActionTable( models.ScheduledOperation.objects.filter(workflow=workflow.id), orderable=False), 'workflow': workflow })
def test_send_with_incorrect_email(self): """Test the do_import_action functionality.""" user = get_user_model().objects.get(email='*****@*****.**') wflow = models.Workflow.objects.get(name=self.workflow_name) email_column = wflow.columns.get(name='email') action = wflow.actions.first() # POST a send operation with the wrong email resp = self.get_response( 'action:run', url_params={'pk': action.id}, method='POST', req_params={ 'item_column': email_column.pk, 'subject': 'message subject'}) payload = SessionPayload.get_session_payload(self.last_request) self.assertTrue(status.is_success(resp.status_code)) self.assertTrue( 'Incorrect email address ' in str(resp.content)) self.assertTrue( 'incorrectemail.com' in str(resp.content))
def process_run_request_done( self, request: http.HttpRequest, workflow: models.Action, payload: SessionPayload, action: Optional[models.Action] = None, ) -> http.HttpResponse: """Finish processing the valid POST request. Get the action and redirect to the action_done page to prompt the download of the zip file. """ # Get the information from the payload if not action: action = workflow.actions.filter(pk=payload['action_id']).first() if not action: return redirect('home') self._create_log_event(request.user, action, payload.get_store()) # Successful processing. return render(request, 'action/action_zip_done.html', {})
def finish( self, request: http.HttpRequest, payload: SessionPayload, schedule_item: models.ScheduledOperation = None, ) -> Optional[http.HttpResponse]: """Finalize the creation of a scheduled operation. All required data is passed through the payload. :param request: Request object received :param schedule_item: ScheduledOperation item being processed. If None, it has to be extracted from the information in the payload. :param payload: Dictionary with all the required data coming from previous requests. :return: Http Response """ s_item_id = payload.pop('schedule_id', None) schedule_item = None if s_item_id: # Get the item being processed if not schedule_item: schedule_item = models.ScheduledOperation.objects.filter( id=s_item_id).first() if not schedule_item: messages.error(request, _('Incorrect request for operation scheduling')) return redirect('action:index') else: action = models.Action.objects.get(pk=payload.pop('action_id')) payload['workflow'] = action.workflow payload['action'] = action # Remove some parameters from the payload for key in [ 'button_label', 'valuerange', 'step', 'prev_url', 'post_url', 'confirm_items', 'action_id', 'page_title', ]: payload.pop(key, None) try: schedule_item = self.create_or_update(request.user, payload.get_store(), schedule_item) except Exception as exc: messages.error( request, str(_('Unable to create scheduled operation: {0}')).format( str(exc))) return redirect('action:index') schedule_item.log(models.Log.SCHEDULE_EDIT) # Reset object to carry action info throughout dialogs SessionPayload.flush(request.session) # Successful processing. tdelta = create_timedelta_string(schedule_item.execute, schedule_item.frequency, schedule_item.execute_until) return render(request, 'scheduler/schedule_done.html', { 'tdelta': tdelta, 's_item': schedule_item })