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.

605 lines
21 KiB

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