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.

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