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.

210 lines
8.4 KiB

10 years ago
10 years ago
10 years ago
  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. from openerp.tools.safe_eval import safe_eval
  28. class tile(orm.Model):
  29. _name = 'tile.tile'
  30. _order = 'sequence, name'
  31. def median(self, aList):
  32. # https://docs.python.org/3/library/statistics.html#statistics.median
  33. # TODO : refactor, using statistics.median when Odoo will be available
  34. # in Python 3.4
  35. even = (0 if len(aList) % 2 else 1) + 1
  36. half = (len(aList) - 1) / 2
  37. return sum(sorted(aList)[half:half + even]) / float(even)
  38. def _get_tile_info(self, cr, uid, ids, fields, args, context=None):
  39. ima_obj = self.pool['ir.model.access']
  40. res = {}
  41. records = self.browse(cr, uid, ids, context=context)
  42. for r in records:
  43. res[r.id] = {
  44. 'active': False,
  45. 'count': 0,
  46. 'computed_value': 0,
  47. 'helper': '',
  48. }
  49. if ima_obj.check(
  50. cr, uid, r.model_id.model, 'read', False, context):
  51. # Compute count item
  52. model = self.pool.get(r.model_id.model)
  53. count = model.search_count(
  54. cr, uid, safe_eval(r.domain), context=context)
  55. res[r.id].update({
  56. 'active': True,
  57. 'count': count,
  58. })
  59. # Compute datas for field_id depending of field_function
  60. if r.field_function and r.field_id and count != 0:
  61. ids = model.search(
  62. cr, uid, safe_eval(r.domain), context=context)
  63. vals = [x[r.field_id.name] for x in model.read(
  64. cr, uid, ids, [r.field_id.name], context=context)]
  65. desc = r.field_id.field_description
  66. if r.field_function == 'min':
  67. value = min(vals)
  68. helper = _("Minimum value of '%s'") % desc
  69. elif r.field_function == 'max':
  70. value = max(vals)
  71. helper = _("Maximum value of '%s'") % desc
  72. elif r.field_function == 'sum':
  73. value = sum(vals)
  74. helper = _("Total value of '%s'") % desc
  75. elif r.field_function == 'avg':
  76. value = sum(vals) / len(vals)
  77. helper = _("Average value of '%s'") % desc
  78. elif r.field_function == 'median':
  79. value = self.median(vals)
  80. helper = _("Median value of '%s'") % desc
  81. res[r.id].update({
  82. 'computed_value': value,
  83. 'helper': helper,
  84. })
  85. return res
  86. def _search_active(self, cr, uid, obj, name, arg, context=None):
  87. ima_obj = self.pool['ir.model.access']
  88. ids = []
  89. cr.execute("""
  90. SELECT tt.id, im.model
  91. FROM tile_tile tt
  92. INNER JOIN ir_model im
  93. ON tt.model_id = im.id""")
  94. for result in cr.fetchall():
  95. if (ima_obj.check(cr, uid, result[1], 'read', False) ==
  96. arg[0][2]):
  97. ids.append(result[0])
  98. return [('id', 'in', ids)]
  99. _columns = {
  100. 'name': fields.char('Tile Name'),
  101. 'model_id': fields.many2one('ir.model', 'Model', required=True),
  102. 'user_id': fields.many2one('res.users', 'User'),
  103. 'domain': fields.text('Domain'),
  104. 'action_id': fields.many2one('ir.actions.act_window', 'Action'),
  105. 'count': fields.function(
  106. _get_tile_info, type='int', string='Count',
  107. multi='tile_info', readonly=True),
  108. 'computed_value': fields.function(
  109. _get_tile_info, type='float', string='Computed Value',
  110. multi='tile_info', readonly=True),
  111. 'helper': fields.function(
  112. _get_tile_info, type='char', string='Helper Text',
  113. multi='tile_info', readonly=True),
  114. 'field_function': fields.selection([
  115. ('min', 'Minimum'),
  116. ('max', 'Maximum'),
  117. ('sum', 'Sum'),
  118. ('avg', 'Average'),
  119. ('median', 'Median'),
  120. ], 'Function'),
  121. 'field_id': fields.many2one(
  122. 'ir.model.fields', 'Field',
  123. domain="[('model_id', '=', model_id),"
  124. " ('ttype', 'in', ['float', 'int'])]"),
  125. 'active': fields.function(
  126. _get_tile_info, type='boolean', string='Active',
  127. multi='tile_info', readonly=True, fnct_search=_search_active),
  128. 'color': fields.char('Background color'),
  129. 'font_color': fields.char('Font Color'),
  130. 'sequence': fields.integer(
  131. 'Sequence', required=True),
  132. }
  133. # Constraint Section
  134. def _check_model_id_field_id(self, cr, uid, ids, context=None):
  135. for t in self.browse(cr, uid, ids, context=context):
  136. if t.field_id and t.field_id.model_id.id != t.model_id.id:
  137. return False
  138. return True
  139. def _check_field_id_field_function(self, cr, uid, ids, context=None):
  140. for t in self.browse(cr, uid, ids, context=context):
  141. if t.field_id and not t.field_function or\
  142. t.field_function and not t.field_id:
  143. return False
  144. return True
  145. _constraints = [
  146. (
  147. _check_model_id_field_id,
  148. "Error ! Please select a field of the selected model.",
  149. ['model_id', 'field_id']),
  150. (
  151. _check_field_id_field_function,
  152. "Error ! Please set both fields: 'Field' and 'Function'.",
  153. ['field_id', 'field_function']),
  154. ]
  155. _defaults = {
  156. 'domain': '[]',
  157. 'color': '#0E6C7E',
  158. 'font_color': '#FFFFFF',
  159. 'sequence': 0,
  160. }
  161. def open_link(self, cr, uid, ids, context=None):
  162. tile_id = ids[0]
  163. tile_object = self.browse(cr, uid, tile_id, context=context)
  164. if tile_object.action_id:
  165. act_obj = self.pool.get('ir.actions.act_window')
  166. result = act_obj.read(cr, uid, [tile_object.action_id.id],
  167. context=context)[0]
  168. # FIXME: restore original Domain + Filter would be better
  169. result['domain'] = tile_object.domain
  170. return result
  171. # we have no action_id stored,
  172. # so try to load a default tree view
  173. return {
  174. 'name': tile_object.name,
  175. 'view_type': 'form',
  176. 'view_mode': 'tree',
  177. 'view_id': [False],
  178. 'res_model': tile_object.model_id.model,
  179. 'type': 'ir.actions.act_window',
  180. 'context': context,
  181. 'nodestroy': True,
  182. 'target': 'current',
  183. 'domain': tile_object.domain,
  184. }
  185. def add(self, cr, uid, vals, context=None):
  186. # TODO: check if string
  187. if 'model_id' in vals:
  188. # need to replace model_name with its id
  189. model_ids = self.pool.get('ir.model').search(cr, uid,
  190. [('model', '=',
  191. vals['model_id'])])
  192. vals['model_id'] = model_ids[0]
  193. return self.create(cr, uid, vals, context)