def action_index( request: HttpRequest, wid: Optional[int] = None, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Show all the actions attached to the workflow. :param request: HTTP Request :param pk: Primary key of the workflow object to use :return: HTTP response """ # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() return render( request, 'action/index.html', { 'workflow': workflow, 'table': ActionTable(workflow.actions.all(), orderable=False), }, )
def run_email_done( request: HttpRequest, action_info: Optional[EmailPayload] = None, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Create the log object, queue the operation request and render done. :param request: HTTP request (GET) :param action_info: Dictionary containing all the required parameters. If empty, the dictionary is taken from the session. :return: HTTP response """ # Get the payload from the session if not given action_info = get_or_set_action_info(request.session, EmailPayload, action_info=action_info) if action_info is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect email action invocation.')) return redirect('action:index') # Get the information from the payload action = workflow.actions.filter(pk=action_info['action_id']).first() if not action: return redirect('home') # Log the event log_item = Log.objects.register( request.user, Log.SCHEDULE_EMAIL_EXECUTE, action.workflow, { 'action': action.name, 'action_id': action.id, 'from_email': request.user.email, 'subject': action_info['subject'], 'cc_email': action_info['cc_email'], 'bcc_email': action_info['bcc_email'], 'send_confirmation': action_info['send_confirmation'], 'track_read': action_info['track_read'], 'exported_workflow': action_info['export_wf'], 'exclude_values': action_info['exclude_values'], 'item_column': action_info['item_column'], 'status': 'Preparing to execute', }) # Update the last_execution_log action.last_executed_log = log_item action.save() # Send the emails! run_task.delay(request.user.id, log_item.id, action_info.get_store()) # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() # Successful processing. return render(request, 'action/action_done.html', { 'log_id': log_item.id, 'download': action_info['export_wf'] })
def zip_action( req: HttpRequest, pk: int, workflow: Optional[Workflow] = None, action: Optional[Action] = None, ) -> HttpResponse: """Request data to create a zip file. Form asking for participant column, user file name column, file suffix, if it is a ZIP for Moodle and confirm users step. :param req: HTTP request (GET) :param pk: Action key :return: HTTP response """ # Get the payload from the session, and if not, use the given one action_info = get_or_set_action_info(req.session, ZipPayload, initial_values={ 'action_id': action.id, 'prev_url': reverse('action:zip_action', kwargs={'pk': action.id}), 'post_url': reverse('action:zip_done'), }) # Create the form to ask for the email subject and other information form = ZipActionForm( req.POST or None, column_names=[col.name for col in workflow.columns.all()], action=action, form_info=action_info, ) if req.method == 'POST' and form.is_valid(): if action_info['confirm_items']: # Add information to the session object to execute the next pages action_info['button_label'] = ugettext('Create ZIP') action_info['valuerange'] = 2 action_info['step'] = 2 set_action_payload(req.session, action_info.get_store()) return redirect('action:item_filter') # Go straight to the final step. return run_zip_done(req, action_info=action_info, workflow=workflow) # Render the form return render( req, 'action/action_zip_step1.html', { 'action': action, 'num_msgs': action.get_rows_selected(), 'form': form, 'valuerange': range(2) })
def run_json_list_action( req: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Request data to send JSON objects. Form asking for token and export wf :param req: HTTP request (GET) :param workflow: workflow being processed :param action: Action begin run :return: HTTP response """ # Get the payload from the session, and if not, use the given one action_info = JSONListPayload({'action_id': action.id}) # Create the form to ask for the email subject and other information form = JSONListActionForm(req.POST or None, form_info=action_info) if req.method == 'POST' and form.is_valid(): # Log the event log_item = Log.objects.register( req.user, Log.SCHEDULE_JSON_EXECUTE, action.workflow, { 'action': action.name, 'action_id': action.id, 'exported_workflow': action_info['export_wf'], 'status': 'Preparing to execute', 'target_url': action.target_url }) # Update the last_execution_log action.last_executed_log = log_item action.save() # Send the objects run_task.delay(req.user.id, log_item.id, action_info.get_store()) # Reset object to carry action info throughout dialogs set_action_payload(req.session) req.session.save() # Successful processing. return render(req, 'action/action_done.html', { 'log_id': log_item.id, 'download': action_info['export_wf'] }) # Render the form return render( req, 'action/request_json_list_data.html', { 'action': action, 'num_msgs': action.get_rows_selected(), 'form': form, 'valuerange': range(2), 'rows_all_false': action.get_row_all_false_count() })
def run_email_action( req: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Request data to send emails. Form asking for subject line, email column, etc. :param req: HTTP request (GET) :param workflow: workflow being processed :param action: Action being run :return: HTTP response """ # Get the payload from the session, and if not, use the given one action_info = get_or_set_action_info(req.session, EmailPayload, initial_values={ 'action_id': action.id, 'prev_url': reverse('action:run', kwargs={'pk': action.id}), 'post_url': reverse('action:email_done') }) # Create the form to ask for the email subject and other information form = EmailActionForm(req.POST or None, column_names=[ col.name for col in workflow.columns.filter(is_key=True) ], action=action, form_info=action_info) # Request is a POST and is valid if req.method == 'POST' and form.is_valid(): if action_info['confirm_items']: # Add information to the session object to execute the next pages action_info['button_label'] = ugettext('Send') action_info['valuerange'] = 2 action_info['step'] = 2 set_action_payload(req.session, action_info.get_store()) return redirect('action:item_filter') # Go straight to the final step. return run_email_done(req, action_info=action_info, workflow=workflow) # Render the form return render( req, 'action/request_email_data.html', { 'action': action, 'num_msgs': action.get_rows_selected(), 'form': form, 'valuerange': range(2) })
def save_json_schedule( request: HttpRequest, action: Action, schedule_item: ScheduledOperation, op_payload: Dict, ) -> HttpResponse: """Create and edit scheduled json actions. :param request: Http request being processed :param action: Action item related to the schedule :param schedule_item: Schedule item or None if it is new :param op_payload: dictionary to carry over the request to the next step :return: Http response with the rendered page """ # Create the form to ask for the email subject and other information form = JSONScheduleForm( form_data=request.POST or None, action=action, instance=schedule_item, columns=action.workflow.columns.filter(is_key=True), form_info=op_payload) # Processing a valid POST request if request.method == 'POST' and form.is_valid(): if op_payload['confirm_items']: # Create a dictionary in the session to carry over all the # information to execute the next steps op_payload['button_label'] = ugettext('Schedule') op_payload['valuerange'] = 2 op_payload['step'] = 2 set_action_payload(request.session, op_payload) return redirect('action:item_filter') # Go straight to the final step return finish_scheduling(request, schedule_item, op_payload) # Render the form return render( request, 'scheduler/edit.html', { 'action': action, 'form': form, 'now': datetime.now(pytz.timezone(settings.TIME_ZONE)), 'valuerange': range(2), }, )
def run_action_item_filter( request: HttpRequest, workflow: Optional[Workflow] = None, ) -> 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'. :return: HTTP response """ # Get the payload from the session, and if not, use the given one action_info = get_action_payload(request.session) if not action_info: # 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 action = Action.objects.get(id=action_info['action_id']) form = ValueExcludeForm(request.POST or None, action=action, column_name=action_info['item_column'], form_info=action_info) context = { 'form': form, 'action': action, 'button_label': action_info['button_label'], 'valuerange': range(action_info['valuerange']), 'step': action_info['step'], 'prev_step': action_info['prev_url'] } # The post is correct if request.method == 'POST' and form.is_valid(): # Updating the payload in the session set_action_payload(request.session, action_info) return redirect(action_info['post_url']) return render(request, 'action/item_filter.html', context)
def action_zip_export( request: HttpRequest, workflow: Optional[Workflow] = None, ) -> 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 :return: Response (download) """ # Get the payload from the session if not given action_info = get_or_set_action_info(request.session, ZipPayload) if not action_info: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect ZIP action invocation.')) return redirect('action:index') # Get the information from the payload action = workflow.actions.filter(pk=action_info['action_id']).first() if not action: return redirect('home') # Create the file name template if action_info['zip_for_moodle']: file_name_template = ('{user_fname}_{part_id}_assignsubmission_file_') else: if action_info['user_fname_column']: file_name_template = '{part_id}_{user_fname}_' else: file_name_template = '{part_id}' if action_info['file_suffix']: file_name_template += action_info['file_suffix'] else: file_name_template += 'feedback.html' # Create the ZIP with the eval data tuples and return it for download sbuf = create_zip( create_eval_data_tuple( action, action_info['item_column'], action_info['exclude_values'], action_info['user_fname_column'], ), action_info['zip_for_moodle'], file_name_template) # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() return create_response(sbuf)
def run_json_done( request: HttpRequest, action_info: Optional[JSONPayload] = None, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Create the log object, queue the operation request, render DONE page. :param request: HTTP request (GET) :param action_info: Dictionary containing all the required parameters. If empty, the dictionary is taken from the session. :return: HTTP response """ # Get the payload from the session if not given action_info = get_or_set_action_info(request.session, JSONPayload, action_info=action_info) if action_info is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect JSON action invocation.')) return redirect('action:index') # Get the information from the payload action = workflow.actions.filter(pk=action_info['action_id']).first() if not action: return redirect('home') # Log the event log_item = action.log(request.user, Log.ACTION_RUN_JSON, exclude_values=action_info['exclude_values'], item_column=action_info['item_column'], exported_workflow=action_info['export_wf']) # Send the objects run_task.delay(request.user.id, log_item.id, action_info.get_store()) # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() # Successful processing. return render(request, 'action/action_done.html', { 'log_id': log_item.id, 'download': action_info['export_wf'] })
def run_zip_done( request: HttpRequest, action_info: Optional[ZipPayload] = None, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Create the zip object, send it for download and render the DONE page. :param request: HTTP request (GET) :param action_info: Dictionary containing all the required parameters. If empty, the dictionary is taken from the session. :return: HTTP response """ # Get the payload from the session if not given action_info = get_or_set_action_info(request.session, ZipPayload, action_info=action_info) if action_info is None: # Something is wrong with this execution. Return to action table. messages.error(request, _('Incorrect ZIP action invocation.')) return redirect('action:index') # Get the information from the payload action = workflow.actions.filter(pk=action_info['action_id']).first() if not action: return redirect('home') # Log the event log_item = Log.objects.register( request.user, Log.DOWNLOAD_ZIP_ACTION, action.workflow, { 'action': action.name, 'action_id': action.id, 'user_fname_column': action_info['user_fname_column'], 'item_column': action_info['item_column'], 'file_suffix': action_info['file_suffix'], 'zip_for_moodle': action_info['zip_for_moodle'], 'exclude_values': action_info['exclude_values'], }) # Store the payload in the session for the download part set_action_payload(request.session, action_info.get_store()) # Successful processing. return render(request, 'action/action_zip_done.html', {})
def index( request: HttpRequest, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Render the list of actions attached to a workflow. :param request: Request object :return: HTTP response with the table. """ # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() # Get the actions s_items = ScheduledAction.objects.filter(action__workflow=workflow.id) return render( request, 'scheduler/index.html', { 'table': ScheduleActionTable(s_items, orderable=False), 'workflow': workflow, }, )
def finish_scheduling(request: HttpRequest, schedule_item: ScheduledAction = None, payload: Dict = None): """Finalize the creation of a scheduled action. All required data is passed through the payload. :param request: Request object received :param schedule_item: ScheduledAction 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: """ # Get the payload from the session if not given if payload is None: payload = request.session.get(action_session_dictionary) # 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 action scheduling invocation.')) return redirect('action:index') # Get the scheduled item if needed s_item_id = payload.pop('schedule_id', None) action = Action.objects.get(pk=payload.pop('action_id')) column_name = payload.pop('item_column', None) column = None if column_name: column = action.workflow.columns.get(name=column_name) # Clean up some parameters from the payload payload = { key: payload[key] for key in payload if key not in [ 'button_label', 'valuerange', 'step', 'prev_url', 'post_url', 'confirm_items' ] } # Create the payload to record the event in the log log_payload = payload.copy() if not s_item_id: schedule_item = ScheduledAction( user=request.user, action=action, name=payload.pop('name'), description_text=payload.pop('description_text'), item_column=column, execute=parse_datetime(payload.pop('execute')), exclude_values=payload.pop('exclude_values', [])) else: # Get the item being processed if not schedule_item: schedule_item = ScheduledAction.objects.filter( id=s_item_id).first() if not schedule_item: messages.error(None, _('Incorrect request in action scheduling')) return redirect('action:index') schedule_item.name = payload.pop('name') schedule_item.description_text = payload.pop('description_text') schedule_item.item_column = column schedule_item.execute = parse_datetime(payload.pop('execute')) schedule_item.exclude_values = payload.pop('exclude_values', []) # Check for exclude schedule_item.status = ScheduledAction.STATUS_PENDING schedule_item.payload = payload schedule_item.save() # Create the payload to record the event in the log if schedule_item.action.action_type == Action.personalized_text: log_type = Log.SCHEDULE_EMAIL_EDIT elif schedule_item.action.action_type == Action.send_list: log_type = Log.SCHEDULE_SEND_LIST_EDIT elif schedule_item.action.action_type == Action.personalized_json: ivalue = None if schedule_item.item_column: ivalue = schedule_item.item_column.name log_type = Log.SCHEDULE_JSON_EDIT elif schedule_item.action.action_type == Action.send_list_json: log_type = Log.SCHEDULE_JSON_LIST_EDIT elif schedule_item.action.action_type == Action.personalized_canvas_email: ivalue = None if schedule_item.item_column: ivalue = schedule_item.item_column.name log_type = Log.SCHEDULE_CANVAS_EMAIL_EDIT else: messages.error(request, _('This type of actions cannot be scheduled')) return redirect('action:index') # Create the log Log.objects.register(request.user, log_type, schedule_item.action.workflow, log_payload) # Reset object to carry action info throughout dialogs set_action_payload(request.session) request.session.save() # Successful processing. return render( request, 'scheduler/schedule_done.html', { 'tdelta': create_timedelta_string(schedule_item.execute), 's_item': schedule_item })
def edit( request: HttpRequest, pk: int, workflow: Optional[Workflow] = None, ) -> HttpResponse: """Edit an existing scheduled email action. :param request: HTTP request :param pk: primary key of the action :return: HTTP response """ # Distinguish between creating a new element or editing an existing one is_a_new_item = request.path.endswith(reverse( 'scheduler:create', kwargs={'pk': pk})) if is_a_new_item: action = workflow.actions.filter(pk=pk).filter( Q(workflow__user=request.user) | Q(workflow__shared=request.user), ).first() if not action: return redirect('home') s_item = None else: # Get the scheduled action from the parameter in the URL s_item = ScheduledAction.objects.filter(pk=pk).first() if not s_item: return redirect('home') action = s_item.action # Get the payload from the session, and if not, use the given one op_payload = request.session.get(action_session_dictionary) if not op_payload: op_payload = { 'action_id': action.id, 'prev_url': reverse( 'scheduler:create', kwargs={'pk': action.id}), 'post_url': reverse( 'scheduler:finish_scheduling'), } if s_item: op_payload.update(s_item.payload) set_action_payload(request.session, op_payload) request.session.save() # Verify that celery is running! if not celery_is_up(): messages.error( request, _( 'Unable to schedule actions due to a misconfiguration. ' + 'Ask your system administrator to enable queueing.')) return redirect(reverse('action:index')) if s_item: op_payload['schedule_id'] = s_item.id if action.action_type == Action.personalized_text: return save_email_schedule(request, action, s_item, op_payload) elif action.action_type == Action.send_list: return save_send_list_schedule(request, action, s_item, op_payload) elif action.action_type == Action.send_list_json: return save_send_list_json_schedule( request, action, s_item, op_payload) elif action.action_type == Action.personalized_canvas_email: return save_canvas_email_schedule( request, action, s_item, op_payload) elif action.action_type == Action.personalized_json: return save_json_schedule(request, action, s_item, op_payload) # Action type not found, so return to the main table view messages.error( request, _('This action does not support scheduling')) return redirect('scheduler:index')
def run_canvas_email_action( req: WSGIRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Request data to send JSON objects. Form asking for subject, item column (contains ids to select unique users), confirm items (add extra step to drop items), export workflow and target_rul (if needed). :param req: HTTP request (GET) :param workflow: workflow being processed :param action: Action begin run :return: HTTP response """ # Get the payload from the session, and if not, use the given one action_info = get_or_set_action_info( req.session, CanvasEmailPayload, initial_values={ 'action_id': action.id, 'prev_url': reverse('action:run', kwargs={'pk': action.id}), 'post_url': reverse('action:canvas_email_done'), }, ) # Create the form to ask for the email subject and other information form = CanvasEmailActionForm( req.POST or None, column_names=[ col.name for col in workflow.columns.filter(is_key=True) ], action=action, form_info=action_info) if req.method == 'POST' and form.is_valid(): # Request is a POST and is valid if action_info['confirm_items']: # Create a dictionary in the session to carry over all the # information to execute the next pages action_info['button_label'] = ugettext('Send') action_info['valuerange'] = 2 action_info['step'] = 2 set_action_payload(req.session, action_info.get_store()) continue_url = 'action:item_filter' else: continue_url = 'action:canvas_email_done' # Check for the CANVAS token and proceed to the continue_url return canvas_get_or_set_oauth_token(req, action_info['target_url'], continue_url) # Render the form return render( req, 'action/request_canvas_email_data.html', { 'action': action, 'num_msgs': action.get_rows_selected(), 'form': form, 'valuerange': range(2), 'rows_all_false': action.get_row_all_false_count() })
def _edit_scheduled_action_run( request: HttpRequest, s_item: Optional[ScheduledOperation] = None, action: Optional[Action] = None, ) -> HttpResponse: """Edit a scheduled operation (either new or existing) :param request: HTTP request :param s_item: Existing schedule item being processed :param schedule_type: Schedule operation type, or if empty, it is contained in the scheduled_item :param workflow: Corresponding workflow for the schedule operation type, or if empty, it is contained in the scheduled_item :param action: Corresponding action for the schedule operation type, or if empty, it is contained in the scheduled_item :return: HTTP response """ if s_item: action = s_item.action exclude_values = s_item.exclude_values else: exclude_values = [] # Get the payload from the session, and if not, use the given one op_payload = request.session.get(action_session_dictionary) if not op_payload: op_payload = { 'action_id': action.id, 'prev_url': reverse('scheduler:create_action_run', kwargs={'pk': action.id}), 'post_url': reverse('scheduler:finish_scheduling'), 'exclude_values': exclude_values } if s_item: op_payload.update(s_item.payload) set_action_payload(request.session, op_payload) request.session.save() if s_item: op_payload['schedule_id'] = s_item.id if action.action_type == Action.PERSONALIZED_TEXT: return save_email_schedule(request, action, s_item, op_payload) elif action.action_type == Action.SEND_LIST: return save_send_list_schedule(request, action, s_item, op_payload) elif action.action_type == Action.SEND_LIST_JSON: return save_send_list_json_schedule(request, action, s_item, op_payload) elif action.action_type == Action.PERSONALIZED_CANVAS_EMAIL: return save_canvas_email_schedule(request, action, s_item, op_payload) elif action.action_type == Action.PERSONALIZED_JSON: return save_json_schedule(request, action, s_item, op_payload) # Action type not found, so return to the main table view messages.error(request, _('This action does not support scheduling')) return redirect('scheduler:index')