From c6ea593670d0ae807d05c8fa5acce01d06bebf8c Mon Sep 17 00:00:00 2001 From: aheficent Date: Mon, 17 Jul 2017 17:14:49 +0200 Subject: [PATCH] [IMP] Add test + several fixes --- bi_sql_editor/README.rst | 7 ++- bi_sql_editor/__init__.py | 1 + bi_sql_editor/__openerp__.py | 5 +- bi_sql_editor/demo/bi_sql_view.xml | 59 ------------------ bi_sql_editor/demo/bi_sql_view_demo.xml | 59 ++++++++++++++++++ bi_sql_editor/demo/res_groups.xml | 18 ------ bi_sql_editor/demo/res_groups_demo.xml | 18 ++++++ bi_sql_editor/hooks.py | 12 ++++ bi_sql_editor/models/bi_sql_view.py | 69 +++++++++++++++------ bi_sql_editor/models/bi_sql_view_field.py | 6 +- bi_sql_editor/tests/__init__.py | 3 + bi_sql_editor/tests/test_bi_sql_view.py | 75 +++++++++++++++++++++++ bi_sql_editor/views/view_bi_sql_view.xml | 3 +- 13 files changed, 228 insertions(+), 107 deletions(-) delete mode 100644 bi_sql_editor/demo/bi_sql_view.xml create mode 100644 bi_sql_editor/demo/bi_sql_view_demo.xml delete mode 100644 bi_sql_editor/demo/res_groups.xml create mode 100644 bi_sql_editor/demo/res_groups_demo.xml create mode 100644 bi_sql_editor/hooks.py create mode 100644 bi_sql_editor/tests/__init__.py create mode 100644 bi_sql_editor/tests/test_bi_sql_view.py diff --git a/bi_sql_editor/README.rst b/bi_sql_editor/README.rst index 01f21e17..2eb6b3fe 100644 --- a/bi_sql_editor/README.rst +++ b/bi_sql_editor/README.rst @@ -77,7 +77,8 @@ To configure this module, you need to: * Click on the button 'Create SQL View, Indexes and Models'. (this step could take a while, if view is materialized) -* If it's a MATERIALIZED view: +* If it's a MATERIALIZED view: + * a cron task is created to refresh the view. You can so define the frequency of the refresh. * the size of view (and the indexes is displayed) @@ -93,9 +94,9 @@ Usage To use this module, you need to: -* Go to 'Reporting' / 'Custom Reports' +#. Go to 'Reporting' / 'Custom Reports' -* select the desired report +#. Select the desired report .. figure:: /bi_sql_editor/static/description/05_reporting_pivot.png :width: 800 px diff --git a/bi_sql_editor/__init__.py b/bi_sql_editor/__init__.py index cde864ba..e4f76a9e 100644 --- a/bi_sql_editor/__init__.py +++ b/bi_sql_editor/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import models +from .hooks import uninstall_hook diff --git a/bi_sql_editor/__openerp__.py b/bi_sql_editor/__openerp__.py index a69a1cad..edd4bf5f 100644 --- a/bi_sql_editor/__openerp__.py +++ b/bi_sql_editor/__openerp__.py @@ -21,8 +21,9 @@ 'views/menu.xml', ], 'demo': [ - 'demo/res_groups.xml', - 'demo/bi_sql_view.xml', + 'demo/res_groups_demo.xml', + 'demo/bi_sql_view_demo.xml', ], 'installable': True, + 'uninstall_hook': 'uninstall_hook' } diff --git a/bi_sql_editor/demo/bi_sql_view.xml b/bi_sql_editor/demo/bi_sql_view.xml deleted file mode 100644 index 75231916..00000000 --- a/bi_sql_editor/demo/bi_sql_view.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - Draft Incorrect SQL View - incorrect_view - - - - - - Partners View - partners_view - - - - - - - - Modules by Authors - modules_view - - - - - - - - - - - - diff --git a/bi_sql_editor/demo/bi_sql_view_demo.xml b/bi_sql_editor/demo/bi_sql_view_demo.xml new file mode 100644 index 00000000..7758110b --- /dev/null +++ b/bi_sql_editor/demo/bi_sql_view_demo.xml @@ -0,0 +1,59 @@ + + + + + + + + Draft Incorrect SQL View + incorrect_view + + + + + + Partners View + partners_view + + + + + + Modules by Authors + modules_view + + + + + + + + + + + + + diff --git a/bi_sql_editor/demo/res_groups.xml b/bi_sql_editor/demo/res_groups.xml deleted file mode 100644 index 2e9eadeb..00000000 --- a/bi_sql_editor/demo/res_groups.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - diff --git a/bi_sql_editor/demo/res_groups_demo.xml b/bi_sql_editor/demo/res_groups_demo.xml new file mode 100644 index 00000000..0a3803c5 --- /dev/null +++ b/bi_sql_editor/demo/res_groups_demo.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/bi_sql_editor/hooks.py b/bi_sql_editor/hooks.py new file mode 100644 index 00000000..972135e9 --- /dev/null +++ b/bi_sql_editor/hooks.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-2017 Onestein () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import SUPERUSER_ID +from openerp.api import Environment + + +def uninstall_hook(cr, registry): + env = Environment(cr, SUPERUSER_ID, {}) + recs = env['bi.sql.view'].search([]) + for rec in recs: + rec.button_set_draft() diff --git a/bi_sql_editor/models/bi_sql_view.py b/bi_sql_editor/models/bi_sql_view.py index 685276a5..dffa9b57 100644 --- a/bi_sql_editor/models/bi_sql_view.py +++ b/bi_sql_editor/models/bi_sql_view.py @@ -27,14 +27,14 @@ class BiSQLView(models.Model): _STATE_SQL_EDITOR = [ ('model_valid', 'SQL View and Model Created'), - ('ui_valid', 'Graph, action and Menu Created'), + ('ui_valid', 'Views, Action and Menu Created'), ] technical_name = fields.Char( string='Technical Name', required=True, - help="Suffix of the SQL view. (SQL full name will be computed and" - " prefixed by 'x_bi_sql_view_'. Should have correct" - "syntax. For more information, see https://www.postgresql.org/" + help="Suffix of the SQL view. SQL full name will be computed and" + " prefixed by 'x_bi_sql_view_'. Syntax should follow: " + "https://www.postgresql.org/" "docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS") view_name = fields.Char( @@ -59,6 +59,14 @@ class BiSQLView(models.Model): state = fields.Selection(selection_add=_STATE_SQL_EDITOR) + view_order = fields.Char(string='View Order', + required=True, + readonly=False, + states={'ui_valid': [('readonly', True)]}, + default="pivot,graph,tree", + help='Comma-separated text. Possible values:' + ' "graph", "pivot" or "tree"') + query = fields.Text( help="SQL Request that will be inserted as the view. Take care to :\n" " * set a name for all your selected fields, specially if you use" @@ -113,6 +121,16 @@ class BiSQLView(models.Model): rule_id = fields.Many2one( string='Odoo Rule', comodel_name='ir.rule', readonly=True) + @api.constrains('view_order') + @api.multi + def _check_view_order(self): + for rec in self: + if rec.view_order: + for vtype in rec.view_order.split(','): + if vtype not in ('graph', 'pivot', 'tree'): + raise UserError(_( + 'Only graph, pivot or tree views are supported')) + # Compute Section @api.depends('is_materialized') @api.multi @@ -148,7 +166,7 @@ class BiSQLView(models.Model): ('state', 'not in', ('draft', 'sql_valid'))]) if non_draft_views: raise UserError(_("You can only unlink draft views")) - return self.unlink() + return super(BiSQLView, self).unlink() @api.multi def copy(self, default=None): @@ -185,7 +203,8 @@ class BiSQLView(models.Model): for sql_view in self: if sql_view.state in ('model_valid', 'ui_valid'): # Drop SQL View (and indexes by cascade) - sql_view._drop_view() + if sql_view.is_materialized: + sql_view._drop_view() # Drop ORM sql_view._drop_model_and_fields() @@ -233,7 +252,7 @@ class BiSQLView(models.Model): 'type': 'ir.actions.act_window', 'res_model': self.model_id.model, 'search_view_id': self.search_view_id.id, - 'view_mode': 'graph,pivot,tree', + 'view_mode': self.action_id.view_mode, } # Prepare Function @@ -357,12 +376,20 @@ class BiSQLView(models.Model): @api.multi def _prepare_action(self): self.ensure_one() + view_mode = self.view_order + first_view = view_mode.split(',')[0] + if first_view == 'tree': + view_id = self.tree_view_id.id + elif first_view == 'pivot': + view_id = self.pivot_view_id.id + else: + view_id = self.graph_view_id.id return { 'name': self.name, 'res_model': self.model_id.model, 'type': 'ir.actions.act_window', - 'view_mode': 'graph,pivot,tree', - 'view_id': self.graph_view_id.id, + 'view_mode': view_mode, + 'view_id': view_id, 'search_view_id': self.search_view_id.id, } @@ -438,7 +465,8 @@ class BiSQLView(models.Model): @api.multi def _drop_model_and_fields(self): for sql_view in self: - sql_view.model_id.unlink() + if sql_view.model_id: + sql_view.model_id.unlink() @api.multi def _hook_executed_request(self): @@ -522,16 +550,17 @@ class BiSQLView(models.Model): @api.multi def _refresh_materialized_view(self): for sql_view in self: - req = "REFRESH %s VIEW %s" % ( - sql_view.materialized_text, sql_view.view_name) - self._log_execute(req) - sql_view._refresh_size() - if sql_view.action_id: - # Alter name of the action, to display last refresh datetime - # of the materialized view - sql_view.action_id.name = "%s (%s)" % ( - self.name, - datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC"))) + if sql_view.is_materialized: + req = "REFRESH %s VIEW %s" % ( + sql_view.materialized_text, sql_view.view_name) + self._log_execute(req) + sql_view._refresh_size() + if sql_view.action_id: + # Alter name of the action, to display last refresh + # datetime of the materialized view + sql_view.action_id.name = "%s (%s)" % ( + self.name, + datetime.utcnow().strftime(_("%m/%d/%Y %H:%M:%S UTC"))) @api.multi def _refresh_size(self): diff --git a/bi_sql_editor/models/bi_sql_view_field.py b/bi_sql_editor/models/bi_sql_view_field.py index 5c464e13..40ac2436 100644 --- a/bi_sql_editor/models/bi_sql_view_field.py +++ b/bi_sql_editor/models/bi_sql_view_field.py @@ -84,7 +84,7 @@ class BiSQLViewField(models.Model): ttype = fields.Selection( string='Field Type', selection=_TTYPE_SELECTION, help="Type of the" - " Odoo field that will be created. Let empty if you don't want to" + " Odoo field that will be created. Keep empty if you don't want to" " create a new field. If empty, this field will not be displayed" " neither available for search or group by function") @@ -98,7 +98,7 @@ class BiSQLViewField(models.Model): many2one_model_id = fields.Many2one( comodel_name='ir.model', string='Model', help="For 'Many2one' Odoo field.\n" - " Co Model of the field.") + " Comodel of the field.") # Compute Section @api.multi @@ -174,8 +174,6 @@ class BiSQLViewField(models.Model): 'selection': self.ttype == 'selection' and self.selection or False, 'relation': self.ttype == 'many2one' and self.many2one_model_id.model or False, - 'tree_visibility': self.field_description and - 'available' or 'unavailable', } @api.multi diff --git a/bi_sql_editor/tests/__init__.py b/bi_sql_editor/tests/__init__.py new file mode 100644 index 00000000..e01cc721 --- /dev/null +++ b/bi_sql_editor/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_bi_sql_view diff --git a/bi_sql_editor/tests/test_bi_sql_view.py b/bi_sql_editor/tests/test_bi_sql_view.py new file mode 100644 index 00000000..12af27cd --- /dev/null +++ b/bi_sql_editor/tests/test_bi_sql_view.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Onestein () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests.common import TransactionCase, at_install, post_install +from openerp.exceptions import AccessError + + +class TestBiSqlViewEditor(TransactionCase): + + def setUp(self): + super(TestBiSqlViewEditor, self).setUp() + self.res_partner = self.env['res.partner'] + self.res_users = self.env['res.users'] + self.bi_sql_view = self.env['bi.sql.view'] + self.group_bi_user = self.env.ref( + 'sql_request_abstract.group_sql_request_manager') + self.group_user = self.env.ref( + 'base.group_user') + self.view = self.bi_sql_view.create({ + 'name': 'Partners View 2', + 'is_materialized': False, + 'technical_name': 'partners_view_2', + 'query': "SELECT name as x_name, street as x_street," + "company_id as x_company_id FROM res_partner " + "ORDER BY name" + }) + self.company = self.env.ref('base.main_company') + # Create bi user + self.bi_user = self._create_user('bi_user', [self.group_bi_user], + self.company) + self.no_bi_user = self._create_user('no_bi_user', [self.group_user], + self.company) + + def _create_user(self, login, groups, company): + """Create a user.""" + group_ids = [group.id for group in groups] + user = self.res_users.create({ + 'name': 'Test BI User', + 'login': login, + 'password': 'demo', + 'email': 'example@yourcompany.com', + 'company_id': company.id, + 'groups_id': [(6, 0, group_ids)] + }) + return user + + @at_install(False) + @post_install(True) + def test_process_view(self): + view = self.view + self.assertEqual(view.state, 'draft', 'state not draft') + view.button_validate_sql_expression() + self.assertEqual(view.state, 'sql_valid', 'state not sql_valid') + + def test_copy(self): + copy_view = self.view.copy() + self.assertEqual( + copy_view.name, 'Partners View 2 (Copy)', 'Wrong name') + + def test_security(self): + with self.assertRaises(AccessError): + self.bi_sql_view.sudo(self.no_bi_user.id).search( + [('name', '=', 'Partners View 2')]) + bi = self.bi_sql_view.sudo(self.bi_user.id).search( + [('name', '=', 'Partners View 2')]) + self.assertEqual(len(bi), 1, 'Bi user should not have access to ' + 'bi %s' % self.view.name) + + def test_unlink(self): + self.assertEqual(self.view.state, 'draft', 'state not draft') + self.view.button_validate_sql_expression() + self.view.unlink() + res = self.bi_sql_view.search([('name', '=', 'Partners View 2')]) + self.assertEqual(len(res), 0, 'View not deleted') diff --git a/bi_sql_editor/views/view_bi_sql_view.xml b/bi_sql_editor/views/view_bi_sql_view.xml index 210a90d8..14aecd12 100644 --- a/bi_sql_editor/views/view_bi_sql_view.xml +++ b/bi_sql_editor/views/view_bi_sql_view.xml @@ -53,7 +53,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - + +