You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.8 KiB
236 lines
7.8 KiB
# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
|
|
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
import re
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class BiSQLViewField(models.Model):
|
|
_name = 'bi.sql.view.field'
|
|
_description = 'Bi SQL View Field'
|
|
_order = 'sequence'
|
|
|
|
_TTYPE_SELECTION = [
|
|
('boolean', 'boolean'),
|
|
('char', 'char'),
|
|
('date', 'date'),
|
|
('datetime', 'datetime'),
|
|
('float', 'float'),
|
|
('integer', 'integer'),
|
|
('many2one', 'many2one'),
|
|
('selection', 'selection'),
|
|
]
|
|
|
|
_GRAPH_TYPE_SELECTION = [
|
|
('col', 'Column'),
|
|
('row', 'Row'),
|
|
('measure', 'Measure'),
|
|
]
|
|
|
|
_TREE_VISIBILITY_SELECTION = [
|
|
('unavailable', 'Unavailable'),
|
|
('hidden', 'Hidden'),
|
|
('available', 'Available'),
|
|
]
|
|
|
|
# Mapping to guess Odoo field type, from SQL column type
|
|
_SQL_MAPPING = {
|
|
'boolean': 'boolean',
|
|
'bigint': 'integer',
|
|
'integer': 'integer',
|
|
'double precision': 'float',
|
|
'numeric': 'float',
|
|
'text': 'char',
|
|
'character varying': 'char',
|
|
'date': 'date',
|
|
'timestamp without time zone': 'datetime',
|
|
}
|
|
|
|
name = fields.Char(string='Name', required=True, readonly=True)
|
|
|
|
sql_type = fields.Char(
|
|
string='SQL Type', required=True, readonly=True,
|
|
help="SQL Type in the database")
|
|
|
|
sequence = fields.Integer(string='sequence', required=True, readonly=True)
|
|
|
|
bi_sql_view_id = fields.Many2one(
|
|
string='SQL View', comodel_name='bi.sql.view', ondelete='cascade')
|
|
|
|
is_index = fields.Boolean(
|
|
string='Is Index', help="Check this box if you want to create"
|
|
" an index on that field. This is recommended for searchable and"
|
|
" groupable fields, to reduce duration")
|
|
|
|
is_group_by = fields.Boolean(
|
|
string='Is Group by', help="Check this box if you want to create"
|
|
" a 'group by' option in the search view")
|
|
|
|
index_name = fields.Char(
|
|
string='Index Name', compute='_compute_index_name')
|
|
|
|
graph_type = fields.Selection(
|
|
string='Graph Type', selection=_GRAPH_TYPE_SELECTION)
|
|
|
|
tree_visibility = fields.Selection(
|
|
string='Tree Visibility', selection=_TREE_VISIBILITY_SELECTION,
|
|
default='available', required=True)
|
|
|
|
field_description = fields.Char(
|
|
string='Field Description', help="This will be used as the name"
|
|
" of the Odoo field, displayed for users")
|
|
|
|
ttype = fields.Selection(
|
|
string='Field Type', selection=_TTYPE_SELECTION, help="Type of the"
|
|
" 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")
|
|
|
|
selection = fields.Text(
|
|
string='Selection Options', default='[]',
|
|
help="For 'Selection' Odoo field.\n"
|
|
" List of options, specified as a Python expression defining a list of"
|
|
" (key, label) pairs. For example:"
|
|
" [('blue','Blue'), ('yellow','Yellow')]")
|
|
|
|
many2one_model_id = fields.Many2one(
|
|
comodel_name='ir.model', string='Model',
|
|
help="For 'Many2one' Odoo field.\n"
|
|
" Comodel of the field.")
|
|
|
|
# Constrains Section
|
|
@api.constrains('is_index')
|
|
@api.multi
|
|
def _check_index_materialized(self):
|
|
for rec in self.filtered(lambda x: x.is_index):
|
|
if not rec.bi_sql_view_id.is_materialized:
|
|
raise UserError(_(
|
|
'You can not create indexes on non materialized views'))
|
|
|
|
# Compute Section
|
|
@api.multi
|
|
def _compute_index_name(self):
|
|
for sql_field in self:
|
|
sql_field.index_name = '%s_%s' % (
|
|
sql_field.bi_sql_view_id.view_name, sql_field.name)
|
|
|
|
# Overload Section
|
|
@api.multi
|
|
def create(self, vals):
|
|
field_without_prefix = vals['name'][2:]
|
|
# guess field description
|
|
field_description = re.sub(
|
|
r'\w+', lambda m: m.group(0).capitalize(),
|
|
field_without_prefix.replace('_id', '').replace('_', ' '))
|
|
|
|
# Guess ttype
|
|
# Don't execute as simple .get() in the dict to manage
|
|
# correctly the type 'character varying(x)'
|
|
ttype = False
|
|
for k, v in self._SQL_MAPPING.items():
|
|
if k in vals['sql_type']:
|
|
ttype = v
|
|
|
|
# Guess many2one_model_id
|
|
many2one_model_id = False
|
|
if vals['sql_type'] == 'integer' and(
|
|
vals['name'][-3:] == '_id'):
|
|
ttype = 'many2one'
|
|
model_name = self._model_mapping().get(field_without_prefix, '')
|
|
many2one_model_id = self.env['ir.model'].search(
|
|
[('model', '=', model_name)]).id
|
|
|
|
vals.update({
|
|
'ttype': ttype,
|
|
'field_description': field_description,
|
|
'many2one_model_id': many2one_model_id,
|
|
})
|
|
return super(BiSQLViewField, self).create(vals)
|
|
|
|
# Custom Section
|
|
@api.model
|
|
def _model_mapping(self):
|
|
"""Return dict of key value, to try to guess the model based on a
|
|
field name. Sample :
|
|
{'account_id': 'account.account'; 'product_id': 'product.product'}
|
|
"""
|
|
relation_fields = self.env['ir.model.fields'].search([
|
|
('ttype', '=', 'many2one')])
|
|
res = {}
|
|
keys_to_pop = []
|
|
for field in relation_fields:
|
|
if field.name in res and res.get(field.name) != field.relation:
|
|
# The field name is not predictive
|
|
keys_to_pop.append(field.name)
|
|
else:
|
|
res.update({field.name: field.relation})
|
|
|
|
for key in list(set(keys_to_pop)):
|
|
res.pop(key)
|
|
|
|
return res
|
|
|
|
@api.multi
|
|
def _prepare_model_field(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': self.name,
|
|
'field_description': self.field_description,
|
|
'model_id': self.bi_sql_view_id.model_id.id,
|
|
'ttype': self.ttype,
|
|
'selection': self.ttype == 'selection' and self.selection or False,
|
|
'relation': self.ttype == 'many2one' and
|
|
self.many2one_model_id.model or False,
|
|
}
|
|
|
|
@api.multi
|
|
def _prepare_tree_field(self):
|
|
self.ensure_one()
|
|
res = ''
|
|
if self.field_description and self.tree_visibility != 'unavailable':
|
|
res = """<field name="{}" {}/>""".format(
|
|
self.name,
|
|
self.tree_visibility == 'hidden' and 'invisible="1"' or '')
|
|
return res
|
|
|
|
@api.multi
|
|
def _prepare_graph_field(self):
|
|
self.ensure_one()
|
|
res = ''
|
|
if self.graph_type and self.field_description:
|
|
res = """<field name="{}" type="{}" />""".format(
|
|
self.name, self.graph_type)
|
|
return res
|
|
|
|
@api.multi
|
|
def _prepare_pivot_field(self):
|
|
self.ensure_one()
|
|
res = ''
|
|
if self.graph_type and self.field_description:
|
|
res = """<field name="{}" type="{}" />""".format(
|
|
self.name, self.graph_type)
|
|
return res
|
|
|
|
@api.multi
|
|
def _prepare_search_field(self):
|
|
self.ensure_one()
|
|
res = ''
|
|
if self.field_description:
|
|
res = """<field name="{}"/>""".format(self.name)
|
|
return res
|
|
|
|
@api.multi
|
|
def _prepare_search_filter_field(self):
|
|
self.ensure_one()
|
|
res = ''
|
|
if self.field_description and self.is_group_by:
|
|
res = \
|
|
"""<filter name="%s" string="%s"
|
|
context="{'group_by':'%s'}"/>""" % (
|
|
self.field_description.lower().replace(' ', '_'),
|
|
self.field_description, self.name
|
|
)
|
|
return res
|