diff --git a/autovacuum_message_attachment/__init__.py b/autovacuum_message_attachment/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/autovacuum_message_attachment/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/autovacuum_message_attachment/__manifest__.py b/autovacuum_message_attachment/__manifest__.py new file mode 100644 index 000000000..e09663fa8 --- /dev/null +++ b/autovacuum_message_attachment/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2018 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + "name": "AutoVacuum Mail Message and Attachment", + "version": "12.0.1.0.0", + "category": "Tools", + "website": "https://github.com/OCA/server-tools", + "author": "Akretion, Odoo Community Association (OCA)", + "license": "LGPL-3", + "installable": True, + "summary": "Automatically delete old mail messages and attachments", + "depends": [ + "mail", + ], + "data": [ + "data/data.xml", + "views/rule_vacuum.xml", + "security/ir.model.access.csv", + ], +} diff --git a/autovacuum_message_attachment/data/data.xml b/autovacuum_message_attachment/data/data.xml new file mode 100644 index 000000000..e036e64f2 --- /dev/null +++ b/autovacuum_message_attachment/data/data.xml @@ -0,0 +1,31 @@ + + + + + + AutoVacuum Mails and Messages + + + 1 + days + -1 + code + model.autovacuum('message') + + + + + + AutoVacuum Attachments + + + 1 + days + -1 + code + model.autovacuum('attachment') + + + + + diff --git a/autovacuum_message_attachment/i18n/autovacuum_mail_message.pot b/autovacuum_message_attachment/i18n/autovacuum_mail_message.pot new file mode 100644 index 000000000..5dbcbe976 --- /dev/null +++ b/autovacuum_message_attachment/i18n/autovacuum_mail_message.pot @@ -0,0 +1,158 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * autovacuum_mail_message +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "All" +msgstr "" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "Comment" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_company_id +msgid "Company" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_create_uid +msgid "Created by" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_create_date +msgid "Created on" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_display_name +msgid "Display Name" +msgstr "" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "Email" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_empty_subtype +msgid "Empty subtype" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_id +msgid "ID" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule___last_update +msgid "Last Modified on" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_write_date +msgid "Last Updated on" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model,name:autovacuum_mail_message.model_mail_message +msgid "Message" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Models" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Subtypes" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.actions.act_window,name:autovacuum_mail_message.action_message_vacuum_rule +#: model:ir.ui.menu,name:autovacuum_mail_message.menu_action_message_vacuum_rule +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Vacuum Rule" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_message_subtype_ids +msgid "Message subtypes concerned by the rule. If left empty, the system won't take the subtype into account to find the messages to delete" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_message_type +msgid "Message type" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_model_ids +msgid "Models" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_model_ids +msgid "Models concerned by the rule. If left empty, it will take all models into account" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_name +msgid "Name" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_retention_time +msgid "Number of days the messages concerned by this rule will be keeped in the database after creation. Once the delay is passed, they will be automatically deleted." +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_retention_time +msgid "Retention time" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model,name:autovacuum_mail_message.model_message_vacuum_rule +msgid "Rules Used to delete message historic" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_message_subtype_ids +msgid "Subtypes" +msgstr "" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "System notification" +msgstr "" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_empty_subtype +msgid "Take also into account messages with no subtypes" +msgstr "" + +#. module: autovacuum_mail_message +#: code:addons/autovacuum_mail_message/models/message_vacuum_rule.py:48 +#, python-format +msgid "The Retention Time can't be 0 days" +msgstr "" + diff --git a/autovacuum_message_attachment/i18n/fr.po b/autovacuum_message_attachment/i18n/fr.po new file mode 100644 index 000000000..095986ef9 --- /dev/null +++ b/autovacuum_message_attachment/i18n/fr.po @@ -0,0 +1,180 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * autovacuum_mail_message +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-29 11:30+0000\n" +"PO-Revision-Date: 2018-03-29 11:30+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "All" +msgstr "Tous" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "Comment" +msgstr "Commentaires" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_company_id +msgid "Company" +msgstr "Société" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_create_date +msgid "Created on" +msgstr "Créé le" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_display_name +msgid "Display Name" +msgstr "Nom à afficher" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "Email" +msgstr "Email" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_empty_subtype +msgid "Empty subtype" +msgstr "Sous-type Vide" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_id +msgid "ID" +msgstr "ID" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule___last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_write_uid +msgid "Last Updated by" +msgstr "Dernière modification par" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: autovacuum_mail_message +#: model:ir.model,name:autovacuum_mail_message.model_mail_message +msgid "Message" +msgstr "Message" + +#. module: autovacuum_mail_message +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Models" +msgstr "Documents des messages" + +#. module: autovacuum_mail_message +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Subtypes" +msgstr "Sous-types des messages" + +#. module: autovacuum_mail_message +#: model:ir.actions.act_window,name:autovacuum_mail_message.action_message_vacuum_rule +#: model:ir.ui.menu,name:autovacuum_mail_message.menu_action_message_vacuum_rule +#: model:ir.ui.view,arch_db:autovacuum_mail_message.message_vacuum_rule_form_view +msgid "Message Vacuum Rule" +msgstr "Règle de supression des messages" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_message_subtype_ids +msgid "" +"Message subtypes concerned by the rule. If left empty, the system won't take " +"the subtype into account to find the messages to delete" +msgstr "" +"Sous-types de message concernés par cette règle. Si c'est laissé vide, le " +"système ne prendra pas en compte les sous type pour trouver les messages à " +"supprimer" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_message_type +msgid "Message type" +msgstr "Type de message" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_model_ids +msgid "Models" +msgstr "Documents" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_model_ids +msgid "" +"Models concerned by the rule. If left empty, it will take all models into " +"account" +msgstr "" +"Documents concernés par la règle. Si c'est laissé vide, les messages de tous " +"les modèles seront pris en compte" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_name +msgid "Name" +msgstr "Nom" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_retention_time +msgid "" +"Number of days the messages concerned by this rule will be keeped in the " +"database after creation. Once the delay is passed, they will be " +"automatically deleted." +msgstr "" +"Nombre de jour de rétention des messages concerné par la règle. Une fois ce " +"délai passé, les messages sont automatiquement supprimés" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_retention_time +msgid "Retention time" +msgstr "Temps de rétention" + +#. module: autovacuum_mail_message +#: model:ir.model,name:autovacuum_mail_message.model_message_vacuum_rule +msgid "Rules Used to delete message historic" +msgstr "Règle de supression automatique de message" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,field_description:autovacuum_mail_message.field_message_vacuum_rule_message_subtype_ids +msgid "Subtypes" +msgstr "Sous-types" + +#. module: autovacuum_mail_message +#: selection:message.vacuum.rule,message_type:0 +msgid "System notification" +msgstr "Notification Système" + +#. module: autovacuum_mail_message +#: model:ir.model.fields,help:autovacuum_mail_message.field_message_vacuum_rule_empty_subtype +msgid "Take also into account messages with no subtypes" +msgstr "Prend également en compte les messages sans aucun sous-type" + +#. module: autovacuum_mail_message +#: code:addons/autovacuum_mail_message/models/message_vacuum_rule.py:48 +#, python-format +msgid "The Retention Time can't be 0 days" +msgstr "Le temps de retention ne peut pas être de 0 jours." + +#~ msgid "Companies" +#~ msgstr "Sociétés" + +#~ msgid "mail_message_subtype" +#~ msgstr "mail_message_subtype" diff --git a/autovacuum_message_attachment/models/__init__.py b/autovacuum_message_attachment/models/__init__.py new file mode 100644 index 000000000..1093858b6 --- /dev/null +++ b/autovacuum_message_attachment/models/__init__.py @@ -0,0 +1,5 @@ +from . import autovacuum_mixin +from . import ir_attachment +from . import mail_message +from . import vacuum_rule +from . import base diff --git a/autovacuum_message_attachment/models/autovacuum_mixin.py b/autovacuum_message_attachment/models/autovacuum_mixin.py new file mode 100644 index 000000000..ac4c6425c --- /dev/null +++ b/autovacuum_message_attachment/models/autovacuum_mixin.py @@ -0,0 +1,71 @@ +# Copyright (C) 2019 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import logging + +import odoo +from odoo import api, models +from odoo.tools.safe_eval import safe_eval +import datetime + +_logger = logging.getLogger(__name__) + + +class AutovacuumMixin(models.AbstractModel): + _name = "autovacuum.mixin" + _description = "Mixin used to delete messages or attachments" + + @api.multi + def batch_unlink(self): + with api.Environment.manage(): + with odoo.registry( + self.env.cr.dbname).cursor() as new_cr: + new_env = api.Environment(new_cr, self.env.uid, + self.env.context) + try: + while self: + batch_delete = self[0:1000] + self -= batch_delete + # do not attach new env to self because it may be + # huge, and the cache is cleaned after each unlink + # so we do not want to much record is the env in + # which we call unlink because odoo would prefetch + # fields, cleared right after. + batch_delete.with_env(new_env).unlink() + new_env.cr.commit() + except Exception as e: + _logger.exception( + "Failed to delete Ms : %s" % (self._name, str(e))) + + # Call by cron + @api.model + def autovacuum(self, ttype='message'): + rules = self.env['vacuum.rule'].search([('ttype', '=', ttype)]) + for rule in rules: + records = rule._search_autovacuum_records() + records.batch_unlink() + + def _get_autovacuum_domain(self, rule): + return [] + + def _get_autovacuum_records(self, rule): + if rule.model_id and rule.model_filter_domain: + return self._get_autovacuum_records_model(rule) + return self.search(self._get_autovacuum_domain(rule)) + + def _get_autovacuum_records_model(self, rule): + domain = self._get_autovacuum_domain(rule) + record_domain = safe_eval(rule.model_filter_domain, + locals_dict={'datetime': datetime}) + autovacuum_relation = self._autovacuum_relation + for leaf in domain: + if not isinstance(leaf, (tuple, list)): + record_domain.append(leaf) + continue + field, operator, value = leaf + record_domain.append( + ('%s.%s' % (autovacuum_relation, field), operator, value)) + records = self.env[rule.model_id.model].search(record_domain) + return self.search( + domain + [('res_id', 'in', records.ids)] + ) diff --git a/autovacuum_message_attachment/models/base.py b/autovacuum_message_attachment/models/base.py new file mode 100644 index 000000000..727ef9d2a --- /dev/null +++ b/autovacuum_message_attachment/models/base.py @@ -0,0 +1,13 @@ +# Copyright (C) 2019 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class Base(models.AbstractModel): + _inherit = "base" + + attachment_ids = fields.One2many( + 'ir.attachment', 'res_id', string='Attachments', + domain=lambda self: [('res_model', '=', self._name)], auto_join=True + ) diff --git a/autovacuum_message_attachment/models/ir_attachment.py b/autovacuum_message_attachment/models/ir_attachment.py new file mode 100644 index 000000000..20e343ca2 --- /dev/null +++ b/autovacuum_message_attachment/models/ir_attachment.py @@ -0,0 +1,27 @@ +# Copyright (C) 2018 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models +from datetime import timedelta + + +class IrAttachment(models.Model): + _name = "ir.attachment" + _inherit = ["ir.attachment", "autovacuum.mixin"] + _autovacuum_relation = 'attachment_ids' + + def _get_autovacuum_domain(self, rule): + domain = super()._get_autovacuum_domain(rule) + today = fields.Datetime.now() + limit_date = today - timedelta(days=rule.retention_time) + domain += [('create_date', '<', limit_date)] + if rule.filename_pattern: + domain += [('name', 'ilike', rule.filename_pattern)] + if rule.model_ids: + models = rule.model_ids.mapped('model') + domain += [('res_model', 'in', models)] + else: + # Avoid deleting attachment without model, if there are, it is + # probably some attachments created by Odoo + domain += [('res_model', '!=', False)] + return domain diff --git a/autovacuum_message_attachment/models/mail_message.py b/autovacuum_message_attachment/models/mail_message.py new file mode 100644 index 000000000..4940aad8c --- /dev/null +++ b/autovacuum_message_attachment/models/mail_message.py @@ -0,0 +1,32 @@ +# Copyright (C) 2018 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models +from datetime import timedelta + + +class MailMessage(models.Model): + _name = "mail.message" + _inherit = ["mail.message", "autovacuum.mixin"] + _autovacuum_relation = 'message_ids' + + def _get_autovacuum_domain(self, rule): + domain = super()._get_autovacuum_domain(rule) + today = fields.Datetime.now() + limit_date = today - timedelta(days=rule.retention_time) + domain += [('date', '<', limit_date)] + if rule.message_type != 'all': + domain += [('message_type', '=', rule.message_type)] + if rule.model_ids: + models = rule.model_ids.mapped('model') + domain += [('model', 'in', models)] + subtype_ids = rule.message_subtype_ids.ids + if subtype_ids and rule.empty_subtype: + domain = [ + '|', ('subtype_id', 'in', subtype_ids), + ('subtype_id', '=', False)] + elif subtype_ids and not rule.empty_subtype: + domain += [('subtype_id', 'in', subtype_ids)] + elif not subtype_ids and not rule.empty_subtype: + domain += [('subtype_id', '!=', False)] + return domain diff --git a/autovacuum_message_attachment/models/vacuum_rule.py b/autovacuum_message_attachment/models/vacuum_rule.py new file mode 100644 index 000000000..d3e87dd92 --- /dev/null +++ b/autovacuum_message_attachment/models/vacuum_rule.py @@ -0,0 +1,86 @@ +# Copyright (C) 2018 Akretion +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import _, api, exceptions, fields, models + + +class VacuumRule(models.Model): + _name = "vacuum.rule" + _description = "Rules Used to delete message historic" + + @api.depends('model_ids') + @api.multi + def _get_model_id(self): + for rule in self: + if rule.model_ids and len(rule.model_ids) == 1: + rule.model_id = rule.model_ids.id + rule.model = rule.model_id.model + else: + rule.model_id = False + rule.model = False + + name = fields.Char(required=True) + ttype = fields.Selection( + selection=[('attachment', 'Attachment'), + ('message', 'Message')], + string="Type", + required=True) + filename_pattern = fields.Char( + help=("If set, only attachments containing this pattern will be" + " deleted.")) + company_id = fields.Many2one( + 'res.company', string="Company", + default=lambda self: self.env['res.company']._company_default_get( + 'vacuum.rule')) + message_subtype_ids = fields.Many2many( + 'mail.message.subtype', string="Subtypes", + help="Message subtypes concerned by the rule. If left empty, the " + "system won't take the subtype into account to find the " + "messages to delete") + empty_subtype = fields.Boolean( + help="Take also into account messages with no subtypes") + model_ids = fields.Many2many( + 'ir.model', string="Models", + help="Models concerned by the rule. If left empty, it will take all " + "models into account") + model_id = fields.Many2one( + 'ir.model', readonly=True, + compute='_get_model_id', + help="Technical field used to set attributes (invisible/required, " + "domain, etc...for other fields, like the domain filter") + model_filter_domain = fields.Text( + string='Model Filter Domain') + model = fields.Char( + readonly=True, + compute='_get_model_id', + string='Model code' + ) + message_type = fields.Selection([ + ('email', 'Email'), + ('comment', 'Comment'), + ('notification', 'System notification'), + ('all', 'All')]) + retention_time = fields.Integer( + required=True, default=365, + help="Number of days the messages concerned by this rule will be " + "keeped in the database after creation. Once the delay is " + "passed, they will be automatically deleted.") + active = fields.Boolean(default=True) + description = fields.Text() + + @api.multi + @api.constrains('retention_time') + def retention_time_not_null(self): + for rule in self: + if not rule.retention_time: + raise exceptions.ValidationError( + _("The Retention Time can't be 0 days")) + + def _search_autovacuum_records(self): + self.ensure_one() + model = self.ttype + if model == 'message': + model = 'mail.message' + elif model == 'attachment': + model = 'ir.attachment' + return self.env[model]._get_autovacuum_records(self) diff --git a/autovacuum_message_attachment/readme/CONFIGURE.rst b/autovacuum_message_attachment/readme/CONFIGURE.rst new file mode 100644 index 000000000..a422d44b8 --- /dev/null +++ b/autovacuum_message_attachment/readme/CONFIGURE.rst @@ -0,0 +1,6 @@ +* Go to the menu configuration => Technical => Email => Message And Attachment Vacuum Rules +* Add the adequates rules for your company. On each rule, you can indicate the models, type and subtypes for which you want to delete the messages, along with a retention time (in days). Or for attachment, you can specify a substring of the name. +* Activate the cron AutoVacuum Mails and Messages and/or AutoVacuum Attachments + +It is recommanded to run it frequently and when the system is not very loaded. +(For instance : once a day, during the night.) diff --git a/autovacuum_message_attachment/readme/CONTRIBUTORS.rst b/autovacuum_message_attachment/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..8d20627d8 --- /dev/null +++ b/autovacuum_message_attachment/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Florian da Costa +* Enric Tobella diff --git a/autovacuum_message_attachment/readme/DESCRIPTION.rst b/autovacuum_message_attachment/readme/DESCRIPTION.rst new file mode 100644 index 000000000..b7e6fcec4 --- /dev/null +++ b/autovacuum_message_attachment/readme/DESCRIPTION.rst @@ -0,0 +1,4 @@ +Odoo create a lot of message and/or mails. With time it can slow the system or take a lot of disk space. +The goal of this module is to clean these message once they are obsolete. +The same may happen with attachment that we store. +You can choose various criterias manage which messages you want to delete automatically. diff --git a/autovacuum_message_attachment/readme/ROADMAP.rst b/autovacuum_message_attachment/readme/ROADMAP.rst new file mode 100644 index 000000000..8c221582b --- /dev/null +++ b/autovacuum_message_attachment/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +You have to be careful with rules regarding attachment deletion because Odoo find the attachment to delete with their name. +Odoo will find all attachments containing the substring configured on the rule, so you have to be specific enough on the other criterias (concerned models...) to avoid unwanted attachment deletion. diff --git a/autovacuum_message_attachment/security/ir.model.access.csv b/autovacuum_message_attachment/security/ir.model.access.csv new file mode 100644 index 000000000..e6ba8428c --- /dev/null +++ b/autovacuum_message_attachment/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_full_vaccum_rule,access.full.vaccum.rule,model_vacuum_rule,base.group_system,1,1,1,1 diff --git a/autovacuum_message_attachment/static/description/icon.png b/autovacuum_message_attachment/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/autovacuum_message_attachment/static/description/icon.png differ diff --git a/autovacuum_message_attachment/tests/__init__.py b/autovacuum_message_attachment/tests/__init__.py new file mode 100644 index 000000000..008d37fbf --- /dev/null +++ b/autovacuum_message_attachment/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_vacuum_rule diff --git a/autovacuum_message_attachment/tests/test_vacuum_rule.py b/autovacuum_message_attachment/tests/test_vacuum_rule.py new file mode 100644 index 000000000..dd55cd290 --- /dev/null +++ b/autovacuum_message_attachment/tests/test_vacuum_rule.py @@ -0,0 +1,169 @@ +# © 2018 Akretion (Florian da Costa) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import date, timedelta + +from odoo import api, exceptions +from odoo.tests import common +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +import base64 + + +class TestVacuumRule(common.TransactionCase): + + def create_mail_message(self, message_type, subtype): + vals = { + 'message_type': message_type, + 'subtype_id': subtype and subtype.id or False, + 'date': self.before_400_days, + 'model': 'res.partner', + 'res_id': self.env.ref('base.partner_root').id, + 'subject': 'Test', + 'body': 'Body Test', + } + return self.message_obj.create(vals) + + def tearDown(self): + self.registry.leave_test_mode() + super(TestVacuumRule, self).tearDown() + + def setUp(self): + super(TestVacuumRule, self).setUp() + self.registry.enter_test_mode(self.env.cr) + self.env = api.Environment(self.registry.test_cr, self.env.uid, + self.env.context) + self.subtype = self.env.ref('mail.mt_comment') + self.message_obj = self.env['mail.message'] + self.attachment_obj = self.env['ir.attachment'] + self.partner_model = self.env.ref('base.model_res_partner') + today = date.today() + self.before_400_days = today - timedelta(days=400) + + def test_mail_vacuum_rules(self): + rule_vals = { + 'name': 'Subtype Model', + 'ttype': 'message', + 'retention_time': 399, + 'message_type': 'email', + 'model_ids': [(6, 0, [self.env.ref('base.model_res_partner').id])], + 'message_subtype_ids': [(6, 0, [self.subtype.id])], + } + rule = self.env['vacuum.rule'].create(rule_vals) + m1 = self.create_mail_message('notification', self.subtype) + m2 = self.create_mail_message('email', self.env.ref('mail.mt_note')) + m3 = self.create_mail_message('email', False) + message_ids = [m1.id, m2.id, m3.id] + self.message_obj.autovacuum(ttype='message') + message = self.message_obj.search( + [('id', 'in', message_ids)]) + # no message deleted because either message_type is wrong or subtype + # is wrong or subtype is empty + self.assertEqual(len(message), + 3) + + rule.write({'message_type': 'notification', 'retention_time': 405}) + self.message_obj.autovacuum(ttype='message') + message = self.message_obj.search( + [('id', 'in', message_ids)]) + # no message deleted because of retention time + self.assertEqual(len(message), + 3) + rule.write({'retention_time': 399}) + self.message_obj.autovacuum(ttype='message') + message = self.message_obj.search( + [('id', 'in', message_ids)]) + + self.assertEqual(len(message), + 2) + + rule.write({'message_type': 'email', + 'message_subtype_ids': [(6, 0, [])], + 'empty_subtype': True}) + self.message_obj.autovacuum(ttype='message') + message = self.message_obj.search( + [('id', 'in', message_ids)]) + self.assertEqual(len(message), + 0) + + def create_attachment(self, name): + vals = { + 'name': name, + 'datas': base64.b64encode(b'Content'), + 'datas_fname': name, + 'res_id': self.env.ref('base.partner_root').id, + 'res_model': 'res.partner', + } + return self.env['ir.attachment'].create(vals) + + def test_attachment_vacuum_rule(self): + rule_vals = { + 'name': 'Partner Attachments', + 'ttype': 'attachment', + 'retention_time': 100, + 'model_ids': [(6, 0, [self.partner_model.id])], + 'filename_pattern': 'test', + } + self.env['vacuum.rule'].create(rule_vals) + a1 = self.create_attachment('Test-dummy') + a2 = self.create_attachment('test24') + # Force create date to old date to test deletion with 100 days + # retention time + before_102_days = date.today() - timedelta(days=102) + before_102_days_str = before_102_days.strftime( + DEFAULT_SERVER_DATE_FORMAT) + self.env.cr.execute(""" + UPDATE ir_attachment SET create_date = '%s' + WHERE id = %s + """ % (before_102_days_str, a2.id)) + a2.write({'create_date': date.today() - timedelta(days=102)}) + a3 = self.create_attachment('other') + self.env.cr.execute(""" + UPDATE ir_attachment SET create_date = '%s' + WHERE id = %s + """ % (before_102_days_str, a3.id)) + attachment_ids = [a1.id, a2.id, a3.id] + self.attachment_obj.autovacuum(ttype='attachment') + attachments = self.attachment_obj.search( + [('id', 'in', attachment_ids)]) + # Only one message deleted because other 2 are with bad name or to + # recent. + self.assertEqual(len(attachments), + 2) + + def test_retention_time_constraint(self): + rule_vals = { + 'name': 'Subtype Model', + 'ttype': 'message', + 'retention_time': 0, + 'message_type': 'email', + } + with self.assertRaises(exceptions.ValidationError): + self.env['vacuum.rule'].create(rule_vals) + + def test_res_model_domain(self): + partner = self.env['res.partner'].create({'name': 'Test Partner'}) + # automatic creation message + self.assertEqual(len(partner.message_ids), 1) + # change date message to simulate it is an old one + partner.message_ids.write({'date': '2017-01-01'}) + partner_model = self.env.ref('base.model_res_partner') + + rule_vals = { + 'name': 'Partners', + 'ttype': 'message', + 'retention_time': 399, + 'message_type': 'all', + 'model_ids': [(6, 0, [partner_model.id])], + 'model_filter_domain': "[['name', '=', 'Dummy']]", + 'empty_subtype': True, + } + rule = self.env['vacuum.rule'].create(rule_vals) + self.message_obj.autovacuum(ttype='message') + # no message deleted as the filter does not match + self.assertEqual(len(partner.message_ids), 1) + + rule.write({ + 'model_filter_domain': "[['name', '=', 'Test Partner']]" + }) + self.message_obj.autovacuum(ttype='message') + self.assertEqual(len(partner.message_ids), 0) diff --git a/autovacuum_message_attachment/views/rule_vacuum.xml b/autovacuum_message_attachment/views/rule_vacuum.xml new file mode 100644 index 000000000..578a74392 --- /dev/null +++ b/autovacuum_message_attachment/views/rule_vacuum.xml @@ -0,0 +1,67 @@ + + + + + + vacuum.rule.form.view + vacuum.rule + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + vacuum.rule.form.view + vacuum.rule + + + + + + + + + + + Message and Attachment Vacuum Rule + vacuum.rule + form + tree,form + + + + +