diff --git a/bi_sql_editor/README.rst b/bi_sql_editor/README.rst index aab31bac0a..c4e575dd0b 100644 --- a/bi_sql_editor/README.rst +++ b/bi_sql_editor/README.rst @@ -17,13 +17,13 @@ BI SQL Editor :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github - :target: https://github.com/OCA/reporting-engine/tree/17.0/bi_sql_editor + :target: https://github.com/OCA/reporting-engine/tree/18.0/bi_sql_editor :alt: OCA/reporting-engine .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/reporting-engine-17-0/reporting-engine-17-0-bi_sql_editor + :target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-bi_sql_editor :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=17.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -126,10 +126,10 @@ to make reporting depending on the current companies of the user. - Finally, click on 'Create UI', to create new menu, action, graph view and search view. -.. |image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/01_sql_request.png -.. |image2| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/02_security_access.png -.. |image3| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/03_field_mapping.png -.. |image4| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/04_materialized_view_setting.png +.. |image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/01_sql_request.png +.. |image2| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/02_security_access.png +.. |image3| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/03_field_mapping.png +.. |image4| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/04_materialized_view_setting.png Usage ===== @@ -145,7 +145,7 @@ To use this module, you need to: - You can switch to 'Graph' or 'tree' views as any report. -.. |usage-image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/17.0/bi_sql_editor/static/description/05_reporting_pivot.png +.. |usage-image1| image:: https://raw.githubusercontent.com/OCA/reporting-engine/18.0/bi_sql_editor/static/description/05_reporting_pivot.png Bug Tracker =========== @@ -153,7 +153,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -176,6 +176,8 @@ Contributors - Guillem Casassas guillem.casassas@forgeflow.com +- Thien Vo thienvh@trobz.com + - This module is highly inspired by the work of - Onestein: (http://www.onestein.nl/) Module: @@ -189,6 +191,12 @@ Contributors grap/odoo-addons-misc/pos_sale_reporting link: https://github.com/grap/odoo-addons-misc/tree/7.0/pos_sale_reporting +Other credits +------------- + +The migration of this module from 17.0 to 18.0 was financially supported +by Camptocamp. + Maintainers ----------- @@ -210,6 +218,6 @@ Current `maintainer `__: |maintainer-legalsylvain| -This module is part of the `OCA/reporting-engine `_ project on GitHub. +This module is part of the `OCA/reporting-engine `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/bi_sql_editor/__manifest__.py b/bi_sql_editor/__manifest__.py index 7158a635b6..96f342c8d9 100644 --- a/bi_sql_editor/__manifest__.py +++ b/bi_sql_editor/__manifest__.py @@ -5,7 +5,7 @@ { "name": "BI SQL Editor", "summary": "BI Views builder, based on Materialized or Normal SQL Views", - "version": "17.0.1.1.0", + "version": "18.0.1.0.0", "license": "AGPL-3", "category": "Reporting", "author": "GRAP,Odoo Community Association (OCA)", diff --git a/bi_sql_editor/models/bi_sql_view.py b/bi_sql_editor/models/bi_sql_view.py index b57f6597be..f608e6e783 100644 --- a/bi_sql_editor/models/bi_sql_view.py +++ b/bi_sql_editor/models/bi_sql_view.py @@ -6,10 +6,11 @@ from datetime import datetime, timedelta from psycopg2 import ProgrammingError +from psycopg2.sql import SQL, Identifier from odoo import SUPERUSER_ID, _, api, fields, models from odoo.exceptions import UserError, ValidationError -from odoo.tools import sql, table_columns +from odoo.tools import sql from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -44,14 +45,12 @@ class BiSQLView(models.Model): view_name = fields.Char( compute="_compute_view_name", - readonly=True, store=True, help="Full name of the SQL view", ) model_name = fields.Char( compute="_compute_model_name", - readonly=True, store=True, help="Full Qualified Name of the transient model that will" " be created.", ) @@ -65,7 +64,6 @@ class BiSQLView(models.Model): size = fields.Char( string="Database Size", - readonly=True, help="Size of the materialized view and its indexes", ) @@ -73,8 +71,8 @@ class BiSQLView(models.Model): view_order = fields.Char( required=True, - default="pivot,graph,tree", - help="Comma-separated text. Possible values:" ' "graph", "pivot" or "tree"', + default="pivot,graph,list", + help="Comma-separated text. Possible values:" ' "graph", "pivot" or "list"', ) query = fields.Text( @@ -111,9 +109,7 @@ class BiSQLView(models.Model): inverse_name="bi_sql_view_id", ) - model_id = fields.Many2one( - string="Odoo Model", comodel_name="ir.model", readonly=True - ) + model_id = fields.Many2one(string="Odoo Model", comodel_name="ir.model") # UI related fields # 1. Editable fields, which can be set by the user (optional) before # creating the UI elements @@ -133,39 +129,30 @@ def _default_parent_menu_id(self): ) # 2. Readonly fields, non editable by the user - tree_view_id = fields.Many2one( - string="Odoo Tree View", comodel_name="ir.ui.view", readonly=True - ) + tree_view_id = fields.Many2one(string="Odoo List View", comodel_name="ir.ui.view") - graph_view_id = fields.Many2one( - string="Odoo Graph View", comodel_name="ir.ui.view", readonly=True - ) + graph_view_id = fields.Many2one(string="Odoo Graph View", comodel_name="ir.ui.view") - pivot_view_id = fields.Many2one( - string="Odoo Pivot View", comodel_name="ir.ui.view", readonly=True - ) + pivot_view_id = fields.Many2one(string="Odoo Pivot View", comodel_name="ir.ui.view") search_view_id = fields.Many2one( - string="Odoo Search View", comodel_name="ir.ui.view", readonly=True + string="Odoo Search View", comodel_name="ir.ui.view" ) action_id = fields.Many2one( - string="Odoo Action", comodel_name="ir.actions.act_window", readonly=True + string="Odoo Action", comodel_name="ir.actions.act_window" ) - menu_id = fields.Many2one( - string="Odoo Menu", comodel_name="ir.ui.menu", readonly=True - ) + menu_id = fields.Many2one(string="Odoo Menu", comodel_name="ir.ui.menu") cron_id = fields.Many2one( string="Odoo Cron", comodel_name="ir.cron", - readonly=True, help="Cron Task that will refresh the materialized view", ondelete="cascade", ) - rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule", readonly=True) + rule_id = fields.Many2one(string="Odoo Rule", comodel_name="ir.rule") sequence = fields.Integer(string="sequence") @@ -183,9 +170,9 @@ 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"): + if vtype not in ("graph", "pivot", "list"): raise UserError( - _("Only graph, pivot or tree views are supported") + _("Only graph, pivot or list views are supported") ) # Compute Section @@ -244,15 +231,15 @@ def write(self, vals): rec.menu_id.sequence = rec.sequence return res - def unlink(self): + @api.ondelete(at_uninstall=False) + def _check_unlink_constraints(self): if any(view.state not in ("draft", "sql_valid") for view in self): raise UserError( _( - "You can only unlink draft views." + "You can only unlink draft views. " "If you want to delete them, first set them to draft." ) ) - return super().unlink() def copy(self, default=None): self.ensure_one() @@ -395,8 +382,7 @@ def _prepare_cron(self): .search([("model", "=", self._name)], limit=1) .id, "state": "code", - "code": "model._refresh_materialized_view_cron(%s)" % self.ids, - "numbercall": -1, + "code": f"model._refresh_materialized_view_cron({self.ids})", "interval_number": 1, "interval_type": "days", "nextcall": now + timedelta(days=1), @@ -416,11 +402,11 @@ def _prepare_tree_view(self): self.ensure_one() return { "name": self.name, - "type": "tree", + "type": "list", "model": self.model_id.model, "arch": """""" - """{}""" - """""".format( + """{}""" + """""".format( "".join([x._prepare_tree_field() for x in self.bi_sql_view_field_ids]) ), } @@ -477,7 +463,7 @@ def _prepare_action(self): self.ensure_one() view_mode = self.view_order first_view = view_mode.split(",")[0] - if first_view == "tree": + if first_view == "list": view_id = self.tree_view_id.id elif first_view == "pivot": view_id = self.pivot_view_id.id @@ -510,19 +496,22 @@ def _prepare_menu(self): return { "name": self.name, "parent_id": self.parent_menu_id.id, - "action": "ir.actions.act_window,%s" % self.action_id.id, + "action": f"ir.actions.act_window,{self.action_id.id}", "sequence": self.sequence, } # Custom Section def _log_execute(self, req): - _logger.info("Executing SQL Request %s ..." % req) + _logger.info(f"Executing SQL Request {req} ...") self.env.cr.execute(req) def _drop_view(self): for sql_view in self: self._log_execute( - f"DROP {sql_view.materialized_text} VIEW IF EXISTS {sql_view.view_name}" + SQL("DROP {materialized_text} VIEW IF EXISTS {view_name}").format( + materialized_text=SQL(sql_view.materialized_text), + view_name=Identifier(sql_view.view_name), + ) ) sql_view.size = False @@ -551,8 +540,11 @@ def _create_index(self): lambda x: x.is_index is True ): self._log_execute( - f"CREATE INDEX {sql_field.index_name} ON {sql_view.view_name} " - f"({sql_field.name});" + SQL("CREATE INDEX {index_name} ON {view_name} ({name});").format( + index_name=SQL(sql_field.index_name), + view_name=Identifier(sql_view.view_name), + name=Identifier(sql_field.name), + ) ) def _create_model_and_fields(self): @@ -562,7 +554,7 @@ def _create_model_and_fields(self): sql_view.rule_id = self.env["ir.rule"].create(self._prepare_rule()).id # Drop table, created by the ORM if sql.table_exists(self._cr, sql_view.view_name): - req = "DROP TABLE %s" % sql_view.view_name + req = SQL("DROP TABLE {}").format(Identifier(sql_view.view_name)) self._log_execute(req) def _create_model_access(self): @@ -585,28 +577,29 @@ def _drop_model_and_fields(self): def _hook_executed_request(self): self.ensure_one() - req = ( + req = SQL( """ SELECT attnum, attname AS column, format_type(atttypid, atttypmod) AS type FROM pg_attribute - WHERE attrelid = '%s'::regclass + WHERE attrelid = '{view_name}'::regclass AND NOT attisdropped AND attnum > 0 ORDER BY attnum;""" - % self.view_name - ) + ).format(view_name=Identifier(self.view_name)) self._log_execute(req) return self.env.cr.fetchall() def _prepare_request_check_execution(self): self.ensure_one() - return f"CREATE VIEW {self.view_name} AS ({self.query});" + return SQL("CREATE VIEW {view_name} AS ({query});").format( + view_name=Identifier(self.view_name), query=SQL(self.query) + ) def _prepare_request_for_execution(self): self.ensure_one() - query = ( + query = SQL( """ SELECT CAST(row_number() OVER () as integer) AS id, @@ -616,11 +609,14 @@ def _prepare_request_for_execution(self): CAST(Null as integer) as write_uid, my_query.* FROM - (%s) as my_query + ({}) as my_query """ - % self.query + ).format(SQL(self.query)) + return SQL("CREATE {materialized_text} VIEW {view_name} AS ({query});").format( + materialized_text=SQL(self.materialized_text), + view_name=Identifier(self.view_name), + query=query, ) - return f"CREATE {self.materialized_text} VIEW {self.view_name} AS ({query});" def _check_execution(self): """Ensure that the query is valid, trying to execute it. @@ -689,8 +685,8 @@ def _refresh_materialized_view(self): def _refresh_size(self): for sql_view in self: - req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % ( - sql_view.view_name + req = SQL("SELECT pg_size_pretty(pg_total_relation_size('{}'));").format( + Identifier(sql_view.view_name) ) self._log_execute(req) sql_view.size = self.env.cr.fetchone()[0] @@ -700,7 +696,7 @@ def check_manual_fields(self, model): # early on install / startup - particularly problematic during upgrade if model._name.startswith( self._model_prefix - ) and "group_operator" in table_columns(self.env.cr, "bi_sql_view_field"): + ) and "group_operator" in sql.table_columns(self.env.cr, "bi_sql_view_field"): # Use SQL instead of ORM, as ORM might not be fully initialised - # we have no control over the order that fields are defined! # We are not concerned about user security rules. diff --git a/bi_sql_editor/models/bi_sql_view_field.py b/bi_sql_editor/models/bi_sql_view_field.py index f098f20c28..f8bfe2b92b 100644 --- a/bi_sql_editor/models/bi_sql_view_field.py +++ b/bi_sql_editor/models/bi_sql_view_field.py @@ -149,9 +149,8 @@ def _check_index_materialized(self): # Compute Section def _compute_index_name(self): for sql_field in self: - sql_field.index_name = "{}_{}".format( - sql_field.bi_sql_view_id.view_name, - sql_field.name, + sql_field.index_name = ( + f"{sql_field.bi_sql_view_id.view_name}_{sql_field.name}" ) # Overload Section @@ -209,8 +208,8 @@ def _model_mapping(self): field name. Sample : {'account_id': 'account.account'; 'product_id': 'product.product'} """ - relation_fields = self.env["ir.model.fields"].search( - [("ttype", "=", "many2one")] + relation_fields = ( + self.env["ir.model.fields"].sudo().search([("ttype", "=", "many2one")]) ) res = {} keys_to_pop = [] diff --git a/bi_sql_editor/readme/CONTRIBUTORS.md b/bi_sql_editor/readme/CONTRIBUTORS.md index c787a3c70e..2f6bce1bbe 100644 --- a/bi_sql_editor/readme/CONTRIBUTORS.md +++ b/bi_sql_editor/readme/CONTRIBUTORS.md @@ -6,6 +6,8 @@ - Guillem Casassas +- Thien Vo + - This module is highly inspired by the work of - Onestein: () Module: OCA/server-tools/bi_view_editor. Link: diff --git a/bi_sql_editor/readme/CREDITS.md b/bi_sql_editor/readme/CREDITS.md new file mode 100644 index 0000000000..83b3ec91f7 --- /dev/null +++ b/bi_sql_editor/readme/CREDITS.md @@ -0,0 +1 @@ +The migration of this module from 17.0 to 18.0 was financially supported by Camptocamp. diff --git a/bi_sql_editor/static/description/index.html b/bi_sql_editor/static/description/index.html index d4ee7a4f7d..f5b63b420d 100644 --- a/bi_sql_editor/static/description/index.html +++ b/bi_sql_editor/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -368,7 +369,7 @@

BI SQL Editor

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:1acb226c184032febd7db6e48ee618c7e5a6899c4685aa78b5b2e7578ebd9ce9 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/reporting-engine Translate me on Weblate Try me on Runboat

This module extends the functionality of reporting, to support creation of extra custom reports. It allows user to write a custom SQL request. (Generally, admin users)

@@ -421,10 +422,10 @@

Configuration

  • Go to Dashboard / Configuration / SQL Views

  • tip your SQL request

    -

    image1

    +

    image1

  • Select the group(s) that could have access to the view

    -

    image2

    +

    image2

  • Optionnaly, you can add a domain.

    @@ -438,7 +439,7 @@

    Configuration

    view, and propose field mapping. For each field, you can decide to create an index and set if it will be displayed on the pivot graph as a column, a row or a measure.

    -

    image3

    +

    image3

  • Click on the button ‘Create SQL elements’. (this step could take a while, if view is materialized)

    @@ -451,7 +452,7 @@

    Configuration

  • the size of view (and the indexes is displayed)
  • -

    image4

    +

    image4

  • Before applying the final step, you will need to add a specific Parent Menu to use when creating the UI Menu for the report. By @@ -473,7 +474,7 @@

    Usage

    -usage-image1
    +usage-image1
    • You can switch to ‘Graph’ or ‘tree’ views as any report.
    @@ -483,7 +484,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -503,6 +504,7 @@

    Contributors

  • Richard deMeester, WilldooIT (http://www.willdooit.com/)
  • David James, WilldooIT (http://www.willdooit.com/)
  • Guillem Casassas guillem.casassas@forgeflow.com
  • +
  • Thien Vo thienvh@trobz.com
  • This module is highly inspired by the work of
    • Onestein: (http://www.onestein.nl/) Module: OCA/server-tools/bi_view_editor. Link: @@ -518,16 +520,23 @@

      Contributors

    +
    +

    Other credits

    +

    The migration of this module from 17.0 to 18.0 was financially supported +by Camptocamp.

    +

    Maintainers

    This module is maintained by the OCA.

    -Odoo Community Association + +Odoo Community Association +

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    Current maintainer:

    legalsylvain

    -

    This module is part of the OCA/reporting-engine project on GitHub.

    +

    This module is part of the OCA/reporting-engine project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/bi_sql_editor/tests/test_bi_sql_view.py b/bi_sql_editor/tests/test_bi_sql_view.py index d7999ebfa0..125c8c930e 100644 --- a/bi_sql_editor/tests/test_bi_sql_view.py +++ b/bi_sql_editor/tests/test_bi_sql_view.py @@ -1,6 +1,8 @@ # Copyright 2017 Onestein () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + from odoo.exceptions import AccessError, UserError, ValidationError from odoo.tests import tagged from odoo.tests.common import SingleTransactionCase @@ -73,7 +75,7 @@ def test_security(self): [("name", "=", self.view.name)] ) self.assertEqual( - len(bi), 1, "Bi Manager should have access to bi %s" % self.view.name + len(bi), 1, f"Bi Manager should have access to bi {self.view.name}" ) def test_unlink(self): @@ -90,7 +92,9 @@ def test_unlink(self): self.assertEqual(copy_view.state, "ui_valid") with self.assertRaises(UserError): copy_view.unlink() - copy_view.button_set_draft() + logger_name = "odoo.addons.base.models.ir_model" + with self.assertLogs(logger_name, level=logging.WARNING): + copy_view.button_set_draft() self.assertNotEqual( copy_view.cron_id, False, diff --git a/bi_sql_editor/views/view_bi_sql_view.xml b/bi_sql_editor/views/view_bi_sql_view.xml index 4590e19a14..3c2fa79c2c 100644 --- a/bi_sql_editor/views/view_bi_sql_view.xml +++ b/bi_sql_editor/views/view_bi_sql_view.xml @@ -5,7 +5,6 @@ Copyright (C) 2017 - Today: GRAP (http://www.grap.coop) License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> - bi.sql.view - + @@ -90,7 +89,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - + - + @@ -160,7 +161,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). readonly="state == 'ui_valid'" /> - + @@ -185,16 +186,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - - - - - - + + + + + - - - - - - - + + + + + + - - - - + + + - - - - - - - - - - - - + + + + + + + + + + + @@ -246,7 +243,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). SQL Views ir.actions.act_window bi.sql.view - tree,form + list,form -