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.

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