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.

236 lines
7.8 KiB

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. name = fields.Char(string='Name', required=True, readonly=True)
  44. sql_type = fields.Char(
  45. string='SQL Type', required=True, readonly=True,
  46. help="SQL Type in the database")
  47. sequence = fields.Integer(string='sequence', required=True, readonly=True)
  48. bi_sql_view_id = fields.Many2one(
  49. string='SQL View', comodel_name='bi.sql.view', ondelete='cascade')
  50. is_index = fields.Boolean(
  51. string='Is Index', help="Check this box if you want to create"
  52. " an index on that field. This is recommended for searchable and"
  53. " groupable fields, to reduce duration")
  54. is_group_by = fields.Boolean(
  55. string='Is Group by', help="Check this box if you want to create"
  56. " a 'group by' option in the search view")
  57. index_name = fields.Char(
  58. string='Index Name', compute='_compute_index_name')
  59. graph_type = fields.Selection(
  60. string='Graph Type', selection=_GRAPH_TYPE_SELECTION)
  61. tree_visibility = fields.Selection(
  62. string='Tree Visibility', selection=_TREE_VISIBILITY_SELECTION,
  63. default='available', required=True)
  64. field_description = fields.Char(
  65. string='Field Description', help="This will be used as the name"
  66. " of the Odoo field, displayed for users")
  67. ttype = fields.Selection(
  68. string='Field Type', selection=_TTYPE_SELECTION, help="Type of the"
  69. " Odoo field that will be created. Keep empty if you don't want to"
  70. " create a new field. If empty, this field will not be displayed"
  71. " neither available for search or group by function")
  72. selection = fields.Text(
  73. string='Selection Options', default='[]',
  74. help="For 'Selection' Odoo field.\n"
  75. " List of options, specified as a Python expression defining a list of"
  76. " (key, label) pairs. For example:"
  77. " [('blue','Blue'), ('yellow','Yellow')]")
  78. many2one_model_id = fields.Many2one(
  79. comodel_name='ir.model', string='Model',
  80. help="For 'Many2one' Odoo field.\n"
  81. " Comodel of the field.")
  82. # Constrains Section
  83. @api.constrains('is_index')
  84. @api.multi
  85. def _check_index_materialized(self):
  86. for rec in self.filtered(lambda x: x.is_index):
  87. if not rec.bi_sql_view_id.is_materialized:
  88. raise UserError(_(
  89. 'You can not create indexes on non materialized views'))
  90. # Compute Section
  91. @api.multi
  92. def _compute_index_name(self):
  93. for sql_field in self:
  94. sql_field.index_name = '%s_%s' % (
  95. sql_field.bi_sql_view_id.view_name, sql_field.name)
  96. # Overload Section
  97. @api.multi
  98. def create(self, vals):
  99. field_without_prefix = vals['name'][2:]
  100. # guess field description
  101. field_description = re.sub(
  102. r'\w+', lambda m: m.group(0).capitalize(),
  103. field_without_prefix.replace('_id', '').replace('_', ' '))
  104. # Guess ttype
  105. # Don't execute as simple .get() in the dict to manage
  106. # correctly the type 'character varying(x)'
  107. ttype = False
  108. for k, v in self._SQL_MAPPING.items():
  109. if k in vals['sql_type']:
  110. ttype = v
  111. # Guess many2one_model_id
  112. many2one_model_id = False
  113. if vals['sql_type'] == 'integer' and(
  114. vals['name'][-3:] == '_id'):
  115. ttype = 'many2one'
  116. model_name = self._model_mapping().get(field_without_prefix, '')
  117. many2one_model_id = self.env['ir.model'].search(
  118. [('model', '=', model_name)]).id
  119. vals.update({
  120. 'ttype': ttype,
  121. 'field_description': field_description,
  122. 'many2one_model_id': many2one_model_id,
  123. })
  124. return super(BiSQLViewField, self).create(vals)
  125. # Custom Section
  126. @api.model
  127. def _model_mapping(self):
  128. """Return dict of key value, to try to guess the model based on a
  129. field name. Sample :
  130. {'account_id': 'account.account'; 'product_id': 'product.product'}
  131. """
  132. relation_fields = self.env['ir.model.fields'].search([
  133. ('ttype', '=', 'many2one')])
  134. res = {}
  135. keys_to_pop = []
  136. for field in relation_fields:
  137. if field.name in res and res.get(field.name) != field.relation:
  138. # The field name is not predictive
  139. keys_to_pop.append(field.name)
  140. else:
  141. res.update({field.name: field.relation})
  142. for key in list(set(keys_to_pop)):
  143. res.pop(key)
  144. return res
  145. @api.multi
  146. def _prepare_model_field(self):
  147. self.ensure_one()
  148. return {
  149. 'name': self.name,
  150. 'field_description': self.field_description,
  151. 'model_id': self.bi_sql_view_id.model_id.id,
  152. 'ttype': self.ttype,
  153. 'selection': self.ttype == 'selection' and self.selection or False,
  154. 'relation': self.ttype == 'many2one' and
  155. self.many2one_model_id.model or False,
  156. }
  157. @api.multi
  158. def _prepare_tree_field(self):
  159. self.ensure_one()
  160. res = ''
  161. if self.field_description and self.tree_visibility != 'unavailable':
  162. res = """<field name="{}" {}/>""".format(
  163. self.name,
  164. self.tree_visibility == 'hidden' and 'invisible="1"' or '')
  165. return res
  166. @api.multi
  167. def _prepare_graph_field(self):
  168. self.ensure_one()
  169. res = ''
  170. if self.graph_type and self.field_description:
  171. res = """<field name="{}" type="{}" />""".format(
  172. self.name, self.graph_type)
  173. return res
  174. @api.multi
  175. def _prepare_pivot_field(self):
  176. self.ensure_one()
  177. res = ''
  178. if self.graph_type and self.field_description:
  179. res = """<field name="{}" type="{}" />""".format(
  180. self.name, self.graph_type)
  181. return res
  182. @api.multi
  183. def _prepare_search_field(self):
  184. self.ensure_one()
  185. res = ''
  186. if self.field_description:
  187. res = """<field name="{}"/>""".format(self.name)
  188. return res
  189. @api.multi
  190. def _prepare_search_filter_field(self):
  191. self.ensure_one()
  192. res = ''
  193. if self.field_description and self.is_group_by:
  194. res = \
  195. """<filter name="%s" string="%s"
  196. context="{'group_by':'%s'}"/>""" % (
  197. self.field_description.lower().replace(' ', '_'),
  198. self.field_description, self.name
  199. )
  200. return res