Example #1
0
    def do_operand(self,
                   input_value,
                   op_value,
                   type_value,
                   value1,
                   value2,
                   value3):

        result1 = evaluate(
            self.set_skel(input_value,
                          op_value.format(''),
                          type_value,
                          value1),
            NodeEvaluation.EVAL_EXP,
            {'variable': value2}
        )
        result2 = evaluate(
            self.set_skel(input_value,
                          op_value.format(''),
                          type_value,
                          value1),
            NodeEvaluation.EVAL_EXP,
            {'variable': value3}
        )

        if op_value.endswith('null') or value2 is not None:
            # If value2 is not None, expect regular results
            self.assertTrue(result1)
        else:
            # If value2 is None, then all formulas should be false
            self.assertFalse(result1)

        self.assertFalse(result2)

        if op_value.find('{0}') != -1:
            result1 = evaluate(
                self.set_skel(input_value,
                              op_value.format('not_'),
                              type_value,
                              value1),
                NodeEvaluation.EVAL_EXP,
                {'variable': value2}
            )
            result2 = evaluate(
                self.set_skel(input_value,
                              op_value.format('not_'),
                              type_value,
                              value1),
                NodeEvaluation.EVAL_EXP,
                {'variable': value3}
            )

            self.assertFalse(result1)
            if op_value.endswith('null') or value3 is not None:
                # If value2 is not None, expect regular results
                self.assertTrue(result2)
            else:
                # If value2 is None, then all formulas should be false
                self.assertFalse(result2)
Example #2
0
    def get_formula_text(self):
        """
        Return the content of the formula in a string that is human readable
        :return: String
        """

        return evaluate(self.formula, NodeEvaluation.EVAL_TXT)
Example #3
0
    def get_evaluation_context(self, row_values):
        """
        Given an action and a set of row_values, prepare the dictionary with the
        condition names, attribute names and column names and their
        corresponding values.
        :param row_values: Values to use in the evaluation of conditions.
        :return: Context dictionary, or None if there has been some anomaly
        """

        # If no row values are given, there is nothing to do here.
        if row_values is None:
            # No rows satisfy the given condition
            return None

        # Step 1: Evaluate all the conditions
        condition_eval = {}
        for condition in self.conditions.filter(is_filter=False).values(
                'name', 'is_filter', 'formula'):
            # Evaluate the condition
            try:
                condition_eval[condition['name']] = evaluate(
                    condition['formula'], NodeEvaluation.EVAL_EXP, row_values)
            except OnTaskException:
                # Something went wrong evaluating a condition. Stop.
                return None

        # Step 2: Create the context with the attributes, the evaluation of the
        # conditions and the values of the columns.
        attributes = self.workflow.attributes

        return dict(dict(row_values, **condition_eval), **attributes)
Example #4
0
def get_table_cursor(pk, cond_filter, column_names):
    """
    Execute a select query in the database with an optional filter obtained
    from the jquery QueryBuilder.

    :param pk: Primary key of the workflow storing the data
    :param cond_filter: Condition object to filter the data (or None)
    :param column_names: optional list of columns to select
    :return: ([list of column names], QuerySet with the data rows)
    """

    # Create the query
    safe_column_names = [fix_pctg_in_name(x) for x in column_names]
    query = 'SELECT "{0}" from "{1}"'.format(
        '", "'.join(safe_column_names),
        create_table_name(pk)
    )

    # See if the action has a filter or not
    fields = []
    if cond_filter is not None:
        cond_filter, fields = evaluate(cond_filter.formula,
                                       NodeEvaluation.EVAL_SQL)
        if cond_filter:
            # The condition may be empty, in which case, nothing is needed.
            query += ' WHERE ' + cond_filter

    # Execute the query
    cursor = connection.cursor()
    cursor.execute(query, fields)

    return cursor
Example #5
0
    def test_evaluate_formula(self):

        self.assertTrue(
            formula_evaluation.evaluate(self.formula1, NodeEvaluation.EVAL_EXP,
                                        {
                                            'Course_Code_a': 'df',
                                            'ANOTHER': 'v2'
                                        }))
Example #6
0
def delete_filter(request, pk):
    """
    Handle the AJAX request to delete a filter
    :param request: AJAX request
    :param pk: Filter ID
    :return: AJAX response
    """
    # Get the filter
    try:
        cond_filter = Condition.objects.filter(
            Q(action__workflow__user=request.user)
            | Q(action__workflow__shared=request.user),
            is_filter=True).distinct().get(pk=pk)
    except (KeyError, ObjectDoesNotExist):
        return redirect('workflow:index')

    data = dict()
    data['form_is_valid'] = False

    # Treat the two types of requests
    if request.method == 'POST':

        # If the request has 'action_content', update the action
        action_content = request.POST.get('action_content', None)
        if action_content:
            cond_filter.action.set_content(action_content)
            cond_filter.action.save()

        # Log the event
        formula, fields = evaluate(cond_filter.formula,
                                   NodeEvaluation.EVAL_SQL)
        Log.objects.register(
            request.user, Log.FILTER_DELETE, cond_filter.action.workflow, {
                'id': cond_filter.id,
                'name': cond_filter.name,
                'selected_rows': cond_filter.n_rows_selected,
                'formula': formula,
                'formula_fields': fields
            })

        # Get the action object for further processing
        action = cond_filter.action

        # Perform the delete operation
        cond_filter.delete()

        # Number of selected rows now needs to be updated in all remaining
        # conditions
        action.update_n_rows_selected()

        return JsonResponse({'form_is_valid': True, 'html_redirect': ''})

    data['html_form'] = \
        render_to_string('action/includes/partial_filter_delete.html',
                         {'id': cond_filter.id},
                         request=request)

    return JsonResponse(data)
Example #7
0
def delete_condition(request, pk):
    """
    Handle the AJAX request to delete a condition. The pk is the condition ID.
    :param request: HTTP request
    :param pk: condition or filter id
    :return: AJAX response to render
    """
    # AJAX result
    data = {}

    # Get the condition
    try:
        condition = Condition.objects.filter(
            Q(action__workflow__user=request.user)
            | Q(action__workflow__shared=request.user),
            is_filter=False).distinct().get(pk=pk)
    except (KeyError, ObjectDoesNotExist):
        data['form_is_valid'] = True
        data['html_redirect'] = reverse('workflow:index')
        return JsonResponse(data)

    data = {'form_is_valid': False}

    # Treat the two types of requests
    if request.method == 'POST':
        # If the request has the 'action_content', update the action
        action_content = request.POST.get('action_content', None)
        if action_content:
            condition.action.set_content(action_content)
            condition.action.save()

        formula, fields = evaluate(condition.formula, NodeEvaluation.EVAL_SQL)
        Log.objects.register(
            request.user, Log.CONDITION_DELETE, condition.action.workflow, {
                'id': condition.id,
                'name': condition.name,
                'formula': formula,
                'formula_fields': fields
            })

        # Perform the delete operation
        condition.delete()
        data['form_is_valid'] = True
        data['html_redirect'] = ''
        return JsonResponse(data)

    data['html_form'] = \
        render_to_string('action/includes/partial_condition_delete.html',
                         {'condition_id': condition.id},
                         request=request)

    return JsonResponse(data)
Example #8
0
def get_table_row_by_key(workflow, cond_filter, kv_pair, column_names):
    """
    Select the set of elements after filtering and with the key=value pair

    :param workflow: workflow object to get to the table
    :param cond_filter: Condition object to filter the data (or None)
    :param kv_pair: A key=value pair to identify the row. Key is suppose to
           be unique.
    :param column_names: Optional list of column names to select
    :return: A dictionary with the (column_name, value) data or None if the
     row has not been found
    """

    # Create the query
    safe_column_names = [fix_pctg_in_name(x) for x in column_names]
    query = 'SELECT "{0}" FROM "{1}" WHERE ("{2}" = %s)'.format(
        '", "'.join(safe_column_names),
        create_table_name(workflow.id),
        fix_pctg_in_name(kv_pair[0])
    )
    fields = [kv_pair[1]]

    # See if the action has a filter or not
    if cond_filter is not None:
        cond_filter, filter_fields = \
            evaluate(cond_filter.formula, NodeEvaluation.EVAL_SQL)
        query += ' AND (' + cond_filter + ')'
        fields = fields + filter_fields

    # Execute the query
    cursor = connection.cursor()
    cursor.execute(query, fields)

    # Get the data
    qs = cursor.fetchall()

    # If there is anything different than one element, return None
    if len(qs) != 1:
        return None

    # Get the only element
    qs = qs[0]

    # ZIP the values to create a dictionary
    return OrderedDict(list(zip(workflow.get_column_names(), qs)))
Example #9
0
def num_rows_by_name(table_name, cond_filter=None):
    """
    Given a table name, get its number of rows
    :param table_name: Table name
    :param cond_filter: Condition element used to filter the query
    :return: integer
    """

    # Initial query with the table name
    query = query_count_rows.format(table_name)

    fields = []
    if cond_filter is not None:
        cond_filter, fields = evaluate(cond_filter, NodeEvaluation.EVAL_SQL)
        query += ' WHERE ' + cond_filter

    cursor = connection.cursor()
    cursor.execute(query, fields)
    return cursor.fetchone()[0]
Example #10
0
def get_filter_query(table_name, column_names, filter_exp):
    """

    Given a set of columns and a filter expression, return a pair of SQL query
    and params to be executed
    :param table_name: Table to query
    :param column_names: list of columns to consider or None to consider all
    :param filter_exp: Text filter expression
    :return: (sql query, sql params)
    """

    # Create the query
    safe_column_names = [fix_pctg_in_name(x) for x in column_names]
    query = 'SELECT "{0}" FROM "{1}"'.format('", "'.join(safe_column_names),
                                             table_name)

    # Calculate the first suffix to add to the query
    filter_txt = ''
    filter_fields = []
    if filter_exp:
        filter_txt, filter_fields = evaluate(filter_exp,
                                             NodeEvaluation.EVAL_SQL)

    # Build the query so far appending the filter and/or the cv_tuples
    if filter_txt:
        query += ' WHERE '

    fields = []
    # If there has been a suffix from the filter, add it.
    if filter_txt:
        query += filter_txt

    if filter_fields:
        fields.extend(filter_fields)

    return (query, fields)
Example #11
0
def save_condition_form(request, form, template_name, action, condition,
                        is_filter):
    """
    Function to 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 condition: Condition object being manipulated or None if creating
    :param is_filter: The condition is a filter
    :return:
    """
    # Ajax response
    data = dict()

    # In principle we re-render until proven otherwise
    data['form_is_valid'] = False

    # The condition is new if no value is given
    is_new = condition is None

    if is_new:
        condition_id = -1
    else:
        condition_id = condition.id

    # Context for rendering
    context = {
        'form': form,
        'action_id': action.id,
        'condition_id': condition_id,
        'add': is_new
    }

    # If the method is GET or the form is not valid, re-render the page.
    if request.method == 'GET' or not form.is_valid():
        data['html_form'] = render_to_string(template_name,
                                             context,
                                             request=request)
        return JsonResponse(data)

    # If the request has the 'action_content' field, update the action
    action_content = request.POST.get('action_content', None)
    if action_content:
        action.set_content(action_content)
        action.save()

    if is_filter:
        # Process the filter form
        # If this is a create filter operation, but the action has one,
        # flag the error
        if is_new and action.get_filter():
            # Should not happen. Go back to editing the action
            data['form_is_valid'] = True
            data['html_redirect'] = ''
            return JsonResponse(data)
    else:
        # Verify that the condition name does not exist yet (Uniqueness FIX)
        qs = Condition.objects.filter(name=form.cleaned_data['name'],
                                      action=action,
                                      is_filter=False)
        if (is_new and qs.exists()) or \
                (not is_new and qs.filter(~Q(id=condition_id)).exists()):
            form.add_error(
                'name',
                _('A condition with that name already exists in this action'))
            data['html_form'] = render_to_string(template_name,
                                                 context,
                                                 request=request)
            return JsonResponse(data)
        # Verify that the condition name does not collide with column names
        workflow = get_workflow(request, action.workflow.id)
        if not workflow:
            # Workflow is not accessible. Go back to the index.
            data['form_is_valid'] = True
            data['html_redirect'] = reverse('workflow:index')
            return JsonResponse(data)

        # New condition name does not collide with column name
        if form.cleaned_data['name'] in workflow.get_column_names():
            form.add_error('name',
                           _('A column name with that name already exists.'))
            context = {
                'form': form,
                'action_id': action.id,
                'condition_id': condition_id,
                'add': is_new
            }
            data['html_form'] = render_to_string(template_name,
                                                 context,
                                                 request=request)
            return JsonResponse(data)

        # New condition name does not collide with attribute names
        if form.cleaned_data['name'] in list(workflow.attributes.keys()):
            form.add_error('name',
                           _('The workflow has an attribute with this name.'))
            context = {
                'form': form,
                'action_id': action.id,
                'condition_id': condition_id,
                'add': is_new
            }
            data['html_form'] = render_to_string(template_name,
                                                 context,
                                                 request=request)
            return JsonResponse(data)

        # If condition name has changed, rename appearances in the content
        # field of the action.
        if form.old_name and 'name' in form.changed_data:
            # Performing string substitution in the content and saving
            # TODO: Review!
            replacing = '{{% if {0} %}}'
            action.content = action.content.replace(
                escape(replacing.format(form.old_name)),
                escape(replacing.format(condition.name)))
            action.save()

    # Ok, here we can say that the data in the form is correct.
    data['form_is_valid'] = True

    # Proceed to update the DB
    if is_new:
        # Get the condition from the form, but don't commit as there are
        # changes pending.
        condition = form.save(commit=False)
        condition.action = action
        condition.is_filter = is_filter
        condition.save()
    else:
        condition = form.save()

    # Update the number of selected rows for the conditions
    condition.update_n_rows_selected()

    # Update the columns field
    condition.columns.set(
        action.workflow.columns.filter(
            name__in=get_variables(condition.formula)))

    # Update the condition
    condition.save()

    # Log the event
    formula, _ = evaluate(condition.formula, NodeEvaluation.EVAL_SQL)
    if is_new:
        if is_filter:
            log_type = Log.FILTER_CREATE
        else:
            log_type = Log.CONDITION_CREATE
    else:
        if is_filter:
            log_type = Log.FILTER_UPDATE
        else:
            log_type = Log.CONDITION_UPDATE

    # Log the event
    Log.objects.register(
        request.user, log_type, condition.action.workflow, {
            'id': condition.id,
            'name': condition.name,
            'selected_rows': condition.n_rows_selected,
            'formula': formula
        })

    data['html_redirect'] = ''
    return JsonResponse(data)
Example #12
0
def search_table_rows(workflow_id,
                      cv_tuples=None,
                      any_join=True,
                      order_col_name=None,
                      order_asc=True,
                      column_names=None,
                      pre_filter=None):
    """
    Select rows where for every (column, value) pair, column contains value (
    as in LIKE %value%, these are combined with OR if any is TRUE, or AND if
    any is false, and the result is ordered by the given column and type (if
    given)

    :param workflow_id: workflow object to get to the table
    :param cv_tuples: A column, value, type tuple to search the value in the
    column
    :param any_join: Boolean encoding if values should be combined with OR (or
    AND)
    :param order_col_name: Order results by this column
    :param order_asc: Order results in ascending values (or descending)
    :param column_names: Optional list of column names to select
    :param pre_filter: Optional filter condition to pre filter the query set.
           the query is built with these terms as requirement AND the cv_tuples.
    :return: The resulting query set
    """

    # Create the query
    safe_column_names = [fix_pctg_in_name(x) for x in column_names]
    query = 'SELECT "{0}" FROM "{1}"'.format('", "'.join(safe_column_names),
                                             create_table_name(workflow_id))

    # Calculate the first suffix to add to the query
    filter_txt = ''
    filter_fields = []
    if pre_filter:
        filter_txt, filter_fields = evaluate(pre_filter,
                                             NodeEvaluation.EVAL_SQL)

    if cv_tuples:
        likes = []
        tuple_fields = []
        for name, value, data_type in cv_tuples:
            # Make sure we escape the name and search as text
            name = fix_pctg_in_name(name)
            mod_name = '(CAST("{0}" AS TEXT) LIKE %s)'.format(name)

            # Create the second part of the query setting column LIKE '%value%'
            likes.append(mod_name)
            tuple_fields.append('%' + value + '%')

        # Combine the search subqueries
        if any_join:
            tuple_txt = '(' + ' OR '.join(likes) + ')'
        else:
            tuple_txt = '(' + ' AND '.join(likes) + ')'

    # Build the query so far appending the filter and/or the cv_tuples
    if filter_txt or cv_tuples:
        query += ' WHERE '

    fields = []
    # If there has been a suffix from the filter, add it.
    if filter_txt:
        query += filter_txt
        fields.extend(filter_fields)

    # If there is a pre-filter, the suffix needs to be "AND" with the ones
    # just calculated
    if filter_txt and cv_tuples:
        query += ' AND '

    if cv_tuples:
        query += tuple_txt
        fields.extend(tuple_fields)

    # Add the order if needed
    if order_col_name:
        query += ' ORDER BY "{0}"'.format(fix_pctg_in_name(order_col_name))
    if not order_asc:
        query += ' DESC'

    # Execute the query
    cursor = connection.cursor()
    result = cursor.execute(query, fields)

    # Get the data
    return cursor.fetchall()