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.

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