From cfeb10038edbac672c3955ab88adea2a52954cb8 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Wed, 22 Feb 2017 03:58:01 +0100 Subject: [PATCH] [REF] create a new module sql_request_abstract --- sql_export/README.rst | 25 ++-- sql_export/__openerp__.py | 40 +++--- sql_export/demo/sql_export.xml | 17 +++ sql_export/i18n/fr.po | 136 ++++++++++++++++---- sql_export/i18n/sql_export.pot | 110 ++++++++++++++-- sql_export/models/sql_export.py | 88 +------------ sql_export/security/ir.model.access.csv | 2 +- sql_export/security/sql_export_security.xml | 5 - sql_export/tests/test_sql_query.py | 56 ++++---- sql_export/views/sql_export_view.xml | 54 +++++--- sql_export/wizard/wizard_file.py | 40 +++--- sql_export/wizard/wizard_file_view.xml | 2 - 12 files changed, 351 insertions(+), 224 deletions(-) create mode 100644 sql_export/demo/sql_export.xml diff --git a/sql_export/README.rst b/sql_export/README.rst index 12e64e49d..bb734679d 100644 --- a/sql_export/README.rst +++ b/sql_export/README.rst @@ -12,16 +12,21 @@ A new menu named Export is created. Known issues / Roadmap ====================== -Some words are prohibeted and can't be used is the query in anyways, even in a select query : - -* delete -* drop -* insert -* alter -* truncate -* execute -* create -* update +* Some words are prohibeted and can't be used is the query in anyways, even in a select query : + * delete + * drop + * insert + * alter + * truncate + * execute + * create + * update + +See sql_request_abstract module to fix this issue. + +* checking SQL request by execution and rollback is disabled in this module + since variables features has been introduced. This can be fixed by + overloading _prepare_request_check_execution() function. Bug Tracker diff --git a/sql_export/__openerp__.py b/sql_export/__openerp__.py index ec6a00d30..853739166 100644 --- a/sql_export/__openerp__.py +++ b/sql_export/__openerp__.py @@ -19,21 +19,25 @@ # ############################################################################## -{'name': 'SQL Export', - 'version': '9.0.1.0.0', - 'author': 'Akretion,Odoo Community Association (OCA)', - 'website': 'http://www.akretion.com', - 'license': 'AGPL-3', - 'category': 'Generic Modules/Others', - 'summary': 'Export data in csv file with SQL requests', - 'depends': ['base', - ], - 'data': [ - 'views/sql_export_view.xml', - 'wizard/wizard_file_view.xml', - 'security/sql_export_security.xml', - 'security/ir.model.access.csv', - ], - 'installable': True, - 'images': [], - } +{ + 'name': 'SQL Export', + 'version': '9.0.1.0.0', + 'author': 'Akretion,Odoo Community Association (OCA)', + 'website': 'http://www.akretion.com', + 'license': 'AGPL-3', + 'category': 'Generic Modules/Others', + 'summary': 'Export data in csv file with SQL requests', + 'depends': [ + 'sql_request_abstract', + ], + 'data': [ + 'views/sql_export_view.xml', + 'wizard/wizard_file_view.xml', + 'security/sql_export_security.xml', + 'security/ir.model.access.csv', + ], + 'demo': [ + 'demo/sql_export.xml', + ], + 'installable': True, + } diff --git a/sql_export/demo/sql_export.xml b/sql_export/demo/sql_export.xml new file mode 100644 index 000000000..70499c9f8 --- /dev/null +++ b/sql_export/demo/sql_export.xml @@ -0,0 +1,17 @@ + + + + + + + Export Partners (Demo Data) + SELECT name, street FROM res_partner; + + + + + diff --git a/sql_export/i18n/fr.po b/sql_export/i18n/fr.po index d1b7ff6e7..9e8255483 100644 --- a/sql_export/i18n/fr.po +++ b/sql_export/i18n/fr.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 8.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-02-05 13:10+0000\n" -"PO-Revision-Date: 2016-02-05 13:10+0000\n" +"POT-Creation-Date: 2017-02-27 12:18+0000\n" +"PO-Revision-Date: 2017-02-27 12:18+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -21,6 +21,7 @@ msgid "Allow the user to save the file with sql request's data" msgstr "Permet à l'utilisateur de sauvegarder le fichier contenant les données de la requête SQL" #. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form #: field:sql.export,group_ids:0 msgid "Allowed Groups" msgstr "Groupes Autorisés" @@ -31,15 +32,20 @@ msgstr "Groupes Autorisés" msgid "Allowed Users" msgstr "Utilisateurs Autorisés" +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Cancel" +msgstr "Annuler" + #. module: sql_export #: view:sql.export:sql_export.sql_export_view_form -msgid "Allowed Users Groups" -msgstr "Groupes d'utilisateurs Autorisés" +msgid "Clean and Check Request" +msgstr "Corriger et vérifier la requête" #. module: sql_export #: field:sql.export,copy_options:0 msgid "Copy Options" -msgstr "Copy Options" +msgstr "Options de copie" #. module: sql_export #: field:sql.export,create_uid:0 @@ -58,10 +64,32 @@ msgstr "Créé le" msgid "Csv File" msgstr "Fichier CSV" +#. module: sql_export +#: field:sql.export,display_name:0 +#: field:sql.file.wizard,display_name:0 +msgid "Display Name" +msgstr "Nom affiché" + +#. module: sql_export +#: selection:sql.export,state:0 +msgid "Draft" +msgstr "En brouillon" + #. module: sql_export #: view:sql.export:sql_export.sql_export_view_form +#: view:sql.export:sql_export.sql_export_view_tree msgid "Execute Query" -msgstr "Exécuter" +msgstr "Execute la requête" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Export" +msgstr "Exporter" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Export file" +msgstr "Fichier d'export" #. module: sql_export #: field:sql.file.wizard,binary_file:0 @@ -71,7 +99,7 @@ msgstr "Fichier" #. module: sql_export #: field:sql.file.wizard,file_name:0 msgid "File Name" -msgstr "Nom du fichier" +msgstr "Nom de fichier" #. module: sql_export #: field:sql.export,id:0 @@ -79,39 +107,78 @@ msgstr "Nom du fichier" msgid "ID" msgstr "ID" +#. module: sql_export +#: field:sql.export,__last_update:0 +#: field:sql.file.wizard,__last_update:0 +msgid "Last Modified on" +msgstr "Dernière modification le" + #. module: sql_export #: field:sql.export,write_uid:0 #: field:sql.file.wizard,write_uid:0 msgid "Last Updated by" -msgstr "Last Updated by" +msgstr "Dernière mise à jour par" #. module: sql_export #: field:sql.export,write_date:0 #: field:sql.file.wizard,write_date:0 msgid "Last Updated on" -msgstr "Last Updated on" +msgstr "Dernière mise à jour le" #. module: sql_export #: field:sql.export,name:0 msgid "Name" msgstr "Nom" +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +#: field:sql.export,field_ids:0 +msgid "Parameters" +msgstr "Paramètres" + #. module: sql_export #: field:sql.export,query:0 msgid "Query" msgstr "Requête" +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "Request Name" +msgstr "Nom de la requête" + #. module: sql_export #: model:ir.actions.act_window,name:sql_export.sql_export_tree_action #: view:sql.export:sql_export.sql_export_view_tree msgid "SQL Export" -msgstr "SQL Export" +msgstr "Export SQL" + +#. module: sql_export +#: model:ir.actions.act_window,name:sql_export.sql_parameter_tree_action +#: view:ir.model.fields:sql_export.sql_parameter_view_tree +msgid "SQL Parameter" +msgstr "Paramètre SQL" + +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "SQL Request" +msgstr "Requête SQL" + +#. module: sql_export +#: selection:sql.export,state:0 +msgid "SQL Valid" +msgstr "SQL Validé" #. module: sql_export #: model:ir.model,name:sql_export.model_sql_export +#: view:ir.model.fields:sql_export.sql_parameter_view_form #: view:sql.export:sql_export.sql_export_view_form msgid "SQL export" -msgstr "Export SQL" +msgstr "export SQL" + +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "Set to Draft" +msgstr "Remettre en brouillon" #. module: sql_export #: model:ir.ui.menu,name:sql_export.sql_export_menu @@ -120,23 +187,46 @@ msgid "Sql Export" msgstr "Export SQL" #. module: sql_export -#: model:res.groups,name:sql_export.group_sql_request_editor -msgid "Sql Request Editor" -msgstr "Edition de Requête SQL" +#: model:ir.ui.menu,name:sql_export.sql_parameter_menu_view +msgid "Sql Export Variables" +msgstr "Variables d'export SQL" #. module: sql_export -#: code:addons/sql_export/sql_export.py:132 -#, python-format -msgid "The Sql query is not valid." -msgstr "La requête SQL n'est pas valide" +#: field:sql.file.wizard,sql_export_id:0 +msgid "Sql export id" +msgstr "Sql export id" #. module: sql_export -#: constraint:sql.export:0 -msgid "The query you want make is not allowed : prohibited actions (delete, drop, insert, alter, truncate, execute, create, update)" -msgstr "La requête que vous voulez faire n'est pas autorisée : actions interdites (delete, drop, insert, alter, truncate, execute, create, update)" +#: field:sql.export,state:0 +msgid "State" +msgstr "Etat" + +#. module: sql_export +#: help:sql.export,state:0 +msgid "State of the Request:\n" +" * 'Draft': Not tested\n" +" * 'SQL Valid': SQL Request has been checked and is valid" +msgstr "Etat de la requête:\n" +" * 'En brouillon': non testée\n" +" * 'SQL Validé': La requête SQL a été vérifiée et est valide" #. module: sql_export #: help:sql.export,query:0 -msgid "You can't use the following word : delete, drop, create, insert, alter, truncate, execute, update" -msgstr "Vous ne pouvez pas utiliser les mots suivants : delete, drop, create, insert, alter, truncate, execute, update" +msgid "You can't use the following words: DELETE, DROP, CREATE, INSERT, ALTER, TRUNCATE, EXECUTE, UPDATE" +msgstr "Vous ne pouvez pas utiliser les termes suivants : DELETE, DROP, CREATE, INSERT, ALTER, TRUNCATE, EXECUTE, UPDATE" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "or" +msgstr "ou" + +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "select * from res_partner" +msgstr "select * from res_partner" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "variables_placeholder" +msgstr "variables_placeholder" diff --git a/sql_export/i18n/sql_export.pot b/sql_export/i18n/sql_export.pot index 39cc40538..acca91d1a 100644 --- a/sql_export/i18n/sql_export.pot +++ b/sql_export/i18n/sql_export.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 8.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-02-05 13:10+0000\n" -"PO-Revision-Date: 2016-02-05 13:10+0000\n" +"POT-Creation-Date: 2017-02-27 12:24+0000\n" +"PO-Revision-Date: 2017-02-27 12:24+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -21,6 +21,7 @@ msgid "Allow the user to save the file with sql request's data" msgstr "" #. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form #: field:sql.export,group_ids:0 msgid "Allowed Groups" msgstr "" @@ -31,9 +32,14 @@ msgstr "" msgid "Allowed Users" msgstr "" +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Cancel" +msgstr "" + #. module: sql_export #: view:sql.export:sql_export.sql_export_view_form -msgid "Allowed Users Groups" +msgid "Clean and Check Request" msgstr "" #. module: sql_export @@ -58,11 +64,33 @@ msgstr "" msgid "Csv File" msgstr "" +#. module: sql_export +#: field:sql.export,display_name:0 +#: field:sql.file.wizard,display_name:0 +msgid "Display Name" +msgstr "" + +#. module: sql_export +#: selection:sql.export,state:0 +msgid "Draft" +msgstr "" + #. module: sql_export #: view:sql.export:sql_export.sql_export_view_form +#: view:sql.export:sql_export.sql_export_view_tree msgid "Execute Query" msgstr "" +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Export" +msgstr "" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "Export file" +msgstr "" + #. module: sql_export #: field:sql.file.wizard,binary_file:0 msgid "File" @@ -79,6 +107,12 @@ msgstr "" msgid "ID" msgstr "" +#. module: sql_export +#: field:sql.export,__last_update:0 +#: field:sql.file.wizard,__last_update:0 +msgid "Last Modified on" +msgstr "" + #. module: sql_export #: field:sql.export,write_uid:0 #: field:sql.file.wizard,write_uid:0 @@ -96,23 +130,56 @@ msgstr "" msgid "Name" msgstr "" +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +#: field:sql.export,field_ids:0 +msgid "Parameters" +msgstr "" + #. module: sql_export #: field:sql.export,query:0 msgid "Query" msgstr "" +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "Request Name" +msgstr "" + #. module: sql_export #: model:ir.actions.act_window,name:sql_export.sql_export_tree_action #: view:sql.export:sql_export.sql_export_view_tree msgid "SQL Export" msgstr "" +#. module: sql_export +#: model:ir.actions.act_window,name:sql_export.sql_parameter_tree_action +#: view:ir.model.fields:sql_export.sql_parameter_view_tree +msgid "SQL Parameter" +msgstr "" + +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "SQL Request" +msgstr "" + +#. module: sql_export +#: selection:sql.export,state:0 +msgid "SQL Valid" +msgstr "" + #. module: sql_export #: model:ir.model,name:sql_export.model_sql_export +#: view:ir.model.fields:sql_export.sql_parameter_view_form #: view:sql.export:sql_export.sql_export_view_form msgid "SQL export" msgstr "" +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "Set to Draft" +msgstr "" + #. module: sql_export #: model:ir.ui.menu,name:sql_export.sql_export_menu #: model:ir.ui.menu,name:sql_export.sql_export_menu_view @@ -120,23 +187,44 @@ msgid "Sql Export" msgstr "" #. module: sql_export -#: model:res.groups,name:sql_export.group_sql_request_editor -msgid "Sql Request Editor" +#: model:ir.ui.menu,name:sql_export.sql_parameter_menu_view +msgid "Sql Export Variables" msgstr "" #. module: sql_export -#: code:addons/sql_export/sql_export.py:132 -#, python-format -msgid "The Sql query is not valid." +#: field:sql.file.wizard,sql_export_id:0 +msgid "Sql export id" msgstr "" #. module: sql_export -#: constraint:sql.export:0 -msgid "The query you want make is not allowed : prohibited actions (delete, drop, insert, alter, truncate, execute, create, update)" +#: field:sql.export,state:0 +msgid "State" +msgstr "" + +#. module: sql_export +#: help:sql.export,state:0 +msgid "State of the Request:\n" +" * 'Draft': Not tested\n" +" * 'SQL Valid': SQL Request has been checked and is valid" msgstr "" #. module: sql_export #: help:sql.export,query:0 -msgid "You can't use the following word : delete, drop, create, insert, alter, truncate, execute, update" +msgid "You can't use the following words: DELETE, DROP, CREATE, INSERT, ALTER, TRUNCATE, EXECUTE, UPDATE" +msgstr "" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "or" +msgstr "" + +#. module: sql_export +#: view:sql.export:sql_export.sql_export_view_form +msgid "select * from res_partner" +msgstr "" + +#. module: sql_export +#: view:sql.file.wizard:sql_export.sql_file_wizard_view_form +msgid "variables_placeholder" msgstr "" diff --git a/sql_export/models/sql_export.py b/sql_export/models/sql_export.py index c3964e47c..21a0d2601 100644 --- a/sql_export/models/sql_export.py +++ b/sql_export/models/sql_export.py @@ -19,65 +19,24 @@ # ############################################################################## -import re from openerp import models, fields, api class SqlExport(models.Model): _name = "sql.export" + _inherit = ['sql.request.mixin'] _description = "SQL export" - PROHIBITED_WORDS = [ - 'delete', - 'drop', - 'insert', - 'alter', - 'truncate', - 'execute', - 'create', - 'update' - ] + _sql_request_groups_relation = 'groups_sqlquery_rel' - @api.multi - def _check_query_allowed(self): - for obj in self: - query = obj.query.lower() - for word in self.PROHIBITED_WORDS: - expr = r'\b%s\b' % word - is_not_safe = re.search(expr, query) - if is_not_safe: - return False - return True + _sql_request_users_relation = 'users_sqlquery_rel' - @api.model - def _get_editor_group(self): - ir_model_obj = self.env['ir.model.data'] - return [ir_model_obj.xmlid_to_res_id( - 'sql_export.group_sql_request_editor')] + _check_execution_enabled = False - name = fields.Char('Name', required=True) - query = fields.Text( - 'Query', - required=True, - help="You can't use the following word : delete, drop, create, " - "insert, alter, truncate, execute, update") copy_options = fields.Char( - 'Copy Options', - required=True, + string='Copy Options', required=True, default="CSV HEADER DELIMITER ';'") - group_ids = fields.Many2many( - 'res.groups', - 'groups_sqlquery_rel', - 'sql_id', - 'group_id', - 'Allowed Groups', - default=_get_editor_group) - user_ids = fields.Many2many( - 'res.users', - 'users_sqlquery_rel', - 'sql_id', - 'user_id', - 'Allowed Users') + field_ids = fields.Many2many( 'ir.model.fields', 'fields_sqlquery_rel', @@ -85,18 +44,11 @@ class SqlExport(models.Model): 'field_id', 'Parameters', domain=[('model', '=', 'sql.file.wizard')]) - valid = fields.Boolean() - - _constraints = [(_check_query_allowed, - 'The query you want make is not allowed : prohibited ' - 'actions (%s)' % ', '.join(PROHIBITED_WORDS), - ['query'])] @api.multi def export_sql_query(self): self.ensure_one() wiz = self.env['sql.file.wizard'].create({ - 'valid': self.valid, 'sql_export_id': self.id}) return { 'view_type': 'form', @@ -108,31 +60,3 @@ class SqlExport(models.Model): 'context': self._context, 'nodestroy': True, } - - @api.model - def check_query_syntax(self, vals): - if vals.get('query', False): - vals['query'] = vals['query'].strip() - if vals['query'][-1] == ';': - vals['query'] = vals['query'][:-1] - # Can't test the query because of variables -# try: -# self.env.cr.execute(vals['query']) -# except: -# raise exceptions.Warning( -# _("The Sql query is not valid.")) -# finally: -# self.env.cr.rollback() - return vals - - @api.multi - def write(self, vals): - vals = self.check_query_syntax(vals) - if 'query' in vals: - vals['valid'] = False - return super(SqlExport, self).write(vals) - - @api.model - def create(self, vals): - vals = self.check_query_syntax(vals) - return super(SqlExport, self).create(vals) diff --git a/sql_export/security/ir.model.access.csv b/sql_export/security/ir.model.access.csv index 65b1b713f..cb4a0bb48 100644 --- a/sql_export/security/ir.model.access.csv +++ b/sql_export/security/ir.model.access.csv @@ -1,3 +1,3 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_sql_export_all","access_sql_export_all","model_sql_export",,1,0,0,0 -"access_sql_export_editor","access_sql_export_editor","model_sql_export",group_sql_request_editor,1,1,1,1 +"access_sql_export_editor","access_sql_export_editor","model_sql_export",sql_request_abstract.group_sql_request_manager,1,1,1,1 diff --git a/sql_export/security/sql_export_security.xml b/sql_export/security/sql_export_security.xml index 9338bfc40..af5cea3a4 100644 --- a/sql_export/security/sql_export_security.xml +++ b/sql_export/security/sql_export_security.xml @@ -2,11 +2,6 @@ - - Sql Request Editor - - - SQL Export users and groups rules diff --git a/sql_export/tests/test_sql_query.py b/sql_export/tests/test_sql_query.py index 742a77e24..f1e516dc4 100644 --- a/sql_export/tests/test_sql_query.py +++ b/sql_export/tests/test_sql_query.py @@ -20,35 +20,27 @@ ############################################################################## import base64 from openerp.tests.common import TransactionCase -from openerp import exceptions +from openerp.exceptions import Warning as UserError class TestExportSqlQuery(TransactionCase): def setUp(self): super(TestExportSqlQuery, self).setUp() - query_vals = { - 'name': 'test', - 'query': "SELECT name, street FROM res_partner;" - } - self.sql_model = self.registry('sql.export') - self.query_id = self.sql_model.create( - self.cr, - self.uid, - query_vals) + self.sql_export_obj = self.env['sql.export'] + self.wizard_obj = self.env['sql.file.wizard'] + self.sql_report_demo = self.env.ref('sql_export.sql_export_partner') def test_sql_query(self): - test = self.sql_model.export_sql_query( - self.cr, self.uid, [self.query_id]) - self.registry('sql.file.wizard').export_sql( - self.cr, self.uid, test['res_id']) - wizard = self.registry('sql.file.wizard').browse( - self.cr, self.uid, test['res_id']) + wizard = self.wizard_obj.create({ + 'sql_export_id': self.sql_report_demo.id, + }) + wizard.export_sql() export = base64.b64decode(wizard.binary_file) self.assertEqual(export.split(';')[0], 'name') self.assertTrue(len(export.split(';')) > 6) - def test_prohibited_queries_creation(self): + def test_prohibited_queries(self): prohibited_queries = [ "upDaTe res_partner SET name = 'test' WHERE id = 1", "DELETE FROM sql_export WHERE name = 'test';", @@ -60,14 +52,22 @@ class TestExportSqlQuery(TransactionCase): """, ] for query in prohibited_queries: - with self.assertRaises(exceptions.ValidationError): - self.sql_model.create( - self.cr, self.uid, - {'name': 'test_prohibited', - 'query': query}) - ok_query = { - 'name': 'test ok', - 'query': "SELECT create_date FROM res_partner" - } - query_id = self.sql_model.create(self.cr, self.uid, ok_query) - self.assertIsNotNone(query_id) + with self.assertRaises(UserError): + sql_export = self.sql_export_obj.create({ + 'name': 'test_prohibited', + 'query': query}) + sql_export.button_clean_check_request() + + def test_authorized_queries(self): + authorized_queries = [ + "SELECT create_date FROM res_partner", + ] + + for query in authorized_queries: + sql_export = self.sql_export_obj.create({ + 'name': 'test_authorized', + 'query': query}) + sql_export.button_clean_check_request() + self.assertEqual( + sql_export.state, 'sql_valid', + "%s is a valid request" % (query)) diff --git a/sql_export/views/sql_export_view.xml b/sql_export/views/sql_export_view.xml index 611a269a7..39f2d5deb 100644 --- a/sql_export/views/sql_export_view.xml +++ b/sql_export/views/sql_export_view.xml @@ -8,26 +8,40 @@ sql.export
- - -