def evaluate_action( action: Action, extra_string: str = None, column_name: str = None, exclude_values: List[str] = None, ) -> List[List]: """Evaluate the content in an action based on the values in the columns. Given an action object and an optional string: 1) Access the attached workflow 2) Obtain the data from the appropriate data frame 3) Loop over each data row and 3.1) Evaluate the conditions with respect to the values in the row 3.2) Create a context with the result of evaluating the conditions, attributes and column names to values 3.3) Run the template with the context 3.4) Run the optional string argument with the template and the context 3.5) Select the optional column_name 4) Return the resulting objects: List of (HTMLs body, extra string, column name value) or an error message :param action: Action object with pointers to conditions, filter, workflow, etc. :param extra_string: An extra string to process (something like the email subject line) with the same dictionary as the text in the action. :param column_name: Column from where to extract the special value ( typically the email address) and include it in the result. :param exclude_values: List of values in the column to exclude :return: list of lists resulting from the evaluation of the action. Each element in the list contains the HTML body, the extra string (if provided) and the column value. """ # Get the table data rows = get_rows( action.workflow.get_data_frame_table_name(), filter_formula=action.get_filter_formula()) list_of_renders = [] for row in rows: if exclude_values and str(row[column_name]) in exclude_values: # Skip the row with the col_name in exclude values continue # Step 4: Create the context with the attributes, the evaluation of the # conditions and the values of the columns. context = get_action_evaluation_context(action, row) # Append result list_of_renders.append( _render_tuple_result(action, context, extra_string, column_name), ) if settings.DEBUG: # Check that n_rows_selected is equal to rows.rowcount action_filter = action.get_filter() if action_filter and action_filter.n_rows_selected != rows.rowcount: raise ontask.OnTaskException('Inconsisten n_rows_selected') return list_of_renders
def _send_confirmation_message( user, action: models.Action, nmsgs: int, ) -> None: """Send the confirmation message. :param user: Destination email :param action: Action being considered :param nmsgs: Number of messages being sent :return: """ # Creating the context for the confirmation email now = datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)) cfilter = action.get_filter() context = { 'user': user, 'action': action, 'num_messages': nmsgs, 'email_sent_datetime': now, 'filter_present': cfilter is not None, 'num_rows': action.workflow.nrows, 'num_selected': action.get_rows_selected(), } # Create template and render with context try: html_content = Template( str(getattr(ontask_settings, 'NOTIFICATION_TEMPLATE')), ).render(Context(context)) text_content = strip_tags(html_content) except TemplateSyntaxError as exc: raise Exception( _('Syntax error in notification template ({0})').format( str(exc)), ) # Log the event context = { 'num_messages': nmsgs, 'email_sent_datetime': str(now), 'filter_present': cfilter is not None, 'num_rows': action.workflow.nrows, 'subject': str(ontask_settings.NOTIFICATION_SUBJECT), 'body': text_content, 'from_email': str(ontask_settings.NOTIFICATION_SENDER), 'to_email': [user.email] } action.log(user, models.Log.ACTION_EMAIL_NOTIFY, **context) # Send email out try: send_mail(str(ontask_settings.NOTIFICATION_SUBJECT), text_content, str(ontask_settings.NOTIFICATION_SENDER), [user.email], html_message=html_content) except Exception as exc: raise Exception( _('Error when sending the notification: {0}').format(str(exc)), )
def get_render_context( action: models.Action, form: Optional[Type[forms.ModelForm]] = None, form_filter: Optional[condition_forms.FilterForm] = None, ) -> Dict: """Get the initial context to render the response.""" filter_condition = action.get_filter() return { # Workflow elements 'attribute_names': [attr for attr in list(action.workflow.attributes.keys())], 'columns': action.workflow.columns.all(), 'has_data': action.workflow.has_table(), 'total_rows': action.workflow.nrows, # Action Elements 'action': action, 'form': form, 'form_filter': form_filter, 'filter_condition': filter_condition, 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'is_email_report': action.action_type == models.Action.EMAIL_REPORT, 'is_report': (action.action_type == models.Action.EMAIL_REPORT or action.action_type == models.Action.JSON_REPORT), 'is_personalized_text': (action.action_type == models.Action.PERSONALIZED_TEXT), 'is_rubric': action.action_type == models.Action.RUBRIC_TEXT, 'is_survey': action.action_type == models.Action.SURVEY, 'all_false_conditions': any(cond.n_rows_selected == 0 for cond in action.conditions.all()), 'rows_all_false': action.get_row_all_false_count(), # Page elements 'load_summernote': (action.action_type == models.Action.PERSONALIZED_TEXT or action.action_type == models.Action.EMAIL_REPORT or action.action_type == models.Action.RUBRIC_TEXT), 'query_builder_ops': action.workflow.get_query_builder_ops_as_str(), 'vis_scripts': PlotlyHandler.get_engine_scripts() }
def send_json( user, action: Action, log_item: Log, action_info: Mapping, ): """Send json objects to target URL. Sends the json objects evaluated per row to the URL in the action :param user: User object that executed the action :param action: Action from where to take the messages :param log_item: Log object to store results :return: Nothing """ # Evaluate the action string and obtain the list of list of JSON objects action_evals = evaluate_action( action, column_name=action_info['item_column'], exclude_values=action_info['exclude_values'], ) # Create the headers to use for all requests headers = { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Authorization': 'Bearer {0}'.format(action_info['token']), } # Create the context for the log events context = { 'user': user.id, 'action': action.id, } # Iterate over all json objects to create the strings and check for # correctness for json_string in action_evals: _send_and_log_json( user, action, json.loads(json_string[0]), headers, context, ) # Update data in the overall log item log_item.payload['objects_sent'] = len(action_evals) log_item.payload['filter_present'] = action.get_filter() is not None log_item.payload['datetime'] = str( datetime.datetime.now(pytz.timezone(settings.TIME_ZONE))) log_item.save()
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 save_condition_form( request: HttpRequest, form, action: models.Action, is_filter: Optional[bool] = False, ) -> JsonResponse: """Process the AJAX form POST to create and update conditions and filters. :param request: HTTP request :param form: Form being used to ask for the fields :param action: The action to which the condition is attached to :param is_filter: The condition is a filter :return: JSON response """ if is_filter and form.instance.id is None and action.get_filter(): # Should not happen. Go back to editing the action return JsonResponse({'html_redirect': ''}) is_new = form.instance.id is None # Update fields and save the condition condition = form.save(commit=False) condition.formula_text = None condition.action = action condition.is_filter = is_filter condition.save() condition.columns.set( action.workflow.columns.filter(name__in=formula.get_variables( condition.formula), )) # If the request has the 'action_content' field, update the action action_content = request.POST.get('action_content') if action_content: action.set_text_content(action_content) _propagate_changes(condition, form.changed_data, form.old_name, is_new) # Store the type of event to log if is_new: log_type = models.Log.CONDITION_CREATE else: log_type = models.Log.CONDITION_UPDATE condition.log(request.user, log_type) return JsonResponse({'html_redirect': ''})
def send_json_list( user, action: Action, log_item: Log, action_info: Mapping, ): """Send single json object to target URL. Sends a single json object to the URL in the action :param user: User object that executed the action :param action: Action from where to take the messages :param log_item: Log object to store results :param action_info: Object with the additional parameters :return: Nothing """ # Evaluate the action string and obtain the list of list of JSON objects action_text = evaluate_row_action_out( action, get_action_evaluation_context(action, {})) _send_and_log_json( user, action, json.loads(action_text), { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Authorization': 'Bearer {0}'.format(action_info['token']), }, {'user': user.id, 'action': action.id}) # Update data in the overall log item log_item.payload['filter_present'] = action.get_filter() is not None log_item.payload['datetime'] = str( datetime.datetime.now(pytz.timezone(settings.TIME_ZONE))) log_item.save()
def _create_row_preview_response( action: Action, idx: int, page_context: Dict, prelude: str = None, ): """Create the elements to render a sigle row preview. :param action: Action being previewed. :param idx: :param page_context: :return: page_context is modified to include the appropriate items """ # Get the total number of items filter_obj = action.get_filter() if filter_obj: n_items = filter_obj.n_rows_selected else: n_items = action.workflow.nrows # Set the correct values to the indeces prv, idx, nxt = _get_navigation_index(idx, n_items) row_values = get_row_values(action, idx) # Obtain the dictionary with the condition evaluation condition_evaluation = action_condition_evaluation(action, row_values) # Get the dictionary containing column names, attributes and condition # valuations: eval_context = get_action_evaluation_context(action, row_values, condition_evaluation) all_false = False if action.conditions.filter(is_filter=False).count(): # If there are conditions, check if they are all false all_false = all(not bool_val for __, bool_val in condition_evaluation.items()) # Evaluate the action content. show_values = '' incorrect_json = False if action.is_out: action_content = evaluate_row_action_out(action, eval_context) if action.action_type == Action.personalized_json: incorrect_json = not _check_json_is_correct(action_content) else: action_content = evaluate_row_action_in(action, eval_context) if action_content is None: action_content = _( 'Error while retrieving content for student {0}', ).format(idx) else: # Get the conditions used in the action content act_cond = action.get_used_conditions() # Get the variables/columns from the conditions act_vars = set().union( *[ cond.columns.all() for cond in action.conditions.filter(name__in=act_cond) ], ) # Sort the variables/columns by position and get the name show_values = ', '.join([ '{0} = {1}'.format(col.name, row_values[col.name]) for col in act_vars ], ) uses_plain_text = (action.action_type == Action.personalized_canvas_email or action.action_type == Action.personalized_json) if uses_plain_text: action_content = escape(action_content) if prelude: prelude = evaluate_row_action_out(action, eval_context, prelude) # Update the context page_context.update({ 'n_items': n_items, 'nxt': nxt, 'prv': prv, 'incorrect_json': incorrect_json, 'show_values': show_values, 'all_false': all_false, 'prelude': prelude, 'action_content': action_content, 'show_navigation': True }) return
def save_condition_form( request: HttpRequest, form, template_name: str, action: Action, is_filter: Optional[bool] = False, ) -> JsonResponse: """ Process the AJAX form to create and update conditions and filters. :param request: HTTP request :param form: Form being used to ask for the fields :param template_name: Template being used to render the form :param action: The action to which the condition is attached to :param is_filter: The condition is a filter :return: JSON response """ if request.method == 'POST' and form.is_valid(): if not form.has_changed(): return JsonResponse({'html_redirect': None}) if is_filter and form.instance.id is None and action.get_filter(): # Should not happen. Go back to editing the action return JsonResponse({'html_redirect': ''}) is_new = form.instance.id is None # Update fields and save the condition condition = form.save(commit=False) condition.formula_text = None condition.action = action condition.is_filter = is_filter condition.save() condition.columns.set(action.workflow.columns.filter( name__in=get_variables(condition.formula), )) # If the request has the 'action_content' field, update the action action_content = request.POST.get('action_content') if action_content: action.set_text_content(action_content) propagate_changes(condition, form.changed_data, form.old_name, is_new) # Store the type of event to log if is_new: log_type = Log.CONDITION_CREATE else: log_type = Log.CONDITION_UPDATE condition.log(request.user, log_type) return JsonResponse({'html_redirect': ''}) # GET request or invalid form return JsonResponse({ 'html_form': render_to_string( template_name, { 'form': form, 'action_id': action.id, 'condition': form.instance}, request=request), })
def create_row_preview_context( action: models.Action, idx: int, context: Dict, prelude: Optional[str] = None, ): """Create the elements to render a single row preview. :param action: Action being previewed. :param idx: :param context: :param prelude: Optional additional text to include in the preview. :return: context is modified to include the appropriate items """ # Get the total number of items filter_obj = action.get_filter() if filter_obj: n_items = filter_obj.n_rows_selected else: n_items = action.workflow.nrows # Set the correct values to the indeces prv, idx, nxt = _get_navigation_index(idx, n_items) row_values = get_row_values(action, idx) # Obtain the dictionary with the condition evaluation condition_evaluation = action_condition_evaluation(action, row_values) # Get the dictionary containing column names, attributes and condition # valuations: eval_context = get_action_evaluation_context(action, row_values, condition_evaluation) all_false = False if action.conditions.filter(is_filter=False).count(): # If there are conditions, check if they are all false all_false = all(not bool_val for __, bool_val in condition_evaluation.items()) # Evaluate the action content. show_values = '' incorrect_json = False if action.is_out: action_content = evaluate_row_action_out(action, eval_context) if action.action_type == models.Action.PERSONALIZED_JSON: incorrect_json = not _check_json_is_correct(action_content) else: action_content = _evaluate_row_action_in(action, eval_context) if action_content is None: action_content = _( 'Error while retrieving content (index: {0})', ).format(idx) else: # Get the conditions used in the action content act_cond = action.get_used_conditions() # Get the variables/columns from the conditions act_vars = set().union(*[ cond.columns.all() for cond in action.conditions.filter(name__in=act_cond) ]) act_vars = act_vars.union( {triplet.column for triplet in action.column_condition_pair.all()}) # Sort the variables/columns by position and get the name show_values = ', '.join([ '"{0}" = {1}'.format(col.name, row_values[col.name]) for col in act_vars ]) uses_plain_text = ( action.action_type == models.Action.PERSONALIZED_CANVAS_EMAIL or action.action_type == models.Action.PERSONALIZED_JSON) if uses_plain_text: action_content = escape(action_content) if prelude: prelude = evaluate_row_action_out(action, eval_context, prelude) # Update the context context.update({ 'n_items': n_items, 'nxt': nxt, 'prv': prv, 'incorrect_json': incorrect_json, 'show_values': show_values, 'show_conditions': ', '.join([ '"{0}" = {1}'.format(cond_name, str(cond_value)) for cond_name, cond_value in condition_evaluation.items() ]), 'all_false': all_false, 'prelude': prelude, 'action_content': action_content, 'show_navigation': True })
def edit_action_in( request: HttpRequest, workflow: Workflow, action: Action, ) -> HttpResponse: """Edit an action in. :param request: Request object :param workflow: workflow :param action: Action :return: HTTP response """ # All tuples (action, column, condition) to consider tuples = action.column_condition_pair.all() # Columns all_columns = workflow.columns # Conditions filter_condition = action.get_filter() all_conditions = action.conditions.filter(is_filter=False) # Create the context info. context = { 'action': action, # Workflow elements 'total_rows': workflow.nrows, 'query_builder_ops': workflow.get_query_builder_ops_as_str(), 'has_data': workflow.has_table(), 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'all_false_conditions': any( cond.n_rows_selected == 0 for cond in all_conditions ), # Column elements 'key_columns': all_columns.filter(is_key=True), 'stat_columns': all_columns.filter(is_key=False), 'key_selected': tuples.filter(column__is_key=True).first(), 'has_no_key': tuples.filter(column__is_key=False).exists(), 'any_empty_description': tuples.filter( column__description_text='', column__is_key=False, ).exists(), 'columns_to_insert': all_columns.exclude( column_condition_pair__action=action, ).exclude( is_key=True, ).distinct().order_by('position'), 'column_selected_table': ColumnSelectedTable( tuples.filter(column__is_key=False).values( 'id', 'column__id', 'column__name', 'column__description_text', 'condition__name', ), orderable=False, extra_columns=[( 'operations', OperationsColumn( verbose_name='', template_file=ColumnSelectedTable.ops_template, template_context=lambda record: { 'id': record['column__id'], 'aid': action.id}), )], condition_list=all_conditions, ), # Conditions 'filter_condition': filter_condition, 'conditions': all_conditions, 'vis_scripts': PlotlyHandler.get_engine_scripts(), 'other_conditions': Condition.objects.filter( action__workflow=workflow, is_filter=False, ).exclude(action=action), } return render(request, 'action/edit_in.html', context)
def send_canvas_emails( user, action: Action, log_item: Log, action_info: Mapping, ): """Send CANVAS emails with the action content evaluated for each row. Performs the submission of the emails for the given action and with the given subject. The subject will be evaluated also with respect to the rows, attributes, and conditions. :param user: User object that executed the action :param action: Action from where to take the messages :param log_item: Log object to store results :param action_info: Mapping key, value as defined in CanvasEmailPayload :return: Send the emails """ # Evaluate the action string, evaluate the subject, and get the value of # the email column. action_evals = evaluate_action( action, extra_string=action_info['subject'], column_name=action_info['item_column'], exclude_values=action_info['exclude_values']) # Get the oauth info target_url = action_info['target_url'] oauth_info = settings.CANVAS_INFO_DICT.get(target_url) if not oauth_info: raise Exception(_('Unable to find OAuth Information Record')) # Get the token user_token = OAuthUserToken.objects.filter( user=user, instance_name=target_url, ).first() if not user_token: # There is no token, execution cannot proceed raise Exception(_('Incorrect execution due to absence of token')) # Create the headers to use for all requests headers = { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Authorization': 'Bearer {0}'.format(user_token.access_token), } # Create the context for the log events context = { 'user': user.id, 'action': action.id, } # Send the objects to the given URL idx = 1 burst = oauth_info['aux_params'].get('burst') burst_pause = oauth_info['aux_params'].get('pause', 0) domain = oauth_info['domain_port'] conversation_url = oauth_info['conversation_url'].format(domain) for msg_body, msg_subject, msg_to in action_evals: # # JSON object to send. Taken from method.conversations.create in # https://canvas.instructure.com/doc/api/conversations.html # canvas_email_payload = { 'recipients[]': int(msg_to), 'body': msg_body, 'subject': msg_subject, } # Manage the bursts do_burst_pause(burst, burst_pause, idx) # Index to detect bursts idx += 1 # Send the email if settings.EXECUTE_ACTION_JSON_TRANSFER: result_msg, response_status = send_single_canvas_message( target_url, conversation_url, canvas_email_payload, headers, oauth_info, ) else: # Print the JSON that would be sent through the logger logger.info( 'SEND JSON(%s): %s', target_url, json.dumps(canvas_email_payload)) result_msg = 'SENT TO LOGGER' response_status = 200 # Log message sent context['object'] = json.dumps(canvas_email_payload) context['status'] = response_status context['result'] = result_msg context['email_sent_datetime'] = str( datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)), ) Log.objects.register( user, Log.ACTION_CANVAS_EMAIL_SENT, action.workflow, context) # Update data in the overall log item log_item.payload['objects_sent'] = len(action_evals) log_item.payload['filter_present'] = action.get_filter() is not None log_item.payload['datetime'] = str(datetime.datetime.now(pytz.timezone( settings.TIME_ZONE))) log_item.save() return 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)
def send_emails( user, action: Action, log_item: Log, action_info: Dict, ) -> None: """Send action content evaluated for each row. Sends the emails for the given action and with the given subject. The subject will be evaluated also with respect to the rows, attributes, and conditions. The messages are sent in bursts with a pause in seconds as specified by the configuration variables EMAIL_BURST and EMAIL_BURST_PAUSE :param user: User object that executed the action :param action: Action from where to take the messages :param log_item: Log object to store results :param action_info: Dictionary key, value as defined in EmailPayload :return: Send the emails """ # Evaluate the action string, evaluate the subject, and get the value of # the email column. action_evals = evaluate_action( action, extra_string=action_info['subject'], column_name=action_info['item_column'], exclude_values=action_info['exclude_values']) # Turn cc_email and bcc email into lists action_info['cc_email'] = action_info['cc_email'].split() action_info['bcc_email'] = action_info['bcc_email'].split() _check_cc_lists(action_info['cc_email'], action_info['bcc_email']) track_col_name = '' if action_info['track_read']: track_col_name = _create_track_column(action) # Get the log item payload to store the tracking column log_item.payload['track_column'] = track_col_name log_item.save() msgs = _create_messages( user, action, action_evals, track_col_name, action_info, ) _deliver_msg_burst(msgs) # Update data in the log item log_item.payload['objects_sent'] = len(action_evals) log_item.payload['filter_present'] = action.get_filter() is not None log_item.payload['datetime'] = str( datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)), ) log_item.save() if action_info['send_confirmation']: # Confirmation message requested _send_confirmation_message(user, action, len(msgs))