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.

256 lines
8.5 KiB

  1. # Copyright 2015-2019 Onestein (<https://www.onestein.eu>)
  2. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  3. from collections import defaultdict
  4. from odoo import api, models, registry
  5. NO_BI_MODELS = [
  6. 'temp.range',
  7. 'account.statement.operation.template',
  8. 'fetchmail.server'
  9. ]
  10. NO_BI_TTYPES = [
  11. 'many2many',
  12. 'one2many',
  13. 'html',
  14. 'binary',
  15. 'reference'
  16. ]
  17. def dict_for_field(field):
  18. return {
  19. 'id': field.id,
  20. 'name': field.name,
  21. 'description': field.field_description,
  22. 'type': field.ttype,
  23. 'relation': field.relation,
  24. 'custom': False,
  25. 'model_id': field.model_id.id,
  26. 'model': field.model_id.model,
  27. 'model_name': field.model_id.name
  28. }
  29. def dict_for_model(model):
  30. return {
  31. 'id': model.id,
  32. 'name': model.name,
  33. 'model': model.model
  34. }
  35. class IrModel(models.Model):
  36. _inherit = 'ir.model'
  37. @api.model
  38. def _filter_bi_models(self, model):
  39. def _check_name(model_model):
  40. if model_model in NO_BI_MODELS:
  41. return 1
  42. return 0
  43. def _check_startswith(model_model):
  44. if model_model.startswith('workflow') or \
  45. model_model.startswith('ir.') or \
  46. model_model.startswith('base_'):
  47. return 1
  48. return 0
  49. def _check_contains(model_model):
  50. if 'mail' in model_model or \
  51. 'report' in model_model or \
  52. 'edi.' in model_model:
  53. return 1
  54. return 0
  55. def _check_unknown(model_name):
  56. if model_name == 'Unknown' or '.' in model_name:
  57. return 1
  58. return 0
  59. model_model = model['model']
  60. model_name = model['name']
  61. count_check = 0
  62. count_check += _check_name(model_model)
  63. count_check += _check_startswith(model_model)
  64. count_check += _check_contains(model_model)
  65. count_check += _check_unknown(model_name)
  66. if not count_check:
  67. return self.env['ir.model.access'].check(
  68. model['model'], 'read', False)
  69. return False
  70. @api.model
  71. def sort_filter_models(self, models_list):
  72. res = sorted(
  73. filter(self._filter_bi_models, models_list),
  74. key=lambda x: x['name'])
  75. return res
  76. def get_model_list(self, model_table_map):
  77. if not model_table_map:
  78. return []
  79. domain = [('model_id', 'in', list(model_table_map.keys())),
  80. ('store', '=', True),
  81. ('ttype', '=', 'many2one')]
  82. fields = self.env['ir.model.fields'].sudo().search(domain)
  83. model_list = []
  84. for field in fields:
  85. for table_alias in model_table_map[field.model_id.id]:
  86. model_list.append(dict(
  87. dict_for_field(field),
  88. table_alias=table_alias,
  89. join_node=-1,
  90. ))
  91. return model_list
  92. def get_relation_list(self, model_table_map):
  93. if not model_table_map:
  94. return []
  95. model_names = {}
  96. for model in self.sudo().browse(model_table_map.keys()):
  97. model_names.update({model.model: model.id})
  98. domain = [('relation', 'in', list(model_names.keys())),
  99. ('store', '=', True),
  100. ('ttype', '=', 'many2one')]
  101. fields = self.env['ir.model.fields'].sudo().search(domain)
  102. relation_list = []
  103. for field in fields:
  104. model_id = model_names[field.relation]
  105. for join_node in model_table_map[model_id]:
  106. relation_list.append(dict(
  107. dict_for_field(field),
  108. join_node=join_node,
  109. table_alias=-1
  110. ))
  111. return relation_list
  112. @api.model
  113. def get_related_models(self, model_table_map):
  114. """ Return list of model dicts for all models that can be
  115. joined with the already selected models.
  116. """
  117. domain = [('transient', '=', False)]
  118. if model_table_map:
  119. model_list = self.get_model_list(model_table_map)
  120. relation_list = self.get_relation_list(model_table_map)
  121. model_ids = [f['model_id'] for f in relation_list + model_list]
  122. model_ids += list(model_table_map.keys())
  123. relations = [f['relation'] for f in model_list]
  124. domain += [
  125. '|', ('id', 'in', model_ids), ('model', 'in', relations)]
  126. return self.sudo().search(domain)
  127. @api.model
  128. def get_models(self, table_model_map=None):
  129. """ Return list of model dicts for all available models.
  130. """
  131. self = self.with_context(lang=self.env.user.lang)
  132. model_table_map = defaultdict(list)
  133. for k, v in (table_model_map or {}).items():
  134. model_table_map[v].append(k)
  135. models_list = []
  136. for model in self.get_related_models(model_table_map):
  137. models_list.append(dict_for_model(model))
  138. return self.sort_filter_models(models_list)
  139. @api.model
  140. def get_join_nodes(self, field_data, new_field):
  141. """ Return list of field dicts of join nodes
  142. Return all possible join nodes to add new_field to the query
  143. containing model_ids.
  144. """
  145. def _get_model_table_map(field_data):
  146. table_map = defaultdict(list)
  147. for data in field_data:
  148. table_map[data['model_id']].append(data['table_alias'])
  149. return table_map
  150. def _get_join_nodes_dict(model_table_map, new_field):
  151. join_nodes = []
  152. for alias in model_table_map[new_field['model_id']]:
  153. join_nodes.append({'table_alias': alias})
  154. for field in self.get_model_list(model_table_map):
  155. if new_field['model'] == field['relation']:
  156. join_nodes.append(field)
  157. for field in self.get_relation_list(model_table_map):
  158. if new_field['model_id'] == field['model_id']:
  159. join_nodes.append(field)
  160. return join_nodes
  161. def remove_duplicate_nodes(join_nodes):
  162. seen = set()
  163. nodes_list = []
  164. for node in join_nodes:
  165. node_tuple = tuple(node.items())
  166. if node_tuple not in seen:
  167. seen.add(node_tuple)
  168. nodes_list.append(node)
  169. return nodes_list
  170. self = self.with_context(lang=self.env.user.lang)
  171. model_table_map = _get_model_table_map(field_data)
  172. keys = [(field['table_alias'], field['id'])
  173. for field in field_data if field.get('join_node', -1) != -1]
  174. join_nodes = _get_join_nodes_dict(model_table_map, new_field)
  175. join_nodes = remove_duplicate_nodes(join_nodes)
  176. return list(filter(
  177. lambda x: 'id' not in x or
  178. (x['table_alias'], x['id']) not in keys, join_nodes))
  179. @api.model
  180. def get_fields(self, model_id):
  181. self = self.with_context(lang=self.env.user.lang)
  182. domain = [
  183. ('model_id', '=', model_id),
  184. ('store', '=', True),
  185. ('name', 'not in', models.MAGIC_COLUMNS),
  186. ('ttype', 'not in', NO_BI_TTYPES)
  187. ]
  188. fields_dict = []
  189. for field in self.env['ir.model.fields'].sudo().search(domain):
  190. fields_dict.append({
  191. 'id': field.id,
  192. 'model_id': model_id,
  193. 'name': field.name,
  194. 'description': field.field_description,
  195. 'type': field.ttype,
  196. 'model': field.model,
  197. })
  198. return sorted(
  199. fields_dict,
  200. key=lambda x: x['description'],
  201. reverse=True
  202. )
  203. @api.model
  204. def create(self, vals):
  205. if self.env.context and self.env.context.get('bve'):
  206. vals['state'] = 'base'
  207. res = super().create(vals)
  208. # this sql update is necessary since a write method here would
  209. # be not working (an orm constraint is restricting the modification
  210. # of the state field while updating ir.model)
  211. q = "UPDATE ir_model SET state = 'manual' WHERE id = %s"
  212. self.env.cr.execute(q, (res.id, ))
  213. # # update registry
  214. if self.env.context.get('bve'):
  215. # setup models; this reloads custom models in registry
  216. self.pool.setup_models(self._cr)
  217. # signal that registry has changed
  218. registry(self.env.cr.dbname).signal_changes()
  219. return res