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.

209 lines
8.4 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # Copyright (C) 2010-2013 OpenERP s.a. (<http://openerp.com>).
  6. # Copyright (C) 2014 initOS GmbH & Co. KG (<http://www.initos.com>).
  7. # Copyright (C) 2015-Today GRAP
  8. # Author Markus Schneider <markus.schneider at initos.com>
  9. # @author Sylvain LE GAL (https://twitter.com/legalsylvain)
  10. #
  11. # This program is free software: you can redistribute it and/or modify
  12. # it under the terms of the GNU Affero General Public License as
  13. # published by the Free Software Foundation, either version 3 of the
  14. # License, or (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU Affero General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU Affero General Public License
  22. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. #
  24. ##############################################################################
  25. from openerp.osv import orm, fields
  26. from openerp.tools.translate import _
  27. class tile(orm.Model):
  28. _name = 'tile.tile'
  29. _order = 'sequence, name'
  30. def median(self, aList):
  31. # https://docs.python.org/3/library/statistics.html#statistics.median
  32. # TODO : refactor, using statistics.median when Odoo will be available
  33. # in Python 3.4
  34. even = (0 if len(aList) % 2 else 1) + 1
  35. half = (len(aList) - 1) / 2
  36. return sum(sorted(aList)[half:half + even]) / float(even)
  37. def _get_tile_info(self, cr, uid, ids, fields, args, context=None):
  38. ima_obj = self.pool['ir.model.access']
  39. res = {}
  40. records = self.browse(cr, uid, ids, context=context)
  41. for r in records:
  42. res[r.id] = {
  43. 'active': False,
  44. 'count': 0,
  45. 'computed_value': 0,
  46. 'helper': '',
  47. }
  48. if ima_obj.check(
  49. cr, uid, r.model_id.model, 'read', False, context):
  50. # Compute count item
  51. model = self.pool.get(r.model_id.model)
  52. count = model.search_count(
  53. cr, uid, eval(r.domain), context=context)
  54. res[r.id].update({
  55. 'active': True,
  56. 'count': count,
  57. })
  58. # Compute datas for field_id depending of field_function
  59. if r.field_function and r.field_id and count != 0:
  60. ids = model.search(
  61. cr, uid, eval(r.domain), context=context)
  62. vals = [x[r.field_id.name] for x in model.read(
  63. cr, uid, ids, [r.field_id.name], context=context)]
  64. desc = r.field_id.field_description
  65. if r.field_function == 'min':
  66. value = min(vals)
  67. helper = _("Minimum value of '%s'") % desc
  68. elif r.field_function == 'max':
  69. value = max(vals)
  70. helper = _("Maximum value of '%s'") % desc
  71. elif r.field_function == 'sum':
  72. value = sum(vals)
  73. helper = _("Total value of '%s'") % desc
  74. elif r.field_function == 'avg':
  75. value = sum(vals) / len(vals)
  76. helper = _("Average value of '%s'") % desc
  77. elif r.field_function == 'median':
  78. value = self.median(vals)
  79. helper = _("Median value of '%s'") % desc
  80. res[r.id].update({
  81. 'computed_value': value,
  82. 'helper': helper,
  83. })
  84. return res
  85. def _search_active(self, cr, uid, obj, name, arg, context=None):
  86. ima_obj = self.pool['ir.model.access']
  87. ids = []
  88. cr.execute("""
  89. SELECT tt.id, im.model
  90. FROM tile_tile tt
  91. INNER JOIN ir_model im
  92. ON tt.model_id = im.id""")
  93. for result in cr.fetchall():
  94. if (ima_obj.check(cr, uid, result[1], 'read', False) ==
  95. arg[0][2]):
  96. ids.append(result[0])
  97. return [('id', 'in', ids)]
  98. _columns = {
  99. 'name': fields.char('Tile Name'),
  100. 'model_id': fields.many2one('ir.model', 'Model', required=True),
  101. 'user_id': fields.many2one('res.users', 'User'),
  102. 'domain': fields.text('Domain'),
  103. 'action_id': fields.many2one('ir.actions.act_window', 'Action'),
  104. 'count': fields.function(
  105. _get_tile_info, type='int', string='Count',
  106. multi='tile_info', readonly=True),
  107. 'computed_value': fields.function(
  108. _get_tile_info, type='float', string='Computed Value',
  109. multi='tile_info', readonly=True),
  110. 'helper': fields.function(
  111. _get_tile_info, type='char', string='Helper Text',
  112. multi='tile_info', readonly=True),
  113. 'field_function': fields.selection([
  114. ('min', 'Minimum'),
  115. ('max', 'Maximum'),
  116. ('sum', 'Sum'),
  117. ('avg', 'Average'),
  118. ('median', 'Median'),
  119. ], 'Function'),
  120. 'field_id': fields.many2one(
  121. 'ir.model.fields', 'Field',
  122. domain="[('model_id', '=', model_id),"
  123. " ('ttype', 'in', ['float', 'int'])]"),
  124. 'active': fields.function(
  125. _get_tile_info, type='boolean', string='Active',
  126. multi='tile_info', readonly=True, fnct_search=_search_active),
  127. 'color': fields.char('Background color'),
  128. 'font_color': fields.char('Font Color'),
  129. 'sequence': fields.integer(
  130. 'Sequence', required=True),
  131. }
  132. # Constraint Section
  133. def _check_model_id_field_id(self, cr, uid, ids, context=None):
  134. for t in self.browse(cr, uid, ids, context=context):
  135. if t.field_id and t.field_id.model_id.id != t.model_id.id:
  136. return False
  137. return True
  138. def _check_field_id_field_function(self, cr, uid, ids, context=None):
  139. for t in self.browse(cr, uid, ids, context=context):
  140. if t.field_id and not t.field_function or\
  141. t.field_function and not t.field_id:
  142. return False
  143. return True
  144. _constraints = [
  145. (
  146. _check_model_id_field_id,
  147. "Error ! Please select a field of the selected model.",
  148. ['model_id', 'field_id']),
  149. (
  150. _check_field_id_field_function,
  151. "Error ! Please set both fields: 'Field' and 'Function'.",
  152. ['field_id', 'field_function']),
  153. ]
  154. _defaults = {
  155. 'domain': '[]',
  156. 'color': '#0E6C7E',
  157. 'font_color': '#FFFFFF',
  158. 'sequence': 0,
  159. }
  160. def open_link(self, cr, uid, ids, context=None):
  161. tile_id = ids[0]
  162. tile_object = self.browse(cr, uid, tile_id, context=context)
  163. if tile_object.action_id:
  164. act_obj = self.pool.get('ir.actions.act_window')
  165. result = act_obj.read(cr, uid, [tile_object.action_id.id],
  166. context=context)[0]
  167. # FIXME: restore original Domain + Filter would be better
  168. result['domain'] = tile_object.domain
  169. return result
  170. # we have no action_id stored,
  171. # so try to load a default tree view
  172. return {
  173. 'name': tile_object.name,
  174. 'view_type': 'form',
  175. 'view_mode': 'tree',
  176. 'view_id': [False],
  177. 'res_model': tile_object.model_id.model,
  178. 'type': 'ir.actions.act_window',
  179. 'context': context,
  180. 'nodestroy': True,
  181. 'target': 'current',
  182. 'domain': tile_object.domain,
  183. }
  184. def add(self, cr, uid, vals, context=None):
  185. # TODO: check if string
  186. if 'model_id' in vals:
  187. # need to replace model_name with its id
  188. model_ids = self.pool.get('ir.model').search(cr, uid,
  189. [('model', '=',
  190. vals['model_id'])])
  191. vals['model_id'] = model_ids[0]
  192. return self.create(cr, uid, vals, context)