diff --git a/bi_view_editor/models/bve_view.py b/bi_view_editor/models/bve_view.py index 3039da31..58ac76a8 100644 --- a/bi_view_editor/models/bve_view.py +++ b/bi_view_editor/models/bve_view.py @@ -98,6 +98,19 @@ class BveView(models.Model): compute='_compute_users', store=True) query = fields.Text(compute='_compute_sql_query') + over_condition = fields.Text( + states={ + 'draft': [ + ('readonly', False), + ], + }, + readonly=True, + help="Condition to be inserted in the OVER part " + "of the ID's row_number function.\n" + "For instance, 'ORDER BY t1.id' would create " + "IDs ordered in the same way as t1's IDs; otherwise " + "IDs are assigned with no specific order.", + ) er_diagram_image = fields.Binary(compute='_compute_er_diagram_image') _sql_constraints = [ @@ -282,14 +295,23 @@ class BveView(models.Model): self._cr.execute('DROP TABLE IF EXISTS %s', (AsIs(view_name), )) # create postgres view - self.env.cr.execute('CREATE or REPLACE VIEW %s as (%s)', ( - AsIs(view_name), AsIs(query), )) + try: + with self.env.cr.savepoint(): + self.env.cr.execute('CREATE or REPLACE VIEW %s as (%s)', ( + AsIs(view_name), AsIs(query), )) + except Exception as e: + raise UserError( + _("Error creating the view '{query}':\n{error}") + .format( + query=query, + error=e)) - @api.depends('line_ids', 'state') + @api.depends('line_ids', 'state', 'over_condition') def _compute_sql_query(self): for bve_view in self: tables_map = {} - select_str = '\n CAST(row_number() OVER () as integer) AS id' + select_str = '\n CAST(row_number() OVER ({}) as integer) AS id' \ + .format(bve_view.over_condition or '') for line in bve_view.field_ids: table = line.table_alias select = line.field_id.name diff --git a/bi_view_editor/readme/USAGE.rst b/bi_view_editor/readme/USAGE.rst index 986ce305..b243f0ef 100644 --- a/bi_view_editor/readme/USAGE.rst +++ b/bi_view_editor/readme/USAGE.rst @@ -21,3 +21,6 @@ To access the created BI View with a dedicated menu: A more advanced UI is also available under the "Details" tab. It provides extra possibilities for more advanced users, like to use LEFT JOIN instead of the default INNER JOIN. + +It also possible to improve the IDs generation for new views by adding an `Over Condition` in the "SQL" tab, see https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS for further details. +For instance, an ORDER BY clause helps preventing unreliable behavior when filtering the generated views. diff --git a/bi_view_editor/tests/test_bi_view.py b/bi_view_editor/tests/test_bi_view.py index 38e303b8..7404dcd9 100644 --- a/bi_view_editor/tests/test_bi_view.py +++ b/bi_view_editor/tests/test_bi_view.py @@ -5,6 +5,7 @@ import json import odoo from odoo.tests.common import TransactionCase +from odoo.tools import mute_logger from odoo.exceptions import UserError, ValidationError from ..hooks import post_load, uninstall_hook @@ -407,3 +408,24 @@ class TestBiViewEditor(TransactionCase): bi_view1 = self.env['bve.view'].create(vals) bi_view1.action_create() self.assertEqual(len(bi_view1.line_ids), 4) + + @mute_logger('odoo.sql_db') + def test_20_broken_view(self): + """ + Create a broken query, a nice UserError should be raised. + odoo.sql_db logger is muted to avoid the + ERROR: bad_query line in the logs. + """ + vals = self.bi_view1_vals + vals.update({ + 'name': 'Test View broken', + 'over_condition': 'bad SQL code', + }) + bi_view = self.env['bve.view'].create(vals) + with self.assertRaises(UserError) as ue: + bi_view.action_create() + + self.assertEqual(bi_view.state, 'draft') + self.assertIn(bi_view.over_condition, str(ue.exception)) + # remove view + bi_view.unlink() diff --git a/bi_view_editor/views/bve_view.xml b/bi_view_editor/views/bve_view.xml index 2599fc67..101fa0e9 100644 --- a/bi_view_editor/views/bve_view.xml +++ b/bi_view_editor/views/bve_view.xml @@ -85,8 +85,9 @@ + - +