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 clean(self): """Detect uniques values in one column, and different column names.""" form_data = super().clean() # Move data to the payload so that is ready to be used self.store_fields_in_dict([('item_column', None), ('user_fname_column', None), ('file_suffix', None), ('zip_for_moodle', None), ('confirm_items', None)]) # Participant column must be unique pcolumn = form_data['item_column'] ufname_column = form_data['user_fname_column'] # The given column must have unique values if not is_column_unique( self.action.workflow.get_data_frame_table_name(), pcolumn, ): self.add_error( 'item_column', _('Column needs to have all unique values (no empty cells)'), ) return form_data # If both values are given and they are identical, return with error if pcolumn and ufname_column and pcolumn == ufname_column: self.add_error( None, _('The two columns must be different'), ) return form_data # If a moodle zip has been requested if form_data.get('zip_for_moodle'): if not pcolumn or not ufname_column: self.add_error( None, _('A Moodle ZIP requires two column names'), ) return form_data # Participant columns must match the pattern 'Participant [0-9]+' pcolumn_data = get_rows( self.action.workflow.get_data_frame_table_name(), column_names=[pcolumn]) participant_error = any( not participant_re.search(str(row[pcolumn])) for row in pcolumn_data) if participant_error: self.add_error( 'item_column', _('Values in column must have format ' + '"Participant [number]"'), ) return form_data
def extra_validation(self, validated_data): """Validate the presence of certain fields.""" act, execute, column, exclude, payload = super().extra_validation( validated_data) if act.action_type != Action.PERSONALIZED_TEXT: raise APIException(_('Incorrect type of action to schedule.')) subject = payload.get('subject') if not subject: raise APIException(_('Personalized text needs a subject.')) if not column: raise APIException(_('Personalized text needs an item_column')) # Check if the values in the email column are correct emails try: column_data = get_rows( act.workflow.get_data_frame_table_name(), column_names=[column.name]) if not all( is_correct_email(row[column.name]) for row in column_data ): # column has incorrect email addresses raise APIException( _('The column with email addresses has incorrect values.')) except TypeError: raise APIException( _('The column with email addresses has incorrect values.')) try: if not all( is_correct_email(email_val) for email_val in payload.get('cc_email', []) if email_val ): raise APIException( _('cc_email must be a comma-separated list of emails.')) except TypeError: raise APIException( _('cc_email must be a comma-separated list of emails.')) try: if not all( is_correct_email(email) for email in payload.get('bcc_email', []) if email ): raise APIException( _('bcc_email must be a comma-separated list of emails.')) except TypeError: raise APIException( _('bcc_email must be a comma-separated list of emails.')) return act, execute, column, exclude, payload
def ot_insert_column_list(context, column_name): """Insert in the text a column list.""" action = context['ONTASK_ACTION_CONTEXT_VARIABLE___'] column_values = [ str(citem[0]) for citem in get_rows( action.workflow.get_data_frame_table_name(), column_names=[column_name], filter_formula=action.get_filter_formula())] if action.action_type == Action.send_list_json: return mark_safe(json.dumps(column_values)) return ', '.join(column_values)
def __init__(self, form_data, *args, **kwargs): """Store action, column name and exclude init, adjust fields.""" self.action: Action = kwargs.pop('action', None) self.column_name: str = kwargs.pop('column_name', None) self.exclude_init: List[str] = kwargs.pop('exclude_values', list) super().__init__(form_data, *args, **kwargs) self.fields['exclude_values'].choices = get_rows( self.action.workflow.get_data_frame_table_name(), column_names=[self.column_name, self.column_name], filter_formula=self.action.get_filter_formula()).fetchall() self.set_field_from_dict('exclude_values')
def create_eval_data_tuple( action: Action, item_column: str, exclude_values: List, user_fname_column: Optional[str], ) -> List[Tuple[str, str, str]]: """Evaluate text and create list of tuples [filename, part id, text]. Evaluate the conditions in the actions based on the given item_column excluding the values in exclude_values. This returns a list with tuples [action text, None, participant column value]. Process that list to insert as second element of the tuple the corresponding value in user_fname_column (if given). The final result is a list of triplets with: - Filename - part id as extracted from the participation column - HTML body text :param action: Action being processed :param item_column: The column used to iterate :param exclude_values: List of values to exclude from evaluation :param user_fname_column: Column name to use for filename creation :return: List[Tuple[text, text, text]] """ # Obtain the personalised text action_evals = evaluate_action(action, column_name=item_column, exclude_values=exclude_values) if user_fname_column: # Get the user_fname_column values user_fname_data = [ row[user_fname_column] for row in get_rows(action.workflow.get_data_frame_table_name(), column_names=[user_fname_column], filter_formula=None).fetchall() ] else: # Array of empty strings to concatenate user_fname_data = [''] * len(action_evals) return [(user_fname, part_id, html_body.format(msg_body)) for (msg_body, part_id), user_fname in zip(action_evals, user_fname_data)]