def do_clone_action( action: Action, new_workflow: Workflow = None, new_name: str = None, ): """Clone an action. Function that given an action clones it and changes workflow and name :param action: Object to clone :param new_workflow: New workflow object to point :param new_name: New name :return: Cloned object """ if new_name is None: new_name = action.name if new_workflow is None: new_workflow = action.workflow new_action = Action( name=new_name, description_text=action.description_text, workflow=new_workflow, last_executed_log=None, action_type=action.action_type, serve_enabled=action.serve_enabled, active_from=action.active_from, active_to=action.active_to, rows_all_false=copy.deepcopy(action.rows_all_false), text_content=action.text_content, target_url=action.target_url, shuffle=action.shuffle, ) new_action.save() try: # Clone the column/condition pairs field. for acc_tuple in action.column_condition_pair.all(): cname = acc_tuple.condition.name if acc_tuple.condition else None ActionColumnConditionTuple.objects.get_or_create( action=new_action, column=new_action.workflow.columns.get( name=acc_tuple.column.name), condition=new_action.conditions.filter(name=cname).first(), ) # Clone the conditions for condition in action.conditions.all(): do_clone_condition(condition, new_action) # Update new_action.save() except Exception as exc: new_action.delete() raise exc return new_action
def create(self, validated_data, **kwargs): """Create the action. :param validated_data: Validated data :param kwargs: Extra material :return: Create the action in the DB """ action_obj = None try: action_type = validated_data.get('action_type') if not action_type: if validated_data['is_out']: action_type = Action.PERSONALIZED_TEXT else: action_type = Action.SURVEY action_obj = Action( workflow=self.context['workflow'], name=validated_data['name'], description_text=validated_data['description_text'], action_type=action_type, serve_enabled=validated_data['serve_enabled'], active_from=validated_data['active_from'], active_to=validated_data['active_to'], text_content=validated_data.get( 'content', validated_data.get('text_content'), # Legacy ), target_url=validated_data.get('target_url', ''), shuffle=validated_data.get('shuffle', False), ) action_obj.save() # Load the conditions pointing to the action condition_data = ConditionSerializer( data=validated_data.get('conditions', []), many=True, context={'action': action_obj}) if condition_data.is_valid(): condition_data.save() else: raise Exception(_('Invalid condition data')) # Process the fields columns (legacy) and column_condition_pairs self.create_column_condition_pairs( validated_data, action_obj, self.context['workflow'].columns.all(), ) except Exception: if action_obj and action_obj.id: ActionColumnConditionTuple.objects.filter( action=action_obj, ).delete() action_obj.delete() raise return action_obj
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_send_list_action( req: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Request data to send a list. Form asking for subject line and target email. :param req: HTTP request (GET) :param workflow: workflow being processed :param action: Action being run :return: HTTP response """ # Create the payload object with the required information action_info = SendListPayload({'action_id': action.id}) # Create the form to ask for the email subject and other information form = SendListActionForm(req.POST or None, action=action, form_info=action_info) # Request is a POST and is valid if req.method == 'POST' and form.is_valid(): # Log the event log_item = Log.objects.register( req.user, Log.SCHEDULE_EMAIL_EXECUTE, action.workflow, { 'action': action.name, 'action_id': action.id, 'from_email': req.user.email, 'recipient_email': action_info['email_to'], 'subject': action_info['subject'], 'cc_email': action_info['cc_email'], 'bcc_email': action_info['bcc_email'], 'status': 'Preparing to execute', }) # Update the last_execution_log action.last_executed_log = log_item action.save() # Send the emails! run_task.delay(req.user.id, log_item.id, action_info.get_store()) # 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_send_list_data.html', { 'action': action, 'form': form, 'valuerange': range(2) })
def process_edit_request( self, request: http.HttpRequest, workflow: models.Workflow, action: models.Action ) -> http.HttpResponse: """Process the action edit request.""" form = self.edit_form_class(request.POST or None, instance=action) form_filter = condition_forms.FilterForm( request.POST or None, instance=action.get_filter(), action=action ) # Processing the request after receiving the text from the editor if ( request.method == 'POST' and form.is_valid() and form_filter.is_valid() ): # Log the event action.log(request.user, models.Log.ACTION_UPDATE) # Text is good. Update the content of the action action.set_text_content(form.cleaned_data['text_content']) if 'target_url' in form.cleaned_data: action.target_url = form.cleaned_data['target_url'] action.save(update_fields=['target_url']) if request.POST['Submit'] == 'Submit': return redirect(request.get_full_path()) return redirect('action:index') # This is a GET request or a faulty POST request context = self.get_render_context(action, form, form_filter) try: self.extend_edit_context(workflow, action, context) except Exception as exc: messages.error(request, str(exc)) return redirect(reverse('action:index')) # Return the same form in the same page return render(request, self.edit_template, context=context)
def update_row_values( request: http.HttpRequest, action: models.Action, row_data: Tuple[List, List, str, Any], ): """Serve a request for action in. Function that given a request, and an action IN, it performs the lookup and data input of values. :param request: HTTP request :param action: Action In :param row_data: Tuple containing keys, values, where_field, where_value. Keys and values are the values in the row. Where field, and where value is pair find the given row :return: """ keys, values, where_field, where_value = row_data # Execute the query sql.update_row( action.workflow.get_data_frame_table_name(), keys, values, filter_dict={where_field: where_value}, ) # Recompute all the values of the conditions in each of the actions # TODO: Explore how to do this asynchronously (or lazy) for act in action.workflow.actions.all(): act.update_n_rows_selected() # Log the event and update its content in the action log_item = action.log( request.user, models.Log.ACTION_SURVEY_INPUT, action_id=action.id, action=action.name, new_values=json.dumps(dict(zip(keys, values)))) # Modify the time of execution for the action action.last_executed_log = log_item action.save(update_fields=['last_executed_log'])
def serve_survey_row( request: HttpRequest, action: Action, user_attribute_name: str, ) -> HttpResponse: """Serve a request for action in. Function that given a request, and an action IN, it performs the lookup and data input of values. :param request: HTTP request :param action: Action In :param user_attribute_name: The column name used to check for email :return: """ # Get the attribute value depending if the user is managing the workflow # User is instructor, and either owns the workflow or is allowed to access # it as shared manager = has_access(request.user, action.workflow) user_attribute_value = None if manager: user_attribute_value = request.GET.get('uatv') if not user_attribute_value: user_attribute_value = request.user.email # Get the dictionary containing column names, attributes and condition # valuations: context = get_action_evaluation_context( action, get_row_values( action, (user_attribute_name, user_attribute_value), ), ) if not context: # If the data has not been found, flag if not manager: return ontask_handler404(request, None) messages.error(request, _('Data not found in the table')) return redirect(reverse('action:run', kwargs={'pk': action.id})) # Get the active columns attached to the action colcon_items = extract_survey_questions(action, request.user) # Bind the form with the existing data form = EnterActionIn( request.POST or None, tuples=colcon_items, context=context, values=[context[colcon.column.name] for colcon in colcon_items], show_key=manager) keep_processing = (request.method == 'POST' and form.is_valid() and not request.POST.get('lti_version')) if keep_processing: # Update the content in the DB row_keys, row_values = survey_update_row_values( action, colcon_items, manager, form.cleaned_data, 'email', request.user.email, context) # Log the event and update its content in the action log_item = action.log(request.user, Log.ACTION_SURVEY_INPUT, new_values=json.dumps( dict(zip(row_keys, row_values)))) # Modify the time of execution for the action action.last_executed_log = log_item action.save() # If not instructor, just thank the user! if not manager: return render(request, 'thanks.html', {}) # Back to running the action return redirect(reverse('action:run', kwargs={'pk': action.id})) return render( request, 'action/run_survey_row.html', { 'form': form, 'action': action, 'cancel_url': reverse( 'action:run', kwargs={'pk': action.id}, ) if manager else None, }, )
def edit_action_rubric( request: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Edit action out. :param request: Request object :param workflow: The workflow with the action :param action: Action :return: HTML response """ # Create the form form = EditActionOutForm(request.POST or None, instance=action) form_filter = FilterForm(request.POST or None, instance=action.get_filter(), action=action) # Processing the request after receiving the text from the editor if request.method == 'POST' and form.is_valid() and form_filter.is_valid(): # Get content text_content = form.cleaned_data.get('text_content') # Render the content as a template and catch potential problems. if text_renders_correctly(text_content, action, form): # Log the event action.log(request.user, Log.ACTION_UPDATE) # Text is good. Update the content of the action action.set_text_content(text_content) action.save() if request.POST['Submit'] == 'Submit': return redirect(request.get_full_path()) return redirect('action:index') # Get the filter or None filter_condition = action.get_filter() criteria = action.column_condition_pair.all() if not _verify_criteria_loas(criteria): messages.error(request, _('Inconsistent LOA in rubric criteria')) return redirect(reverse('action:index')) columns_to_insert_qs = action.workflow.columns.exclude( column_condition_pair__action=action, ).exclude( is_key=True, ).distinct().order_by('position') if criteria: columns_to_insert = [ column for column in columns_to_insert_qs if set(column.categories) == set(criteria[0].column.categories) ] else: columns_to_insert = [ column for column in columns_to_insert_qs if column.categories ] # This is a GET request or a faulty POST request context = { 'form': form, 'form_filter': form_filter, 'filter_condition': filter_condition, 'action': action, 'load_summernote': Action.LOAD_SUMMERNOTE[action.action_type], 'query_builder_ops': action.workflow.get_query_builder_ops_as_str(), 'attribute_names': [attr for attr in list(action.workflow.attributes.keys())], 'columns': action.workflow.columns.all(), 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'has_data': action.workflow.has_table(), 'is_send_list': (action.action_type == Action.SEND_LIST or action.action_type == Action.SEND_LIST_JSON), 'is_personalized_text': action.action_type == Action.PERSONALIZED_TEXT, 'is_rubric_cell': action.action_type == Action.RUBRIC_TEXT, 'rows_all_false': action.get_row_all_false_count(), 'total_rows': action.workflow.nrows, 'all_false_conditions': False, 'columns_to_insert': columns_to_insert, 'vis_scripts': PlotlyHandler.get_engine_scripts() } # Get additional context to render the page depending on the action type if criteria: _create_rubric_table(request, action, criteria, context) # Return the same form in the same page return render(request, 'action/edit_rubric.html', context=context)
def edit_action_out( request: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Edit action out. :param request: Request object :param workflow: The workflow with the action :param action: Action :return: HTML response """ # Create the form form = EditActionOutForm(request.POST or None, instance=action) form_filter = FilterForm(request.POST or None, instance=action.get_filter(), action=action) # Processing the request after receiving the text from the editor if request.method == 'POST' and form.is_valid() and form_filter.is_valid(): # Get content text_content = form.cleaned_data.get('text_content') # Render the content as a template and catch potential problems. if _text_renders_correctly(text_content, action, form): # Log the event Log.objects.register( request.user, Log.ACTION_UPDATE, action.workflow, { 'id': action.id, 'name': action.name, 'workflow_id': workflow.id, 'workflow_name': workflow.name, 'content': text_content }) # Text is good. Update the content of the action action.set_text_content(text_content) # If it is a JSON action, store the target_url if (action.action_type == Action.personalized_json or action.action_type == Action.send_list_json): # Update the target_url field action.target_url = form.cleaned_data['target_url'] action.save() if request.POST['Submit'] == 'Submit': return redirect(request.get_full_path()) return redirect('action:index') # This is a GET request or a faulty POST request # Get the filter or None filter_condition = action.get_filter() # Context to render the form context = { 'filter_condition': filter_condition, 'action': action, 'load_summernote': (action.action_type == Action.personalized_text or action.action_type == Action.send_list), 'conditions': action.conditions.filter(is_filter=False), 'other_conditions': Condition.objects.filter( action__workflow=workflow, is_filter=False, ).exclude(action=action), 'query_builder_ops': workflow.get_query_builder_ops_as_str(), 'attribute_names': [attr for attr in list(workflow.attributes.keys())], 'columns': workflow.columns.all(), 'stat_columns': workflow.columns.filter(is_key=False), 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'has_data': action.workflow.has_table(), 'is_send_list': (action.action_type == Action.send_list or action.action_type == Action.send_list_json), 'all_false_conditions': any(cond.n_rows_selected == 0 for cond in action.conditions.all()), 'rows_all_false': action.get_row_all_false_count(), 'total_rows': workflow.nrows, 'form': form, 'form_filter': form_filter, 'vis_scripts': PlotlyHandler.get_engine_scripts(), } # Return the same form in the same page return render(request, 'action/edit_out.html', context=context)