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.

606 lines
21 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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. _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.multi
  126. @api.depends("model_record")
  127. def _compute_model_res_id(self):
  128. for rec in self:
  129. rec.model = rec.model_record and rec.model_record._name or False
  130. rec.res_id = rec.model_record and rec.model_record.id or False
  131. @api.depends("message_id")
  132. @api.multi
  133. def _compute_get_can_move(self):
  134. for r in self:
  135. r.get_can_move_one()
  136. @api.multi
  137. def _compute_is_read(self):
  138. messages = (
  139. self.env["mail.message"]
  140. .sudo()
  141. .browse(self.message_id.all_child_ids.ids + [self.message_id.id])
  142. )
  143. self.message_to_read = True in [m.needaction for m in messages]
  144. @api.multi
  145. def get_can_move_one(self):
  146. self.ensure_one()
  147. # message was not moved before OR message is a top message of previous move
  148. self.can_move = (
  149. not self.message_id.moved_by_message_id
  150. or self.message_id.moved_by_message_id.id == self.message_id.id
  151. )
  152. @api.onchange("move_back")
  153. def on_change_move_back(self):
  154. if not self.move_back:
  155. return
  156. self.parent_id = self.message_id.moved_from_parent_id
  157. message = self.message_id
  158. if message.is_moved:
  159. self.model_record = self.env[message.moved_from_model].browse(
  160. message.moved_from_res_id
  161. )
  162. @api.onchange("parent_id", "model_record")
  163. def update_move_back(self):
  164. model = self.message_id.moved_from_model
  165. self.move_back = (
  166. self.parent_id == self.message_id.moved_from_parent_id
  167. and self.res_id == self.message_id.moved_from_res_id
  168. and (self.model == model or (not self.model and not model))
  169. )
  170. @api.onchange("parent_id")
  171. def on_change_parent_id(self):
  172. if self.parent_id and self.parent_id.model:
  173. self.model = self.parent_id.model
  174. self.res_id = self.parent_id.res_id
  175. else:
  176. self.model = None
  177. self.res_id = None
  178. @api.onchange("model", "filter_by_partner", "partner_id")
  179. def on_change_partner(self):
  180. domain = {"res_id": [("id", "!=", self.message_id.res_id)]}
  181. if self.model and self.filter_by_partner and self.partner_id:
  182. fields = self.env[self.model].fields_get(False)
  183. contact_field = False
  184. for n, f in fields.items():
  185. if f["type"] == "many2one" and f["relation"] == "res.partner":
  186. contact_field = n
  187. break
  188. if contact_field:
  189. domain["res_id"].append((contact_field, "=", self.partner_id.id))
  190. if self.model:
  191. res_id = self.env[self.model].search(
  192. domain["res_id"], order="id desc", limit=1
  193. )
  194. self.res_id = res_id and res_id[0].id
  195. else:
  196. self.res_id = None
  197. return {"domain": domain}
  198. @api.multi
  199. def check_access(self):
  200. for r in self:
  201. r.check_access_one()
  202. @api.multi
  203. def check_access_one(self):
  204. self.ensure_one()
  205. operation = "write"
  206. if not (self.model and self.res_id):
  207. return True
  208. model_obj = self.env[self.model]
  209. mids = model_obj.browse(self.res_id).exists()
  210. if hasattr(model_obj, "check_mail_message_access"):
  211. model_obj.check_mail_message_access(mids.ids, operation)
  212. else:
  213. self.env["mail.thread"].check_mail_message_access(
  214. mids.ids, operation, model_name=self.model
  215. )
  216. @api.multi
  217. def open_moved_by_message_id(self):
  218. message_id = None
  219. for r in self:
  220. message_id = r.message_moved_by_message_id.id
  221. return {
  222. "type": "ir.actions.act_window",
  223. "res_model": "mail_move_message.wizard",
  224. "view_mode": "form",
  225. "view_type": "form",
  226. "views": [[False, "form"]],
  227. "target": "new",
  228. "context": {"default_message_id": message_id},
  229. }
  230. @api.multi
  231. def move(self):
  232. for r in self:
  233. if not r.model:
  234. raise exceptions.except_orm(
  235. _("Record field is empty!"),
  236. _("Select a record for relocation first"),
  237. )
  238. for r in self:
  239. r.check_access()
  240. if not r.parent_id or not (
  241. r.parent_id.model == r.model and r.parent_id.res_id == r.res_id
  242. ):
  243. # link with the first message of record
  244. parent = self.env["mail.message"].search(
  245. [("model", "=", r.model), ("res_id", "=", r.res_id)],
  246. order="id",
  247. limit=1,
  248. )
  249. r.parent_id = parent.id or None
  250. r.message_id.move(
  251. r.parent_id.id,
  252. r.res_id,
  253. r.model,
  254. r.move_back,
  255. r.move_followers,
  256. r.message_to_read,
  257. r.partner_id,
  258. )
  259. if r.model in ["mail.message", "mail.channel", False]:
  260. return {
  261. "name": "Chess game page",
  262. "type": "ir.actions.act_url",
  263. "url": "/web",
  264. "target": "self",
  265. }
  266. return {
  267. "name": _("Record"),
  268. "view_type": "form",
  269. "view_mode": "form",
  270. "res_model": r.model,
  271. "res_id": r.res_id,
  272. "views": [(False, "form")],
  273. "type": "ir.actions.act_window",
  274. }
  275. @api.multi
  276. def delete(self):
  277. for r in self:
  278. r.delete_one()
  279. @api.multi
  280. def delete_one(self):
  281. self.ensure_one()
  282. msg_id = self.message_id.id
  283. # Send notification
  284. notification = {"id": msg_id}
  285. self.env["bus.bus"].sendone(
  286. (self._cr.dbname, "mail_move_message.delete_message"), notification
  287. )
  288. self.message_id.unlink()
  289. return {}
  290. @api.multi
  291. def read_close(self):
  292. for r in self:
  293. r.read_close_one()
  294. @api.multi
  295. def read_close_one(self):
  296. self.ensure_one()
  297. self.message_id.set_message_done()
  298. self.message_id.child_ids.set_message_done()
  299. return {"type": "ir.actions.act_window_close"}
  300. class MailMessage(models.Model):
  301. _inherit = "mail.message"
  302. is_moved = fields.Boolean("Is moved")
  303. moved_from_res_id = fields.Integer("Related Document ID (Original)")
  304. moved_from_model = fields.Char("Related Document Model (Original)")
  305. moved_from_parent_id = fields.Many2one(
  306. "mail.message", "Parent Message (Original)", ondelete="set null"
  307. )
  308. moved_by_message_id = fields.Many2one(
  309. "mail.message",
  310. "Moved by message",
  311. ondelete="set null",
  312. help="Top message, that initate moving this message",
  313. )
  314. moved_by_user_id = fields.Many2one(
  315. "res.users", "Moved by user", ondelete="set null"
  316. )
  317. all_child_ids = fields.One2many(
  318. "mail.message",
  319. string="All childs",
  320. compute="_compute_get_all_childs",
  321. help="all childs, including subchilds",
  322. )
  323. moved_as_unread = fields.Boolean("Was Unread", default=False)
  324. @api.multi
  325. def _compute_get_all_childs(self, include_myself=True):
  326. for r in self:
  327. r._get_all_childs_one(include_myself=include_myself)
  328. @api.multi
  329. def _get_all_childs_one(self, include_myself=True):
  330. self.ensure_one()
  331. ids = []
  332. if include_myself:
  333. ids.append(self.id)
  334. while True:
  335. new_ids = self.search([("parent_id", "in", ids), ("id", "not in", ids)]).ids
  336. if new_ids:
  337. ids = ids + new_ids
  338. continue
  339. break
  340. moved_childs = self.search([("moved_by_message_id", "=", self.id)]).ids
  341. self.all_child_ids = ids + moved_childs
  342. @api.multi
  343. def move_followers(self, model, ids):
  344. fol_obj = self.env["mail.followers"]
  345. for message in self:
  346. followers = fol_obj.sudo().search(
  347. [("res_model", "=", message.model), ("res_id", "=", message.res_id)]
  348. )
  349. for f in followers:
  350. self.env[model].browse(ids).message_subscribe(
  351. [f.partner_id.id], [s.id for s in f.subtype_ids]
  352. )
  353. @api.multi
  354. def move(
  355. self,
  356. parent_id,
  357. res_id,
  358. model,
  359. move_back,
  360. move_followers=False,
  361. message_to_read=False,
  362. author=False,
  363. ):
  364. for r in self:
  365. r.move_one(
  366. parent_id,
  367. res_id,
  368. model,
  369. move_back,
  370. move_followers=move_followers,
  371. message_to_read=message_to_read,
  372. author=author,
  373. )
  374. @api.multi
  375. def move_one(
  376. self,
  377. parent_id,
  378. res_id,
  379. model,
  380. move_back,
  381. move_followers=False,
  382. message_to_read=False,
  383. author=False,
  384. ):
  385. self.ensure_one()
  386. if parent_id == self.id:
  387. # if for any reason method is called to move message with parent
  388. # equal to oneself, we need stop to prevent infinitive loop in
  389. # building message tree
  390. return
  391. if not self.author_id:
  392. self.write({"author_id": author.id})
  393. vals = {}
  394. if move_back:
  395. # clear variables if we move everything back
  396. vals["is_moved"] = False
  397. vals["moved_by_user_id"] = None
  398. vals["moved_by_message_id"] = None
  399. vals["moved_from_res_id"] = None
  400. vals["moved_from_model"] = None
  401. vals["moved_from_parent_id"] = None
  402. vals["moved_as_unread"] = None
  403. else:
  404. vals["parent_id"] = parent_id
  405. vals["res_id"] = res_id
  406. vals["model"] = model
  407. vals["is_moved"] = True
  408. vals["moved_by_user_id"] = self.env.user.id
  409. vals["moved_by_message_id"] = self.id
  410. vals["moved_as_unread"] = message_to_read
  411. # Update record_name in message
  412. vals["record_name"] = self._get_record_name(vals)
  413. # unread message remains unread after moving back to origin
  414. if self.moved_as_unread and move_back:
  415. notification = {
  416. "mail_message_id": self.id,
  417. "res_partner_id": self.env.user.partner_id.id,
  418. "is_read": False,
  419. }
  420. self.write({"notification_ids": [(0, 0, notification)]})
  421. for r in self.all_child_ids:
  422. r_vals = vals.copy()
  423. if not r.is_moved:
  424. # moved_from_* variables contain not last, but original
  425. # reference
  426. r_vals["moved_from_parent_id"] = r.parent_id.id or r.env.context.get(
  427. "uid"
  428. )
  429. r_vals["moved_from_res_id"] = r.res_id or r.id
  430. r_vals["moved_from_model"] = r.model or r._name
  431. elif move_back:
  432. r_vals["parent_id"] = r.moved_from_parent_id.id
  433. r_vals["res_id"] = r.moved_from_res_id
  434. r_vals["model"] = (
  435. r.moved_from_model
  436. and r.moved_from_model
  437. not in ["mail.message", "mail.channel", False]
  438. ) and r.moved_from_model
  439. r_vals["record_name"] = (
  440. r_vals["model"]
  441. and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
  442. )
  443. if move_followers:
  444. r.sudo().move_followers(r_vals.get("model"), r_vals.get("res_id"))
  445. r.sudo().write(r_vals)
  446. r.attachment_ids.sudo().write(
  447. {"res_id": r_vals.get("res_id"), "res_model": r_vals.get("model")}
  448. )
  449. # Send notification
  450. notification = {
  451. "id": self.id,
  452. "res_id": vals.get("res_id"),
  453. "model": vals.get("model"),
  454. "is_moved": vals["is_moved"],
  455. "record_name": "record_name" in vals and vals["record_name"],
  456. }
  457. self.env["bus.bus"].sendone(
  458. (self._cr.dbname, "mail_move_message"), notification
  459. )
  460. @api.multi
  461. def name_get(self):
  462. context = self.env.context
  463. if not (context or {}).get("extended_name"):
  464. return super(MailMessage, self).name_get()
  465. reads = self.read(["record_name", "model", "res_id"])
  466. res = []
  467. for record in reads:
  468. name = record["record_name"] or ""
  469. extended_name = " [{}] ID {}".format(
  470. record.get("model", "UNDEF"), record.get("res_id", "UNDEF"),
  471. )
  472. res.append((record["id"], name + extended_name))
  473. return res
  474. @api.multi
  475. def message_format(self):
  476. message_values = super(MailMessage, self).message_format()
  477. message_index = {message["id"]: message for message in message_values}
  478. for item in self:
  479. msg = message_index.get(item.id)
  480. if msg:
  481. msg["is_moved"] = item.is_moved
  482. return message_values
  483. class MailMoveMessageConfiguration(models.TransientModel):
  484. _inherit = "res.config.settings"
  485. model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
  486. move_followers = fields.Boolean("Move Followers")
  487. @api.model
  488. def get_values(self):
  489. res = super(MailMoveMessageConfiguration, self).get_values()
  490. config_parameters = self.env["ir.config_parameter"].sudo()
  491. model_names = config_parameters.sudo().get_param("mail_relocation_models")
  492. model_names = model_names.split(",")
  493. model_ids = self.env["ir.model"].sudo().search([("model", "in", model_names)])
  494. res.update(
  495. model_ids=[m.id for m in model_ids],
  496. move_followers=config_parameters.sudo().get_param(
  497. "mail_relocation_move_followers"
  498. ),
  499. )
  500. return res
  501. @api.multi
  502. def set_values(self):
  503. super(MailMoveMessageConfiguration, self).set_values()
  504. config_parameters = self.env["ir.config_parameter"].sudo()
  505. for record in self:
  506. model_names = ",".join([x.model for x in record.model_ids])
  507. config_parameters.set_param("mail_relocation_models", model_names or "")
  508. config_parameters.set_param(
  509. "mail_relocation_move_followers", record.move_followers or ""
  510. )
  511. class ResPartner(models.Model):
  512. _inherit = "res.partner"
  513. @api.model
  514. def create(self, vals):
  515. res = super(ResPartner, self).create(vals)
  516. if "update_message_author" in self.env.context and "email" in vals:
  517. mail_message_obj = self.env["mail.message"]
  518. # Escape special SQL characters in email_address to avoid invalid matches
  519. email_address = (
  520. vals["email"]
  521. .replace("\\", "\\\\")
  522. .replace("%", "\\%")
  523. .replace("_", "\\_")
  524. )
  525. email_brackets = "<%s>" % email_address
  526. messages = mail_message_obj.search(
  527. [
  528. "|",
  529. ("email_from", "=ilike", email_address),
  530. ("email_from", "ilike", email_brackets),
  531. ("author_id", "=", False),
  532. ]
  533. )
  534. if messages:
  535. messages.sudo().write({"author_id": res.id})
  536. return res
  537. @api.model
  538. def default_get(self, default_fields):
  539. contextual_self = self
  540. if (
  541. "mail_move_message" in self.env.context
  542. and self.env.context["mail_move_message"]
  543. ):
  544. contextual_self = self.with_context(
  545. default_name=self.env.context["message_name_from"] or "",
  546. default_email=self.env.context["message_email_from"] or "",
  547. )
  548. return super(ResPartner, contextual_self).default_get(default_fields)