OCA reporting engine fork for dev and update.
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.
 
 
 
 
 
 

251 lines
7.9 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")
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
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,
)
# Overload Section
@api.model
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
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,
}
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
def _prepare_graph_field(self):
self.ensure_one()
res = ""
if self.graph_type and self.field_description:
res = """<field name="{}" type="{}" />\n""".format(
self.name, self.graph_type
)
return res
def _prepare_pivot_field(self):
self.ensure_one()
res = ""
if self.field_description:
graph_type_text = self.graph_type and 'type="%s"' % (self.graph_type) or ""
res = """<field name="{}" {} />\n""".format(self.name, graph_type_text)
return res
def _prepare_search_field(self):
self.ensure_one()
res = ""
if self.field_description:
res = """<field name="{}"/>\n""".format(self.name)
return res
def _prepare_search_filter_field(self):
self.ensure_one()
res = ""
if self.field_description and self.is_group_by:
res = """<filter name="group_by_%s" string="%s"
context="{'group_by':'%s'}"/>\n""" % (
self.name,
self.field_description,
self.name,
)
return res