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.

317 lines
10 KiB

  1. ###################################################################################
  2. #
  3. # Copyright (C) 2018 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 time
  20. import logging
  21. import datetime
  22. import dateutil
  23. from pytz import timezone
  24. from odoo import _
  25. from odoo import models, api, fields
  26. from odoo.exceptions import ValidationError, Warning
  27. from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
  28. from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
  29. from odoo.tools.safe_eval import safe_eval, test_python_expr
  30. _logger = logging.getLogger(__name__)
  31. class AutoVacuumRules(models.Model):
  32. _name = 'muk_autovacuum.rules'
  33. _description = "Auto Vacuum Rules"
  34. _order = "sequence asc"
  35. #----------------------------------------------------------
  36. # Defaults
  37. #----------------------------------------------------------
  38. def _default_sequence(self):
  39. record = self.sudo().search([], order='sequence desc', limit=1)
  40. if record.exists():
  41. return record.sequence + 1
  42. else:
  43. return 1
  44. #----------------------------------------------------------
  45. # Database
  46. #----------------------------------------------------------
  47. name = fields.Char(
  48. string='Name',
  49. required=True)
  50. active = fields.Boolean(
  51. string='Active',
  52. default=True)
  53. state = fields.Selection(
  54. selection=[
  55. ('time', 'Time Based'),
  56. ('size', 'Size Based'),
  57. ('domain', 'Domain Based'),
  58. ('code', 'Code Based')],
  59. string='Rule Type',
  60. default='time',
  61. required=True)
  62. sequence = fields.Integer(
  63. string='Sequence',
  64. default=_default_sequence,
  65. required=True)
  66. model = fields.Many2one(
  67. comodel_name='ir.model',
  68. string="Model",
  69. required=True,
  70. ondelete='cascade',
  71. help="Model on which the rule is applied.")
  72. model_name = fields.Char(
  73. related='model.model',
  74. string="Model Name",
  75. readonly=True,
  76. store=True)
  77. time_field = fields.Many2one(
  78. comodel_name='ir.model.fields',
  79. domain="[('model_id', '=', model), ('ttype', '=', 'datetime')]",
  80. string='Time Field',
  81. ondelete='cascade',
  82. states={
  83. 'time': [('required', True)],
  84. 'size': [('invisible', True)],
  85. 'domain': [('invisible', True)],
  86. 'code': [('invisible', True)]})
  87. time_type = fields.Selection(
  88. selection=[
  89. ('minutes', 'Minutes'),
  90. ('hours', 'Hours'),
  91. ('days', 'Days'),
  92. ('weeks', 'Weeks'),
  93. ('months', 'Months'),
  94. ('years', 'Years')],
  95. string='Time Unit',
  96. default='months',
  97. states={
  98. 'time': [('required', True)],
  99. 'size': [('invisible', True)],
  100. 'domain': [('invisible', True)],
  101. 'code': [('invisible', True)]})
  102. time = fields.Integer(
  103. string='Time',
  104. default=1,
  105. states={
  106. 'time': [('required', True)],
  107. 'size': [('invisible', True)],
  108. 'domain': [('invisible', True)],
  109. 'code': [('invisible', True)]},
  110. help="Delete older data than x.")
  111. size_type = fields.Selection(
  112. selection=[
  113. ('fixed', 'Fixed Value'),
  114. ('parameter', 'System Parameter')],
  115. string='Size Type',
  116. default='fixed',
  117. states={
  118. 'time': [('invisible', True)],
  119. 'size': [('required', True)],
  120. 'domain': [('invisible', True)],
  121. 'code': [('invisible', True)]})
  122. size_parameter = fields.Many2one(
  123. comodel_name='ir.config_parameter',
  124. string='System Parameter',
  125. ondelete='cascade',
  126. states={
  127. 'time': [('invisible', True)],
  128. 'size': [('required', True)],
  129. 'domain': [('invisible', True)],
  130. 'code': [('invisible', True)]})
  131. size_parameter_value = fields.Integer(
  132. compute='_compute_size_parameter_value',
  133. string='Size Value',
  134. states={
  135. 'time': [('invisible', True)],
  136. 'size': [('readonly', True)],
  137. 'domain': [('invisible', True)],
  138. 'code': [('invisible', True)]},
  139. help="Delete records with am index greater than x.")
  140. size_order = fields.Char(
  141. string='Size Order',
  142. default='create_date desc',
  143. states={
  144. 'time': [('invisible', True)],
  145. 'size': [('required', True)],
  146. 'domain': [('invisible', True)],
  147. 'code': [('invisible', True)]},
  148. help="Order by which the index is defined.")
  149. size = fields.Integer(
  150. string='Size',
  151. default=200,
  152. states={
  153. 'time': [('invisible', True)],
  154. 'size': [('required', True)],
  155. 'domain': [('invisible', True)],
  156. 'code': [('invisible', True)]},
  157. help="Delete records with am index greater than x.")
  158. domain = fields.Char(
  159. string='Domain',
  160. states={
  161. 'time': [('invisible', True)],
  162. 'size': [('invisible', True)],
  163. 'domain': [('required', True)],
  164. 'code': [('invisible', True)]},
  165. help="Delete all records which match the domain.")
  166. code = fields.Text(
  167. string='Code',
  168. states={
  169. 'time': [('invisible', True)],
  170. 'size': [('invisible', True)],
  171. 'domain': [('invisible', True)] ,
  172. 'code': [('required', True)]},
  173. help="Code which will be executed during the clean up.")
  174. protect_starred = fields.Boolean(
  175. string='Protect Starred',
  176. default=True,
  177. states={
  178. 'time': [('invisible', False)],
  179. 'size': [('invisible', True)],
  180. 'domain': [('invisible', True)],
  181. 'code': [('invisible', True)]},
  182. help="""Do not delete starred records.
  183. Checks for the following fields:
  184. - starred
  185. - favorite
  186. - is_starred
  187. - is_favorite""")
  188. only_inactive = fields.Boolean(
  189. string='Only Archived',
  190. default=False,
  191. states={
  192. 'time': [('invisible', False)],
  193. 'size': [('invisible', True)],
  194. 'domain': [('invisible', True)],
  195. 'code': [('invisible', True)]},
  196. help="Only delete archived records.")
  197. only_attachments = fields.Boolean(
  198. string='Only Attachments',
  199. default=False,
  200. states={
  201. 'time': [('invisible', False)],
  202. 'size': [('invisible', False)],
  203. 'domain': [('invisible', False)],
  204. 'code': [('invisible', True)]},
  205. help="Only delete record attachments.")
  206. #----------------------------------------------------------
  207. # Functions
  208. #----------------------------------------------------------
  209. @api.model
  210. def _get_eval_domain_context(self):
  211. return {
  212. 'datetime': datetime,
  213. 'dateutil': dateutil,
  214. 'time': time,
  215. 'uid': self.env.uid,
  216. 'user': self.env.user
  217. }
  218. @api.model
  219. def _get_eval_code_context(self, rule):
  220. return {
  221. 'env': self.env,
  222. 'model': self.env[rule.model_name],
  223. 'uid': self.env.user.id,
  224. 'user': self.env.user,
  225. 'time': time,
  226. 'datetime': datetime,
  227. 'dateutil': dateutil,
  228. 'timezone': timezone,
  229. 'date_format': DEFAULT_SERVER_DATE_FORMAT,
  230. 'datetime_format': DEFAULT_SERVER_DATETIME_FORMAT,
  231. 'Warning': Warning,
  232. 'logger': logging.getLogger("%s (%s)" % (__name__, rule.name)),
  233. }
  234. #----------------------------------------------------------
  235. # View
  236. #----------------------------------------------------------
  237. @api.onchange('model')
  238. def _onchange_model(self):
  239. field_domain = [
  240. ('model_id', '=', self.model.id),
  241. ('ttype', '=', 'datetime'),
  242. ('name', '=', 'create_date')]
  243. record = self.env['ir.model.fields'].sudo().search(field_domain, limit=1)
  244. if record.exists():
  245. self.time_field = record
  246. else:
  247. return None
  248. #----------------------------------------------------------
  249. # Read
  250. #----------------------------------------------------------
  251. @api.depends('size_parameter')
  252. def _compute_size_parameter_value(self):
  253. for record in self:
  254. try:
  255. record.size_parameter_value = int(record.size_parameter.value)
  256. except ValueError:
  257. record.size_parameter_value = None
  258. #----------------------------------------------------------
  259. # Create, Update, Delete
  260. #----------------------------------------------------------
  261. @api.constrains('code')
  262. def _check_code(self):
  263. for record in self.sudo().filtered('code'):
  264. message = test_python_expr(expr=record.code.strip(), mode="exec")
  265. if message:
  266. raise ValidationError(message)
  267. @api.constrains(
  268. 'state', 'model', 'domain', 'code',
  269. 'time_field', 'time_type', 'time',
  270. 'size_type', 'size_parameter', 'size_order', 'size')
  271. def _validate(self):
  272. validators = {
  273. 'time': lambda rec: rec.time_field and rec.time_type and rec.time,
  274. 'size': lambda rec: rec.size_order and (rec.size_parameter or rec.size),
  275. 'domain': lambda rec: rec.domain,
  276. 'code': lambda rec: rec.code,
  277. }
  278. for record in self:
  279. if not validators[record.state](record):
  280. raise ValidationError(_("Rule validation has failed!"))