def update_n_rows_selected(self, column=None, filter_formula=None): """ Given a condition update the number of rows for which this condition will have true result. In other words, we calculate the number of rows for which the condition is true. The function implements two algorithms depending on the condition being a filter or not: Case 1: Condition is a filter - Evaluate the filter and update field. - Param filter_formula is ignored Case 2: Condition is NOT a filter - If there is a filter_formula, create the conjunction together with the condition formula. :param column: Column that has changed value (None when unknown) :param filter_formula: Formula provided by another filter condition and to take the conjunction with the condition formula. :return: Nothing. Effect recorded in DB objects """ if column and column not in self.columns.all(): # The column is not part of this condition. Nothing to do return # Case 1: Condition is a filter if self.is_filter: self.n_rows_selected = \ pandas_db.num_rows(self.action.workflow.id, self.formula) self.save() # Propagate the filter effect to the rest of actions. for condition in self.action.conditions.filter(is_filter=False): # Update the rest of conditions. The column=None because # the filter has changed, thus the condition needs to be # reevaluated regardless. condition.update_n_rows_selected(column=None, filter_formula=self.formula) return # Case 2: Condition is NOT a filter formula = self.formula if filter_formula: # There is a formula to add to the condition, create a conjunction formula = { 'condition': 'AND', 'not': False, 'rules': [filter_formula, self.formula], 'valid': True } self.n_rows_selected = pandas_db.num_rows(self.action.workflow.id, formula) self.save() return
def num_rows(self): """ Number of rows considered by this view :return: Number of rows resulting from using the formula """ if not self.nrows: self.nrows = \ pandas_db.num_rows(self.workflow.id, self.formula) return self.nrows
def edit_action_out(request, pk): """ View to handle the AJAX form to edit an action (editor, conditions, filters, etc). :param request: Request object :param pk: Action PK :return: JSON response """ # Try to get the workflow first workflow = get_workflow(request) if not workflow: return redirect('workflow:index') # Get the action and create the form try: action = Action.objects.filter( Q(workflow__user=request.user) | Q(workflow__shared=request.user)).distinct().get(pk=pk) except ObjectDoesNotExist: return redirect('action:index') # Create the form form = EditActionOutForm(request.POST or None, instance=action) # See if the action has a filter or not try: filter_condition = Condition.objects.get( action=action, is_filter=True ) except Condition.DoesNotExist: filter_condition = None except Condition.MultipleObjectsReturned: return render(request, 'error.html', {'message': 'Malfunction detected when retrieving filter ' '(action: {0})'.format(action.id)}) # Conditions to show in the page as well. conditions = Condition.objects.filter( action=action, is_filter=False ).order_by('created').values('id', 'name') # Boolean to find out if there is a table attached to this workflow has_data = ops.workflow_has_table(action.workflow) # Get the total number of rows in DF and those selected by filter. total_rows = workflow.nrows if filter_condition: action.n_selected_rows = \ pandas_db.num_rows(action.workflow.id, filter_condition.formula) # Context to render the form context = {'filter_condition': filter_condition, 'action': action, 'conditions': conditions, 'query_builder_ops': workflow.get_query_builder_ops_as_str(), 'attribute_names': [x for x in workflow.attributes.keys()], 'column_names': workflow.get_column_names(), 'selected_rows': action.n_selected_rows, 'has_data': has_data, 'total_rows': total_rows, 'form': form, 'vis_scripts': PlotlyHandler.get_engine_scripts() } # Processing the request after receiving the text from the editor if request.method == 'POST': # Get the next step next_step = request.POST['Submit'] if form.is_valid(): content = form.cleaned_data.get('content', None) # TODO: Can we detect unused vars only for this invocation? # Render the content as a template and catch potential problems. # This seems to be only possible if dealing directly with Jinja2 # instead of Django. try: render_template(content, {}, action) except Exception as e: # Pass the django exception to the form (fingers crossed) form.add_error(None, e.message) return render(request, 'action/edit_out.html', context) # Log the event logs.ops.put(request.user, 'action_update', action.workflow, {'id': action.id, 'name': action.name, 'workflow_id': workflow.id, 'workflow_name': workflow.name, 'content': content}) # Text is good. Update the content of the action action.content = content action.save() # Closing if next_step == 'Save-and-close': return redirect('action:index') # Return the same form in the same page return render(request, 'action/edit_out.html', context=context)
def edit_action_in(request, pk): """ View to handle the AJAX form to edit an action in (filter + columns). :param request: Request object :param pk: Action PK :return: HTTP response """ # Check if the workflow is locked workflow = get_workflow(request) if not workflow: return redirect('workflow:index') if workflow.nrows == 0: messages.error(request, 'Workflow has no data. ' 'Go to Dataops to upload data.') return redirect(reverse('action:index')) # Get the action and the columns try: action = Action.objects.filter( Q(workflow__user=request.user) | Q(workflow__shared=request.user) ).distinct().prefetch_related('columns').get(pk=pk) except ObjectDoesNotExist: return redirect('action:index') if action.is_out: # Trying to edit an incorrect action. Redirect to index return redirect('action:index') # See if the action has a filter or not try: filter_condition = Condition.objects.get( action=action, is_filter=True ) except Condition.DoesNotExist: filter_condition = None except Condition.MultipleObjectsReturned: return render(request, 'error.html', {'message': 'Malfunction detected when retrieving filter ' '(action: {0})'.format(action.id)}) # Get the number of rows in DF selected by filter. if filter_condition: filter_condition.n_rows_selected = \ pandas_db.num_rows(action.workflow.id, filter_condition.formula) filter_condition.save() # Column names suitable to insert columns_selected = action.columns.filter(is_key=False).order_by('position') columns_to_insert = [c for c in workflow.columns.all() if not c.is_key and c not in columns_selected] # Has key column and has no-key column has_no_key = action.columns.filter(is_key=False).exists() has_empty_description = columns_selected.filter( description_text='' ).exists() # Create the context info. ctx = {'action': action, 'filter_condition': filter_condition, 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'total_rows': workflow.nrows, 'query_builder_ops': workflow.get_query_builder_ops_as_str(), 'has_data': ops.workflow_has_table(action.workflow), 'key_selected': action.columns.filter(is_key=True).first(), 'columns_to_insert': columns_to_insert, 'column_selected_table': ColumnSelectedTable( columns_selected.values('id', 'name', 'description_text'), orderable=False, extra_columns=[ ('operations', OperationsColumn( verbose_name='Ops', template_file=ColumnSelectedTable.ops_template, template_context=lambda record: {'id': record['id'], 'aid': action.id}) )] ), 'has_no_key': has_no_key, 'has_empty_description': has_empty_description} return render(request, 'action/edit_in.html', ctx)
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 } # If the method is GET or the form is not valid, re-render the page. if request.method == 'GET' or not form.is_valid(): # If the request has the 'action_content' field, update the action action_content = request.GET.get('action_content', None) if action_content: action.content = action_content action.save() data['html_form'] = render_to_string(template_name, context, request=request) return JsonResponse(data) 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 Condition.objects.filter(action=action, is_filter=True).exists(): # Should not happen. Go back to editing the action data['form_is_valid'] = True data['html_redirect'] = reverse('action:edit_out', kwargs={'pk': action.id}) return JsonResponse(data) log_type = 'filter' 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 } 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 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 } 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 replacing = '{{% if {0} %}}' action.content = action.content.replace( replacing.format(form.old_name), replacing.format(condition.name)) action.save() log_type = 'condition' # 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: # Update the fields not in the form # 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() if is_filter: # Update the number of selected rows applying the new formula action.n_selected_rows = \ pandas_db.num_rows(action.workflow.id, condition.formula) # Update the action action.save() # Log the event formula, _ = evaluate_node_sql(condition.formula) if is_new: log_type += '_create' else: log_type += '_update' # Log the event logs.ops.put( request.user, log_type, condition.action.workflow, { 'id': condition.id, 'name': condition.name, 'selected_rows': action.n_selected_rows, 'formula': formula }) data['html_redirect'] = reverse('action:edit_out', kwargs={'pk': action.id}) return JsonResponse(data)
def edit_action_in(request, workflow, action): """ View to handle the AJAX form to edit an action in (filter + columns). :param request: Request object :param workflow: workflow :param action: Action :return: HTTP response """ # Get filter or None filter_condition = action.get_filter() # Get the number of rows in DF selected by filter. if filter_condition: filter_condition.n_rows_selected = \ pandas_db.num_rows(action.workflow.id, filter_condition.formula) filter_condition.save() # Column names suitable to insert columns_selected = action.columns.filter(is_key=False).order_by('position') columns_to_insert = [ c for c in workflow.columns.all() if not c.is_key and c not in columns_selected ] # Has key column and has no-key column has_no_key = action.columns.filter(is_key=False).exists() has_empty_description = columns_selected.filter( description_text='').exists() # Create the context info. ctx = { 'action': action, 'filter_condition': filter_condition, 'selected_rows': filter_condition.n_rows_selected if filter_condition else -1, 'total_rows': workflow.nrows, 'query_builder_ops': workflow.get_query_builder_ops_as_str(), 'has_data': ops.workflow_has_table(action.workflow), 'key_selected': action.columns.filter(is_key=True).first(), 'columns_to_insert': columns_to_insert, 'column_selected_table': ColumnSelectedTable( columns_selected.values('id', 'name', 'description_text'), orderable=False, extra_columns=[('operations', OperationsColumn( verbose_name='Ops', template_file=ColumnSelectedTable.ops_template, template_context=lambda record: { 'id': record['id'], 'aid': action.id }))]), 'has_no_key': has_no_key, 'has_empty_description': has_empty_description } return render(request, 'action/edit_in.html', ctx)