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.

265 lines
8.3 KiB

7 years ago
7 years ago
  1. # Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
  2. # @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import re
  5. from odoo import _, api, fields, models
  6. from odoo.exceptions import UserError
  7. class BiSQLViewField(models.Model):
  8. _name = "bi.sql.view.field"
  9. _description = "Bi SQL View Field"
  10. _order = "sequence"
  11. _TTYPE_SELECTION = [
  12. ("boolean", "boolean"),
  13. ("char", "char"),
  14. ("date", "date"),
  15. ("datetime", "datetime"),
  16. ("float", "float"),
  17. ("integer", "integer"),
  18. ("many2one", "many2one"),
  19. ("selection", "selection"),
  20. ]
  21. _GRAPH_TYPE_SELECTION = [
  22. ("col", "Column"),
  23. ("row", "Row"),
  24. ("measure", "Measure"),
  25. ]
  26. _TREE_VISIBILITY_SELECTION = [
  27. ("unavailable", "Unavailable"),
  28. ("hidden", "Hidden"),
  29. ("available", "Available"),
  30. ]
  31. # Mapping to guess Odoo field type, from SQL column type
  32. _SQL_MAPPING = {
  33. "boolean": "boolean",
  34. "bigint": "integer",
  35. "integer": "integer",
  36. "double precision": "float",
  37. "numeric": "float",
  38. "text": "char",
  39. "character varying": "char",
  40. "date": "date",
  41. "timestamp without time zone": "datetime",
  42. }
  43. _GROUP_OPERATOR_SELECTION = [
  44. ("sum", "Sum"),
  45. ("avg", "Average"),
  46. ("min", "Minimum"),
  47. ("max", "Maximum"),
  48. ]
  49. name = fields.Char(string="Name", required=True, readonly=True)
  50. sql_type = fields.Char(
  51. string="SQL Type", required=True, readonly=True, help="SQL Type in the database"
  52. )
  53. sequence = fields.Integer(string="sequence", required=True, readonly=True)
  54. bi_sql_view_id = fields.Many2one(
  55. string="SQL View", comodel_name="bi.sql.view", ondelete="cascade"
  56. )
  57. is_index = fields.Boolean(
  58. string="Is Index",
  59. help="Check this box if you want to create"
  60. " an index on that field. This is recommended for searchable and"
  61. " groupable fields, to reduce duration",
  62. )
  63. is_group_by = fields.Boolean(
  64. string="Is Group by",
  65. help="Check this box if you want to create"
  66. " a 'group by' option in the search view",
  67. )
  68. index_name = fields.Char(string="Index Name", compute="_compute_index_name")
  69. graph_type = fields.Selection(string="Graph Type", selection=_GRAPH_TYPE_SELECTION)
  70. tree_visibility = fields.Selection(
  71. string="Tree Visibility",
  72. selection=_TREE_VISIBILITY_SELECTION,
  73. default="available",
  74. required=True,
  75. )
  76. field_description = fields.Char(
  77. string="Field Description",
  78. help="This will be used as the name" " of the Odoo field, displayed for users",
  79. )
  80. ttype = fields.Selection(
  81. string="Field Type",
  82. selection=_TTYPE_SELECTION,
  83. help="Type of the"
  84. " Odoo field that will be created. Keep empty if you don't want to"
  85. " create a new field. If empty, this field will not be displayed"
  86. " neither available for search or group by function",
  87. )
  88. selection = fields.Text(
  89. string="Selection Options",
  90. default="[]",
  91. help="For 'Selection' Odoo field.\n"
  92. " List of options, specified as a Python expression defining a list of"
  93. " (key, label) pairs. For example:"
  94. " [('blue','Blue'), ('yellow','Yellow')]",
  95. )
  96. many2one_model_id = fields.Many2one(
  97. comodel_name="ir.model",
  98. string="Model",
  99. help="For 'Many2one' Odoo field.\n" " Comodel of the field.",
  100. )
  101. group_operator = fields.Selection(
  102. string="Group Operator",
  103. selection=_GROUP_OPERATOR_SELECTION,
  104. help="By default, Odoo will sum the values when grouping. If you wish "
  105. "to alter the behaviour, choose an alternate Group Operator",
  106. )
  107. # Constrains Section
  108. @api.constrains("is_index")
  109. def _check_index_materialized(self):
  110. for rec in self.filtered(lambda x: x.is_index):
  111. if not rec.bi_sql_view_id.is_materialized:
  112. raise UserError(
  113. _("You can not create indexes on non materialized views")
  114. )
  115. # Compute Section
  116. def _compute_index_name(self):
  117. for sql_field in self:
  118. sql_field.index_name = "{}_{}".format(
  119. sql_field.bi_sql_view_id.view_name,
  120. sql_field.name,
  121. )
  122. # Overload Section
  123. @api.model
  124. def create(self, vals):
  125. field_without_prefix = vals["name"][2:]
  126. # guess field description
  127. field_description = re.sub(
  128. r"\w+",
  129. lambda m: m.group(0).capitalize(),
  130. field_without_prefix.replace("_id", "").replace("_", " "),
  131. )
  132. # Guess ttype
  133. # Don't execute as simple .get() in the dict to manage
  134. # correctly the type 'character varying(x)'
  135. ttype = False
  136. for k, v in self._SQL_MAPPING.items():
  137. if k in vals["sql_type"]:
  138. ttype = v
  139. # Guess many2one_model_id
  140. many2one_model_id = False
  141. if vals["sql_type"] == "integer" and (vals["name"][-3:] == "_id"):
  142. ttype = "many2one"
  143. model_name = self._model_mapping().get(field_without_prefix, "")
  144. many2one_model_id = (
  145. self.env["ir.model"].search([("model", "=", model_name)]).id
  146. )
  147. vals.update(
  148. {
  149. "ttype": ttype,
  150. "field_description": field_description,
  151. "many2one_model_id": many2one_model_id,
  152. }
  153. )
  154. return super(BiSQLViewField, self).create(vals)
  155. # Custom Section
  156. @api.model
  157. def _model_mapping(self):
  158. """Return dict of key value, to try to guess the model based on a
  159. field name. Sample :
  160. {'account_id': 'account.account'; 'product_id': 'product.product'}
  161. """
  162. relation_fields = self.env["ir.model.fields"].search(
  163. [("ttype", "=", "many2one")]
  164. )
  165. res = {}
  166. keys_to_pop = []
  167. for field in relation_fields:
  168. if field.name in res and res.get(field.name) != field.relation:
  169. # The field name is not predictive
  170. keys_to_pop.append(field.name)
  171. else:
  172. res.update({field.name: field.relation})
  173. for key in list(set(keys_to_pop)):
  174. res.pop(key)
  175. return res
  176. def _prepare_model_field(self):
  177. self.ensure_one()
  178. return {
  179. "name": self.name,
  180. "field_description": self.field_description,
  181. "model_id": self.bi_sql_view_id.model_id.id,
  182. "ttype": self.ttype,
  183. "selection": self.ttype == "selection" and self.selection or False,
  184. "relation": self.ttype == "many2one"
  185. and self.many2one_model_id.model
  186. or False,
  187. }
  188. def _prepare_tree_field(self):
  189. self.ensure_one()
  190. res = ""
  191. if self.field_description and self.tree_visibility != "unavailable":
  192. res = """<field name="{}" {}/>""".format(
  193. self.name, self.tree_visibility == "hidden" and 'invisible="1"' or ""
  194. )
  195. return res
  196. def _prepare_graph_field(self):
  197. self.ensure_one()
  198. res = ""
  199. if self.graph_type and self.field_description:
  200. res = """<field name="{}" type="{}" />\n""".format(
  201. self.name, self.graph_type
  202. )
  203. return res
  204. def _prepare_pivot_field(self):
  205. self.ensure_one()
  206. res = ""
  207. if self.field_description:
  208. graph_type_text = self.graph_type and 'type="%s"' % (self.graph_type) or ""
  209. res = """<field name="{}" {} />\n""".format(self.name, graph_type_text)
  210. return res
  211. def _prepare_search_field(self):
  212. self.ensure_one()
  213. res = ""
  214. if self.field_description:
  215. res = """<field name="{}"/>\n""".format(self.name)
  216. return res
  217. def _prepare_search_filter_field(self):
  218. self.ensure_one()
  219. res = ""
  220. if self.field_description and self.is_group_by:
  221. res = """<filter name="group_by_%s" string="%s"
  222. context="{'group_by':'%s'}"/>\n""" % (
  223. self.name,
  224. self.field_description,
  225. self.name,
  226. )
  227. return res