Fix several issues
Fix error '... is not a table or foreign table' Fix view (colors + oe_highlight) Fix tests Fix README + manifest Fix back to draft Fix cron call + default values Use Postgres version 9.6 for travis buildspull/389/head
parent
5cf60df708
commit
6a1caf24b4
|
@ -1,4 +1,4 @@
|
||||||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
:alt: License: AGPL-3
|
:alt: License: AGPL-3
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'BI SQL Editor',
|
'name': 'BI SQL Editor',
|
||||||
'summary': "BI Views builder, based on Materialized or Normal SQL Views",
|
'summary': 'BI Views builder, based on Materialized or Normal SQL Views',
|
||||||
'version': '10.0.1.0.0',
|
'version': '10.0.1.0.0',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'category': 'Reporting',
|
'category': 'Reporting',
|
||||||
'author': 'GRAP,Odoo Community Association (OCA)',
|
'author': 'GRAP,Odoo Community Association (OCA)',
|
||||||
'website': 'https://www.odoo-community.org',
|
'website': 'https://github.com/OCA/reporting-engine',
|
||||||
'depends': [
|
'depends': [
|
||||||
|
'base',
|
||||||
'sql_request_abstract',
|
'sql_request_abstract',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
|
|
|
@ -13,6 +13,22 @@ from odoo.exceptions import UserError
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(models.AbstractModel):
|
||||||
|
_inherit = 'base'
|
||||||
|
|
||||||
|
@api.model_cr_context
|
||||||
|
def _auto_init(self):
|
||||||
|
if self._name.startswith(BiSQLView._model_prefix):
|
||||||
|
self._auto = False
|
||||||
|
return super(BaseModel, self)._auto_init()
|
||||||
|
|
||||||
|
@api.model_cr_context
|
||||||
|
def _auto_end(self):
|
||||||
|
if self._name.startswith(BiSQLView._model_prefix):
|
||||||
|
self._foreign_keys = set()
|
||||||
|
return super(BaseModel, self)._auto_end()
|
||||||
|
|
||||||
|
|
||||||
class BiSQLView(models.Model):
|
class BiSQLView(models.Model):
|
||||||
_name = 'bi.sql.view'
|
_name = 'bi.sql.view'
|
||||||
_inherit = ['sql.request.mixin']
|
_inherit = ['sql.request.mixin']
|
||||||
|
@ -161,10 +177,7 @@ class BiSQLView(models.Model):
|
||||||
# Overload Section
|
# Overload Section
|
||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
non_draft_views = self.search([
|
if any(view.state not in ('draft', 'sql_valid') for view in self):
|
||||||
('id', 'in', self.ids),
|
|
||||||
('state', 'not in', ('draft', 'sql_valid'))])
|
|
||||||
if non_draft_views:
|
|
||||||
raise UserError(_("You can only unlink draft views"))
|
raise UserError(_("You can only unlink draft views"))
|
||||||
return super(BiSQLView, self).unlink()
|
return super(BiSQLView, self).unlink()
|
||||||
|
|
||||||
|
@ -201,6 +214,15 @@ class BiSQLView(models.Model):
|
||||||
@api.multi
|
@api.multi
|
||||||
def button_set_draft(self):
|
def button_set_draft(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
|
sql_view.menu_id.unlink()
|
||||||
|
sql_view.action_id.unlink()
|
||||||
|
sql_view.tree_view_id.unlink()
|
||||||
|
sql_view.graph_view_id.unlink()
|
||||||
|
sql_view.pivot_view_id.unlink()
|
||||||
|
sql_view.search_view_id.unlink()
|
||||||
|
if sql_view.cron_id:
|
||||||
|
sql_view.cron_id.unlink()
|
||||||
|
|
||||||
if sql_view.state in ('model_valid', 'ui_valid'):
|
if sql_view.state in ('model_valid', 'ui_valid'):
|
||||||
# Drop SQL View (and indexes by cascade)
|
# Drop SQL View (and indexes by cascade)
|
||||||
if sql_view.is_materialized:
|
if sql_view.is_materialized:
|
||||||
|
@ -209,14 +231,6 @@ class BiSQLView(models.Model):
|
||||||
# Drop ORM
|
# Drop ORM
|
||||||
sql_view._drop_model_and_fields()
|
sql_view._drop_model_and_fields()
|
||||||
|
|
||||||
sql_view.tree_view_id.unlink()
|
|
||||||
sql_view.graph_view_id.unlink()
|
|
||||||
sql_view.pivot_view_id.unlink()
|
|
||||||
sql_view.search_view_id.unlink()
|
|
||||||
sql_view.action_id.unlink()
|
|
||||||
sql_view.menu_id.unlink()
|
|
||||||
if sql_view.cron_id:
|
|
||||||
sql_view.cron_id.unlink()
|
|
||||||
sql_view.write({'state': 'draft', 'has_group_changed': False})
|
sql_view.write({'state': 'draft', 'has_group_changed': False})
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -293,7 +307,8 @@ class BiSQLView(models.Model):
|
||||||
'name': _('Refresh Materialized View %s') % (self.view_name),
|
'name': _('Refresh Materialized View %s') % (self.view_name),
|
||||||
'user_id': SUPERUSER_ID,
|
'user_id': SUPERUSER_ID,
|
||||||
'model': 'bi.sql.view',
|
'model': 'bi.sql.view',
|
||||||
'function': 'button_refresh_materialized_view',
|
'function': '_refresh_materialized_view_cron',
|
||||||
|
'numbercall': -1,
|
||||||
'args': repr(([self.id],))
|
'args': repr(([self.id],))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +462,7 @@ class BiSQLView(models.Model):
|
||||||
self._prepare_rule()).id
|
self._prepare_rule()).id
|
||||||
# Drop table, created by the ORM
|
# Drop table, created by the ORM
|
||||||
req = "DROP TABLE %s" % (sql_view.view_name)
|
req = "DROP TABLE %s" % (sql_view.view_name)
|
||||||
self.env.cr.execute(req)
|
self._log_execute(req)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _create_model_access(self):
|
def _create_model_access(self):
|
||||||
|
@ -467,7 +482,7 @@ class BiSQLView(models.Model):
|
||||||
if sql_view.rule_id:
|
if sql_view.rule_id:
|
||||||
sql_view.rule_id.unlink()
|
sql_view.rule_id.unlink()
|
||||||
if sql_view.model_id:
|
if sql_view.model_id:
|
||||||
sql_view.model_id.unlink()
|
sql_view.model_id.with_context(_force_unlink=True).unlink()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _hook_executed_request(self):
|
def _hook_executed_request(self):
|
||||||
|
@ -481,7 +496,7 @@ class BiSQLView(models.Model):
|
||||||
AND NOT attisdropped
|
AND NOT attisdropped
|
||||||
AND attnum > 0
|
AND attnum > 0
|
||||||
ORDER BY attnum;""" % (self.view_name)
|
ORDER BY attnum;""" % (self.view_name)
|
||||||
self.env.cr.execute(req)
|
self._log_execute(req)
|
||||||
return self.env.cr.fetchall()
|
return self.env.cr.fetchall()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -548,6 +563,11 @@ class BiSQLView(models.Model):
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _refresh_materialized_view_cron(self, view_ids):
|
||||||
|
sql_views = self.browse(view_ids)
|
||||||
|
return sql_views._refresh_materialized_view()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _refresh_materialized_view(self):
|
def _refresh_materialized_view(self):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
|
@ -568,7 +588,7 @@ class BiSQLView(models.Model):
|
||||||
for sql_view in self:
|
for sql_view in self:
|
||||||
req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
|
req = "SELECT pg_size_pretty(pg_total_relation_size('%s'));" % (
|
||||||
sql_view.view_name)
|
sql_view.view_name)
|
||||||
self.env.cr.execute(req)
|
self._log_execute(req)
|
||||||
sql_view.size = self.env.cr.fetchone()[0]
|
sql_view.size = self.env.cr.fetchone()[0]
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|
|
@ -2,56 +2,68 @@
|
||||||
# Copyright 2017 Onestein (<http://www.onestein.eu>)
|
# Copyright 2017 Onestein (<http://www.onestein.eu>)
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
from odoo.tests.common import TransactionCase, at_install, post_install
|
from odoo.tests.common import SingleTransactionCase, at_install, post_install
|
||||||
from odoo.exceptions import AccessError
|
from odoo.exceptions import AccessError, UserError
|
||||||
|
|
||||||
|
|
||||||
class TestBiSqlViewEditor(TransactionCase):
|
@at_install(False)
|
||||||
|
@post_install(True)
|
||||||
|
class TestBiSqlViewEditor(SingleTransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
super(TestBiSqlViewEditor, self).setUp()
|
def setUpClass(cls):
|
||||||
self.res_partner = self.env['res.partner']
|
super(TestBiSqlViewEditor, cls).setUpClass()
|
||||||
self.res_users = self.env['res.users']
|
|
||||||
self.bi_sql_view = self.env['bi.sql.view']
|
cls.res_partner = cls.env['res.partner']
|
||||||
self.group_bi_user = self.env.ref(
|
cls.res_users = cls.env['res.users']
|
||||||
|
cls.bi_sql_view = cls.env['bi.sql.view']
|
||||||
|
cls.group_bi_user = cls.env.ref(
|
||||||
'sql_request_abstract.group_sql_request_manager')
|
'sql_request_abstract.group_sql_request_manager')
|
||||||
self.group_user = self.env.ref(
|
cls.group_user = cls.env.ref(
|
||||||
'base.group_user')
|
'base.group_user')
|
||||||
self.view = self.bi_sql_view.create({
|
cls.view = cls.bi_sql_view.create({
|
||||||
'name': 'Partners View 2',
|
'name': 'Partners View 2',
|
||||||
'is_materialized': False,
|
'is_materialized': True,
|
||||||
'technical_name': 'partners_view_2',
|
'technical_name': 'partners_view_2',
|
||||||
'query': "SELECT name as x_name, street as x_street,"
|
'query': "SELECT name as x_name, street as x_street,"
|
||||||
"company_id as x_company_id FROM res_partner "
|
"company_id as x_company_id FROM res_partner "
|
||||||
"ORDER BY name"
|
"ORDER BY name"
|
||||||
})
|
})
|
||||||
self.company = self.env.ref('base.main_company')
|
cls.company = cls.env.ref('base.main_company')
|
||||||
# Create bi user
|
# Create bi user
|
||||||
self.bi_user = self._create_user('bi_user', [self.group_bi_user],
|
cls.bi_user = cls._create_user('bi_user', cls.group_bi_user,
|
||||||
self.company)
|
cls.company)
|
||||||
self.no_bi_user = self._create_user('no_bi_user', [self.group_user],
|
cls.no_bi_user = cls._create_user('no_bi_user', cls.group_user,
|
||||||
self.company)
|
cls.company)
|
||||||
|
|
||||||
def _create_user(self, login, groups, company):
|
@classmethod
|
||||||
|
def _create_user(cls, login, groups, company):
|
||||||
"""Create a user."""
|
"""Create a user."""
|
||||||
group_ids = [group.id for group in groups]
|
user = cls.res_users.create({
|
||||||
user = self.res_users.create({
|
|
||||||
'name': 'Test BI User',
|
'name': 'Test BI User',
|
||||||
'login': login,
|
'login': login,
|
||||||
'password': 'demo',
|
'password': 'demo',
|
||||||
'email': 'example@yourcompany.com',
|
'email': 'example@yourcompany.com',
|
||||||
|
'notify_email': 'none',
|
||||||
'company_id': company.id,
|
'company_id': company.id,
|
||||||
'groups_id': [(6, 0, group_ids)]
|
'groups_id': [(6, 0, groups.ids)]
|
||||||
})
|
})
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@at_install(False)
|
|
||||||
@post_install(True)
|
|
||||||
def test_process_view(self):
|
def test_process_view(self):
|
||||||
view = self.view
|
view = self.view
|
||||||
self.assertEqual(view.state, 'draft', 'state not draft')
|
self.assertEqual(view.state, 'draft', 'state not draft')
|
||||||
view.button_validate_sql_expression()
|
view.button_validate_sql_expression()
|
||||||
self.assertEqual(view.state, 'sql_valid', 'state not sql_valid')
|
self.assertEqual(view.state, 'sql_valid', 'state not sql_valid')
|
||||||
|
view.button_create_sql_view_and_model()
|
||||||
|
self.assertEqual(view.state, 'model_valid', 'state not model_valid')
|
||||||
|
view.button_create_ui()
|
||||||
|
self.assertEqual(view.state, 'ui_valid', 'state not ui_valid')
|
||||||
|
view.button_update_model_access()
|
||||||
|
self.assertEqual(view.has_group_changed, False,
|
||||||
|
'has_group_changed not False')
|
||||||
|
cron_res = view.cron_id.method_direct_trigger()
|
||||||
|
self.assertEqual(cron_res, True, 'something went wrong with the cron')
|
||||||
|
|
||||||
def test_copy(self):
|
def test_copy(self):
|
||||||
copy_view = self.view.copy()
|
copy_view = self.view.copy()
|
||||||
|
@ -68,8 +80,10 @@ class TestBiSqlViewEditor(TransactionCase):
|
||||||
'bi %s' % self.view.name)
|
'bi %s' % self.view.name)
|
||||||
|
|
||||||
def test_unlink(self):
|
def test_unlink(self):
|
||||||
self.assertEqual(self.view.state, 'draft', 'state not draft')
|
self.assertEqual(self.view.state, 'ui_valid', 'state not ui_valid')
|
||||||
self.view.button_validate_sql_expression()
|
with self.assertRaises(UserError):
|
||||||
|
self.view.unlink()
|
||||||
|
self.view.button_set_draft()
|
||||||
self.view.unlink()
|
self.view.unlink()
|
||||||
res = self.bi_sql_view.search([('name', '=', 'Partners View 2')])
|
res = self.bi_sql_view.search([('name', '=', 'Partners View 2')])
|
||||||
self.assertEqual(len(res), 0, 'View not deleted')
|
self.assertEqual(len(res), 0, 'View not deleted')
|
||||||
|
|
|
@ -10,7 +10,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
<record id="view_bi_sql_view_tree" model="ir.ui.view">
|
<record id="view_bi_sql_view_tree" model="ir.ui.view">
|
||||||
<field name="model">bi.sql.view</field>
|
<field name="model">bi.sql.view</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree colors="blue:state=='draft'; brown:state in ('sql_valid', 'model_valid')">
|
<tree decoration-info="state=='draft'" decoration-warning="state in ('sql_valid', 'model_valid')">
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="technical_name"/>
|
<field name="technical_name"/>
|
||||||
<field name="size"/>
|
<field name="size"/>
|
||||||
|
@ -29,7 +29,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
<button name="button_set_draft" type="object" states="sql_valid,model_valid,ui_valid"
|
<button name="button_set_draft" type="object" states="sql_valid,model_valid,ui_valid"
|
||||||
string="Set to Draft" groups="sql_request_abstract.group_sql_request_manager"
|
string="Set to Draft" groups="sql_request_abstract.group_sql_request_manager"
|
||||||
confirm="Are you sure you want to set to draft this SQL View. It will delete the materialized view, and all the previous mapping realized with the columns"/>
|
confirm="Are you sure you want to set to draft this SQL View. It will delete the materialized view, and all the previous mapping realized with the columns"/>
|
||||||
<button name="button_preview_sql_expression" type="object" states="draft" string="Preview SQL Expression" class="oe_highlight"/>
|
<button name="button_preview_sql_expression" type="object" states="draft" string="Preview SQL Expression" />
|
||||||
<button name="button_create_sql_view_and_model" type="object" states="sql_valid"
|
<button name="button_create_sql_view_and_model" type="object" states="sql_valid"
|
||||||
string="Create SQL View, Indexes and Models" class="oe_highlight"
|
string="Create SQL View, Indexes and Models" class="oe_highlight"
|
||||||
help="This will try to create an SQL View, based on the SQL request and the according Transient Model and fields, based on settings"/>
|
help="This will try to create an SQL View, based on the SQL request and the according Transient Model and fields, based on settings"/>
|
||||||
|
@ -70,7 +70,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
</page>
|
</page>
|
||||||
<page string="SQL Fields" attrs="{'invisible': [('state', '=', 'draft')]}">
|
<page string="SQL Fields" attrs="{'invisible': [('state', '=', 'draft')]}">
|
||||||
<field name="bi_sql_view_field_ids" nolabel="1" colspan="4" attrs="{'readonly': [('state', '!=', 'sql_valid')]}">
|
<field name="bi_sql_view_field_ids" nolabel="1" colspan="4" attrs="{'readonly': [('state', '!=', 'sql_valid')]}">
|
||||||
<tree editable="bottom" colors="blue:field_description==False">
|
<tree editable="bottom" decoration-info="field_description==False">
|
||||||
<field name="sequence"/>
|
<field name="sequence"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="sql_type"/>
|
<field name="sql_type"/>
|
||||||
|
|
Loading…
Reference in New Issue