Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
from . import autovacuum_mixin
from . import ir_attachment
from . import mail_message
from . import vacuum_rule
from . import base

View File

@@ -0,0 +1,63 @@
# Copyright (C) 2019 Akretion
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
from odoo import api, models
from odoo.modules.registry import Registry
from odoo.tools.safe_eval import datetime, safe_eval
_logger = logging.getLogger(__name__)
class AutovacuumMixin(models.AbstractModel):
_name = "autovacuum.mixin"
_description = "Mixin used to delete messages or attachments"
def batch_unlink(self):
with 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(f"Failed to delete Ms : {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((f"{autovacuum_relation}.{field}", operator, value))
records = self.env[rule.model_id.model].search(record_domain)
return self.search(domain + [("res_id", "in", records.ids)])

View File

@@ -0,0 +1,16 @@
# 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"
assigned_attachment_ids = fields.One2many(
"ir.attachment",
"res_id",
string="Assigned Attachments",
domain=lambda self: [("res_model", "=", self._name)],
auto_join=True,
)

View File

@@ -0,0 +1,37 @@
# Copyright (C) 2018 Akretion
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from datetime import timedelta
from odoo import fields, models
from odoo.osv import expression
class IrAttachment(models.Model):
_name = "ir.attachment"
_inherit = ["ir.attachment", "autovacuum.mixin"]
_autovacuum_relation = "assigned_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)
create_date_domain = [("create_date", "<", limit_date)]
domains = [domain, create_date_domain]
if rule.inheriting_model:
inheriting_model = self.env[rule.inheriting_model]
attachment_link = inheriting_model._inherits.get("ir.attachment")
att_ids = (
inheriting_model.search(create_date_domain).mapped(attachment_link).ids
)
domains.append([("id", "in", att_ids)])
if rule.filename_pattern:
domains.append([("name", "ilike", rule.filename_pattern)])
if rule.model_ids:
models = rule.model_ids.mapped("model")
domains.append([("res_model", "in", models)])
elif not rule.empty_model or not rule.filename_pattern:
# Avoid deleting attachment without model, if there are, it is
# probably some attachments created by Odoo
domains.append([("res_model", "!=", False)])
return expression.AND(domains)

View File

@@ -0,0 +1,38 @@
# Copyright (C) 2018 Akretion
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from datetime import timedelta
from odoo import fields, models
from odoo.osv import expression
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)
domains = [domain, [("date", "<", limit_date)]]
if rule.message_type != "all":
domains.append([("message_type", "=", rule.message_type)])
if rule.model_ids:
models = rule.model_ids.mapped("model")
domains.append([("model", "in", models)])
subtype_ids = rule.message_subtype_ids.ids
if subtype_ids and rule.empty_subtype:
domains.append(
[
"|",
("subtype_id", "in", subtype_ids),
("subtype_id", "=", False),
]
)
elif subtype_ids and not rule.empty_subtype:
domains.append([("subtype_id", "in", subtype_ids)])
elif not subtype_ids and not rule.empty_subtype:
domains.append([("subtype_id", "!=", False)])
return expression.AND(domains)

View File

@@ -0,0 +1,135 @@
# 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"
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.")
)
inheriting_model = fields.Char(
help="If set, this model will be searched and only related attachments will "
"be deleted.\n\nN.B: model must implement _inherits to link ir.attachment"
)
company_id = fields.Many2one(
"res.company",
string="Company",
default=lambda self: self.env.company,
)
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",
compute="_compute_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()
model = fields.Char(compute="_compute_model_id", string="Model code")
empty_model = fields.Boolean(
help="Take into account attachment not linked to any model, but only if a "
"pattern is set, to avoid deleting attachments generated/needed by odoo"
)
message_type = fields.Selection(
[
("email", "Email"),
("comment", "Comment"),
("notification", "System notification"),
("user_notification", "User Specific 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.depends("model_ids")
def _compute_model_id(self):
for rule in self:
model_id = False
model = False
if rule.model_ids and len(rule.model_ids) == 1:
model_id = rule.model_ids.id
model = rule.model_id.model
rule.model_id = model_id
rule.model = model
@api.constrains("retention_time")
def retention_time_not_null(self):
for rule in self:
if not rule.retention_time:
raise exceptions.ValidationError(
self.env._("The Retention Time can't be 0 days")
)
@api.constrains("inheriting_model")
def _check_inheriting_model(self):
for rule in self.filtered(lambda r: r.inheriting_model):
if rule.ttype != "attachment":
raise exceptions.ValidationError(
self.env._(
"Inheriting model cannot be used on "
"rule where type is not attachment"
)
)
if (
rule.inheriting_model
not in self.env["ir.attachment"]._inherits_children
):
raise exceptions.ValidationError(
self.env._(
"No inheritance of ir.attachment "
f"was found on model {rule.inheriting_model}"
)
)
attachment_field = self.env[rule.inheriting_model]._inherits.get(
"ir.attachment"
)
if not attachment_field:
raise exceptions.ValidationError(
self.env._(
"Cannot find relation to ir.attachment "
f"on model {rule.inheriting_model}"
)
)
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)