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.

251 lines
9.3 KiB

  1. ###################################################################################
  2. #
  3. # Copyright (C) 2017 MuK IT GmbH
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. ###################################################################################
  19. import os
  20. import hashlib
  21. import logging
  22. import itertools
  23. from odoo import _
  24. from odoo import models, api, fields
  25. from odoo.exceptions import AccessError
  26. _logger = logging.getLogger(__name__)
  27. class BaseModelLocking(models.AbstractModel):
  28. _name = 'muk_security.locking'
  29. _description = 'MuK Locking Model'
  30. _inherit = 'muk_utils.model'
  31. #----------------------------------------------------------
  32. # Database
  33. #----------------------------------------------------------
  34. locked = fields.Many2one(
  35. comodel_name='muk_security.lock',
  36. compute='_compute_lock',
  37. string="Locked")
  38. locked_state = fields.Boolean(
  39. compute='_compute_lock',
  40. string="Locked")
  41. locked_by = fields.Many2one(
  42. related='locked.locked_by_ref',
  43. comodel_name='res.users',
  44. string="Locked by")
  45. editor = fields.Boolean(
  46. compute='_compute_editor',
  47. string="Editor")
  48. #----------------------------------------------------------
  49. # Functions
  50. #----------------------------------------------------------
  51. @api.model
  52. def generate_operation_key(self):
  53. return hashlib.sha1(os.urandom(128)).hexdigest()
  54. #----------------------------------------------------------
  55. # Locking
  56. #----------------------------------------------------------
  57. @api.multi
  58. def lock(self, user=None, operation=None, *largs, **kwargs):
  59. result = []
  60. for record in self:
  61. lock = record.is_locked()
  62. if lock and lock.operation and lock.operation == operation:
  63. result.append({
  64. 'record': record,
  65. 'lock': lock,
  66. 'token': lock.token})
  67. elif lock and ((lock.operation and lock.operation != operation) or not lock.operation):
  68. raise AccessError(_("The record (%s[%s]) is locked, so it can't be locked again.") %
  69. (record._name, record.id))
  70. else:
  71. token = hashlib.sha1(os.urandom(128)).hexdigest()
  72. lock = self.env['muk_security.lock'].sudo().create({
  73. 'locked_by': user and user.name or "System",
  74. 'locked_by_ref': user and user.id or None,
  75. 'lock_ref': record._name + ',' + str(record.id),
  76. 'token': token,
  77. 'operation': operation})
  78. result.append({
  79. 'record': record,
  80. 'lock': lock,
  81. 'token': token})
  82. return result
  83. @api.multi
  84. def unlock(self, *largs, **kwargs):
  85. for record in self:
  86. locks = self.env['muk_security.lock'].sudo().search(
  87. [('lock_ref', '=', "%s,%s" % (record._name, str(record.id)))])
  88. locks.sudo().unlink()
  89. return True
  90. @api.model
  91. def unlock_operation(self, operation, *largs, **kwargs):
  92. locks = self.env['muk_security.lock'].sudo().search([('operation', '=', operation)])
  93. references = [
  94. list((k, list(map(lambda rec: rec.lock_ref_id, v))))
  95. for k, v in itertools.groupby(
  96. locks.sorted(key=lambda rec: rec.lock_ref_model),
  97. lambda rec: rec.lock_ref_model)]
  98. locks.sudo().unlink()
  99. return references
  100. @api.multi
  101. def is_locked(self, *largs, **kwargs):
  102. self.ensure_one()
  103. lock = self.env['muk_security.lock'].sudo().search(
  104. [('lock_ref', '=', self._name + ',' + str(self.id))], limit=1)
  105. if lock.id:
  106. return lock
  107. return False
  108. @api.multi
  109. def is_locked_by(self, *largs, **kwargs):
  110. self.ensure_one()
  111. lock = self.env['muk_security.lock'].sudo().search(
  112. [('lock_ref', '=', self._name + ',' + str(self.id))], limit=1)
  113. if lock.id:
  114. return lock.locked_by_ref
  115. return False
  116. @api.multi
  117. def _checking_lock_user(self, *largs, **kwargs):
  118. for record in self:
  119. lock = record.is_locked()
  120. if lock and lock.locked_by_ref and not lock.locked_by_ref.id != self.env.user.id:
  121. raise AccessError(_("The record (%s[%s]) is locked by a user, so it can't be changes or deleted.") %
  122. (self._name, self.id))
  123. @api.multi
  124. def _checking_lock(self, operation=None, *largs, **kwargs):
  125. self._checking_lock_user()
  126. for record in self:
  127. lock = record.is_locked()
  128. if lock and lock.operation and lock.operation != operation:
  129. print(operation, lock.operation)
  130. raise IOError
  131. raise AccessError(_("The record (%s[%s]) is locked, so it can't be changes or deleted.") %
  132. (self._name, self.id))
  133. @api.multi
  134. def user_lock(self, *largs, **kwargs):
  135. self.ensure_one()
  136. lock = self.is_locked()
  137. if lock:
  138. if lock.locked_by_ref:
  139. raise AccessError(_("The record is already locked by another user. (%s)") % lock.locked_by_ref.name)
  140. else:
  141. raise AccessError(_("The record is already locked."))
  142. return self.lock(user=self.env.user)
  143. @api.multi
  144. def user_unlock(self, *largs, **kwargs):
  145. self.ensure_one()
  146. lock = self.is_locked()
  147. if lock:
  148. if lock.locked_by_ref and lock.locked_by_ref.id == self.env.user.id:
  149. self.unlock()
  150. else:
  151. if lock.locked_by_ref:
  152. raise AccessError(_("The record is already locked by another user. (%s)") % lock.locked_by_ref.name)
  153. else:
  154. raise AccessError(_("The record is already locked."))
  155. return True
  156. #----------------------------------------------------------
  157. # Read, View
  158. #----------------------------------------------------------
  159. @api.multi
  160. def _compute_lock(self):
  161. for record in self:
  162. locked = record.is_locked()
  163. record.update({
  164. 'locked_state': bool(locked),
  165. 'locked': locked})
  166. @api.depends('locked')
  167. def _compute_editor(self):
  168. for record in self:
  169. record.editor = record.is_locked_by() == record.env.user
  170. #----------------------------------------------------------
  171. # Create, Update, Delete
  172. #----------------------------------------------------------
  173. @api.multi
  174. def write(self, vals):
  175. operation = self.generate_operation_key()
  176. vals = self._before_write_operation(vals, operation)
  177. process_operation = self.env.context['operation'] if 'operation' in self.env.context else operation
  178. result = super(BaseModelLocking, self.with_context(operation=process_operation)).write(vals)
  179. for record in self:
  180. record._after_write_record_operation(vals, operation)
  181. result = self._after_write_operation(result, vals, operation)
  182. return result
  183. @api.multi
  184. def _before_write_operation(self, vals, operation, *largs, **kwargs):
  185. if 'operation' in self.env.context:
  186. self._checking_lock(self.env.context['operation'])
  187. else:
  188. self._checking_lock(operation)
  189. return vals
  190. @api.multi
  191. def _after_write_record_operation(self, vals, operation, *largs, **kwargs):
  192. return vals
  193. @api.multi
  194. def _after_write_operation(self, result, vals, operation, *largs, **kwargs):
  195. return result
  196. @api.multi
  197. def unlink(self):
  198. operation = self.generate_operation_key()
  199. self._before_unlink_operation(operation)
  200. for record in self:
  201. record._before_unlink_record_operation(operation)
  202. process_operation = self.env.context['operation'] if 'operation' in self.env.context else operation
  203. result = super(BaseModelLocking, self.with_context(operation=process_operation)).unlink()
  204. self._after_unlink_operation(result, operation)
  205. return result
  206. @api.multi
  207. def _before_unlink_operation(self, operation, *largs, **kwargs):
  208. if 'operation' in self.env.context:
  209. self._checking_lock(self.env.context['operation'])
  210. else:
  211. self._checking_lock(operation)
  212. @api.multi
  213. def _before_unlink_record_operation(self, operation, *largs, **kwargs):
  214. pass
  215. @api.multi
  216. def _after_unlink_operation(self, result, operation, *largs, **kwargs):
  217. pass