diff --git a/README.md b/README.md index 256a413..ea25817 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ List of repositories: * https://github.com/yelizariev/addons-yelizariev * https://github.com/yelizariev/pos-addons * https://github.com/yelizariev/mail-addons +* https://github.com/yelizariev/rental-addons +* https://github.com/yelizariev/access-addons * https://github.com/yelizariev/website-addons * https://github.com/yelizariev/l10n-addons * https://github.com/yelizariev/odoo-saas-tools diff --git a/mail_check_immediately/README.rst b/mail_check_immediately/README.rst new file mode 100644 index 0000000..a9dbef4 --- /dev/null +++ b/mail_check_immediately/README.rst @@ -0,0 +1,4 @@ +Check mail immediately +====================== + +Description: https://apps.odoo.com/apps/modules/8.0/mail_check_immediately/ diff --git a/mail_check_immediately/__init__.py b/mail_check_immediately/__init__.py new file mode 100644 index 0000000..89d26e2 --- /dev/null +++ b/mail_check_immediately/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import models diff --git a/mail_check_immediately/__openerp__.py b/mail_check_immediately/__openerp__.py new file mode 100644 index 0000000..663ca66 --- /dev/null +++ b/mail_check_immediately/__openerp__.py @@ -0,0 +1,18 @@ +{ + 'name' : 'Check mail immediately', + 'version' : '1.0.1', + 'author' : 'IT-Projects LLC, Ivan Yelizariev', + 'license': 'LGPL-3', + 'category' : 'Social Network', + 'website' : 'https://twitter.com/yelizariev', + 'price': 9.00, + 'currency': 'EUR', + 'depends' : ['base', 'web', 'fetchmail', 'mail'], + 'data': [ + 'views.xml', + ], + 'qweb': [ + "static/src/xml/main.xml", + ], + 'installable': False +} diff --git a/mail_check_immediately/doc/changelog.rst b/mail_check_immediately/doc/changelog.rst new file mode 100644 index 0000000..e72d492 --- /dev/null +++ b/mail_check_immediately/doc/changelog.rst @@ -0,0 +1,9 @@ +.. _changelog: + +Changelog +========= + +`1.0.1` +------- + +- FIX: incorrectly displayed last updated time when multiple threads (--workers) \ No newline at end of file diff --git a/mail_check_immediately/models.py b/mail_check_immediately/models.py new file mode 100644 index 0000000..3306837 --- /dev/null +++ b/mail_check_immediately/models.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +import datetime + +from openerp.tools.translate import _ +from openerp import tools + +from openerp import exceptions +from openerp import models, fields, api + + +class FetchMailServer(models.Model): + _inherit = 'fetchmail.server' + _name = 'fetchmail.server' + + _last_updated = None + + run_time = fields.Datetime(string="Launch time") + + def _run_time(self): + if not self._last_updated: + self._last_updated = tools.datetime.now() + + src_tstamp_str = self._last_updated.strftime(tools.misc.DEFAULT_SERVER_DATETIME_FORMAT) + src_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT + dst_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT + dst_tz_name = self._context.get('tz') or self.env.user.tz + _now = tools.misc.server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name) + + return _now + + @api.model + def _fetch_mails(self): + + if self._context.get('run_fetchmail_manually'): + # if interval less than 5 seconds + if self._last_updated and (datetime.datetime.now() - self._last_updated) < datetime.timedelta(0, 5): + raise exceptions.Warning(_('Error'), _('Task can be started no earlier than 5 seconds.')) + + super(FetchMailServer, self)._fetch_mails() + + res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')]) + if res: + res[0].run_time = self._run_time() + + +class FetchMailImmediately(models.AbstractModel): + + _name = 'fetch_mail.imm' + + @api.model + def get_last_update_time(self): + res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')]) + array = [r.run_time for r in res] + if array: + return array[0] + else: + return None + + @api.model + def run_fetchmail_manually(self): + + fetchmail_task = self.env.ref('fetchmail.ir_cron_mail_gateway_action') + fetchmail_task_id = fetchmail_task.id + fetchmail_model = self.env['fetchmail.server'].sudo() + + fetchmail_task._try_lock() + fetchmail_model.with_context(run_fetchmail_manually=True)._fetch_mails() diff --git a/mail_check_immediately/static/description/icon.png b/mail_check_immediately/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_check_immediately/static/description/icon.png differ diff --git a/mail_check_immediately/static/description/index.html b/mail_check_immediately/static/description/index.html new file mode 100644 index 0000000..0416efa --- /dev/null +++ b/mail_check_immediately/static/description/index.html @@ -0,0 +1,47 @@ +
+
+
+

Check mail immediately

+

Keep your inbox up to date

+
+ +
+
+ +
+
+
+
+ +
+
+
+

Protect your business

+
+
+

+ Sometimes odoo mail fetching system doesn't work for really long time. It could be a real problem, if you will not notice it on time. Until this issue is fixed, you can restart odoo every time when you see that last fetching time is more than 5 minutes. +

+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
diff --git a/mail_check_immediately/static/description/issue.png b/mail_check_immediately/static/description/issue.png new file mode 100644 index 0000000..79ef713 Binary files /dev/null and b/mail_check_immediately/static/description/issue.png differ diff --git a/mail_check_immediately/static/description/screenshot.png b/mail_check_immediately/static/description/screenshot.png new file mode 100644 index 0000000..a408525 Binary files /dev/null and b/mail_check_immediately/static/description/screenshot.png differ diff --git a/mail_check_immediately/static/src/js/main.js b/mail_check_immediately/static/src/js/main.js new file mode 100755 index 0000000..20b07bd --- /dev/null +++ b/mail_check_immediately/static/src/js/main.js @@ -0,0 +1,55 @@ +openerp.mail_check_immediately = function(instance, local) { + + instance.mail.Wall.include({ + + init: function(){ + this._super.apply(this, arguments); + + var _this = this; + + this.imm_model = new instance.web.Model('fetch_mail.imm'); + this.events['click a.oe_fetch_new_mails'] = function(){ + _this.run_fetchmail_manually(); + } + }, + + start: function() { + var _this = this; + + + this._super(); + + this.get_last_fetched_time(); + + this.get_time_loop = setInterval(function(){ + _this.get_last_fetched_time() + }, 30000); + + }, + + run_fetchmail_manually: function(){ + var _this = this; + + this.imm_model.call('run_fetchmail_manually', {context: new instance.web.CompoundContext()}).then(function(){ + _this.get_last_fetched_time() + }) + }, + + get_last_fetched_time: function(){ + var _this = this; + this.imm_model.call('get_last_update_time', {context: new instance.web.CompoundContext()}).then(function(res){ + var value; + if (res) + value = $.timeago(res); + value = value || 'undefined'; + _this.$el.find('span.oe_view_manager_fetch_mail_imm_field').html(value); + }) + }, + + destroy: function(){ + clearInterval(this.get_time_loop); + this._super.apply(this, arguments); + } + + }); +}; diff --git a/mail_check_immediately/static/src/xml/main.xml b/mail_check_immediately/static/src/xml/main.xml new file mode 100755 index 0000000..0329572 --- /dev/null +++ b/mail_check_immediately/static/src/xml/main.xml @@ -0,0 +1,25 @@ + + + + + +
+ + Mails fetched: + + + + +
+ + + +
+ + + + + + + +
diff --git a/mail_check_immediately/views.xml b/mail_check_immediately/views.xml new file mode 100644 index 0000000..66ab096 --- /dev/null +++ b/mail_check_immediately/views.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/mail_delete_odoo_footer/README.rst b/mail_delete_odoo_footer/README.rst new file mode 100644 index 0000000..1cbeaa2 --- /dev/null +++ b/mail_delete_odoo_footer/README.rst @@ -0,0 +1,4 @@ +Delete Odoo footer in email +=========================== + +Tested on 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d diff --git a/mail_delete_odoo_footer/__init__.py b/mail_delete_odoo_footer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mail_delete_odoo_footer/__openerp__.py b/mail_delete_odoo_footer/__openerp__.py new file mode 100644 index 0000000..f1fadac --- /dev/null +++ b/mail_delete_odoo_footer/__openerp__.py @@ -0,0 +1,12 @@ +{ + 'name' : 'Delete Odoo footer in email (TODO)', + 'version' : '1.0.0', + 'author' : 'IT-Projects LLC, Ivan Yelizariev', + 'license': 'LGPL-3', + 'category' : 'Social Network', + 'website' : 'https://yelizariev.github.io', + 'depends' : [], + 'data':[ + ], + 'installable': False +} diff --git a/mail_delete_odoo_footer/static/description/icon.png b/mail_delete_odoo_footer/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_delete_odoo_footer/static/description/icon.png differ diff --git a/mail_fix_553/README.rst b/mail_fix_553/README.rst new file mode 100644 index 0000000..bfb1799 --- /dev/null +++ b/mail_fix_553/README.rst @@ -0,0 +1,41 @@ +Fix mail error 553 +================== + +Module updates 'FROM' field to portal@MYDOMAIN.COM value in order to fix 553 error on a mail service that checks FROM field. + +E.g: + +* Customer send email from USER@CUSTOMER.com to info@MYDOMAIN.COM +* odoo accept email and try to send notifcation to related odoo users. E.g to admin@gmail.com. +* By default odoo prepare notification email with parameters as follows: + + * FROM: user@CUSTOMER.com + * TO: admin@gmail.com + +if you mail service provider, e.g. pdd.yandex.ru, doesn't allow emails with a FROM value differ from ...@MYDOMAIN.COM, then you get 553. This is why you need to update FROM value to portal@MYDOMAIN.COM + +Configuration +============= + +You can configure default alias at Settings -> System Parameters -> mail.catchall.alias_from + +Known issues / Roadmap +====================== + +The module is consist of redefined send function from mail.mail +model. So it is just copy pasted source code with some +modification. This function is changed very rarely, but sometime it +can happens and the module should be updated. You can check commits +for mail_mail.py here: +https://github.com/odoo/odoo/commits/8.0/addons/mail/mail_mail.py + +Tested on Odoo 8.0 d023c079ed86468436f25da613bf486a4a17d625 + +Status +====== + +Related issues at odoo's tracker: +* https://github.com/odoo/odoo/issues/5864 +* https://github.com/odoo/odoo/issues/3347 + +Fix: https://github.com/odoo-dev/odoo/commit/a4597fe34fcfa8dae28b156410080346bb33af33 diff --git a/mail_fix_553/__init__.py b/mail_fix_553/__init__.py new file mode 100644 index 0000000..c978833 --- /dev/null +++ b/mail_fix_553/__init__.py @@ -0,0 +1 @@ +import mail_fix_553 diff --git a/mail_fix_553/__openerp__.py b/mail_fix_553/__openerp__.py new file mode 100644 index 0000000..b4c13e7 --- /dev/null +++ b/mail_fix_553/__openerp__.py @@ -0,0 +1,11 @@ +{ + "name" : "Fix mail error 553", + "version" : "0.3", + "author" : "IT-Projects LLC, Ivan Yelizariev", + 'license': 'LGPL-3', + "category" : "Social Network", + "website" : "https://yelizariev.github.io", + "depends" : ["base", "mail"], + "data": ["data.xml"], + 'installable': False +} diff --git a/mail_fix_553/data.xml b/mail_fix_553/data.xml new file mode 100644 index 0000000..411d614 --- /dev/null +++ b/mail_fix_553/data.xml @@ -0,0 +1,10 @@ + + + + + + mail.catchall.alias_from + portal + + + diff --git a/mail_fix_553/mail_fix_553.py b/mail_fix_553/mail_fix_553.py new file mode 100644 index 0000000..167435a --- /dev/null +++ b/mail_fix_553/mail_fix_553.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +import base64 +import logging +from email.utils import formataddr +from urlparse import urljoin + +from openerp import api, tools +from openerp import SUPERUSER_ID +from openerp.addons.base.ir.ir_mail_server import MailDeliveryException +from openerp.osv import fields, osv +from openerp.tools.safe_eval import safe_eval as eval +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) + +import re + +class mail_mail(osv.Model): + _inherit = "mail.mail" + + def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None): + # copy-paste from addons/mail/mail_mail.py + """ Sends the selected emails immediately, ignoring their current + state (mails that have already been sent should not be passed + unless they should actually be re-sent). + Emails successfully delivered are marked as 'sent', and those + that fail to be deliver are marked as 'exception', and the + corresponding error mail is output in the server logs. + + :param bool auto_commit: whether to force a commit of the mail status + after sending each mail (meant only for scheduler processing); + should never be True during normal transactions (default: False) + :param bool raise_exception: whether to raise an exception if the + email sending process has failed + :return: True + """ + + # NEW STUFF + catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias_from", context=context) + catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) + + correct_email_from = '@%s>?\s*$'%catchall_domain + default_email_from = '%s@%s' % (catchall_alias, catchall_domain) + + + context = dict(context or {}) + ir_mail_server = self.pool.get('ir.mail_server') + ir_attachment = self.pool['ir.attachment'] + for mail in self.browse(cr, SUPERUSER_ID, ids, context=context): + try: + # TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method + if mail.model: + model_id = self.pool['ir.model'].search(cr, SUPERUSER_ID, [('model', '=', mail.model)], context=context)[0] + model = self.pool['ir.model'].browse(cr, SUPERUSER_ID, model_id, context=context) + else: + model = None + if model: + context['model_name'] = model.name + + # load attachment binary data with a separate read(), as prefetching all + # `datas` (binary field) could bloat the browse cache, triggerring + # soft/hard mem limits with temporary data. + attachment_ids = [a.id for a in mail.attachment_ids] + attachments = [(a['datas_fname'], base64.b64decode(a['datas'])) + for a in ir_attachment.read(cr, SUPERUSER_ID, attachment_ids, + ['datas_fname', 'datas'])] + + # specific behavior to customize the send email for notified partners + email_list = [] + if mail.email_to: + email_list.append(self.send_get_email_dict(cr, uid, mail, context=context)) + for partner in mail.recipient_ids: + email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context)) + # headers + headers = {} + bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context) + catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context) + if bounce_alias and catchall_domain: + if mail.model and mail.res_id: + headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain) + else: + headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain) + if mail.headers: + try: + headers.update(eval(mail.headers)) + except Exception: + pass + + # Writing on the mail object may fail (e.g. lock on user) which + # would trigger a rollback *after* actually sending the email. + # To avoid sending twice the same email, provoke the failure earlier + mail.write({'state': 'exception'}) + mail_sent = False + + # build an RFC2822 email.message.Message object and send it without queuing + res = None + for email in email_list: + + # NEW STUFF + email_from = mail.email_from + if re.search(correct_email_from, email_from) is None: + email_from = default_email_from + + msg = ir_mail_server.build_email( + email_from=email_from, # NEW STUFF + email_to=email.get('email_to'), + subject=email.get('subject'), + body=email.get('body'), + body_alternative=email.get('body_alternative'), + email_cc=tools.email_split(mail.email_cc), + reply_to=mail.reply_to, + attachments=attachments, + message_id=mail.message_id, + references=mail.references, + object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)), + subtype='html', + subtype_alternative='plain', + headers=headers) + try: + res = ir_mail_server.send_email(cr, uid, msg, + mail_server_id=mail.mail_server_id.id, + context=context) + except AssertionError as error: + if error.message == ir_mail_server.NO_VALID_RECIPIENT: + # No valid recipient found for this particular + # mail item -> ignore error to avoid blocking + # delivery to next recipients, if any. If this is + # the only recipient, the mail will show as failed. + _logger.warning("Ignoring invalid recipients for mail.mail %s: %s", + mail.message_id, email.get('email_to')) + else: + raise + if res: + mail.write({'state': 'sent', 'message_id': res}) + mail_sent = True + + # /!\ can't use mail.state here, as mail.refresh() will cause an error + # see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1 + if mail_sent: + _logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id) + self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent) + except MemoryError: + # prevent catching transient MemoryErrors, bubble up to notify user or abort cron job + # instead of marking the mail as failed + _logger.exception('MemoryError while processing mail with ID %r and Msg-Id %r. '\ + 'Consider raising the --limit-memory-hard startup option', + mail.id, mail.message_id) + raise + except Exception as e: + _logger.exception('failed sending mail.mail %s', mail.id) + mail.write({'state': 'exception'}) + self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False) + if raise_exception: + if isinstance(e, AssertionError): + # get the args of the original error, wrap into a value and throw a MailDeliveryException + # that is an except_orm, with name and value as arguments + value = '. '.join(e.args) + raise MailDeliveryException(_("Mail Delivery Failed"), value) + raise + + if auto_commit is True: + cr.commit() + return True diff --git a/mail_fix_553/static/description/icon.png b/mail_fix_553/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_fix_553/static/description/icon.png differ diff --git a/mail_fix_empty_body/README.rst b/mail_fix_empty_body/README.rst new file mode 100644 index 0000000..7be4736 --- /dev/null +++ b/mail_fix_empty_body/README.rst @@ -0,0 +1,6 @@ +Fix "False" in empty email body +=============================== + +Description: https://apps.odoo.com/apps/modules/8.0/mail_fix_empty_body/ + +Tested on Odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d diff --git a/mail_fix_empty_body/__init__.py b/mail_fix_empty_body/__init__.py new file mode 100644 index 0000000..bff786c --- /dev/null +++ b/mail_fix_empty_body/__init__.py @@ -0,0 +1 @@ +import models diff --git a/mail_fix_empty_body/__openerp__.py b/mail_fix_empty_body/__openerp__.py new file mode 100644 index 0000000..a4212da --- /dev/null +++ b/mail_fix_empty_body/__openerp__.py @@ -0,0 +1,14 @@ +{ + 'name' : 'Fix "False" in empty email body', + 'version' : '1.0.0', + 'author' : 'IT-Projects LLC, Ivan Yelizariev', + 'license': 'LGPL-3', + 'category': 'Social Network', + 'website' : 'https://twitter.com/yelizariev', + 'price': 9.00, + 'currency': 'EUR', + 'depends' : ['mail'], + 'data':[ + ], + 'installable': False +} diff --git a/mail_fix_empty_body/models.py b/mail_fix_empty_body/models.py new file mode 100644 index 0000000..0d45e2d --- /dev/null +++ b/mail_fix_empty_body/models.py @@ -0,0 +1,10 @@ +from openerp import api, models, fields, SUPERUSER_ID + +class mail_compose_message(models.TransientModel): + _inherit = 'mail.compose.message' + + def get_mail_values(self, cr, uid, wizard, res_ids, context=None): + res = super(mail_compose_message, self).get_mail_values(cr, uid, wizard, res_ids, context) + for id, d in res.iteritems(): + d['body'] = d.get('body') or '' + return res diff --git a/mail_fix_empty_body/static/description/icon.png b/mail_fix_empty_body/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_fix_empty_body/static/description/icon.png differ diff --git a/mail_fix_empty_body/static/description/index.html b/mail_fix_empty_body/static/description/index.html new file mode 100644 index 0000000..836d00d --- /dev/null +++ b/mail_fix_empty_body/static/description/index.html @@ -0,0 +1,65 @@ +
+
+
+

Fix "False" in empty email body

+

Feel free to send emails without body

+
+ +
+

+ Some time you need to send email with empty body.
E.g. to send some file to partner. +

+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ +
+

+ There is a bug in odoo -- it sends "False" if email body if empty. +

+
+
+
+ + +
+
+
+

+ This module just fix the issue. +

+
+
+
+ +
+
+
+
+ +
+
+ +
+
diff --git a/mail_fix_empty_body/static/description/receive-false.png b/mail_fix_empty_body/static/description/receive-false.png new file mode 100644 index 0000000..be440c0 Binary files /dev/null and b/mail_fix_empty_body/static/description/receive-false.png differ diff --git a/mail_fix_empty_body/static/description/receive-ok.png b/mail_fix_empty_body/static/description/receive-ok.png new file mode 100644 index 0000000..845424f Binary files /dev/null and b/mail_fix_empty_body/static/description/receive-ok.png differ diff --git a/mail_fix_empty_body/static/description/send.png b/mail_fix_empty_body/static/description/send.png new file mode 100644 index 0000000..92cafa4 Binary files /dev/null and b/mail_fix_empty_body/static/description/send.png differ diff --git a/mail_fix_header_from/__init__.py b/mail_fix_header_from/__init__.py new file mode 100644 index 0000000..bff786c --- /dev/null +++ b/mail_fix_header_from/__init__.py @@ -0,0 +1 @@ +import models diff --git a/mail_fix_header_from/__openerp__.py b/mail_fix_header_from/__openerp__.py new file mode 100644 index 0000000..4ae0f7d --- /dev/null +++ b/mail_fix_header_from/__openerp__.py @@ -0,0 +1,16 @@ +{ + "name" : "Fix non-ascii header 'from' (OBSOLETE)", + "version" : "0.3", + "author" : "IT-Projects LLC, Ivan Yelizariev", + 'license': 'LGPL-3', + "category" : "Social Network", + "website" : "https://yelizariev.github.io", + "description": """ +Obsolete in odoo 8.0 since Sep 10, 2014 https://github.com/odoo/odoo/commit/f2cf6ced17d3477b8858e3a8f955a42cc8a629ff . You can install this module, if you use older version. + """, + "depends" : ["base"], + #"init_xml" : [], + #"update_xml" : [], + #"active": True, + 'installable': False +} diff --git a/mail_fix_header_from/models.py b/mail_fix_header_from/models.py new file mode 100644 index 0000000..83c8941 --- /dev/null +++ b/mail_fix_header_from/models.py @@ -0,0 +1,4 @@ +import re +from openerp.addons.base.ir import ir_mail_server + +ir_mail_server.name_with_email_pattern = re.compile(r'([^<@>]+)\s*<([^ ,<@]+@[^> ,]+)>') diff --git a/mail_fix_header_from/static/description/icon.png b/mail_fix_header_from/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_fix_header_from/static/description/icon.png differ diff --git a/mail_move_message/README.rst b/mail_move_message/README.rst new file mode 100644 index 0000000..ef727fc --- /dev/null +++ b/mail_move_message/README.rst @@ -0,0 +1,8 @@ +Mail relocation +=============== + +Description: https://www.odoo.com/apps/modules/8.0/mail_move_message/ + +Further information and discussion: http://yelizariev.github.io/odoo/module/2015/04/10/mail-relocation.html + +Tested on Odoo 8.0 d023c079ed86468436f25da613bf486a4a17d625 diff --git a/mail_move_message/__init__.py b/mail_move_message/__init__.py new file mode 100644 index 0000000..f247113 --- /dev/null +++ b/mail_move_message/__init__.py @@ -0,0 +1,2 @@ +import controllers +import mail_move_message_models diff --git a/mail_move_message/__openerp__.py b/mail_move_message/__openerp__.py new file mode 100644 index 0000000..c8dc5e1 --- /dev/null +++ b/mail_move_message/__openerp__.py @@ -0,0 +1,20 @@ +{ + 'name' : 'Mail relocation', + 'version' : '1.0.4', + 'author' : 'IT-Projects LLC, Ivan Yelizariev', + 'license': 'LGPL-3', + 'category' : 'Social Network', + 'website' : 'https://twitter.com/yelizariev', + 'price': 9.00, + 'currency': 'EUR', + 'depends' : ['mail', 'web_polymorphic_field'], + 'images': ['images/inbox.png'], + 'data':[ + 'mail_move_message_views.xml', + 'data/mail_move_message_data.xml', + ], + 'qweb': [ + 'static/src/xml/mail_move_message_main.xml', + ], + 'installable': False +} diff --git a/mail_move_message/controllers/__init__.py b/mail_move_message/controllers/__init__.py new file mode 100644 index 0000000..039d971 --- /dev/null +++ b/mail_move_message/controllers/__init__.py @@ -0,0 +1 @@ +import main \ No newline at end of file diff --git a/mail_move_message/controllers/main.py b/mail_move_message/controllers/main.py new file mode 100644 index 0000000..bef9074 --- /dev/null +++ b/mail_move_message/controllers/main.py @@ -0,0 +1,55 @@ +from openerp.addons.web.controllers.main import DataSet +from openerp.tools.translate import _ +from openerp import http +from openerp.http import request + +class DataSetCustom(DataSet): + + def _extend_name(self, model, records): + cr, uid, context = request.cr, request.uid, request.context + Model = request.registry[model] + fields = Model.fields_get(cr, uid, False, context) + contact_field = False + for n, f in fields.iteritems(): + if f['type'] == 'many2one' and f['relation'] == 'res.partner': + contact_field = n + break + partner_info = {} + if contact_field: + partner_info = Model.read(cr, uid, [r[0] for r in records], [contact_field], context) + partner_info = dict([(p['id'], p[contact_field]) for p in partner_info]) + res = [] + for r in records: + if partner_info.get(r[0]): + res.append((r[0], _('%s [%s] ID %s') % (r[1], partner_info.get(r[0])[1], r[0]))) + else: + res.append((r[0], _('%s ID %s') % (r[1], r[0]))) + return res + + + @http.route('/web/dataset/call_kw//name_search', type='json', auth="user") + def name_search(self, model, method, args, kwargs): + context = kwargs.get('context') + if context and context.get('extended_name_with_contact'): + #add order by ID desc + cr, uid = request.cr, request.uid + Model = request.registry[model] + search_args = list(kwargs.get('args') or []) + limit = int(kwargs.get('limit') or 100) + operator = kwargs.get('operator') + name = kwargs.get('name') + if Model._rec_name and (not name == '' and operator == 'ilike'): + search_args += [(Model._rec_name, operator, name)] + ids = Model.search(cr, uid, search_args, limit=limit, order='id desc', context=context) + res = Model.name_get(cr, uid, ids, context) + return self._extend_name(model, res) + + return self._call_kw(model, method, args, kwargs) + + @http.route('/web/dataset/call_kw//name_get', type='json', auth="user") + def name_get(self, model, method, args, kwargs): + res = self._call_kw(model, method, args, kwargs) + context = kwargs.get('context') + if context and context.get('extended_name_with_contact'): + res = self._extend_name(model, res) + return res diff --git a/mail_move_message/data/mail_move_message_data.xml b/mail_move_message/data/mail_move_message_data.xml new file mode 100644 index 0000000..2b13fe2 --- /dev/null +++ b/mail_move_message/data/mail_move_message_data.xml @@ -0,0 +1,9 @@ + + + + + mail_relocation_models + crm.lead,project.task + + + \ No newline at end of file diff --git a/mail_move_message/doc/changelog.rst b/mail_move_message/doc/changelog.rst new file mode 100644 index 0000000..4704b1f --- /dev/null +++ b/mail_move_message/doc/changelog.rst @@ -0,0 +1,26 @@ +.. _changelog: + +Changelog +========= + +`1.0.4` +------- + +- FIX: don't allow to relocate message to itself as it cause infinitive loop +- ADD: 'Move Followers' option -- Add followers of current record to a new record. + +`1.0.3` +------- + +- FIX email_from parsing. There was an error with specific email_from value (e.g. '"name @ example" ') + +`1.0.2` +------- + +- big improvements in interface + +`1.0.1` +------- + +- fix bug "some messages are not shown in inbox after relocation" +- improve "Move back" tool diff --git a/mail_move_message/i18n/mail_move_message.pot b/mail_move_message/i18n/mail_move_message.pot new file mode 100644 index 0000000..2854cd5 --- /dev/null +++ b/mail_move_message/i18n/mail_move_message.pot @@ -0,0 +1,173 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_move_message +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-08-11 06:53+0000\n" +"PO-Revision-Date: 2015-08-11 06:53+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Cancel" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,create_uid:0 +msgid "Created by" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,create_date:0 +msgid "Created on" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,id:0 +msgid "ID" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,is_moved:0 +msgid "Is moved" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,write_uid:0 +msgid "Last Updated by" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,write_date:0 +msgid "Last Updated on" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,record_url:0 +msgid "Link to record" +msgstr "" + +#. module: mail_move_message +#: model:ir.model,name:mail_move_message.model_mail_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +#: field:mail_move_message.wizard,message_id:0 +msgid "Message" +msgstr "" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Move" +msgstr "" + +#. module: mail_move_message +#: help:mail_move_message.wizard,move_back:0 +msgid "Move message and submessages to original place" +msgstr "" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Move Message" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,move_back:0 +msgid "Move to origin" +msgstr "" + +#. module: mail_move_message +#. openerp-web +#: code:addons/mail_move_message/static/src/xml/mail_move_message_main.xml:5 +#, python-format +msgid "Move to thread" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,moved_by_message_id:0 +msgid "Moved by message" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,moved_by_user_id:0 +msgid "Moved by user" +msgstr "" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Open message" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,moved_from_parent_id:0 +msgid "Parent Message (Original)" +msgstr "" + +#. module: mail_move_message +#: code:addons/mail_move_message/mail_move_message_models.py:107 +#, python-format +msgid "Record" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,res_id:0 +msgid "Record ID" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,model_id:0 +msgid "Record type" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,moved_from_res_id:0 +msgid "Related Document ID (Original)" +msgstr "" + +#. module: mail_move_message +#: field:mail.message,moved_from_model:0 +msgid "Related Document Model (Original)" +msgstr "" + +#. module: mail_move_message +#. openerp-web +#: code:addons/mail_move_message/static/src/js/mail_move_message.js:17 +#, python-format +msgid "Relocate Message" +msgstr "" + +#. module: mail_move_message +#: field:mail_move_message.wizard,parent_id:0 +msgid "Search by name" +msgstr "" + +#. module: mail_move_message +#: help:mail.message,moved_by_message_id:0 +msgid "Top message, that initate moving this message" +msgstr "" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "You cannot move this message. It was already moved with a message bellow. Open one and apply changes there." +msgstr "" + +#. module: mail_move_message +#: help:mail_move_message.wizard,model_id:0 +msgid "List available Models is configured at Settings\Technical\Emails\Mail Relocation. Empty for unassigned email" +msgstr "" + +#. module: mail_move_message +#: help:mail_move_message.wizard,filter_by_partner:0 +msgid "Show only records with the same partner as email author" +msgstr "" + +#. module: mail_move_message +#: help:mail_move_message.wizard,move_followers:0 +msgid "Add followers of current record to a new record.\nYou must use this option, if new record has restricted access.\nYou can change default value for this option at Settings/System Parameters" +msgstr "" \ No newline at end of file diff --git a/mail_move_message/i18n/sl.po b/mail_move_message/i18n/sl.po new file mode 100644 index 0000000..971ffeb --- /dev/null +++ b/mail_move_message/i18n/sl.po @@ -0,0 +1,160 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_move_message +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-08-11 06:53+0000\n" +"PO-Revision-Date: 2015-08-11 08:58+0200\n" +"Last-Translator: Matjaz Mozetic \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: sl\n" +"X-Generator: Poedit 1.8.2\n" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Cancel" +msgstr "Preklic" + +#. module: mail_move_message +#: field:mail_move_message.wizard,create_uid:0 +msgid "Created by" +msgstr "Ustvaril" + +#. module: mail_move_message +#: field:mail_move_message.wizard,create_date:0 +msgid "Created on" +msgstr "Ustvarjeno" + +#. module: mail_move_message +#: field:mail_move_message.wizard,id:0 +msgid "ID" +msgstr "ID" + +#. module: mail_move_message +#: field:mail.message,is_moved:0 +msgid "Is moved" +msgstr "Je premaknjeno" + +#. module: mail_move_message +#: field:mail_move_message.wizard,write_uid:0 +msgid "Last Updated by" +msgstr "Zadnjič posodobil" + +#. module: mail_move_message +#: field:mail_move_message.wizard,write_date:0 +msgid "Last Updated on" +msgstr "Zadnjič posodobljeno" + +#. module: mail_move_message +#: field:mail_move_message.wizard,record_url:0 +msgid "Link to record" +msgstr "Povezava do zapisa" + +#. module: mail_move_message +#: model:ir.model,name:mail_move_message.model_mail_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +#: field:mail_move_message.wizard,message_id:0 +msgid "Message" +msgstr "Sporočilo" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Move" +msgstr "Premik" + +#. module: mail_move_message +#: help:mail_move_message.wizard,move_back:0 +msgid "Move message and submessages to original place" +msgstr "Premik sporočila in podrejenih sporočil na izvorno mesto" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Move Message" +msgstr "Premik sporočila" + +#. module: mail_move_message +#: field:mail_move_message.wizard,move_back:0 +msgid "Move to origin" +msgstr "Premik na izvor" + +#. module: mail_move_message +#. openerp-web +#: code:addons/mail_move_message/static/src/xml/mail_move_message_main.xml:5 +#, python-format +msgid "Move to thread" +msgstr "Premik v nit" + +#. module: mail_move_message +#: field:mail.message,moved_by_message_id:0 +msgid "Moved by message" +msgstr "Premaknjeno s sporočilom" + +#. module: mail_move_message +#: field:mail.message,moved_by_user_id:0 +msgid "Moved by user" +msgstr "Premaknil uporabnik" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "Open message" +msgstr "Odpri sporočilo" + +#. module: mail_move_message +#: field:mail.message,moved_from_parent_id:0 +msgid "Parent Message (Original)" +msgstr "Nadrejeno sporočilo (original)" + +#. module: mail_move_message +#: code:addons/mail_move_message/mail_move_message_models.py:107 +#, python-format +msgid "Record" +msgstr "Zapis" + +#. module: mail_move_message +#: field:mail_move_message.wizard,res_id:0 +msgid "Record ID" +msgstr "ID zapisa" + +#. module: mail_move_message +#: field:mail_move_message.wizard,model_id:0 +msgid "Record type" +msgstr "Tip zapisa" + +#. module: mail_move_message +#: field:mail.message,moved_from_res_id:0 +msgid "Related Document ID (Original)" +msgstr "ID povezanega dokumenta (original)" + +#. module: mail_move_message +#: field:mail.message,moved_from_model:0 +msgid "Related Document Model (Original)" +msgstr "Model povezanega dokumenta (original)" + +#. module: mail_move_message +#. openerp-web +#: code:addons/mail_move_message/static/src/js/mail_move_message.js:17 +#, python-format +msgid "Relocate Message" +msgstr "Premik sporočila" + +#. module: mail_move_message +#: field:mail_move_message.wizard,parent_id:0 +msgid "Search by name" +msgstr "Iskanje po nazivu" + +#. module: mail_move_message +#: help:mail.message,moved_by_message_id:0 +msgid "Top message, that initate moving this message" +msgstr "Zgornje sporočilo, ki je sprožilo premik tega sporočila" + +#. module: mail_move_message +#: view:mail_move_message.wizard:mail_move_message.view_wizard +msgid "You cannot move this message. It was already moved with a message bellow. Open one and apply changes there." +msgstr "Tega sporočila ne morete premakniti, ker je bilo že premaknjeno s spodnjim sporočilom. Tam lahko uveljavljate spremembe." diff --git a/mail_move_message/images/inbox.png b/mail_move_message/images/inbox.png new file mode 100644 index 0000000..f9657b3 Binary files /dev/null and b/mail_move_message/images/inbox.png differ diff --git a/mail_move_message/mail_move_message_models.py b/mail_move_message/mail_move_message_models.py new file mode 100644 index 0000000..0013e9e --- /dev/null +++ b/mail_move_message/mail_move_message_models.py @@ -0,0 +1,382 @@ +from lxml import etree +from openerp import api, models, fields, SUPERUSER_ID +from openerp.tools import email_split +from openerp.tools.translate import _ + +class wizard(models.TransientModel): + _name = 'mail_move_message.wizard' + + def _model_selection(self): + selection = [] + config_parameters = self.env['ir.config_parameter'] + model_names = config_parameters.get_param('mail_relocation_models') + model_names = model_names.split(',') if model_names else [] + + if 'default_message_id' in self.env.context: + message = self.env['mail.message'].browse(self.env.context['default_message_id']) + if message.model and message.model not in model_names: + model_names.append(message.model) + if message.moved_from_model and message.moved_from_model not in model_names: + model_names.append(message.moved_from_model) + if model_names: + selection = [(m.model, m.display_name) for m in self.env['ir.model'].search([('model', 'in', model_names)])] + + return selection + + @api.model + def default_get(self, fields_list): + res = super(wizard, self).default_get(fields_list) + + model_fields = self.fields_get() + if model_fields['model']['selection']: + res['model'] = model_fields['model']['selection'] and model_fields['model']['selection'][0][0] + + if 'message_id' in res: + message = self.env['mail.message'].browse(res['message_id']) + email_from = message.email_from + parts = email_split(email_from.replace(' ',',')) + if parts: + email = parts[0] + name = email_from.find(email) != -1 and email_from[:email_from.index(email)].replace('"', '').replace('<', '').strip() or email_from + else: + name, email = email_from + res['message_name_from'] = name + res['message_email_from'] = email + + res['partner_id'] = message.author_id.id + if message.author_id and self.env.uid not in [u.id for u in message.author_id.user_ids]: + res['filter_by_partner'] = True + if message.author_id and res.get('model'): + res_id = self.env[res['model']].search([], order='id desc', limit=1) + res['res_id'] = res_id and res_id[0].id + + config_parameters = self.env['ir.config_parameter'] + res['move_followers'] = config_parameters.get_param('mail_relocation_move_followers') + + res['uid'] = self.env.uid + + return res + + message_id = fields.Many2one('mail.message', string='Message') + message_body = fields.Html(related='message_id.body', string='Message to move', readonly=True) + message_from = fields.Char(related='message_id.email_from', string='From', readonly=True) + message_subject = fields.Char(related='message_id.subject', string='Subject', readonly=True) + message_moved_by_message_id = fields.Many2one('mail.message', related='message_id.moved_by_message_id', string='Moved with', readonly=True) + message_moved_by_user_id = fields.Many2one('res.users', related='message_id.moved_by_user_id', string='Moved by', readonly=True) + message_is_moved = fields.Boolean(string='Is Moved', related='message_id.is_moved', readonly=True) + parent_id = fields.Many2one('mail.message', string='Search by name', ) + model = fields.Selection(_model_selection, string='Model') + res_id = fields.Integer(string='Record') + can_move = fields.Boolean('Can move', compute='get_can_move') + move_back = fields.Boolean('MOVE TO ORIGIN', help='Move message and submessages to original place') + partner_id = fields.Many2one('res.partner', string='Author') + filter_by_partner = fields.Boolean('Filter Records by partner') + message_email_from = fields.Char() + message_name_from = fields.Char() + # FIXME message_to_read should be True even if current message or any his childs are unread + message_to_read = fields.Boolean(related='message_id.to_read') + uid = fields.Integer() + move_followers = fields.Boolean( + 'Move Followers', + help="Add followers of current record to a new record.\n" + "You must use this option, if new record has restricted access.\n" + "You can change default value for this option at Settings/System Parameters") + + @api.depends('message_id') + @api.one + def get_can_move(self): + # message was not moved before OR message is a top message of previous move + self.can_move = not self.message_id.moved_by_message_id or self.message_id.moved_by_message_id.id == self.message_id.id + + @api.onchange('move_back') + def on_change_move_back(self): + if not self.move_back: + return + self.parent_id = self.message_id.moved_from_parent_id + model = self.message_id.moved_from_model + if self.message_id.is_moved: + self.model = model + self.res_id = self.message_id.moved_from_res_id + + @api.onchange('parent_id', 'res_id', 'model') + def update_move_back(self): + model = self.message_id.moved_from_model + self.move_back = self.parent_id == self.message_id.moved_from_parent_id \ + and self.res_id == self.message_id.moved_from_res_id \ + and (self.model == model or (not self.model and not model)) + + @api.onchange('parent_id') + def on_change_parent_id(self): + if self.parent_id and self.parent_id.model: + self.model = self.parent_id.model + self.res_id = self.parent_id.res_id + else: + self.model = None + self.res_id = None + + @api.onchange('model', 'filter_by_partner', 'partner_id') + def on_change_partner(self): + domain = {'res_id': [('id', '!=', self.message_id.res_id)]} + if self.model and self.filter_by_partner and self.partner_id: + fields = self.env[self.model].fields_get(False) + contact_field = False + for n, f in fields.iteritems(): + if f['type'] == 'many2one' and f['relation'] == 'res.partner': + contact_field = n + break + if contact_field: + domain['res_id'].append((contact_field, '=', self.partner_id.id)) + if self.model: + res_id = self.env[self.model].search(domain['res_id'], order='id desc', limit=1) + self.res_id = res_id and res_id[0].id + else: + self.res_id = None + return {'domain': domain} + + @api.one + def check_access(self): + cr = self._cr + uid = self.env.user.id + operation = 'write' + context = self._context + + if not ( self.model and self.res_id ): + return True + model_obj = self.pool[self.model] + mids = model_obj.exists(cr, uid, [self.res_id]) + if hasattr(model_obj, 'check_mail_message_access'): + model_obj.check_mail_message_access(cr, uid, mids, operation, context=context) + else: + self.pool['mail.thread'].check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context) + + @api.multi + def open_moved_by_message_id(self): + message_id = None + for r in self: + message_id = r.message_moved_by_message_id.id + return { + 'type': 'ir.actions.act_window', + 'res_model': 'mail_move_message.wizard', + 'view_mode': 'form', + 'view_type': 'form', + 'views': [[False, 'form']], + 'target': 'new', + 'context': {'default_message_id': message_id}, + } + + @api.multi + def move(self): + for r in self: + r.check_access() + if not r.parent_id or not (r.parent_id.model == r.model and + r.parent_id.res_id == r.res_id): + #link with the first message of record + parent = self.env['mail.message'].search([('model','=',r.model), ('res_id','=',r.res_id)], order='id', limit=1) + r.parent_id = parent.id or None + + r.message_id.move(r.parent_id.id, r.res_id, r.model, r.move_back, r.move_followers) + + if not ( r.model and r.res_id ): + obj = self.pool.get('ir.model.data').get_object_reference(self._cr, SUPERUSER_ID, 'mail', 'mail_archivesfeeds')[1] + return { + 'type' : 'ir.actions.client', + 'name' : 'Archive', + 'tag' : 'reload', + 'params' : {'menu_id': obj}, + } + return { + 'name': _('Record'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': r.model, + 'res_id': r.res_id, + 'views': [(False, 'form')], + 'type': 'ir.actions.act_window', + } + + @api.one + def delete(self): + self.message_id.unlink() + return {} + + @api.model + def create_partner(self, message_id, relation, partner_id, message_name_from, message_email_from): + model = self.env[relation] + message = self.env['mail.message'].browse(message_id) + if not partner_id and message_name_from: + partner_id = self.env['res.partner'].with_context({'update_message_author': True}).create({ + 'name': message_name_from, + 'email': message_email_from + }).id + + context = {'partner_id': partner_id} + if model._rec_name: + context.update({'default_%s' % model._rec_name: message.subject}) + + fields = model.fields_get() + contact_field = False + for n, f in fields.iteritems(): + if f['type'] == 'many2one' and f['relation'] == 'res.partner': + contact_field = n + break + if contact_field: + context.update({'default_%s' % contact_field: partner_id}) + return context + + @api.one + def read_close(self): + self.message_id.set_message_read(True) + self.message_id.child_ids.set_message_read(True) + return {'type': 'ir.actions.act_window_close'} + + +class mail_message(models.Model): + _inherit = 'mail.message' + + is_moved = fields.Boolean('Is moved') + moved_from_res_id = fields.Integer('Related Document ID (Original)') + moved_from_model = fields.Char('Related Document Model (Original)') + moved_from_parent_id = fields.Many2one('mail.message', 'Parent Message (Original)', ondelete='set null') + moved_by_message_id = fields.Many2one('mail.message', 'Moved by message', ondelete='set null', help='Top message, that initate moving this message') + moved_by_user_id = fields.Many2one('res.users', 'Moved by user', ondelete='set null') + all_child_ids = fields.One2many('mail.message', string='All childs', compute='_get_all_childs', help='all childs, including subchilds') + + @api.one + def _get_all_childs(self, include_myself=True): + ids = [] + if include_myself: + ids.append(self.id) + while True: + new_ids = self.search([('parent_id', 'in', ids), ('id', 'not in', ids)]).ids + if new_ids: + ids = ids + new_ids + continue + break + moved_childs = self.search([('moved_by_message_id', '=', self.id)]).ids + self.all_child_ids = ids + moved_childs + + @api.multi + def move_followers(self, model, ids): + fol_obj = self.env['mail.followers'] + for message in self: + followers = fol_obj.sudo().search([('res_model', '=', message.model), + ('res_id', '=', message.res_id)]) + for f in followers: + self.env[model].browse(ids).message_subscribe([f.partner_id.id], [s.id for s in f.subtype_ids]) + + @api.one + def move(self, parent_id, res_id, model, move_back, move_followers=False): + if parent_id == res_id: + return + vals = {} + if move_back: + # clear variables if we move everything back + vals['is_moved'] = False + vals['moved_by_user_id'] = None + vals['moved_by_message_id'] = None + + vals['moved_from_res_id'] = None + vals['moved_from_model'] = None + vals['moved_from_parent_id'] = None + else: + vals['parent_id'] = parent_id + vals['res_id'] = res_id + vals['model'] = model + + vals['is_moved'] = True + vals['moved_by_user_id'] = self.env.user.id + vals['moved_by_message_id'] = self.id + + for r in self.all_child_ids: + r_vals = vals.copy() + if not r.is_moved: + # moved_from_* variables contain not last, but original + # reference + r_vals['moved_from_parent_id'] = r.parent_id.id + r_vals['moved_from_res_id'] = r.res_id + r_vals['moved_from_model'] = r.model + elif move_back: + r_vals['parent_id'] = r.moved_from_parent_id.id + r_vals['res_id'] = r.moved_from_res_id + r_vals['model'] = r.moved_from_model + print 'update message', r, r_vals + if move_followers: + r.sudo().move_followers(r_vals.get('model'), r_vals.get('res_id')) + r.sudo().write(r_vals) + r.attachment_ids.sudo().write({ + 'res_id': r_vals.get('res_id'), + 'res_model': r_vals.get('model') + }) + + def name_get(self, cr, uid, ids, context=None): + if not (context or {}).get('extended_name'): + return super(mail_message, self).name_get(cr, uid, ids, context=context) + if isinstance(ids, (list, tuple)) and not len(ids): + return [] + if isinstance(ids, (long, int)): + ids = [ids] + reads = self.read(cr, uid, ids, ['record_name','model', 'res_id'], context=context) + res = [] + for record in reads: + name = record['record_name'] or '' + extended_name = ' [%s] ID %s' % (record.get('model', 'UNDEF'), record.get('res_id', 'UNDEF')) + res.append((record['id'], name + extended_name)) + return res + + def _message_read_dict(self, cr, uid, message, parent_id=False, context=None): + res = super(mail_message, self)._message_read_dict(cr, uid, message, parent_id, context) + res['is_moved'] = message.is_moved + return res + + +class mail_move_message_configuration(models.TransientModel): + _name = 'mail_move_message.config.settings' + _inherit = 'res.config.settings' + + model_ids = fields.Many2many(comodel_name='ir.model', string='Models') + move_followers = fields.Boolean('Move Followers') + + @api.model + def get_default_move_message_configs(self, fields): + config_parameters = self.env['ir.config_parameter'] + model_obj = self.env['ir.model'] + model_names = config_parameters.get_param('mail_relocation_models') + if not model_names: + return {} + model_names = model_names.split(',') + model_ids = model_obj.search([('model', 'in', model_names)]) + return { + 'model_ids': [m.id for m in model_ids], + 'move_followers': config_parameters.get_param('mail_relocation_move_followers') + } + + @api.multi + def set_move_message_configs(self): + config_parameters = self.env['ir.config_parameter'] + model_names = '' + for record in self: + model_names = ','.join([m.model for m in record.model_ids]) + config_parameters.set_param('mail_relocation_models', model_names) + config_parameters.set_param('mail_relocation_move_followers', record.move_followers or '') + + +class res_partner(models.Model): + _inherit = 'res.partner' + + @api.model + def create(self, vals): + res = super(res_partner, self).create(vals) + if 'update_message_author' in self.env.context and 'email' in vals: + mail_message_obj = self.env['mail.message'] + # Escape special SQL characters in email_address to avoid invalid matches + email_address = (vals['email'].replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')) + email_brackets = "<%s>" % email_address + messages = mail_message_obj.search([ + '|', + ('email_from', '=ilike', email_address), + ('email_from', 'ilike', email_brackets), + ('author_id', '=', False) + ]) + if messages: + messages.sudo().write({'author_id': res.id}) + return res diff --git a/mail_move_message/mail_move_message_views.xml b/mail_move_message/mail_move_message_views.xml new file mode 100644 index 0000000..475f7d7 --- /dev/null +++ b/mail_move_message/mail_move_message_views.xml @@ -0,0 +1,122 @@ + + + + + + + mail_move_message.wizard.view + mail_move_message.wizard + +
+ + + + + + + + +

You cannot move this message. It was already moved with a message bellow. Open one and apply changes there.

+ + + +