Example #1
0
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
Example #2
0
    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()
        })
Example #4
0
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)
    })
Example #5
0
    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)
Example #6
0
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'])
Example #7
0
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,
        },
    )
Example #8
0
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)