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.

241 lines
8.3 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import time
  5. from odoo import api, models, fields, _
  6. from odoo.exceptions import UserError, ValidationError
  7. from odoo.tools.safe_eval import safe_eval
  8. from odoo import osv
  9. class ExceptionRule(models.Model):
  10. _name = 'exception.rule'
  11. _description = "Exception Rules"
  12. _order = 'active desc, sequence asc'
  13. name = fields.Char('Exception Name', required=True, translate=True)
  14. description = fields.Text('Description', translate=True)
  15. sequence = fields.Integer(
  16. string='Sequence',
  17. help="Gives the sequence order when applying the test")
  18. model = fields.Selection(
  19. selection=[],
  20. string='Apply on', required=True)
  21. exception_type = fields.Selection(
  22. selection=[('by_domain', 'By domain'),
  23. ('by_py_code', 'By python code')],
  24. string='Exception Type', required=True, default='by_py_code',
  25. help="By python code: allow to define any arbitrary check\n"
  26. "By domain: limited to a selection by an odoo domain:\n"
  27. " performance can be better when exceptions "
  28. " are evaluated with several records")
  29. domain = fields.Char('Domain')
  30. active = fields.Boolean('Active', default=True)
  31. code = fields.Text(
  32. 'Python Code',
  33. help="Python code executed to check if the exception apply or "
  34. "not. The code must apply failed = True to apply the "
  35. "exception.",
  36. default="""
  37. # Python code. Use failed = True to block the base.exception.
  38. # You can use the following variables :
  39. # - self: ORM model of the record which is checked
  40. # - object: same as order or line, browse_record of the base.exception or
  41. # base.exception line
  42. # - pool: ORM model pool (i.e. self.pool, deprecated in new api)
  43. # - obj: same as object
  44. # - env: ORM model pool (i.e. self.env)
  45. # - time: Python time module
  46. # - cr: database cursor
  47. # - uid: current user id
  48. # - context: current context
  49. """)
  50. @api.constrains('exception_type', 'domain', 'code')
  51. def check_exception_type_consistency(self):
  52. for rule in self:
  53. if ((rule.exception_type == 'by_py_code' and not rule.code) or
  54. (rule.exception_type == 'by_domain' and not rule.domain)):
  55. raise ValidationError(
  56. _("There is a problem of configuration, python code or "
  57. "domain is missing to match the exception type.")
  58. )
  59. @api.multi
  60. def _get_domain(self):
  61. """ override me to customize domains according exceptions cases """
  62. self.ensure_one()
  63. return safe_eval(self.domain)
  64. class BaseExceptionMethod(models.AbstractModel):
  65. _name = 'base.exception.method'
  66. @api.multi
  67. def _reverse_field(self):
  68. raise NotImplementedError()
  69. def _rule_domain(self):
  70. """Filter exception.rules.
  71. By default, only the rules with the correct model
  72. will be used.
  73. """
  74. return [('model', '=', self._name)]
  75. @api.multi
  76. def detect_exceptions(self):
  77. """List all exception_ids applied on self
  78. Exception ids are also written on records
  79. If self is empty, check exceptions on all records.
  80. """
  81. rules = self.env['exception.rule'].sudo().search(
  82. self._rule_domain())
  83. all_exception_ids = []
  84. for rule in rules:
  85. records_with_exception = self._detect_exceptions(rule)
  86. reverse_field = self._reverse_field()
  87. if self:
  88. commons = self and rule[reverse_field]
  89. to_remove = commons - records_with_exception
  90. to_add = records_with_exception - commons
  91. to_remove_list = [(3, x.id, _) for x in to_remove]
  92. to_add_list = [(4, x.id, _) for x in to_add]
  93. rule.write({reverse_field: to_remove_list + to_add_list})
  94. else:
  95. rule.write({
  96. reverse_field: [(6, 0, records_with_exception.ids)]
  97. })
  98. if records_with_exception:
  99. all_exception_ids.append(rule.id)
  100. return all_exception_ids
  101. @api.model
  102. def _exception_rule_eval_context(self, rec):
  103. return {'self': self.pool.get(rec._name),
  104. 'object': rec,
  105. 'obj': rec,
  106. 'pool': self.pool,
  107. 'env': self.env,
  108. 'cr': self.env.cr,
  109. 'uid': self.env.uid,
  110. 'user': self.env.user,
  111. 'time': time,
  112. # copy context to prevent side-effects of eval
  113. 'context': self.env.context.copy()}
  114. @api.model
  115. def _rule_eval(self, rule, rec):
  116. expr = rule.code
  117. space = self._exception_rule_eval_context(rec)
  118. try:
  119. safe_eval(expr,
  120. space,
  121. mode='exec',
  122. nocopy=True) # nocopy allows to return 'result'
  123. except Exception, e:
  124. raise UserError(
  125. _('Error when evaluating the exception.rule '
  126. 'rule:\n %s \n(%s)') % (rule.name, e))
  127. return space.get('failed', False)
  128. @api.multi
  129. def _detect_exceptions(self, rule):
  130. if rule.exception_type == 'by_py_code':
  131. return self._detect_exceptions_by_py_code(rule)
  132. elif rule.exception_type == 'by_domain':
  133. return self._detect_exceptions_by_domain(rule)
  134. @api.multi
  135. def _get_base_domain(self):
  136. domain = [('ignore_exception', '=', False)]
  137. if self:
  138. domain = osv.expression.AND([domain, [('id', 'in', self.ids)]])
  139. return domain
  140. @api.multi
  141. def _detect_exceptions_by_py_code(self, rule):
  142. """
  143. Find exceptions found on self.
  144. If self is empty, check on all records.
  145. """
  146. domain = self._get_base_domain()
  147. records = self.search(domain)
  148. records_with_exception = self.env[self._name]
  149. for record in records:
  150. if self._rule_eval(rule, record):
  151. records_with_exception |= record
  152. return records_with_exception
  153. @api.multi
  154. def _detect_exceptions_by_domain(self, rule):
  155. """
  156. Find exceptions found on self.
  157. If self is empty, check on all records.
  158. """
  159. base_domain = self._get_base_domain()
  160. rule_domain = rule._get_domain()
  161. domain = osv.expression.AND([base_domain, rule_domain])
  162. return self.search(domain)
  163. class BaseException(models.AbstractModel):
  164. _inherit = 'base.exception.method'
  165. _name = 'base.exception'
  166. _order = 'main_exception_id asc'
  167. main_exception_id = fields.Many2one(
  168. 'exception.rule',
  169. compute='_compute_main_error',
  170. string='Main Exception',
  171. store=True)
  172. exception_ids = fields.Many2many(
  173. 'exception.rule',
  174. string='Exceptions')
  175. ignore_exception = fields.Boolean('Ignore Exceptions', copy=False)
  176. @api.depends('exception_ids', 'ignore_exception')
  177. def _compute_main_error(self):
  178. for obj in self:
  179. if not obj.ignore_exception and obj.exception_ids:
  180. obj.main_exception_id = obj.exception_ids[0]
  181. else:
  182. obj.main_exception_id = False
  183. @api.multi
  184. def _popup_exceptions(self):
  185. action = self._get_popup_action()
  186. action = action.read()[0]
  187. action.update({
  188. 'context': {
  189. 'active_model': self._name,
  190. 'active_id': self.ids[0],
  191. 'active_ids': self.ids
  192. }
  193. })
  194. return action
  195. @api.model
  196. def _get_popup_action(self):
  197. action = self.env.ref('base_exception.action_exception_rule_confirm')
  198. return action
  199. @api.multi
  200. def _check_exception(self):
  201. """
  202. This method must be used in a constraint that must be created in the
  203. object that inherits for base.exception.
  204. for sale :
  205. @api.constrains('ignore_exception',)
  206. def sale_check_exception(self):
  207. ...
  208. ...
  209. self._check_exception
  210. """
  211. exception_ids = self.detect_exceptions()
  212. if exception_ids:
  213. exceptions = self.env['exception.rule'].browse(exception_ids)
  214. raise ValidationError('\n'.join(exceptions.mapped('name')))