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

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
6 years ago
6 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. @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)