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.
591 lines
21 KiB
591 lines
21 KiB
# Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
|
|
# Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
|
|
# Copyright 2016 intero-chz <https://github.com/intero-chz>
|
|
# Copyright 2016 manawi <https://github.com/manawi>
|
|
# Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
|
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
|
|
|
from odoo import api, exceptions, fields, models
|
|
from odoo.tools import email_split
|
|
from odoo.tools.translate import _
|
|
|
|
|
|
class Wizard(models.TransientModel):
|
|
_name = "mail_move_message.wizard"
|
|
_description = "Mail move message wizard"
|
|
|
|
@api.model
|
|
def _model_selection(self):
|
|
selection = []
|
|
config_parameters = self.env["ir.config_parameter"]
|
|
model_names = config_parameters.sudo().get_param("mail_relocation_models")
|
|
model_names = model_names.split(",") if model_names else []
|
|
if "default_message_id" in self.env.context:
|
|
message = self.env["mail.message"].browse(
|
|
self.env.context["default_message_id"]
|
|
)
|
|
if message.model and message.model not in model_names:
|
|
model_names.append(message.model)
|
|
if message.moved_from_model and message.moved_from_model not in model_names:
|
|
model_names.append(message.moved_from_model)
|
|
if model_names:
|
|
selection = [
|
|
(m.model, m.display_name)
|
|
for m in self.env["ir.model"].search([("model", "in", model_names)])
|
|
]
|
|
return selection
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
res = super(Wizard, self).default_get(fields_list)
|
|
|
|
available_models = self._model_selection()
|
|
if len(available_models):
|
|
record = self.env[available_models[0][0]].search([], limit=1)
|
|
res["model_record"] = (
|
|
len(record) and (available_models[0][0] + "," + str(record.id)) or False
|
|
)
|
|
|
|
if "message_id" in res:
|
|
message = self.env["mail.message"].browse(res["message_id"])
|
|
email_from = message.email_from
|
|
parts = email_split(email_from.replace(" ", ","))
|
|
if parts:
|
|
email = parts[0]
|
|
name = (
|
|
email_from.find(email) != -1
|
|
and email_from[: email_from.index(email)]
|
|
.replace('"', "")
|
|
.replace("<", "")
|
|
.strip()
|
|
or email_from
|
|
)
|
|
else:
|
|
name, email = email_from
|
|
res["message_name_from"] = name
|
|
res["message_email_from"] = email
|
|
|
|
res["partner_id"] = message.author_id.id
|
|
if message.author_id and self.env.uid not in [
|
|
u.id for u in message.author_id.user_ids
|
|
]:
|
|
res["filter_by_partner"] = True
|
|
if message.author_id and res.get("model"):
|
|
res_id = self.env[res["model"]].search([], order="id desc", limit=1)
|
|
if res_id:
|
|
res["res_id"] = res_id[0].id
|
|
|
|
config_parameters = self.env["ir.config_parameter"]
|
|
res["move_followers"] = config_parameters.sudo().get_param(
|
|
"mail_relocation_move_followers"
|
|
)
|
|
|
|
res["uid"] = self.env.uid
|
|
return res
|
|
|
|
message_id = fields.Many2one("mail.message", string="Message")
|
|
message_body = fields.Html(
|
|
related="message_id.body", string="Message to move", readonly=True
|
|
)
|
|
message_from = fields.Char(
|
|
related="message_id.email_from", string="From", readonly=True
|
|
)
|
|
message_subject = fields.Char(
|
|
related="message_id.subject", string="Subject", readonly=True
|
|
)
|
|
message_moved_by_message_id = fields.Many2one(
|
|
"mail.message",
|
|
related="message_id.moved_by_message_id",
|
|
string="Moved with",
|
|
readonly=True,
|
|
)
|
|
message_moved_by_user_id = fields.Many2one(
|
|
"res.users",
|
|
related="message_id.moved_by_user_id",
|
|
string="Moved by",
|
|
readonly=True,
|
|
)
|
|
message_is_moved = fields.Boolean(
|
|
string="Is Moved", related="message_id.is_moved", readonly=True
|
|
)
|
|
parent_id = fields.Many2one("mail.message", string="Search by name",)
|
|
model_record = fields.Reference(selection="_model_selection", string="Record")
|
|
model = fields.Char(compute="_compute_model_res_id", string="Model")
|
|
res_id = fields.Integer(compute="_compute_model_res_id", string="Record ID")
|
|
|
|
can_move = fields.Boolean("Can move", compute="_compute_get_can_move")
|
|
move_back = fields.Boolean(
|
|
"MOVE TO ORIGIN", help="Move message and submessages to original place"
|
|
)
|
|
partner_id = fields.Many2one("res.partner", string="Author")
|
|
filter_by_partner = fields.Boolean("Filter Records by partner")
|
|
message_email_from = fields.Char()
|
|
message_name_from = fields.Char()
|
|
# FIXME message_to_read should be True even if current message or any his childs are unread
|
|
message_to_read = fields.Boolean(
|
|
compute="_compute_is_read",
|
|
string="Unread message",
|
|
help="Service field shows that this message were unread when moved",
|
|
)
|
|
uid = fields.Integer()
|
|
move_followers = fields.Boolean(
|
|
"Move Followers",
|
|
help="Add followers of current record to a new record.\n"
|
|
"You must use this option, if new record has restricted access.\n"
|
|
"You can change default value for this option at Settings/System Parameters",
|
|
)
|
|
|
|
@api.depends("model_record")
|
|
def _compute_model_res_id(self):
|
|
for rec in self:
|
|
rec.model = rec.model_record and rec.model_record._name or False
|
|
rec.res_id = rec.model_record and rec.model_record.id or False
|
|
|
|
@api.depends("message_id")
|
|
def _compute_get_can_move(self):
|
|
for r in self:
|
|
r.get_can_move_one()
|
|
|
|
def _compute_is_read(self):
|
|
messages = (
|
|
self.env["mail.message"]
|
|
.sudo()
|
|
.browse(self.message_id.all_child_ids.ids + [self.message_id.id])
|
|
)
|
|
self.message_to_read = True in [m.needaction for m in messages]
|
|
|
|
def get_can_move_one(self):
|
|
self.ensure_one()
|
|
# message was not moved before OR message is a top message of previous move
|
|
self.can_move = (
|
|
not self.message_id.moved_by_message_id
|
|
or self.message_id.moved_by_message_id.id == self.message_id.id
|
|
)
|
|
|
|
@api.onchange("move_back")
|
|
def on_change_move_back(self):
|
|
if not self.move_back:
|
|
return
|
|
self.parent_id = self.message_id.moved_from_parent_id
|
|
message = self.message_id
|
|
if message.is_moved:
|
|
self.model_record = self.env[message.moved_from_model].browse(
|
|
message.moved_from_res_id
|
|
)
|
|
|
|
@api.onchange("parent_id", "model_record")
|
|
def update_move_back(self):
|
|
model = self.message_id.moved_from_model
|
|
self.move_back = (
|
|
self.parent_id == self.message_id.moved_from_parent_id
|
|
and self.res_id == self.message_id.moved_from_res_id
|
|
and (self.model == model or (not self.model and not model))
|
|
)
|
|
|
|
@api.onchange("parent_id")
|
|
def on_change_parent_id(self):
|
|
if self.parent_id and self.parent_id.model:
|
|
self.model = self.parent_id.model
|
|
self.res_id = self.parent_id.res_id
|
|
else:
|
|
self.model = None
|
|
self.res_id = None
|
|
|
|
@api.onchange("model", "filter_by_partner", "partner_id")
|
|
def on_change_partner(self):
|
|
domain = {"res_id": [("id", "!=", self.message_id.res_id)]}
|
|
if self.model and self.filter_by_partner and self.partner_id:
|
|
fields = self.env[self.model].fields_get(False)
|
|
contact_field = False
|
|
for n, f in fields.items():
|
|
if f["type"] == "many2one" and f["relation"] == "res.partner":
|
|
contact_field = n
|
|
break
|
|
if contact_field:
|
|
domain["res_id"].append((contact_field, "=", self.partner_id.id))
|
|
if self.model:
|
|
res_id = self.env[self.model].search(
|
|
domain["res_id"], order="id desc", limit=1
|
|
)
|
|
self.res_id = res_id and res_id[0].id
|
|
else:
|
|
self.res_id = None
|
|
return {"domain": domain}
|
|
|
|
def check_access(self):
|
|
for r in self:
|
|
r.check_access_one()
|
|
|
|
def check_access_one(self):
|
|
self.ensure_one()
|
|
operation = "write"
|
|
|
|
if not (self.model and self.res_id):
|
|
return True
|
|
model_obj = self.env[self.model]
|
|
mids = model_obj.browse(self.res_id).exists()
|
|
if hasattr(model_obj, "check_mail_message_access"):
|
|
model_obj.check_mail_message_access(mids.ids, operation)
|
|
else:
|
|
self.env["mail.thread"].check_mail_message_access(
|
|
mids.ids, operation, model_name=self.model
|
|
)
|
|
|
|
def open_moved_by_message_id(self):
|
|
message_id = None
|
|
for r in self:
|
|
message_id = r.message_moved_by_message_id.id
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "mail_move_message.wizard",
|
|
"view_mode": "form",
|
|
"view_type": "form",
|
|
"views": [[False, "form"]],
|
|
"target": "new",
|
|
"context": {"default_message_id": message_id},
|
|
}
|
|
|
|
def move(self):
|
|
for r in self:
|
|
if not r.model:
|
|
raise exceptions.except_orm(
|
|
_("Record field is empty!"),
|
|
_("Select a record for relocation first"),
|
|
)
|
|
for r in self:
|
|
r.check_access()
|
|
if not r.parent_id or not (
|
|
r.parent_id.model == r.model and r.parent_id.res_id == r.res_id
|
|
):
|
|
# link with the first message of record
|
|
parent = self.env["mail.message"].search(
|
|
[("model", "=", r.model), ("res_id", "=", r.res_id)],
|
|
order="id",
|
|
limit=1,
|
|
)
|
|
r.parent_id = parent.id or None
|
|
r.message_id.move(
|
|
r.parent_id.id,
|
|
r.res_id,
|
|
r.model,
|
|
r.move_back,
|
|
r.move_followers,
|
|
r.message_to_read,
|
|
r.partner_id,
|
|
)
|
|
|
|
if r.model in ["mail.message", "mail.channel", False]:
|
|
return {
|
|
"name": "Chess game page",
|
|
"type": "ir.actions.act_url",
|
|
"url": "/web",
|
|
"target": "self",
|
|
}
|
|
return {
|
|
"name": _("Record"),
|
|
"view_type": "form",
|
|
"view_mode": "form",
|
|
"res_model": r.model,
|
|
"res_id": r.res_id,
|
|
"views": [(False, "form")],
|
|
"type": "ir.actions.act_window",
|
|
}
|
|
|
|
def delete(self):
|
|
for r in self:
|
|
r.delete_one()
|
|
|
|
def delete_one(self):
|
|
self.ensure_one()
|
|
msg_id = self.message_id.id
|
|
|
|
# Send notification
|
|
notification = {"id": msg_id}
|
|
self.env["bus.bus"].sendone(
|
|
(self._cr.dbname, "mail_move_message.delete_message"), notification
|
|
)
|
|
|
|
self.message_id.unlink()
|
|
return {}
|
|
|
|
def read_close(self):
|
|
for r in self:
|
|
r.read_close_one()
|
|
|
|
def read_close_one(self):
|
|
self.ensure_one()
|
|
self.message_id.set_message_done()
|
|
self.message_id.child_ids.set_message_done()
|
|
return {"type": "ir.actions.act_window_close"}
|
|
|
|
|
|
class MailMessage(models.Model):
|
|
_inherit = "mail.message"
|
|
|
|
is_moved = fields.Boolean("Is moved")
|
|
moved_from_res_id = fields.Integer("Related Document ID (Original)")
|
|
moved_from_model = fields.Char("Related Document Model (Original)")
|
|
moved_from_parent_id = fields.Many2one(
|
|
"mail.message", "Parent Message (Original)", ondelete="set null"
|
|
)
|
|
moved_by_message_id = fields.Many2one(
|
|
"mail.message",
|
|
"Moved by message",
|
|
ondelete="set null",
|
|
help="Top message, that initate moving this message",
|
|
)
|
|
moved_by_user_id = fields.Many2one(
|
|
"res.users", "Moved by user", ondelete="set null"
|
|
)
|
|
all_child_ids = fields.One2many(
|
|
"mail.message",
|
|
string="All childs",
|
|
compute="_compute_get_all_childs",
|
|
help="all childs, including subchilds",
|
|
)
|
|
moved_as_unread = fields.Boolean("Was Unread", default=False)
|
|
|
|
def _compute_get_all_childs(self, include_myself=True):
|
|
for r in self:
|
|
r._get_all_childs_one(include_myself=include_myself)
|
|
|
|
def _get_all_childs_one(self, include_myself=True):
|
|
self.ensure_one()
|
|
ids = []
|
|
if include_myself:
|
|
ids.append(self.id)
|
|
while True:
|
|
new_ids = self.search([("parent_id", "in", ids), ("id", "not in", ids)]).ids
|
|
if new_ids:
|
|
ids = ids + new_ids
|
|
continue
|
|
break
|
|
moved_childs = self.search([("moved_by_message_id", "=", self.id)]).ids
|
|
self.all_child_ids = ids + moved_childs
|
|
|
|
def move_followers(self, model, ids):
|
|
fol_obj = self.env["mail.followers"]
|
|
for message in self:
|
|
followers = fol_obj.sudo().search(
|
|
[("res_model", "=", message.model), ("res_id", "=", message.res_id)]
|
|
)
|
|
for f in followers:
|
|
self.env[model].browse(ids).message_subscribe(
|
|
[f.partner_id.id], [s.id for s in f.subtype_ids]
|
|
)
|
|
|
|
def move(
|
|
self,
|
|
parent_id,
|
|
res_id,
|
|
model,
|
|
move_back,
|
|
move_followers=False,
|
|
message_to_read=False,
|
|
author=False,
|
|
):
|
|
for r in self:
|
|
r.move_one(
|
|
parent_id,
|
|
res_id,
|
|
model,
|
|
move_back,
|
|
move_followers=move_followers,
|
|
message_to_read=message_to_read,
|
|
author=author,
|
|
)
|
|
|
|
def move_one(
|
|
self,
|
|
parent_id,
|
|
res_id,
|
|
model,
|
|
move_back,
|
|
move_followers=False,
|
|
message_to_read=False,
|
|
author=False,
|
|
):
|
|
self.ensure_one()
|
|
if parent_id == self.id:
|
|
# if for any reason method is called to move message with parent
|
|
# equal to oneself, we need stop to prevent infinitive loop in
|
|
# building message tree
|
|
return
|
|
if not self.author_id:
|
|
self.write(
|
|
{"author_id": author.id}
|
|
)
|
|
|
|
vals = {}
|
|
if move_back:
|
|
# clear variables if we move everything back
|
|
vals["is_moved"] = False
|
|
vals["moved_by_user_id"] = None
|
|
vals["moved_by_message_id"] = None
|
|
|
|
vals["moved_from_res_id"] = None
|
|
vals["moved_from_model"] = None
|
|
vals["moved_from_parent_id"] = None
|
|
vals["moved_as_unread"] = None
|
|
else:
|
|
vals["parent_id"] = parent_id
|
|
vals["res_id"] = res_id
|
|
vals["model"] = model
|
|
|
|
vals["is_moved"] = True
|
|
vals["moved_by_user_id"] = self.env.user.id
|
|
vals["moved_by_message_id"] = self.id
|
|
vals["moved_as_unread"] = message_to_read
|
|
# Update record_name in message
|
|
vals["record_name"] = self._get_record_name(vals)
|
|
|
|
# unread message remains unread after moving back to origin
|
|
if self.moved_as_unread and move_back:
|
|
notification = {
|
|
"mail_message_id": self.id,
|
|
"res_partner_id": self.env.user.partner_id.id,
|
|
"is_read": False,
|
|
}
|
|
self.write(
|
|
{"notification_ids": [(0, 0, notification)]}
|
|
)
|
|
|
|
for r in self.all_child_ids:
|
|
r_vals = vals.copy()
|
|
if not r.is_moved:
|
|
# moved_from_* variables contain not last, but original
|
|
# reference
|
|
r_vals["moved_from_parent_id"] = r.parent_id.id or r.env.context.get(
|
|
"uid"
|
|
)
|
|
r_vals["moved_from_res_id"] = r.res_id or r.id
|
|
r_vals["moved_from_model"] = r.model or r._name
|
|
elif move_back:
|
|
r_vals["parent_id"] = r.moved_from_parent_id.id
|
|
r_vals["res_id"] = r.moved_from_res_id
|
|
r_vals["model"] = (
|
|
r.moved_from_model
|
|
and r.moved_from_model
|
|
not in ["mail.message", "mail.channel", False]
|
|
) and r.moved_from_model
|
|
r_vals["record_name"] = (
|
|
r_vals["model"]
|
|
and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
|
|
)
|
|
|
|
if move_followers:
|
|
r.sudo().move_followers(r_vals.get("model"), r_vals.get("res_id"))
|
|
r.sudo().write(r_vals)
|
|
r.attachment_ids.sudo().write(
|
|
{"res_id": r_vals.get("res_id"), "res_model": r_vals.get("model")}
|
|
)
|
|
|
|
# Send notification
|
|
notification = {
|
|
"id": self.id,
|
|
"res_id": vals.get("res_id"),
|
|
"model": vals.get("model"),
|
|
"is_moved": vals["is_moved"],
|
|
"record_name": "record_name" in vals and vals["record_name"],
|
|
}
|
|
self.env["bus.bus"].sendone(
|
|
(self._cr.dbname, "mail_move_message"), notification
|
|
)
|
|
|
|
def name_get(self):
|
|
context = self.env.context
|
|
if not (context or {}).get("extended_name"):
|
|
return super(MailMessage, self).name_get()
|
|
reads = self.read(["record_name", "model", "res_id"])
|
|
res = []
|
|
for record in reads:
|
|
name = record["record_name"] or ""
|
|
extended_name = " [{}] ID {}".format(
|
|
record.get("model", "UNDEF"),
|
|
record.get("res_id", "UNDEF"),
|
|
)
|
|
res.append((record["id"], name + extended_name))
|
|
return res
|
|
|
|
def message_format(self):
|
|
message_values = super(MailMessage, self).message_format()
|
|
message_index = {message["id"]: message for message in message_values}
|
|
for item in self:
|
|
msg = message_index.get(item.id)
|
|
if msg:
|
|
msg["is_moved"] = item.is_moved
|
|
return message_values
|
|
|
|
|
|
class MailMoveMessageConfiguration(models.TransientModel):
|
|
_inherit = "res.config.settings"
|
|
|
|
model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
|
|
move_followers = fields.Boolean("Move Followers")
|
|
|
|
@api.model
|
|
def get_values(self):
|
|
res = super(MailMoveMessageConfiguration, self).get_values()
|
|
config_parameters = self.env["ir.config_parameter"].sudo()
|
|
model_names = config_parameters.sudo().get_param("mail_relocation_models")
|
|
model_names = model_names.split(",")
|
|
model_ids = self.env["ir.model"].sudo().search([("model", "in", model_names)])
|
|
res.update(
|
|
model_ids=[m.id for m in model_ids],
|
|
move_followers=config_parameters.sudo().get_param(
|
|
"mail_relocation_move_followers"
|
|
),
|
|
)
|
|
return res
|
|
|
|
def set_values(self):
|
|
super(MailMoveMessageConfiguration, self).set_values()
|
|
config_parameters = self.env["ir.config_parameter"].sudo()
|
|
for record in self:
|
|
model_names = ",".join([x.model for x in record.model_ids])
|
|
config_parameters.set_param("mail_relocation_models", model_names or "")
|
|
config_parameters.set_param(
|
|
"mail_relocation_move_followers", record.move_followers or ""
|
|
)
|
|
|
|
|
|
class ResPartner(models.Model):
|
|
_inherit = "res.partner"
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
res = super(ResPartner, self).create(vals)
|
|
if "update_message_author" in self.env.context and "email" in vals:
|
|
mail_message_obj = self.env["mail.message"]
|
|
# Escape special SQL characters in email_address to avoid invalid matches
|
|
email_address = (
|
|
vals["email"]
|
|
.replace("\\", "\\\\")
|
|
.replace("%", "\\%")
|
|
.replace("_", "\\_")
|
|
)
|
|
email_brackets = "<%s>" % email_address
|
|
messages = mail_message_obj.search(
|
|
[
|
|
"|",
|
|
("email_from", "=ilike", email_address),
|
|
("email_from", "ilike", email_brackets),
|
|
("author_id", "=", False),
|
|
]
|
|
)
|
|
if messages:
|
|
messages.sudo().write({"author_id": res.id})
|
|
return res
|
|
|
|
@api.model
|
|
def default_get(self, default_fields):
|
|
contextual_self = self
|
|
if (
|
|
"mail_move_message" in self.env.context
|
|
and self.env.context["mail_move_message"]
|
|
):
|
|
contextual_self = self.with_context(
|
|
default_name=self.env.context["message_name_from"] or "",
|
|
default_email=self.env.context["message_email_from"] or "",
|
|
)
|
|
return super(ResPartner, contextual_self).default_get(default_fields)
|