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.

469 lines
20 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
  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.multi
  87. @api.depends('model_record')
  88. def _compute_model_res_id(self):
  89. for rec in self:
  90. rec.model = rec.model_record and rec.model_record._name or False
  91. rec.res_id = rec.model_record and rec.model_record.id or False
  92. @api.depends('message_id')
  93. @api.multi
  94. def _compute_get_can_move(self):
  95. for r in self:
  96. r.get_can_move_one()
  97. @api.multi
  98. def _compute_is_read(self):
  99. messages = self.env['mail.message'].sudo().browse(self.message_id.all_child_ids.ids + [self.message_id.id])
  100. self.message_to_read = True in [m.needaction for m in messages]
  101. @api.multi
  102. def get_can_move_one(self):
  103. self.ensure_one()
  104. # message was not moved before OR message is a top message of previous move
  105. self.can_move = not self.message_id.moved_by_message_id or self.message_id.moved_by_message_id.id == self.message_id.id
  106. @api.onchange('move_back')
  107. def on_change_move_back(self):
  108. if not self.move_back:
  109. return
  110. self.parent_id = self.message_id.moved_from_parent_id
  111. message = self.message_id
  112. if message.is_moved:
  113. self.model_record = self.env[message.moved_from_model].browse(message.moved_from_res_id)
  114. @api.onchange('parent_id', 'model_record')
  115. def update_move_back(self):
  116. model = self.message_id.moved_from_model
  117. self.move_back = self.parent_id == self.message_id.moved_from_parent_id \
  118. and self.res_id == self.message_id.moved_from_res_id \
  119. and (self.model == model or (not self.model and not model))
  120. @api.onchange('parent_id')
  121. def on_change_parent_id(self):
  122. if self.parent_id and self.parent_id.model:
  123. self.model = self.parent_id.model
  124. self.res_id = self.parent_id.res_id
  125. else:
  126. self.model = None
  127. self.res_id = None
  128. @api.onchange('model', 'filter_by_partner', 'partner_id')
  129. def on_change_partner(self):
  130. domain = {'res_id': [('id', '!=', self.message_id.res_id)]}
  131. if self.model and self.filter_by_partner and self.partner_id:
  132. fields = self.env[self.model].fields_get(False)
  133. contact_field = False
  134. for n, f in fields.items():
  135. if f['type'] == 'many2one' and f['relation'] == 'res.partner':
  136. contact_field = n
  137. break
  138. if contact_field:
  139. domain['res_id'].append((contact_field, '=', self.partner_id.id))
  140. if self.model:
  141. res_id = self.env[self.model].search(domain['res_id'], order='id desc', limit=1)
  142. self.res_id = res_id and res_id[0].id
  143. else:
  144. self.res_id = None
  145. return {'domain': domain}
  146. @api.multi
  147. def check_access(self):
  148. for r in self:
  149. r.check_access_one()
  150. @api.multi
  151. def check_access_one(self):
  152. self.ensure_one()
  153. operation = 'write'
  154. if not (self.model and self.res_id):
  155. return True
  156. model_obj = self.env[self.model]
  157. mids = model_obj.browse(self.res_id).exists()
  158. if hasattr(model_obj, 'check_mail_message_access'):
  159. model_obj.check_mail_message_access(mids.ids, operation)
  160. else:
  161. self.env['mail.thread'].check_mail_message_access(mids.ids, operation, model_name=self.model)
  162. @api.multi
  163. def open_moved_by_message_id(self):
  164. message_id = None
  165. for r in self:
  166. message_id = r.message_moved_by_message_id.id
  167. return {
  168. 'type': 'ir.actions.act_window',
  169. 'res_model': 'mail_move_message.wizard',
  170. 'view_mode': 'form',
  171. 'view_type': 'form',
  172. 'views': [[False, 'form']],
  173. 'target': 'new',
  174. 'context': {'default_message_id': message_id},
  175. }
  176. @api.multi
  177. def move(self):
  178. for r in self:
  179. if not r.model:
  180. raise exceptions.except_orm(_('Record field is empty!'), _('Select a record for relocation first'))
  181. for r in self:
  182. r.check_access()
  183. if not r.parent_id or not (r.parent_id.model == r.model and
  184. r.parent_id.res_id == r.res_id):
  185. # link with the first message of record
  186. parent = self.env['mail.message'].search([('model', '=', r.model), ('res_id', '=', r.res_id)], order='id', limit=1)
  187. r.parent_id = parent.id or None
  188. 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)
  189. if r.model in ['mail.message', 'mail.channel', False]:
  190. return {
  191. 'name': 'Chess game page',
  192. 'type': 'ir.actions.act_url',
  193. 'url': '/web',
  194. 'target': 'self',
  195. }
  196. return {
  197. 'name': _('Record'),
  198. 'view_type': 'form',
  199. 'view_mode': 'form',
  200. 'res_model': r.model,
  201. 'res_id': r.res_id,
  202. 'views': [(False, 'form')],
  203. 'type': 'ir.actions.act_window',
  204. }
  205. @api.multi
  206. def delete(self):
  207. for r in self:
  208. r.delete_one()
  209. @api.multi
  210. def delete_one(self):
  211. self.ensure_one()
  212. msg_id = self.message_id.id
  213. # Send notification
  214. notification = {'id': msg_id}
  215. self.env['bus.bus'].sendone((self._cr.dbname, 'mail_move_message.delete_message'), notification)
  216. self.message_id.unlink()
  217. return {}
  218. @api.multi
  219. def read_close(self):
  220. for r in self:
  221. r.read_close_one()
  222. @api.multi
  223. def read_close_one(self):
  224. self.ensure_one()
  225. self.message_id.set_message_done()
  226. self.message_id.child_ids.set_message_done()
  227. return {'type': 'ir.actions.act_window_close'}
  228. class MailMessage(models.Model):
  229. _inherit = 'mail.message'
  230. is_moved = fields.Boolean('Is moved')
  231. moved_from_res_id = fields.Integer('Related Document ID (Original)')
  232. moved_from_model = fields.Char('Related Document Model (Original)')
  233. moved_from_parent_id = fields.Many2one('mail.message', 'Parent Message (Original)', ondelete='set null')
  234. moved_by_message_id = fields.Many2one('mail.message', 'Moved by message', ondelete='set null', help='Top message, that initate moving this message')
  235. moved_by_user_id = fields.Many2one('res.users', 'Moved by user', ondelete='set null')
  236. all_child_ids = fields.One2many('mail.message', string='All childs', compute='_compute_get_all_childs', help='all childs, including subchilds')
  237. moved_as_unread = fields.Boolean('Was Unread', default=False)
  238. @api.multi
  239. def _compute_get_all_childs(self, include_myself=True):
  240. for r in self:
  241. r._get_all_childs_one(include_myself=include_myself)
  242. @api.multi
  243. def _get_all_childs_one(self, include_myself=True):
  244. self.ensure_one()
  245. ids = []
  246. if include_myself:
  247. ids.append(self.id)
  248. while True:
  249. new_ids = self.search([('parent_id', 'in', ids), ('id', 'not in', ids)]).ids
  250. if new_ids:
  251. ids = ids + new_ids
  252. continue
  253. break
  254. moved_childs = self.search([('moved_by_message_id', '=', self.id)]).ids
  255. self.all_child_ids = ids + moved_childs
  256. @api.multi
  257. def move_followers(self, model, ids):
  258. fol_obj = self.env['mail.followers']
  259. for message in self:
  260. followers = fol_obj.sudo().search([('res_model', '=', message.model),
  261. ('res_id', '=', message.res_id)])
  262. for f in followers:
  263. self.env[model].browse(ids).message_subscribe([f.partner_id.id], [s.id for s in f.subtype_ids])
  264. @api.multi
  265. def move(self, parent_id, res_id, model, move_back, move_followers=False, message_to_read=False, author=False):
  266. for r in self:
  267. r.move_one(parent_id, res_id, model, move_back, move_followers=move_followers, message_to_read=message_to_read, author=author)
  268. @api.multi
  269. def move_one(self, parent_id, res_id, model, move_back, move_followers=False, message_to_read=False, author=False):
  270. self.ensure_one()
  271. if parent_id == self.id:
  272. # if for any reason method is called to move message with parent
  273. # equal to oneself, we need stop to prevent infinitive loop in
  274. # building message tree
  275. return
  276. if not self.author_id:
  277. self.write({
  278. 'author_id': author.id,
  279. })
  280. vals = {}
  281. if move_back:
  282. # clear variables if we move everything back
  283. vals['is_moved'] = False
  284. vals['moved_by_user_id'] = None
  285. vals['moved_by_message_id'] = None
  286. vals['moved_from_res_id'] = None
  287. vals['moved_from_model'] = None
  288. vals['moved_from_parent_id'] = None
  289. vals['moved_as_unread'] = None
  290. else:
  291. vals['parent_id'] = parent_id
  292. vals['res_id'] = res_id
  293. vals['model'] = model
  294. vals['is_moved'] = True
  295. vals['moved_by_user_id'] = self.env.user.id
  296. vals['moved_by_message_id'] = self.id
  297. vals['moved_as_unread'] = message_to_read
  298. # Update record_name in message
  299. vals['record_name'] = self._get_record_name(vals)
  300. # unread message remains unread after moving back to origin
  301. if self.moved_as_unread and move_back:
  302. notification = {
  303. 'mail_message_id': self.id,
  304. 'res_partner_id': self.env.user.partner_id.id,
  305. 'is_read': False,
  306. }
  307. self.write({
  308. 'notification_ids': [(0, 0, notification)],
  309. })
  310. for r in self.all_child_ids:
  311. r_vals = vals.copy()
  312. if not r.is_moved:
  313. # moved_from_* variables contain not last, but original
  314. # reference
  315. r_vals['moved_from_parent_id'] = r.parent_id.id or r.env.context.get('uid')
  316. r_vals['moved_from_res_id'] = r.res_id or r.id
  317. r_vals['moved_from_model'] = r.model or r._name
  318. elif move_back:
  319. r_vals['parent_id'] = r.moved_from_parent_id.id
  320. r_vals['res_id'] = r.moved_from_res_id
  321. r_vals['model'] = (r.moved_from_model and r.moved_from_model not in ['mail.message', 'mail.channel', False]) and r.moved_from_model
  322. r_vals['record_name'] = r_vals['model'] and self.env[r.moved_from_model].browse(r.moved_from_res_id).name
  323. if move_followers:
  324. r.sudo().move_followers(r_vals.get('model'), r_vals.get('res_id'))
  325. r.sudo().write(r_vals)
  326. r.attachment_ids.sudo().write({
  327. 'res_id': r_vals.get('res_id'),
  328. 'res_model': r_vals.get('model')
  329. })
  330. # Send notification
  331. notification = {
  332. 'id': self.id,
  333. 'res_id': vals.get('res_id'),
  334. 'model': vals.get('model'),
  335. 'is_moved': vals['is_moved'],
  336. 'record_name': 'record_name' in vals and vals['record_name'],
  337. }
  338. self.env['bus.bus'].sendone((self._cr.dbname, 'mail_move_message'), notification)
  339. @api.multi
  340. def name_get(self):
  341. context = self.env.context
  342. if not (context or {}).get('extended_name'):
  343. return super(MailMessage, self).name_get()
  344. reads = self.read(['record_name', 'model', 'res_id'])
  345. res = []
  346. for record in reads:
  347. name = record['record_name'] or ''
  348. extended_name = ' [%s] ID %s' % (record.get('model', 'UNDEF'), record.get('res_id', 'UNDEF'))
  349. res.append((record['id'], name + extended_name))
  350. return res
  351. @api.multi
  352. def message_format(self):
  353. message_values = super(MailMessage, self).message_format()
  354. message_index = {message['id']: message for message in message_values}
  355. for item in self:
  356. msg = message_index.get(item.id)
  357. if msg:
  358. msg['is_moved'] = item.is_moved
  359. return message_values
  360. class MailMoveMessageConfiguration(models.TransientModel):
  361. _inherit = 'res.config.settings'
  362. model_ids = fields.Many2many(comodel_name='ir.model', string='Models')
  363. move_followers = fields.Boolean('Move Followers')
  364. @api.model
  365. def get_values(self):
  366. res = super(MailMoveMessageConfiguration, self).get_values()
  367. config_parameters = self.env["ir.config_parameter"].sudo()
  368. model_names = config_parameters.sudo().get_param('mail_relocation_models')
  369. model_names = model_names.split(',')
  370. model_ids = self.env['ir.model'].sudo().search([('model', 'in', model_names)])
  371. res.update(
  372. model_ids=[m.id for m in model_ids],
  373. move_followers=config_parameters.sudo().get_param('mail_relocation_move_followers'),
  374. )
  375. return res
  376. @api.multi
  377. def set_values(self):
  378. super(MailMoveMessageConfiguration, self).set_values()
  379. config_parameters = self.env["ir.config_parameter"].sudo()
  380. for record in self:
  381. model_names = ','.join([x.model for x in record.model_ids])
  382. config_parameters.set_param("mail_relocation_models", model_names or '')
  383. config_parameters.set_param("mail_relocation_move_followers", record.move_followers or '')
  384. class ResPartner(models.Model):
  385. _inherit = 'res.partner'
  386. @api.model
  387. def create(self, vals):
  388. res = super(ResPartner, self).create(vals)
  389. if 'update_message_author' in self.env.context and 'email' in vals:
  390. mail_message_obj = self.env['mail.message']
  391. # Escape special SQL characters in email_address to avoid invalid matches
  392. email_address = (vals['email'].replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_'))
  393. email_brackets = "<%s>" % email_address
  394. messages = mail_message_obj.search([
  395. '|',
  396. ('email_from', '=ilike', email_address),
  397. ('email_from', 'ilike', email_brackets),
  398. ('author_id', '=', False)
  399. ])
  400. if messages:
  401. messages.sudo().write({'author_id': res.id})
  402. return res
  403. @api.model
  404. def default_get(self, default_fields):
  405. contextual_self = self
  406. if 'mail_move_message' in self.env.context and self.env.context['mail_move_message']:
  407. contextual_self = self.with_context(
  408. default_name=self.env.context['message_name_from'] or '',
  409. default_email=self.env.context['message_email_from'] or '',
  410. )
  411. return super(ResPartner, contextual_self).default_get(default_fields)