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.

611 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.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(
  393. {"author_id": author.id,}
  394. )
  395. vals = {}
  396. if move_back:
  397. # clear variables if we move everything back
  398. vals["is_moved"] = False
  399. vals["moved_by_user_id"] = None
  400. vals["moved_by_message_id"] = None
  401. vals["moved_from_res_id"] = None
  402. vals["moved_from_model"] = None
  403. vals["moved_from_parent_id"] = None
  404. vals["moved_as_unread"] = None
  405. else:
  406. vals["parent_id"] = parent_id
  407. vals["res_id"] = res_id
  408. vals["model"] = model
  409. vals["is_moved"] = True
  410. vals["moved_by_user_id"] = self.env.user.id
  411. vals["moved_by_message_id"] = self.id
  412. vals["moved_as_unread"] = message_to_read
  413. # Update record_name in message
  414. vals["record_name"] = self._get_record_name(vals)
  415. # unread message remains unread after moving back to origin
  416. if self.moved_as_unread and move_back:
  417. notification = {
  418. "mail_message_id": self.id,
  419. "res_partner_id": self.env.user.partner_id.id,
  420. "is_read": False,
  421. }
  422. self.write(
  423. {"notification_ids": [(0, 0, notification)],}
  424. )
  425. for r in self.all_child_ids:
  426. r_vals = vals.copy()
  427. if not r.is_moved:
  428. # moved_from_* variables contain not last, but original
  429. # reference
  430. r_vals["moved_from_parent_id"] = r.parent_id.id or r.env.context.get(
  431. "uid"
  432. )
  433. r_vals["moved_from_res_id"] = r.res_id or r.id
  434. r_vals["moved_from_model"] = r.model or r._name
  435. elif move_back:
  436. r_vals["parent_id"] = r.moved_from_parent_id.id
  437. r_vals["res_id"] = r.moved_from_res_id
  438. r_vals["model"] = (
  439. r.moved_from_model
  440. and r.moved_from_model
  441. not in ["mail.message", "mail.channel", False]
  442. ) and r.moved_from_model
  443. r_vals["record_name"] = (
  444. r_vals["model"]
  445. and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
  446. )
  447. if move_followers:
  448. r.sudo().move_followers(r_vals.get("model"), r_vals.get("res_id"))
  449. r.sudo().write(r_vals)
  450. r.attachment_ids.sudo().write(
  451. {"res_id": r_vals.get("res_id"), "res_model": r_vals.get("model")}
  452. )
  453. # Send notification
  454. notification = {
  455. "id": self.id,
  456. "res_id": vals.get("res_id"),
  457. "model": vals.get("model"),
  458. "is_moved": vals["is_moved"],
  459. "record_name": "record_name" in vals and vals["record_name"],
  460. }
  461. self.env["bus.bus"].sendone(
  462. (self._cr.dbname, "mail_move_message"), notification
  463. )
  464. @api.multi
  465. def name_get(self):
  466. context = self.env.context
  467. if not (context or {}).get("extended_name"):
  468. return super(MailMessage, self).name_get()
  469. reads = self.read(["record_name", "model", "res_id"])
  470. res = []
  471. for record in reads:
  472. name = record["record_name"] or ""
  473. extended_name = " [{}] ID {}".format(
  474. record.get("model", "UNDEF"),
  475. record.get("res_id", "UNDEF"),
  476. )
  477. res.append((record["id"], name + extended_name))
  478. return res
  479. @api.multi
  480. def message_format(self):
  481. message_values = super(MailMessage, self).message_format()
  482. message_index = {message["id"]: message for message in message_values}
  483. for item in self:
  484. msg = message_index.get(item.id)
  485. if msg:
  486. msg["is_moved"] = item.is_moved
  487. return message_values
  488. class MailMoveMessageConfiguration(models.TransientModel):
  489. _inherit = "res.config.settings"
  490. model_ids = fields.Many2many(comodel_name="ir.model", string="Models")
  491. move_followers = fields.Boolean("Move Followers")
  492. @api.model
  493. def get_values(self):
  494. res = super(MailMoveMessageConfiguration, self).get_values()
  495. config_parameters = self.env["ir.config_parameter"].sudo()
  496. model_names = config_parameters.sudo().get_param("mail_relocation_models")
  497. model_names = model_names.split(",")
  498. model_ids = self.env["ir.model"].sudo().search([("model", "in", model_names)])
  499. res.update(
  500. model_ids=[m.id for m in model_ids],
  501. move_followers=config_parameters.sudo().get_param(
  502. "mail_relocation_move_followers"
  503. ),
  504. )
  505. return res
  506. @api.multi
  507. def set_values(self):
  508. super(MailMoveMessageConfiguration, self).set_values()
  509. config_parameters = self.env["ir.config_parameter"].sudo()
  510. for record in self:
  511. model_names = ",".join([x.model for x in record.model_ids])
  512. config_parameters.set_param("mail_relocation_models", model_names or "")
  513. config_parameters.set_param(
  514. "mail_relocation_move_followers", record.move_followers or ""
  515. )
  516. class ResPartner(models.Model):
  517. _inherit = "res.partner"
  518. @api.model
  519. def create(self, vals):
  520. res = super(ResPartner, self).create(vals)
  521. if "update_message_author" in self.env.context and "email" in vals:
  522. mail_message_obj = self.env["mail.message"]
  523. # Escape special SQL characters in email_address to avoid invalid matches
  524. email_address = (
  525. vals["email"]
  526. .replace("\\", "\\\\")
  527. .replace("%", "\\%")
  528. .replace("_", "\\_")
  529. )
  530. email_brackets = "<%s>" % email_address
  531. messages = mail_message_obj.search(
  532. [
  533. "|",
  534. ("email_from", "=ilike", email_address),
  535. ("email_from", "ilike", email_brackets),
  536. ("author_id", "=", False),
  537. ]
  538. )
  539. if messages:
  540. messages.sudo().write({"author_id": res.id})
  541. return res
  542. @api.model
  543. def default_get(self, default_fields):
  544. contextual_self = self
  545. if (
  546. "mail_move_message" in self.env.context
  547. and self.env.context["mail_move_message"]
  548. ):
  549. contextual_self = self.with_context(
  550. default_name=self.env.context["message_name_from"] or "",
  551. default_email=self.env.context["message_email_from"] or "",
  552. )
  553. return super(ResPartner, contextual_self).default_get(default_fields)