Refactor batch execution options to use colander/deform
This commit is contained in:
		
							parent
							
								
									2cbacd6187
								
							
						
					
					
						commit
						dd04459748
					
				
					 12 changed files with 87 additions and 99 deletions
				
			
		| 
						 | 
				
			
			@ -697,7 +697,7 @@ class Form(object):
 | 
			
		|||
        context = kwargs
 | 
			
		||||
        context['form'] = self
 | 
			
		||||
        context['dform'] = dform
 | 
			
		||||
        context['form_kwargs'] = {}
 | 
			
		||||
        context.setdefault('form_kwargs', {})
 | 
			
		||||
        # TODO: deprecate / remove the latter option here
 | 
			
		||||
        if self.auto_disable_save or self.auto_disable:
 | 
			
		||||
            context['form_kwargs']['class_'] = 'autodisable'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								tailbone/static/js/jquery.ui.tailbone.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								tailbone/static/js/jquery.ui.tailbone.js
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -261,6 +261,18 @@
 | 
			
		|||
                that.grid.gridcore();
 | 
			
		||||
                that.element.unmask();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        results_count: function(as_text) {
 | 
			
		||||
            var count = null;
 | 
			
		||||
            var match = /showing \d+ thru \d+ of (\S+)/.exec(this.element.find('.pager .showing').text());
 | 
			
		||||
            if (match) {
 | 
			
		||||
                count = match[1];
 | 
			
		||||
                if (!as_text) {
 | 
			
		||||
                    count = parseInt(count, 10);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return count;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,6 @@
 | 
			
		|||
 | 
			
		||||
$(function() {
 | 
			
		||||
    
 | 
			
		||||
    $('.grid-wrapper').gridwrapper();
 | 
			
		||||
 | 
			
		||||
    $('#execute-batch').click(function() {
 | 
			
		||||
        if (has_execution_options) {
 | 
			
		||||
            $('#execution-options-dialog').dialog({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,6 @@
 | 
			
		|||
  ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.batch.js'))}
 | 
			
		||||
  <script type="text/javascript">
 | 
			
		||||
 | 
			
		||||
    var has_execution_options = ${'true' if master.has_execution_options(batch) else 'false'};
 | 
			
		||||
 | 
			
		||||
    $(function() {
 | 
			
		||||
 | 
			
		||||
        $('#save-refresh').click(function() {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,13 +53,3 @@
 | 
			
		|||
<div class="form-wrapper">
 | 
			
		||||
  ${form.render()|n}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div id="execution-options-dialog" style="display: none;">
 | 
			
		||||
 | 
			
		||||
  ${h.form(url('{}.execute'.format(route_prefix), uuid=batch.uuid), name='batch-execution')}
 | 
			
		||||
  % if master.has_execution_options(batch):
 | 
			
		||||
      ${rendered_execution_options|n}
 | 
			
		||||
  % endif
 | 
			
		||||
  ${h.end_form()}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
## -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
${form.field_div('action', form.select('action', options=ACTION_OPTIONS), label="Action to Perform")}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,11 @@
 | 
			
		|||
        $(function() {
 | 
			
		||||
 | 
			
		||||
            $('#execute-results-button').click(function() {
 | 
			
		||||
                var count = $('.grid-wrapper').gridwrapper('results_count');
 | 
			
		||||
                if (!count) {
 | 
			
		||||
                    alert("There are no batch results to execute.");
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var form = $('form[name="execute-results"]');
 | 
			
		||||
                if (has_execution_options) {
 | 
			
		||||
                    $('#execution-options-dialog').dialog({
 | 
			
		||||
| 
						 | 
				
			
			@ -66,16 +71,11 @@ ${parent.body()}
 | 
			
		|||
 | 
			
		||||
% if master.results_executable and request.has_perm('{}.execute_multiple'.format(permission_prefix)):
 | 
			
		||||
    <div id="execution-options-dialog" style="display: none;">
 | 
			
		||||
      ${h.form(url('{}.execute_results'.format(route_prefix)), name='execute-results')}
 | 
			
		||||
      ${h.csrf_token(request)}
 | 
			
		||||
      <br />
 | 
			
		||||
      <p>
 | 
			
		||||
        Please be advised, you are about to execute multiple batches!
 | 
			
		||||
      </p>
 | 
			
		||||
      <br />
 | 
			
		||||
      % if master.has_execution_options(batch):
 | 
			
		||||
          ${rendered_execution_options|n}
 | 
			
		||||
      % endif
 | 
			
		||||
      ${h.end_form()}
 | 
			
		||||
      ${execute_form.render_deform(form_kwargs={'name': 'execute-results'}, buttons=False)|n}
 | 
			
		||||
    </div>
 | 
			
		||||
% endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
 | 
			
		||||
<%def name="extra_javascript()">
 | 
			
		||||
  ${parent.extra_javascript()}
 | 
			
		||||
  ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.batch.js'))}
 | 
			
		||||
  ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.batch.js') + '?ver={}'.format(tailbone.__version__))}
 | 
			
		||||
  <script type="text/javascript">
 | 
			
		||||
 | 
			
		||||
    var has_execution_options = ${'true' if master.has_execution_options(batch) else 'false'};
 | 
			
		||||
| 
						 | 
				
			
			@ -84,13 +84,6 @@ ${rows_grid|n}
 | 
			
		|||
 | 
			
		||||
% if not batch.executed:
 | 
			
		||||
    <div id="execution-options-dialog" style="display: none;">
 | 
			
		||||
 | 
			
		||||
      ${h.form(url('{}.execute'.format(route_prefix), uuid=batch.uuid), name='batch-execution')}
 | 
			
		||||
      ${h.csrf_token(request)}
 | 
			
		||||
      % if master.has_execution_options(batch):
 | 
			
		||||
          ${rendered_execution_options|n}
 | 
			
		||||
      % endif
 | 
			
		||||
      ${h.end_form()}
 | 
			
		||||
 | 
			
		||||
      ${execute_form.render_deform(form_kwargs={'name': 'batch-execution'}, buttons=False)|n}
 | 
			
		||||
    </div>
 | 
			
		||||
% endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,11 +43,8 @@
 | 
			
		|||
        % if master.bulk_deletable and request.has_perm('{}.bulk_delete'.format(permission_prefix)):
 | 
			
		||||
 | 
			
		||||
        $('form[name="bulk-delete"] button').click(function() {
 | 
			
		||||
            var count = 0;
 | 
			
		||||
            var match = /showing \d+ thru \d+ of (\S+)/.exec($('.pager .showing').text());
 | 
			
		||||
            if (match) {
 | 
			
		||||
                count = match[1];
 | 
			
		||||
            } else {
 | 
			
		||||
            var count = $('.grid-wrapper').gridwrapper('results_count', true);
 | 
			
		||||
            if (count === null) {
 | 
			
		||||
                alert("There don't seem to be any results to delete!");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import
 | 
			
		|||
import os
 | 
			
		||||
import datetime
 | 
			
		||||
import logging
 | 
			
		||||
import tempfile
 | 
			
		||||
from cStringIO import StringIO
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
| 
						 | 
				
			
			@ -39,13 +40,16 @@ from rattail.db import model, Session as RattailSession
 | 
			
		|||
from rattail.threads import Thread
 | 
			
		||||
from rattail.util import load_object, prettify
 | 
			
		||||
 | 
			
		||||
import colander
 | 
			
		||||
import deform
 | 
			
		||||
from deform import widget as dfwidget
 | 
			
		||||
from pyramid import httpexceptions
 | 
			
		||||
from pyramid.renderers import render_to_response
 | 
			
		||||
from pyramid.response import FileResponse
 | 
			
		||||
from pyramid_simpleform import Form
 | 
			
		||||
from pyramid_deform import SessionFileUploadTempStore
 | 
			
		||||
from webhelpers2.html import HTML, tags
 | 
			
		||||
 | 
			
		||||
from tailbone import forms, grids
 | 
			
		||||
from tailbone import forms2 as forms, grids
 | 
			
		||||
from tailbone.db import Session
 | 
			
		||||
from tailbone.views import MasterView
 | 
			
		||||
from tailbone.progress import SessionProgress
 | 
			
		||||
| 
						 | 
				
			
			@ -133,8 +137,9 @@ class BatchMasterView(MasterView):
 | 
			
		|||
        kwargs['handler'] = self.handler
 | 
			
		||||
        kwargs['execute_title'] = self.get_execute_title(batch)
 | 
			
		||||
        kwargs['execute_enabled'] = self.instance_executable(batch)
 | 
			
		||||
        if kwargs['execute_enabled'] and self.has_execution_options(batch):
 | 
			
		||||
            kwargs['rendered_execution_options'] = self.render_execution_options(batch)
 | 
			
		||||
        if kwargs['execute_enabled']:
 | 
			
		||||
            url = self.get_action_url('execute', batch)
 | 
			
		||||
            kwargs['execute_form'] = self.make_execute_form(batch, action_url=url)
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def allow_worksheet(self, batch):
 | 
			
		||||
| 
						 | 
				
			
			@ -179,22 +184,12 @@ class BatchMasterView(MasterView):
 | 
			
		|||
        return batch.id_str
 | 
			
		||||
 | 
			
		||||
    def template_kwargs_index(self, **kwargs):
 | 
			
		||||
        kwargs['execute_enabled'] = self.instance_executable(None)
 | 
			
		||||
        if kwargs['execute_enabled'] and self.has_execution_options():
 | 
			
		||||
            kwargs['rendered_execution_options'] = self.render_execution_options()
 | 
			
		||||
        route_prefix = self.get_route_prefix()
 | 
			
		||||
        if self.results_executable:
 | 
			
		||||
            url = self.request.route_url('{}.execute_results'.format(route_prefix))
 | 
			
		||||
            kwargs['execute_form'] = self.make_execute_form(action_url=url)
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def render_execution_options(self, batch=None):
 | 
			
		||||
        if batch is None:
 | 
			
		||||
            batch = self.model_class
 | 
			
		||||
        form = self.make_execution_options_form(batch)
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'batch': batch,
 | 
			
		||||
            'form': forms.FormRenderer(form),
 | 
			
		||||
        }
 | 
			
		||||
        kwargs = self.get_exec_options_kwargs(**kwargs)
 | 
			
		||||
        return self.render('exec_options', kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_exec_options_kwargs(self, **kwargs):
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -422,10 +417,6 @@ class BatchMasterView(MasterView):
 | 
			
		|||
    def template_kwargs_edit(self, **kwargs):
 | 
			
		||||
        batch = kwargs['instance']
 | 
			
		||||
        kwargs['batch'] = batch
 | 
			
		||||
        kwargs['execute_title'] = self.get_execute_title(batch)
 | 
			
		||||
        kwargs['execute_enabled'] = self.instance_executable(batch)
 | 
			
		||||
        if kwargs['execute_enabled'] and self.has_execution_options(batch):
 | 
			
		||||
            kwargs['rendered_execution_options'] = self.render_execution_options(batch)
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def mobile_mark_complete(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -609,19 +600,28 @@ class BatchMasterView(MasterView):
 | 
			
		|||
    # TODO
 | 
			
		||||
    execution_options_schema = None
 | 
			
		||||
 | 
			
		||||
    def make_execution_options_form(self, batch=None):
 | 
			
		||||
    def make_execute_schema(self):
 | 
			
		||||
        return self.execution_options_schema()
 | 
			
		||||
 | 
			
		||||
    def make_execute_form(self, batch=None, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Return a proper Form for execution options.
 | 
			
		||||
        """
 | 
			
		||||
        if batch is None:
 | 
			
		||||
            batch = self.model_class
 | 
			
		||||
        defaults = {}
 | 
			
		||||
        for field in self.execution_options_schema.fields:
 | 
			
		||||
            key = 'batch.{}.execute_option.{}'.format(batch.batch_key, field)
 | 
			
		||||
            if key in self.request.session:
 | 
			
		||||
                defaults[field] = self.request.session[key]
 | 
			
		||||
        return Form(self.request, schema=self.execution_options_schema,
 | 
			
		||||
                    defaults=defaults or None)
 | 
			
		||||
        route_prefix = self.get_route_prefix()
 | 
			
		||||
 | 
			
		||||
        if self.has_execution_options(batch):
 | 
			
		||||
            if batch is None:
 | 
			
		||||
                batch = self.model_class
 | 
			
		||||
            schema = self.make_execute_schema()
 | 
			
		||||
            for field in schema:
 | 
			
		||||
                key = 'batch.{}.execute_option.{}'.format(batch.batch_key, field.name)
 | 
			
		||||
                if key in self.request.session:
 | 
			
		||||
                    defaults[field.name] = self.request.session[key]
 | 
			
		||||
        else:
 | 
			
		||||
            schema = colander.Schema()
 | 
			
		||||
 | 
			
		||||
        return forms.Form(schema=schema, request=self.request, defaults=defaults, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_execute_title(self, batch):
 | 
			
		||||
        if hasattr(self.handler, 'get_execute_title'):
 | 
			
		||||
| 
						 | 
				
			
			@ -855,7 +855,8 @@ class BatchMasterView(MasterView):
 | 
			
		|||
        """
 | 
			
		||||
        batch = self.get_instance()
 | 
			
		||||
        query = self.get_effective_row_data(sort=False)
 | 
			
		||||
        batch.rowcount -= query.count()
 | 
			
		||||
        if batch.rowcount is not None:
 | 
			
		||||
            batch.rowcount -= query.count()
 | 
			
		||||
        query.update({'removed': True}, synchronize_session=False)
 | 
			
		||||
        return self.redirect(self.get_action_url('view', batch))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -865,17 +866,14 @@ class BatchMasterView(MasterView):
 | 
			
		|||
        displays a progress indicator page.
 | 
			
		||||
        """
 | 
			
		||||
        batch = self.get_instance()
 | 
			
		||||
        if self.request.method == 'POST':
 | 
			
		||||
        self.executing = True
 | 
			
		||||
        form = self.make_execute_form(batch)
 | 
			
		||||
        if form.validate(newstyle=True):
 | 
			
		||||
            kwargs = dict(form.validated)
 | 
			
		||||
 | 
			
		||||
            kwargs = {}
 | 
			
		||||
            if self.has_execution_options(batch):
 | 
			
		||||
                form = self.make_execution_options_form(batch)
 | 
			
		||||
                assert form.validate() # TODO
 | 
			
		||||
                kwargs.update(form.data)
 | 
			
		||||
 | 
			
		||||
                # cache options to use as defaults next time
 | 
			
		||||
                for key, value in form.data.iteritems():
 | 
			
		||||
                    self.request.session['batch.{}.execute_option.{}'.format(batch.batch_key, key)] = value
 | 
			
		||||
            # cache options to use as defaults next time
 | 
			
		||||
            for key, value in form.validated.items():
 | 
			
		||||
                self.request.session['batch.{}.execute_option.{}'.format(batch.batch_key, key)] = value
 | 
			
		||||
 | 
			
		||||
            key = '{}.execute'.format(self.model_class.__tablename__)
 | 
			
		||||
            progress = SessionProgress(self.request, key)
 | 
			
		||||
| 
						 | 
				
			
			@ -888,7 +886,7 @@ class BatchMasterView(MasterView):
 | 
			
		|||
                'cancel_msg': "Batch execution was canceled.",
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        self.request.session.flash("Sorry, you must POST to execute a batch.", 'error')
 | 
			
		||||
        self.request.session.flash("Invalid request: {}".format(form.make_deform_form().error), 'error')
 | 
			
		||||
        return self.redirect(self.get_action_url('view', batch))
 | 
			
		||||
 | 
			
		||||
    def execute_error_message(self, error):
 | 
			
		||||
| 
						 | 
				
			
			@ -949,18 +947,13 @@ class BatchMasterView(MasterView):
 | 
			
		|||
        Starts a separate thread for the execution, and displays a progress
 | 
			
		||||
        indicator page.
 | 
			
		||||
        """
 | 
			
		||||
        if self.request.method == 'POST':
 | 
			
		||||
        form = self.make_execute_form()
 | 
			
		||||
        if form.validate(newstyle=True):
 | 
			
		||||
            kwargs = dict(form.validated)
 | 
			
		||||
 | 
			
		||||
            kwargs = {}
 | 
			
		||||
            if self.has_execution_options():
 | 
			
		||||
                form = self.make_execution_options_form()
 | 
			
		||||
                if not form.validate():
 | 
			
		||||
                    raise RuntimeError("Execution options form did not validate")
 | 
			
		||||
                kwargs.update(form.data)
 | 
			
		||||
 | 
			
		||||
                # cache options to use as defaults next time
 | 
			
		||||
                for key, value in form.data.iteritems():
 | 
			
		||||
                    self.request.session['batch.{}.execute_option.{}'.format(self.model_class.batch_key, key)] = value
 | 
			
		||||
            # cache options to use as defaults next time
 | 
			
		||||
            for key, value in form.validated.items():
 | 
			
		||||
                self.request.session['batch.{}.execute_option.{}'.format(self.model_class.batch_key, key)] = value
 | 
			
		||||
 | 
			
		||||
            key = '{}.execute_results'.format(self.model_class.__tablename__)
 | 
			
		||||
            batches = self.get_effective_data()
 | 
			
		||||
| 
						 | 
				
			
			@ -974,7 +967,7 @@ class BatchMasterView(MasterView):
 | 
			
		|||
                'cancel_msg': "Batch execution was canceled",
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
        self.request.session.flash("Sorry, you must POST to execute batches", 'error')
 | 
			
		||||
        self.request.session.flash("Invalid request: {}".format(form.make_deform_form().error), 'error')
 | 
			
		||||
        return self.redirect(self.get_index_url())
 | 
			
		||||
 | 
			
		||||
    def execute_results_thread(self, batches, user_uuid, progress=None, **kwargs):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,9 +31,10 @@ import os
 | 
			
		|||
from rattail.db import model
 | 
			
		||||
from rattail.util import OrderedDict
 | 
			
		||||
 | 
			
		||||
import formencode as fe
 | 
			
		||||
import colander
 | 
			
		||||
from webhelpers2.html import tags
 | 
			
		||||
 | 
			
		||||
from tailbone import forms2 as forms
 | 
			
		||||
from tailbone.db import Session
 | 
			
		||||
from tailbone.views.batch import FileBatchMasterView
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,10 +45,12 @@ ACTION_OPTIONS = OrderedDict([
 | 
			
		|||
])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExecutionOptions(fe.Schema):
 | 
			
		||||
    allow_extra_fields = True
 | 
			
		||||
    filter_extra_fields = True
 | 
			
		||||
    action = fe.validators.OneOf(ACTION_OPTIONS)
 | 
			
		||||
class ExecutionOptions(colander.Schema):
 | 
			
		||||
 | 
			
		||||
    action = colander.SchemaNode(
 | 
			
		||||
        colander.String(),
 | 
			
		||||
        validator=colander.OneOf(ACTION_OPTIONS),
 | 
			
		||||
        widget=forms.widgets.PlainSelectWidget(values=ACTION_OPTIONS.items()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HandheldBatchView(FileBatchMasterView):
 | 
			
		||||
| 
						 | 
				
			
			@ -207,6 +210,9 @@ class HandheldBatchView(FileBatchMasterView):
 | 
			
		|||
        return super(HandheldBatchView, self).get_execute_success_url(batch)
 | 
			
		||||
 | 
			
		||||
    def get_execute_results_success_url(self, result, **kwargs):
 | 
			
		||||
        if result is True:
 | 
			
		||||
            # no batches were actually executed
 | 
			
		||||
            return self.get_index_url()
 | 
			
		||||
        batch = result
 | 
			
		||||
        return self.get_execute_success_url(batch, result, **kwargs)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,6 +95,7 @@ class MasterView(View):
 | 
			
		|||
    viewing = False
 | 
			
		||||
    editing = False
 | 
			
		||||
    deleting = False
 | 
			
		||||
    executing = False
 | 
			
		||||
    has_pk_fields = False
 | 
			
		||||
 | 
			
		||||
    row_attrs = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								tailbone/views/vendors/catalogs.py
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								tailbone/views/vendors/catalogs.py
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -58,23 +58,26 @@ class VendorCatalogsView(FileBatchMasterView):
 | 
			
		|||
    rows_bulk_deletable = True
 | 
			
		||||
 | 
			
		||||
    grid_columns = [
 | 
			
		||||
        'id',
 | 
			
		||||
        'created',
 | 
			
		||||
        'created_by',
 | 
			
		||||
        'vendor',
 | 
			
		||||
        'effective',
 | 
			
		||||
        'filename',
 | 
			
		||||
        'rowcount',
 | 
			
		||||
        'executed',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    form_fields = [
 | 
			
		||||
        'id',
 | 
			
		||||
        'vendor',
 | 
			
		||||
        'filename',
 | 
			
		||||
        'effective',
 | 
			
		||||
        'created',
 | 
			
		||||
        'created_by',
 | 
			
		||||
        'rowcount',
 | 
			
		||||
        'executed',
 | 
			
		||||
        'executed_by',
 | 
			
		||||
        'rowcount',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    row_grid_columns = [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue