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.

312 lines
11 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2015-2016 ONESTEiN BV (<http://www.onestein.eu>)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import json
  5. from openerp import tools
  6. from openerp import SUPERUSER_ID
  7. from openerp import models, fields, api
  8. from openerp.exceptions import Warning as UserError
  9. from openerp.tools.translate import _
  10. class BveView(models.Model):
  11. _name = 'bve.view'
  12. _description = "BI View Editor"
  13. @api.depends('group_ids')
  14. @api.multi
  15. def _compute_users(self):
  16. for bve_view in self:
  17. if bve_view.sudo().group_ids:
  18. bve_view.user_ids = self.env['res.users'].sudo().browse(
  19. list(set([u.id for group in bve_view.sudo().group_ids
  20. for u in group.users])))
  21. else:
  22. bve_view.user_ids = self.env['res.users'].sudo().search([])
  23. name = fields.Char(size=128, string="Name", required=True)
  24. model_name = fields.Char(size=128, string="Model Name")
  25. note = fields.Text(string="Notes")
  26. state = fields.Selection(
  27. [('draft', 'Draft'),
  28. ('created', 'Created')],
  29. string="State",
  30. default="draft")
  31. data = fields.Text(
  32. string="Data",
  33. help="Use the special query builder to define the query "
  34. "to generate your report dataset. "
  35. "NOTE: Te be edited, the query should be in 'Draft' status.")
  36. action_id = fields.Many2one('ir.actions.act_window', string="Action")
  37. view_id = fields.Many2one('ir.ui.view', string="View")
  38. group_ids = fields.Many2many(
  39. 'res.groups',
  40. string="Groups",
  41. help="User groups allowed to see the generated report; "
  42. "if NO groups are specified the report will be public "
  43. "for everyone.")
  44. user_ids = fields.Many2many(
  45. 'res.users',
  46. string="Users",
  47. compute=_compute_users,
  48. store=True)
  49. _sql_constraints = [
  50. ('name_uniq',
  51. 'unique(name)',
  52. 'Custom BI View names must be unique!'),
  53. ]
  54. @api.multi
  55. def unlink(self):
  56. for view in self:
  57. if view.state == 'created':
  58. raise UserError(
  59. _('You cannot delete a created view! '
  60. 'Reset the view to draft first.'))
  61. super(BveView, self).unlink()
  62. @api.multi
  63. def action_edit_query(self):
  64. return {
  65. 'type': 'ir.actions.client',
  66. 'tag': 'bi_view_editor.open',
  67. 'target': 'new',
  68. 'params': {'bve_view_id': self.id}
  69. }
  70. @api.multi
  71. def action_reset(self):
  72. if self.action_id:
  73. if self.action_id.view_id:
  74. self.action_id.view_id.sudo().unlink()
  75. self.action_id.sudo().unlink()
  76. models = self.env['ir.model'].sudo().search(
  77. [('model', '=', self.model_name)])
  78. for model in models:
  79. model.sudo().unlink()
  80. table_name = self.model_name.replace(".", "_")
  81. tools.drop_view_if_exists(self.env.cr, table_name)
  82. self.write({
  83. 'state': 'draft'
  84. })
  85. return True
  86. def _create_graph_view(self):
  87. fields_info = json.loads(self.data)
  88. return ["""<field name="x_{}" type="{}" />""".format(
  89. field_info['name'],
  90. (field_info['row'] and 'row') or
  91. (field_info['column'] and 'col') or
  92. (field_info['measure'] and 'measure'))
  93. for field_info in fields_info if field_info['row'] or
  94. field_info['column'] or field_info['measure']]
  95. @api.multi
  96. def action_create(self):
  97. def _get_fields_info(fields_data):
  98. fields_info = []
  99. for field_data in fields_data:
  100. field = self.env['ir.model.fields'].browse(field_data["id"])
  101. vals = {
  102. "table": self.env[field.model_id.model]._table,
  103. "table_alias": field_data["table_alias"],
  104. "select_field": field.name,
  105. "as_field": "x_" + field_data["name"],
  106. "join": False,
  107. "model": field.model_id.model
  108. }
  109. if field_data.get("join_node"):
  110. vals.update({"join": field_data["join_node"]})
  111. fields_info.append(vals)
  112. return fields_info
  113. def _build_query():
  114. data = self.data
  115. if not data:
  116. raise UserError(_('No data to process.'))
  117. info = _get_fields_info(json.loads(data))
  118. fields = [("{}.{}".format(f["table_alias"],
  119. f["select_field"]),
  120. f["as_field"]) for f in info if 'join_node' not in f]
  121. tables = set([(f["table"], f["table_alias"]) for f in info])
  122. join_nodes = [
  123. (f["table_alias"],
  124. f["join"],
  125. f["select_field"]) for f in info if f["join"] is not False]
  126. table_name = self.model_name.replace(".", "_")
  127. tools.drop_view_if_exists(self.env.cr, table_name)
  128. basic_fields = [
  129. ("t0.id", "id"),
  130. ("t0.write_uid", "write_uid"),
  131. ("t0.write_date", "write_date"),
  132. ("t0.create_uid", "create_uid"),
  133. ("t0.create_date", "create_date")
  134. ]
  135. q = """CREATE or REPLACE VIEW %s as (
  136. SELECT %s
  137. FROM %s
  138. WHERE %s
  139. )""" % (table_name, ','.join(
  140. ["{} AS {}".format(f[0], f[1])
  141. for f in basic_fields + fields]), ','.join(
  142. ["{} AS {}".format(t[0], t[1])
  143. for t in list(tables)]), " AND ".join(
  144. ["{}.{} = {}.id".format(j[0], j[2], j[1])
  145. for j in join_nodes] + ["TRUE"]))
  146. self.env.cr.execute(q)
  147. def _prepare_field(field_data):
  148. if not field_data["custom"]:
  149. field = self.env['ir.model.fields'].browse(field_data["id"])
  150. vals = {
  151. "name": "x_" + field_data["name"],
  152. "complete_name": field.complete_name,
  153. 'model': self.model_name,
  154. 'relation': field.relation,
  155. "field_description": field_data.get(
  156. "description", field.field_description),
  157. "ttype": field.ttype,
  158. "selection": field.selection,
  159. "size": field.size,
  160. 'state': "manual"
  161. }
  162. if vals['ttype'] == 'monetary':
  163. vals.update({'ttype': 'float'})
  164. if field.ttype == 'selection' and not field.selection:
  165. model_obj = self.env[field.model_id.model]
  166. selection = model_obj._columns[field.name].selection
  167. selection_domain = str(selection)
  168. vals.update({"selection": selection_domain})
  169. return vals
  170. def _prepare_object():
  171. return {
  172. 'name': self.name,
  173. 'model': self.model_name,
  174. 'field_id': [
  175. (0, 0, _prepare_field(field))
  176. for field in json.loads(self.data)
  177. if 'join_node' not in field]
  178. }
  179. def _build_object():
  180. res_id = self.env['ir.model'].sudo().create(_prepare_object())
  181. return res_id
  182. # read access
  183. def group_ids_with_access(model_name, access_mode):
  184. self.env.cr.execute('''SELECT
  185. g.id
  186. FROM
  187. ir_model_access a
  188. JOIN ir_model m ON (a.model_id=m.id)
  189. JOIN res_groups g ON (a.group_id=g.id)
  190. LEFT JOIN ir_module_category c ON (c.id=g.category_id)
  191. WHERE
  192. m.model=%s AND
  193. a.active IS True AND
  194. a.perm_''' + access_mode, (model_name,))
  195. return [x[0] for x in self.env.cr.fetchall()]
  196. def _build_access_rules(obj):
  197. info = json.loads(self.data)
  198. models = list(set([f["model"] for f in info]))
  199. read_groups = set.intersection(*[set(
  200. group_ids_with_access(model, 'read')) for model in models])
  201. for group in read_groups:
  202. self.env['ir.model.access'].sudo().create({
  203. 'name': 'read access to ' + self.model_name,
  204. 'model_id': obj.id,
  205. 'group_id': group,
  206. 'perm_read': True,
  207. })
  208. # edit access
  209. for group in self.group_ids:
  210. self.env['ir.model.access'].sudo().create({
  211. 'name': 'read access to ' + self.model_name,
  212. 'model_id': obj.id,
  213. 'group_id': group.id,
  214. 'perm_read': True,
  215. 'perm_write': True,
  216. })
  217. return
  218. self.model_name = "x_bve." + ''.join(
  219. [x for x in self.name.lower()
  220. if x.isalnum()]).replace("_", ".").replace(" ", ".")
  221. try:
  222. with self.env.cr.savepoint():
  223. _build_query()
  224. obj = _build_object()
  225. _build_access_rules(obj)
  226. except Exception, e:
  227. raise UserError(
  228. _('Generic error. Unable to create the view!'))
  229. from openerp.modules.registry import RegistryManager
  230. self.env.registry = RegistryManager.new(self.env.cr.dbname)
  231. RegistryManager.signal_registry_change(self.env.cr.dbname)
  232. self.pool = self.env.registry
  233. view_id = self.pool.get('ir.ui.view').create(
  234. self.env.cr, SUPERUSER_ID,
  235. {'name': "Analysis",
  236. 'type': 'graph',
  237. 'model': self.model_name,
  238. 'priority': 16,
  239. 'arch': """<?xml version="1.0"?>
  240. <graph string="Analysis"
  241. type="pivot"
  242. stacked="True"> {} </graph>
  243. """.format("".join(self._create_graph_view()))
  244. }, context={})
  245. view_ids = [view_id]
  246. action_vals = {'name': self.name,
  247. 'res_model': self.model_name,
  248. 'type': 'ir.actions.act_window',
  249. 'view_type': 'form',
  250. 'view_mode': 'graph',
  251. 'view_id': view_ids and view_ids[0] or 0,
  252. 'context': "{'service_name': '%s'}" % self.name,
  253. }
  254. act_window = self.env['ir.actions.act_window']
  255. action_id = act_window.sudo().create(action_vals)
  256. self.write({
  257. 'action_id': action_id.id,
  258. 'view_id': view_id,
  259. 'state': 'created'
  260. })
  261. return True
  262. @api.multi
  263. def open_view(self):
  264. return {
  265. 'type': 'ir.actions.act_window',
  266. 'res_model': self.model_name,
  267. 'view_type': 'graph',
  268. 'view_mode': 'graph',
  269. }