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.

586 lines
21 KiB

9 years ago
  1. # Copyright 2016 Ildar Nasyrov <https://it-projects.info/team/iledarn>
  2. # Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
  3. # Copyright 2016 intero-chz <https://github.com/intero-chz>
  4. # Copyright 2016 manawi <https://github.com/manawi>
  5. # Copyright 2018 Kolushov Alexandr <https://it-projects.info/team/KolushovAlexandr>
  6. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
  7. from odoo import api, exceptions, fields, models
  8. from odoo.tools import email_split
  9. from odoo.tools.translate import _
  10. class Wizard(models.TransientModel):
  11. _name = "mail_move_message.wizard"
  12. _description = "Mail move message wizard"
  13. @api.model
  14. def _model_selection(self):
  15. selection = []
  16. config_parameters = self.env["ir.config_parameter"]
  17. model_names = config_parameters.sudo().get_param("mail_relocation_models")
  18. model_names = model_names.split(",") if model_names else []
  19. if "default_message_id" in self.env.context:
  20. message = self.env["mail.message"].browse(
  21. self.env.context["default_message_id"]
  22. )
  23. if message.model and message.model not in model_names:
  24. model_names.append(message.model)
  25. if message.moved_from_model and message.moved_from_model not in model_names:
  26. model_names.append(message.moved_from_model)
  27. if model_names:
  28. selection = [
  29. (m.model, m.display_name)
  30. for m in self.env["ir.model"].search([("model", "in", model_names)])
  31. ]
  32. return selection
  33. @api.model
  34. def default_get(self, fields_list):
  35. res = super(Wizard, self).default_get(fields_list)
  36. available_models = self._model_selection()
  37. if len(available_models):
  38. record = self.env[available_models[0][0]].search([], limit=1)
  39. res["model_record"] = (
  40. len(record) and (available_models[0][0] + "," + str(record.id)) or False
  41. )
  42. if "message_id" in res:
  43. message = self.env["mail.message"].browse(res["message_id"])
  44. email_from = message.email_from
  45. parts = email_split(email_from.replace(" ", ","))
  46. if parts:
  47. email = parts[0]
  48. name = (
  49. email_from.find(email) != -1
  50. and email_from[: email_from.index(email)]
  51. .replace('"', "")
  52. .replace("<", "")
  53. .strip()
  54. or email_from
  55. )
  56. else:
  57. name, email = email_from
  58. res["message_name_from"] = name
  59. res["message_email_from"] = email
  60. res["partner_id"] = message.author_id.id
  61. if message.author_id and self.env.uid not in [
  62. u.id for u in message.author_id.user_ids
  63. ]:
  64. res["filter_by_partner"] = True
  65. if message.author_id and res.get("model"):
  66. res_id = self.env[res["model"]].search([], order="id desc", limit=1)
  67. if res_id:
  68. res["res_id"] = res_id[0].id
  69. config_parameters = self.env["ir.config_parameter"]
  70. res["move_followers"] = config_parameters.sudo().get_param(
  71. "mail_relocation_move_followers"
  72. )
  73. res["uid"] = self.env.uid
  74. return res
  75. message_id = fields.Many2one("mail.message", string="Message")
  76. message_body = fields.Html(
  77. related="message_id.body", string="Message to move", readonly=True
  78. )
  79. message_from = fields.Char(
  80. related="message_id.email_from", string="From", readonly=True
  81. )
  82. message_subject = fields.Char(
  83. related="message_id.subject", string="Subject", readonly=True
  84. )
  85. message_moved_by_message_id = fields.Many2one(
  86. "mail.message",
  87. related="message_id.moved_by_message_id",
  88. string="Moved with",
  89. readonly=True,
  90. )
  91. message_moved_by_user_id = fields.Many2one(
  92. "res.users",
  93. related="message_id.moved_by_user_id",
  94. string="Moved by",
  95. readonly=True,
  96. )
  97. message_is_moved = fields.Boolean(
  98. string="Is Moved", related="message_id.is_moved", readonly=True
  99. )
  100. parent_id = fields.Many2one("mail.message", string="Search by name",)
  101. model_record = fields.Reference(selection="_model_selection", string="Record")
  102. model = fields.Char(compute="_compute_model_res_id", string="Model")
  103. res_id = fields.Integer(compute="_compute_model_res_id", string="Record ID")
  104. can_move = fields.Boolean("Can move", compute="_compute_get_can_move")
  105. move_back = fields.Boolean(
  106. "MOVE TO ORIGIN", help="Move message and submessages to original place"
  107. )
  108. partner_id = fields.Many2one("res.partner", string="Author")
  109. filter_by_partner = fields.Boolean("Filter Records by partner")
  110. message_email_from = fields.Char()
  111. message_name_from = fields.Char()
  112. # FIXME message_to_read should be True even if current message or any his childs are unread
  113. message_to_read = fields.Boolean(
  114. compute="_compute_is_read",
  115. string="Unread message",
  116. help="Service field shows that this message were unread when moved",
  117. )
  118. uid = fields.Integer()
  119. move_followers = fields.Boolean(
  120. "Move Followers",
  121. help="Add followers of current record to a new record.\n"
  122. "You must use this option, if new record has restricted access.\n"
  123. "You can change default value for this option at Settings/System Parameters",
  124. )
  125. @api.depends("model_record")
  126. def _compute_model_res_id(self):
  127. for rec in self:
  128. rec.model = rec.model_record and rec.model_record._name or False
  129. rec.res_id = rec.model_record and rec.model_record.id or False
  130. @api.depends("message_id")
  131. def _compute_get_can_move(self):
  132. for r in self:
  133. r.get_can_move_one()
  134. def _compute_is_read(self):
  135. messages = (
  136. self.env["mail.message"]
  137. .sudo()
  138. .browse(self.message_id.all_child_ids.ids + [self.message_id.id])
  139. )
  140. self.message_to_read = True in [m.needaction for m in messages]
  141. def get_can_move_one(self):
  142. self.ensure_one()
  143. # message was not moved before OR message is a top message of previous move
  144. self.can_move = (
  145. not self.message_id.moved_by_message_id
  146. or self.message_id.moved_by_message_id.id == self.message_id.id
  147. )
  148. @api.onchange("move_back")
  149. def on_change_move_back(self):
  150. if not self.move_back:
  151. return
  152. self.parent_id = self.message_id.moved_from_parent_id
  153. message = self.message_id
  154. if message.is_moved:
  155. self.model_record = self.env[message.moved_from_model].browse(
  156. message.moved_from_res_id
  157. )
  158. @api.onchange("parent_id", "model_record")
  159. def update_move_back(self):
  160. model = self.message_id.moved_from_model
  161. self.move_back = (
  162. self.parent_id == self.message_id.moved_from_parent_id
  163. and self.res_id == self.message_id.moved_from_res_id
  164. and (self.model == model or (not self.model and not model))
  165. )
  166. @api.onchange("parent_id")
  167. def on_change_parent_id(self):
  168. if self.parent_id and self.parent_id.model:
  169. self.model = self.parent_id.model
  170. self.res_id = self.parent_id.res_id
  171. else:
  172. self.model = None
  173. self.res_id = None
  174. @api.onchange("model", "filter_by_partner", "partner_id")
  175. def on_change_partner(self):
  176. domain = {"res_id": [("id", "!=", self.message_id.res_id)]}
  177. if self.model and self.filter_by_partner and self.partner_id:
  178. fields = self.env[self.model].fields_get(False)
  179. contact_field = False
  180. for n, f in fields.items():
  181. if f["type"] == "many2one" and f["relation"] == "res.partner":
  182. contact_field = n
  183. break
  184. if contact_field:
  185. domain["res_id"].append((contact_field, "=", self.partner_id.id))
  186. if self.model:
  187. res_id = self.env[self.model].search(
  188. domain["res_id"], order="id desc", limit=1
  189. )
  190. self.res_id = res_id and res_id[0].id
  191. else:
  192. self.res_id = None
  193. return {"domain": domain}
  194. def check_access(self):
  195. for r in self:
  196. r.check_access_one()
  197. def check_access_one(self):
  198. self.ensure_one()
  199. operation = "write"
  200. if not (self.model and self.res_id):
  201. return True
  202. model_obj = self.env[self.model]
  203. mids = model_obj.browse(self.res_id).exists()
  204. if hasattr(model_obj, "check_mail_message_access"):
  205. model_obj.check_mail_message_access(mids.ids, operation)
  206. else:
  207. self.env["mail.thread"].check_mail_message_access(
  208. mids.ids, operation, model_name=self.model
  209. )
  210. def open_moved_by_message_id(self):
  211. message_id = None
  212. for r in self:
  213. message_id = r.message_moved_by_message_id.id
  214. return {
  215. "type": "ir.actions.act_window",
  216. "res_model": "mail_move_message.wizard",
  217. "view_mode": "form",
  218. "view_type": "form",
  219. "views": [[False, "form"]],
  220. "target": "new",
  221. "context": {"default_message_id": message_id},
  222. }
  223. def move(self):
  224. for r in self:
  225. if not r.model:
  226. raise exceptions.except_orm(
  227. _("Record field is empty!"),
  228. _("Select a record for relocation first"),
  229. )
  230. for r in self:
  231. r.check_access()
  232. if not r.parent_id or not (
  233. r.parent_id.model == r.model and r.parent_id.res_id == r.res_id
  234. ):
  235. # link with the first message of record
  236. parent = self.env["mail.message"].search(
  237. [("model", "=", r.model), ("res_id", "=", r.res_id)],
  238. order="id",
  239. limit=1,
  240. )
  241. r.parent_id = parent.id or None
  242. r.message_id.move(
  243. r.parent_id.id,
  244. r.res_id,
  245. r.model,
  246. r.move_back,
  247. r.move_followers,
  248. r.message_to_read,
  249. r.partner_id,
  250. )
  251. if r.model in ["mail.message", "mail.channel", False]:
  252. return {
  253. "name": "Chess game page",
  254. "type": "ir.actions.act_url",
  255. "url": "/web",
  256. "target": "self",
  257. }
  258. return {
  259. "name": _("Record"),
  260. "view_type": "form",
  261. "view_mode": "form",
  262. "res_model": r.model,
  263. "res_id": r.res_id,
  264. "views": [(False, "form")],
  265. "type": "ir.actions.act_window",
  266. }
  267. def delete(self):
  268. for r in self:
  269. r.delete_one()
  270. def delete_one(self):
  271. self.ensure_one()
  272. msg_id = self.message_id.id
  273. # Send notification
  274. notification = {"id": msg_id}
  275. self.env["bus.bus"].sendone(
  276. (self._cr.dbname, "mail_move_message.delete_message"), notification
  277. )
  278. self.message_id.unlink()
  279. return {}
  280. def read_close(self):
  281. for r in self:
  282. r.read_close_one()
  283. def read_close_one(self):
  284. self.ensure_one()
  285. self.message_id.set_message_done()
  286. self.message_id.child_ids.set_message_done()
  287. return {"type": "ir.actions.act_window_close"}
  288. class MailMessage(models.Model):
  289. _inherit = "mail.message"
  290. is_moved = fields.Boolean("Is moved")
  291. moved_from_res_id = fields.Integer("Related Document ID (Original)")
  292. moved_from_model = fields.Char("Related Document Model (Original)")
  293. moved_from_parent_id = fields.Many2one(
  294. "mail.message", "Parent Message (Original)", ondelete="set null"
  295. )
  296. moved_by_message_id = fields.Many2one(
  297. "mail.message",
  298. "Moved by message",
  299. ondelete="set null",
  300. help="Top message, that initate moving this message",
  301. )
  302. moved_by_user_id = fields.Many2one(
  303. "res.users", "Moved by user", ondelete="set null"
  304. )
  305. all_child_ids = fields.One2many(
  306. "mail.message",
  307. string="All childs",
  308. compute="_compute_get_all_childs",
  309. help="all childs, including subchilds",
  310. )
  311. moved_as_unread = fields.Boolean("Was Unread", default=False)
  312. def _compute_get_all_childs(self, include_myself=True):
  313. for r in self:
  314. r._get_all_childs_one(include_myself=include_myself)
  315. def _get_all_childs_one(self, include_myself=True):
  316. self.ensure_one()
  317. ids = []
  318. if include_myself:
  319. ids.append(self.id)
  320. while True:
  321. new_ids = self.search([("parent_id", "in", ids), ("id", "not in", ids)]).ids
  322. if new_ids:
  323. ids = ids + new_ids
  324. continue
  325. break
  326. moved_childs = self.search([("moved_by_message_id", "=", self.id)]).ids
  327. self.all_child_ids = ids + moved_childs
  328. def move_followers(self, model, ids):
  329. fol_obj = self.env["mail.followers"]
  330. for message in self:
  331. followers = fol_obj.sudo().search(
  332. [("res_model", "=", message.model), ("res_id", "=", message.res_id)]
  333. )
  334. for f in followers:
  335. self.env[model].browse(ids).message_subscribe(
  336. [f.partner_id.id], [s.id for s in f.subtype_ids]
  337. )
  338. def move(
  339. self,
  340. parent_id,
  341. res_id,
  342. model,
  343. move_back,
  344. move_followers=False,
  345. message_to_read=False,
  346. author=False,
  347. ):
  348. for r in self:
  349. r.move_one(
  350. parent_id,
  351. res_id,
  352. model,
  353. move_back,
  354. move_followers=move_followers,
  355. message_to_read=message_to_read,
  356. author=author,
  357. )
  358. def move_one(
  359. self,
  360. parent_id,
  361. res_id,
  362. model,
  363. move_back,
  364. move_followers=False,
  365. message_to_read=False,
  366. author=False,
  367. ):
  368. self.ensure_one()
  369. if parent_id == self.id:
  370. # if for any reason method is called to move message with parent
  371. # equal to oneself, we need stop to prevent infinitive loop in
  372. # building message tree
  373. return
  374. if not self.author_id:
  375. self.write({"author_id": author.id})
  376. vals = {}
  377. if move_back:
  378. # clear variables if we move everything back
  379. vals["is_moved"] = False
  380. vals["moved_by_user_id"] = None
  381. vals["moved_by_message_id"] = None
  382. vals["moved_from_res_id"] = None
  383. vals["moved_from_model"] = None
  384. vals["moved_from_parent_id"] = None
  385. vals["moved_as_unread"] = None
  386. else:
  387. vals["parent_id"] = parent_id
  388. vals["res_id"] = res_id
  389. vals["model"] = model
  390. vals["is_moved"] = True
  391. vals["moved_by_user_id"] = self.env.user.id
  392. vals["moved_by_message_id"] = self.id
  393. vals["moved_as_unread"] = message_to_read
  394. # Update record_name in message
  395. vals["record_name"] = self._get_record_name(vals)
  396. # unread message remains unread after moving back to origin
  397. if self.moved_as_unread and move_back:
  398. notification = {
  399. "mail_message_id": self.id,
  400. "res_partner_id": self.env.user.partner_id.id,
  401. "is_read": False,
  402. }
  403. self.write({"notification_ids": [(0, 0, notification)]})
  404. for r in self.all_child_ids:
  405. r_vals = vals.copy()
  406. if not r.is_moved:
  407. # moved_from_* variables contain not last, but original
  408. # reference
  409. r_vals["moved_from_parent_id"] = r.parent_id.id or r.env.context.get(
  410. "uid"
  411. )
  412. r_vals["moved_from_res_id"] = r.res_id or r.id
  413. r_vals["moved_from_model"] = r.model or r._name
  414. elif move_back:
  415. r_vals["parent_id"] = r.moved_from_parent_id.id
  416. r_vals["res_id"] = r.moved_from_res_id
  417. r_vals["model"] = (
  418. r.moved_from_model
  419. and r.moved_from_model
  420. not in ["mail.message", "mail.channel", False]
  421. ) and r.moved_from_model
  422. r_vals["record_name"] = (
  423. r_vals["model"]
  424. and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
  425. )
  426. if move_followers:
  427. r.sudo().move_followers(r_vals.get("model"), r_vals.get("res_id"))
  428. r.sudo().write(r_vals)
  429. r.attachment_ids.sudo().write(
  430. {"res_id": r_vals.get("res_id"), "res_model": r_vals.get("model")}
  431. )
  432. # Send notification
  433. notification = {
  434. "id": self.id,
  435. "res_id": vals.get("res_id"),
  436. "model": vals.get("model"),
  437. "is_moved": vals["is_moved"],
  438. "record_name": "record_name" in vals and vals["record_name"],
  439. }
  440. self.env["bus.bus"].sendone(
  441. (self._cr.dbname, "mail_move_message"), notification
  442. )
  443. def name_get(self):
  444. context = self.env.context
  445. if not (context or {}).get("extended_name"):
  446. return super(MailMessage, self).name_get()
  447. reads = self.read(["record_name", "model", "res_id"])
  448. res = []
  449. for record in reads:
  450. name = record["record_name"] or ""
  451. extended_name = " [{}] ID {}".format(
  452. record.get("model", "UNDEF"), record.get("res_id", "UNDEF"),
  453. )
  454. res.append((record["id"], name + extended_name))
  455. return res
  456. def message_format(self):
  457. message_values = super(MailMessage, self).message_format()
  458. message_index = {message["id"]: message for message in message_values}
  459. for item in self:
  460. msg = message_index.get(item.id)
  461. if msg:
  462. msg["is_moved"] = item.is_moved
  463. return message_values
  464. class MailMoveMessageConfiguration(models.TransientModel):
  465. _inherit = "res.config.settings"
  466. model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
  467. move_followers = fields.Boolean("Move Followers")
  468. @api.model
  469. def get_values(self):
  470. res = super(MailMoveMessageConfiguration, self).get_values()
  471. config_parameters = self.env["ir.config_parameter"].sudo()
  472. model_names = config_parameters.sudo().get_param("mail_relocation_models")
  473. model_names = model_names.split(",")
  474. model_ids = self.env["ir.model"].sudo().search([("model", "in", model_names)])
  475. res.update(
  476. model_ids=[m.id for m in model_ids],
  477. move_followers=config_parameters.sudo().get_param(
  478. "mail_relocation_move_followers"
  479. ),
  480. )
  481. return res
  482. def set_values(self):
  483. super(MailMoveMessageConfiguration, self).set_values()
  484. config_parameters = self.env["ir.config_parameter"].sudo()
  485. for record in self:
  486. model_names = ",".join([x.model for x in record.model_ids])
  487. config_parameters.set_param("mail_relocation_models", model_names or "")
  488. config_parameters.set_param(
  489. "mail_relocation_move_followers", record.move_followers or ""
  490. )
  491. class ResPartner(models.Model):
  492. _inherit = "res.partner"
  493. @api.model
  494. def create(self, vals):
  495. res = super(ResPartner, self).create(vals)
  496. if "update_message_author" in self.env.context and "email" in vals:
  497. mail_message_obj = self.env["mail.message"]
  498. # Escape special SQL characters in email_address to avoid invalid matches
  499. email_address = (
  500. vals["email"]
  501. .replace("\\", "\\\\")
  502. .replace("%", "\\%")
  503. .replace("_", "\\_")
  504. )
  505. email_brackets = "<%s>" % email_address
  506. messages = mail_message_obj.search(
  507. [
  508. "|",
  509. ("email_from", "=ilike", email_address),
  510. ("email_from", "ilike", email_brackets),
  511. ("author_id", "=", False),
  512. ]
  513. )
  514. if messages:
  515. messages.sudo().write({"author_id": res.id})
  516. return res
  517. @api.model
  518. def default_get(self, default_fields):
  519. contextual_self = self
  520. if (
  521. "mail_move_message" in self.env.context
  522. and self.env.context["mail_move_message"]
  523. ):
  524. contextual_self = self.with_context(
  525. default_name=self.env.context["message_name_from"] or "",
  526. default_email=self.env.context["message_email_from"] or "",
  527. )
  528. return super(ResPartner, contextual_self).default_get(default_fields)