diff --git a/mail_multi_website/ ⇐ ⇐ ⇐/README.rst b/mail_multi_website/ ⇐ ⇐ ⇐/README.rst deleted file mode 100644 index 32b8958..0000000 --- a/mail_multi_website/ ⇐ ⇐ ⇐/README.rst +++ /dev/null @@ -1,14 +0,0 @@ -===================== - Multi-Brand Mailing -===================== - -Mail-related stuff for multi-website support - -Module is available at Odoo Apps Store: -https://www.odoo.com/apps/modules/13.0/mail_multi_website/ - - We do love FOSS, but sometimes we need to eat ¯\\_(ツ)_/¯ - - Please consider buying the module and get karma and support in return - - `IT Projects Labs Team `__ diff --git a/mail_multi_website/README.rst b/mail_multi_website/README.rst new file mode 100644 index 0000000..df1c5a6 --- /dev/null +++ b/mail_multi_website/README.rst @@ -0,0 +1,67 @@ +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://opensource.org/licenses/MIT + :alt: License: MIT + +===================== + Multi-Brand Mailing +===================== + +Mail-related stuff for multi-website support + +* Makes following field in ``res.users`` website-dependent: + + * ``email`` + * ``signature`` + +* Makes following fields in ``mail.template`` website-dependent: + + * ``body_html`` + * ``mail_server_id`` + * ``report_template`` + +* Overrides ``mail.template``'s ``render_template`` method to add ``website`` + variable. It may cause incompatibility with other modules that redefine that + method too. + +Roadmap +======= + +* ``body_html`` becomes untranslatable after module installation + +Credits +======= + +Contributors +------------ +* `Ivan Yelizariev `__ + +Sponsors +-------- +* `e-thos SSII `__ + +Maintainers +----------- +* `IT-Projects LLC `__ + + To get a guaranteed support + you are kindly requested to purchase the module + at `odoo apps store `__. + + Thank you for understanding! + + `IT-Projects Team `__ + +Further information +=================== + +Demo: http://runbot.it-projects.info/demo/mail-addons/13.0 + +HTML Description: https://apps.odoo.com/apps/modules/13.0/mail_multi_website/ + +Usage instructions: ``_ + +Changelog: ``_ + +Notifications on updates: `via Atom `_, `by Email `_ + +Tested on Odoo 13.0 ca67c83e8d36ececaf97a7579c3ff2529b3e227c diff --git a/mail_multi_website/__init__.py b/mail_multi_website/__init__.py new file mode 100644 index 0000000..a627784 --- /dev/null +++ b/mail_multi_website/__init__.py @@ -0,0 +1,43 @@ +# Copyright 2018 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +from . import models +from . import wizard +from .tests import test_mail_model + + +def post_init_hook(cr, registry): + from odoo import api, SUPERUSER_ID + + env = api.Environment(cr, SUPERUSER_ID, {}) + + env.cr.execute("ALTER TABLE res_users ADD COLUMN email VARCHAR") + + # fill new email column with values from partner + for user in env["res.users"].with_context(active_test=False).search([]): + email = user.partner_id.email + if email: + user._force_default("email", email) + + +def uninstall_hook(cr, registry): + from odoo import api, SUPERUSER_ID + + env = api.Environment(cr, SUPERUSER_ID, {}) + + # remove properties + field_ids = [ + env.ref("base.field_res_users__email").id, + env.ref("base.field_res_users__signature").id, + env.ref("mail.field_mail_template__body_html").id, + env.ref("mail.field_mail_template__mail_server_id").id, + env.ref("mail.field_mail_template__report_template").id, + ] + env["ir.property"].search([("fields_id", "in", field_ids)]).unlink() + + # copy emails from user to partner + cr.execute("SELECT partner_id,email FROM res_users") + for partner_id, default_email in cr.fetchall(): + env["res.partner"].browse(partner_id).email = default_email + + # email field is computed (related) and not needed if mail_multi_website is not installed + env.cr.execute("ALTER TABLE res_users DROP COLUMN email") diff --git a/mail_multi_website/__manifest__.py b/mail_multi_website/__manifest__.py new file mode 100644 index 0000000..27c0463 --- /dev/null +++ b/mail_multi_website/__manifest__.py @@ -0,0 +1,45 @@ +# Copyright 2018,2020 Ivan Yelizariev +# Copyright 2018 Kolushov Alexandr +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +{ + "name": """Multi-Brand Mailing""", + "summary": """Use single Backend to manage several Websites""", + "category": "Discuss", + # "live_test_url": "http://apps.it-projects.info/shop/product/website-multi-company?version=11.0", + "images": ["images/main.jpg"], + "version": "13.0.2.0.0", + "application": False, + "author": "IT-Projects LLC, Ivan Yelizariev", + "support": "apps@itpp.dev", + "website": "https://twitter.com/OdooFree", + "license": "OPL-1", + "price": 115.00, + "currency": "EUR", + "depends": [ + "ir_config_parameter_multi_company", + "web_website", + "mail", + "test_mail", + ], + "external_dependencies": {"python": [], "bin": []}, + "data": ["views/website_views.xml"], + "demo": [], + "qweb": [], + "post_load": None, + "pre_init_hook": None, + "post_init_hook": "post_init_hook", + "uninstall_hook": "uninstall_hook", + "auto_install": False, + "installable": True, + # "demo_title": "Email Addresses per Website", + # "demo_addons": [ + # ], + # "demo_addons_hidden": [ + # ], + # "demo_url": "DEMO-URL", + # "demo_summary": "Use single Backend to manage several Websites", + # "demo_images": [ + # "images/MAIN_IMAGE", + # ] +} diff --git a/mail_multi_website/doc/changelog.rst b/mail_multi_website/doc/changelog.rst new file mode 100644 index 0000000..f9986a8 --- /dev/null +++ b/mail_multi_website/doc/changelog.rst @@ -0,0 +1,13 @@ +`2.0.0` +------- +- **Improvement:** adapt module to latest version of ``web_website`` module + +`1.0.1` +------- + +- **Fix:** Issue with module uninstallation + +`1.0.0` +------- + +- **Init version** diff --git a/mail_multi_website/doc/index.rst b/mail_multi_website/doc/index.rst new file mode 100644 index 0000000..2241d21 --- /dev/null +++ b/mail_multi_website/doc/index.rst @@ -0,0 +1,73 @@ +===================== + Multi-Brand Mailing +===================== + +Installation +============ + +* `Install `__ this module in a usual way + +Configuration +============= + + +Access to websites +------------------ + +* Go to menu ``[[ Settings ]] >> Users & Companies >> Users`` +* Select a user +* Grant access ``[x] Multi Websites for Backend`` +* Configure **Allowed Websites** + +User's email per website +------------------------ + +* Refresh page if you just granted your user access to websites +* Use top right-hand corner button with current website name to switch between websites +* Use top right-hand corner button with user name and avatar to open + Preference popup. When you edit **Email** field, it will be saved as a value + for current website. + +Email template per website +-------------------------- + +* Refresh page if you just granted your user access to websites +* `Activate Developer Mode `__ +* Use top right-hand corner button with current website name to switch between websites +* Go to menu ``[[ Settings ]] >> Technical >> Email >> Templates`` +* When you edit template, following fields will be saved as a value for current website: + + * **Body** + * **Outgoing Mail Server** + * **Optional report to print and attach** + +* Additional variable ``website`` is available to configure rest fields (**Subject**, **From**, etc.) + +Note. If related record (e.g. ``sale.order``) has field ``company_id`` or ``website_id`` those values will be used instead of currently selected in Website / Company Switchers + +Alias domain per website +------------------------ + +Configure ``mail.catchall.domain`` per website. See Documentation of the module `Context-dependent values in System Parameters `__. + +Outgoing mails servers per website +---------------------------------- + +If each domain has different Outgoing Mail Server you need following adjustments + +* Go to menu ``[[ Website ]] >> Configuration >> Websites`` +* In each Website specify field **Outgoing Mails** + +Properties +---------- + +To review properties by website use menu ``[[ Settings ]] >> Technical >> Parameters >> Company Properties``. See **How it works** in Documentation of module `Website Switcher in Backend `__. + +Usage +===== + +When you work from backend, Email for current website is used. + +When a user do something on website (e.g. purchase products) and some mail is sent, then email address for that website will be used (mostly Administrator's email address). + +When email is sent, template's value like body, subject, etc. for current values are used. diff --git a/mail_multi_website/doc/src/index.html b/mail_multi_website/doc/src/index.html new file mode 100644 index 0000000..cded8e2 --- /dev/null +++ b/mail_multi_website/doc/src/index.html @@ -0,0 +1,36 @@ + +
+
+
+ +
+ Key features: +
    + +
  • + + Separate Mail Templates per Website +
  • + +
  • + + From address in email has address for current Website +
  • + +
  • + + Reply-To address in email has domain of current Website +
  • + +
  • + + User's signature per Website +
  • + +
+
+ +
+
+
+ diff --git a/mail_multi_website/doc/src/info.yaml b/mail_multi_website/doc/src/info.yaml new file mode 100644 index 0000000..9b09dd8 --- /dev/null +++ b/mail_multi_website/doc/src/info.yaml @@ -0,0 +1 @@ +slogan: Use single Backend to manage several Websites diff --git a/mail_multi_website/i18n/mail_multi_website.pot b/mail_multi_website/i18n/mail_multi_website.pot new file mode 100644 index 0000000..8c36244 --- /dev/null +++ b/mail_multi_website/i18n/mail_multi_website.pot @@ -0,0 +1,123 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_multi_website +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \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_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_email_template_preview__body_html +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_template__body_html +msgid "Body" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_test_simple__company_id +msgid "Company" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_ir_property +msgid "Company Property" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,help:mail_multi_website.field_website__mail_server_id +msgid "Default outgoing mail server" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_res_users__email_multi_website +msgid "Email Multi Website" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_mail_template +msgid "Email Templates" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_mail_compose_message +msgid "Email composition wizard" +msgstr "" + +#. module: mail_multi_website +#: code:addons/mail_multi_website/models/mail_template.py:112 +#, python-format +msgid "Failed to render template %r using values %r" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_mail_message +msgid "Message" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_res_users__email +msgid "Multi Website Email" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,help:mail_multi_website.field_email_template_preview__mail_server_id +#: model:ir.model.fields,help:mail_multi_website.field_mail_template__mail_server_id +msgid "Optional preferred server for outgoing mails. If not set, the highest priority one will be used." +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_email_template_preview__report_template +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_template__report_template +msgid "Optional report to print and attach (Multi-Website)" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_email_template_preview__mail_server_id +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_template__mail_server_id +msgid "Outgoing Mail Server (Multi-Website)" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_website__mail_server_id +msgid "Outgoing Mails" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_compose_message__mail_server_id +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_mail__mail_server_id +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_message__mail_server_id +msgid "Outgoing mail server" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model.fields,field_description:mail_multi_website.field_res_users__signature +msgid "Signature" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_mail_test_simple +msgid "Simple Chatter Model" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_res_users +msgid "Users" +msgstr "" + +#. module: mail_multi_website +#: model:ir.model,name:mail_multi_website.model_website +#: model:ir.model.fields,field_description:mail_multi_website.field_mail_test_simple__website_id +msgid "Website" +msgstr "" + diff --git a/mail_multi_website/images/main.jpg b/mail_multi_website/images/main.jpg new file mode 100644 index 0000000..390e6be Binary files /dev/null and b/mail_multi_website/images/main.jpg differ diff --git a/mail_multi_website/models/__init__.py b/mail_multi_website/models/__init__.py new file mode 100644 index 0000000..a8878fd --- /dev/null +++ b/mail_multi_website/models/__init__.py @@ -0,0 +1,7 @@ +# License MIT (https://opensource.org/licenses/MIT). +from . import res_users +from . import ir_property +from . import mail_template +from . import mail_thread +from . import mail_message +from . import website diff --git a/mail_multi_website/models/ir_property.py b/mail_multi_website/models/ir_property.py new file mode 100644 index 0000000..9eaca97 --- /dev/null +++ b/mail_multi_website/models/ir_property.py @@ -0,0 +1,20 @@ +# Copyright 2018 Ivan Yelizariev +# Copyright 2018 Kolushov Alexandr +# License MIT (https://opensource.org/licenses/MIT). +from odoo import models + + +class IrProperty(models.Model): + _inherit = "ir.property" + + def write(self, vals): + res = super(IrProperty, self).write(vals) + field_object_list = [ + self.env.ref("base.field_res_users__email"), + self.env.ref("mail.field_mail_template__body_html"), + self.env.ref("mail.field_mail_template__mail_server_id"), + self.env.ref("mail.field_mail_template__report_template"), + ] + for fobj in field_object_list: + self._update_db_value_website_dependent(fobj) + return res diff --git a/mail_multi_website/models/mail_message.py b/mail_multi_website/models/mail_message.py new file mode 100644 index 0000000..29137cd --- /dev/null +++ b/mail_multi_website/models/mail_message.py @@ -0,0 +1,13 @@ +# Copyright 2018,2020 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +from odoo import fields, models + + +class Message(models.Model): + _inherit = "mail.message" + + def _default_mail_server_id(self): + return self.env.website.mail_server_id.id + + mail_server_id = fields.Many2one(default=_default_mail_server_id) diff --git a/mail_multi_website/models/mail_template.py b/mail_multi_website/models/mail_template.py new file mode 100644 index 0000000..0c74f07 --- /dev/null +++ b/mail_multi_website/models/mail_template.py @@ -0,0 +1,185 @@ +# Copyright 2018,2020 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +import logging + +from odoo import _, api, fields, models, tools +from odoo.exceptions import UserError + +from odoo.addons.mail.models.mail_template import format_date, format_datetime + +_logger = logging.getLogger(__name__) +FIELDS = ["body_html", "mail_server_id", "report_template"] + +try: + from odoo.addons.mail.models.mail_template import ( + mako_safe_template_env, + mako_template_env, + ) +except ImportError: + _logger.warning("jinja2 not available, templating features will not work!") + + +class MailTemplate(models.Model): + + _inherit = ["mail.template", "website_dependent.mixin"] + _name = "mail.template" + + body_html = fields.Html(company_dependent=True, website_dependent=True) + mail_server_id = fields.Many2one( + string="Outgoing Mail Server (Multi-Website)", + company_dependent=True, + website_dependent=True, + ) + report_template = fields.Many2one( + string="Optional report to print and attach (Multi-Website)", + company_dependent=True, + website_dependent=True, + ) + + def generate_email(self, res_ids, fields=None): + """Remove mail_server_id when not set to recompute in _default_mail_server_id in mail.message""" + multi_mode = True + if isinstance(res_ids, int): + multi_mode = False + res = super(MailTemplate, self).generate_email(res_ids, fields=fields) + if not multi_mode: + list_of_dict = {0: res} + else: + list_of_dict = res + + for _unused, data in list_of_dict.items(): + if "mail_server_id" in data and not data.get("mail_server_id"): + del data["mail_server_id"] + + return res + + @api.model + def _render_template(self, template_txt, model, res_ids, post_process=False): + """Override to add website to context""" + multi_mode = True + if isinstance(res_ids, int): + multi_mode = False + res_ids = [res_ids] + + results = dict.fromkeys(res_ids, u"") + + # try to load the template + try: + mako_env = ( + mako_safe_template_env + if self.env.context.get("safe") + else mako_template_env + ) + template = mako_env.from_string(tools.ustr(template_txt)) + except Exception: + _logger.info("Failed to load template %r", template_txt, exc_info=True) + return multi_mode and results or results[res_ids[0]] + + # prepare template variables + records = self.env[model].browse( + it for it in res_ids if it + ) # filter to avoid browsing [None] + res_to_rec = dict.fromkeys(res_ids, None) + for record in records: + res_to_rec[record.id] = record + variables = { + "format_date": lambda date, date_format=False, lang_code=False: format_date( + self.env, date, date_format, lang_code + ), + "format_datetime": lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime( + self.env, dt, tz, dt_format, lang_code + ), + "format_amount": lambda amount, currency, lang_code=False: tools.format_amount( + self.env, amount, currency, lang_code + ), + "format_duration": lambda value: tools.format_duration(value), + "user": self.env.user, + "ctx": self._context, # context kw would clash with mako internals + } + + # [NEW] Check website and company context + company = self.env["res.company"] # empty value + + company_id = self.env.context.get("force_company") + if company_id: + company = self.env["res.company"].sudo().browse(company_id) + + website = self.env.website + # [/NEW] + + for res_id, record in res_to_rec.items(): + # [NEW] Check website and company context + record_company = company + if not record_company: + if hasattr(record, "company_id") and record.company_id: + record_company = record.company_id + + record_website = website + if hasattr(record, "website_id") and record.website_id: + record_website = record.website_id + + if ( + record_company + and record_website + and record_website.company_id != company + ): + # company and website are incompatible, so keep only company + record_website = self.env["website"] # empty value + + record_context = dict( + force_company=record_company.id, website_id=record_website.id + ) + variables["website"] = record_website + # [/NEW] + + variables["object"] = record + try: + render_result = template.render(variables) + except Exception: + _logger.info( + "Failed to render template %r using values %r" + % (template, variables), + exc_info=True, + ) + raise UserError( + _("Failed to render template %r using values %r") + % (template, variables) + ) + if render_result == u"False": + render_result = u"" + results[res_id] = render_result + + if post_process: + for res_id, result in results.items(): + results[res_id] = self.with_context( + **record_context + ).render_post_process(result) + + return multi_mode and results or results[res_ids[0]] + + @api.model + def create(self, vals): + res = super(MailTemplate, self).create(vals) + # make value company independent + for f in FIELDS: + res._force_default(f, vals.get(f)) + return res + + def write(self, vals): + res = super(MailTemplate, self).write(vals) + + # TODO: will it work with OCA's partner_firstname module? + if "name" in vals: + fields_to_update = FIELDS + else: + fields_to_update = [f for f in FIELDS if f in vals] + for f in fields_to_update: + self._update_properties_label(f) + + return res + + def _auto_init(self): + for f in FIELDS: + self._auto_init_website_dependent(f) + return super(MailTemplate, self)._auto_init() diff --git a/mail_multi_website/models/mail_thread.py b/mail_multi_website/models/mail_thread.py new file mode 100644 index 0000000..1a417c9 --- /dev/null +++ b/mail_multi_website/models/mail_thread.py @@ -0,0 +1,34 @@ +# Copyright 2018,2020 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +from odoo import api, models, tools + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + @api.model + def _message_route_process(self, message, message_dict, routes): + rcpt_tos = ",".join( + [ + tools.decode_message_header(message, "Delivered-To"), + tools.decode_message_header(message, "To"), + tools.decode_message_header(message, "Cc"), + tools.decode_message_header(message, "Resent-To"), + tools.decode_message_header(message, "Resent-Cc"), + ] + ) + rcpt_tos_websiteparts = [ + e.split("@")[1].lower() for e in tools.email_split(rcpt_tos) + ] + website = ( + self.env["website"].sudo().search([("domain", "in", rcpt_tos_websiteparts)]) + ) + company = website.mapped("company_id") + new_self = self.with_context( + allowed_website_ids=website.ids, allowed_company_ids=company.ids + ) + + return super(MailThread, new_self)._message_route_process( + message, message_dict, routes + ) diff --git a/mail_multi_website/models/res_users.py b/mail_multi_website/models/res_users.py new file mode 100644 index 0000000..6e1de8f --- /dev/null +++ b/mail_multi_website/models/res_users.py @@ -0,0 +1,50 @@ +# Copyright 2018 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) +FIELD_NAME = "email" +FIELDS = ["signature"] +ALL_FIELDS = [FIELD_NAME] + FIELDS + + +class User(models.Model): + + _inherit = ["res.users", "website_dependent.mixin"] + _name = "res.users" + + signature = fields.Html(company_dependent=True, website_dependent=True) + + # extra field to detach email field from res.partner + email = fields.Char( + string="Multi Website Email", + company_dependent=True, + website_dependent=True, + inherited=False, + related=None, + readonly=False, + ) + + @api.model + def create(self, vals): + res = super(User, self).create(vals) + # make value company independent + res._force_default(FIELD_NAME, vals.get("email")) + for f in FIELDS: + res._force_default(f, vals.get(f)) + return res + + def write(self, vals): + res = super(User, self).write(vals) + # TODO: will it work with OCA's partner_firstname module? + if any(k in vals for k in ["name"] + FIELDS): + for f in ALL_FIELDS: + self._update_properties_label(f) + return res + + def _auto_init(self): + for f in FIELDS: + self._auto_init_website_dependent(f) + return super(User, self)._auto_init() diff --git a/mail_multi_website/models/website.py b/mail_multi_website/models/website.py new file mode 100644 index 0000000..7e61712 --- /dev/null +++ b/mail_multi_website/models/website.py @@ -0,0 +1,12 @@ +# Copyright 2017 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). + +from odoo import fields, models + + +class Website(models.Model): + _inherit = "website" + + mail_server_id = fields.Many2one( + "ir.mail_server", "Outgoing Mails", help="Default outgoing mail server" + ) diff --git a/mail_multi_website/static/description/icon.png b/mail_multi_website/static/description/icon.png new file mode 100644 index 0000000..79f7d8f Binary files /dev/null and b/mail_multi_website/static/description/icon.png differ diff --git a/mail_multi_website/static/description/index.html b/mail_multi_website/static/description/index.html new file mode 100644 index 0000000..be9dd70 --- /dev/null +++ b/mail_multi_website/static/description/index.html @@ -0,0 +1,134 @@ + +
+
+
+
+

Multi-Brand Mailing

+

Use single Backend to manage several Websites

+

Version: v13.0.2.0.0

+
+
+
+

+ +
Tested and maintained by +
IT Projects Labs +
Assitance: apps@itpp.dev +

+
+
+
+ + + +
+
+
+ +
+ Key features: +
    + +
  • + + Separate Mail Templates per Website +
  • + +
  • + + From address in email has address for current Website +
  • + +
  • + + Reply-To address in email has domain of current Website +
  • + +
  • + + User's signature per Website +
  • + +
+
+ +
+
+
+ + + + +
+
+

Let our expertise work for you!

+

(Doors and windows below are clickable)

+ +
+ + + Free Modules + Sync Studio + REST API + Webhooks + POS: show product qty + All modules + All POS modules + All Mail modules + All Website modules + All eCommerce modules + Ivan Yelizariev + Web Login Background + +
+
+ + + Free Modules + Sync Studio + REST API + Webhooks + POS: show product qty + All modules + All POS modules + All Mail modules + All Website modules + All eCommerce modules + Ivan Yelizariev + Web Login Background + +
+
+ + + Free Modules + Sync Studio + REST API + Webhooks + POS: show product qty + All modules + All POS modules + All Mail modules + All Website modules + All eCommerce modules + Ivan Yelizariev + Web Login Background + +
+
+
+ diff --git a/mail_multi_website/tests/__init__.py b/mail_multi_website/tests/__init__.py new file mode 100644 index 0000000..7e6c468 --- /dev/null +++ b/mail_multi_website/tests/__init__.py @@ -0,0 +1,4 @@ +# License MIT (https://opensource.org/licenses/MIT). +from . import test_send +from . import test_render +from . import test_fetch diff --git a/mail_multi_website/tests/test_fetch.py b/mail_multi_website/tests/test_fetch.py new file mode 100644 index 0000000..b96b699 --- /dev/null +++ b/mail_multi_website/tests/test_fetch.py @@ -0,0 +1,63 @@ +# Copyright 2018,2020 Ivan Yelizariev +# Copyright 2018 Kolushov Alexandr +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +from odoo.tools import mute_logger + +from odoo.addons.test_mail.data.test_mail_data import MAIL_TEMPLATE +from odoo.addons.test_mail.tests.test_mail_mail import TestMail + + +# TODO: Shall we use TestMailgateway class instead? +class TestFetch(TestMail): + at_install = True + post_install = True + + def setUp(self): + super(TestFetch, self).setUp() + self.email_from = '"Sylvie Lelitre" ' + self.website = self.env["website"].create( + {"name": "Test Website", "domain": "example.com"} + ) + self.company = self.env["res.company"].create({"name": "New Test Website"}) + self.website.company_id = self.company + + # copy-paste from mail.tests.test_mail_gateway + mail_test_model = self.env["ir.model"]._get("mail.test.simple") + # groups@.. will cause the creation of new mail.test.simple + self.alias = self.env["mail.alias"].create( + { + "alias_name": "groups", + "alias_user_id": False, + "alias_model_id": mail_test_model.id, + "alias_contact": "everyone", + } + ) + + @mute_logger("odoo.addons.mail.models.mail_thread", "odoo.models") + def test_fetch_multi_website(self): + """ Incoming email on an alias creating a new record + message_new + message details """ + new_groups = self.format_and_process( + MAIL_TEMPLATE, + self.email_from, + "groups@example.com", + subject="My Frogs", + target_model="mail.test.simple", + ) + + # Test: one group created by mailgateway administrator + self.assertEqual( + len(new_groups), + 1, + "message_process: a new mail.test should have been created", + ) + self.assertEqual( + new_groups.website_id, + self.website, + "New record is created with wrong website", + ) + self.assertEqual( + new_groups.company_id, + self.company, + "New record is created with wrong company", + ) diff --git a/mail_multi_website/tests/test_mail_model.py b/mail_multi_website/tests/test_mail_model.py new file mode 100644 index 0000000..e633a5f --- /dev/null +++ b/mail_multi_website/tests/test_mail_model.py @@ -0,0 +1,11 @@ +# Copyright 2018,2020 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +from odoo import fields, models + + +class MailTest(models.Model): + _inherit = "mail.test.simple" + + company_id = fields.Many2one("res.company", default=lambda self: self.env.company) + website_id = fields.Many2one("website", default=lambda self: self.env.website) diff --git a/mail_multi_website/tests/test_render.py b/mail_multi_website/tests/test_render.py new file mode 100644 index 0000000..0b9639a --- /dev/null +++ b/mail_multi_website/tests/test_render.py @@ -0,0 +1,145 @@ +# Copyright 2018,2020 Ivan Yelizariev +# Copyright 2018 Kolushov Alexandr +# License MIT (https://opensource.org/licenses/MIT). +# License OPL-1 (https://www.odoo.com/documentation/user/13.0/legal/licenses/licenses.html#odoo-apps) for derivative work. +import base64 + +from odoo.addons.test_mail.tests.test_mail_mail import TestMail + + +class TestRender(TestMail): + at_install = True + post_install = True + + def setUp(self): + super(TestRender, self).setUp() + + self.original_email = self.env.user.email + self.original_company = self.env.user.company_id + self.email = "superadmin@second-website.example" + self.assertNotEqual(self.original_email, self.email) + + self.website = self.env.ref("website.website2") + self.company = self.env["res.company"].create({"name": "New Test Website"}) + self.website.company_id = self.company + self.mail_server_id = self.env["ir.mail_server"].create( + {"name": "mail server", "smtp_host": "mail.example.com"} + ) + self.website.mail_server_id = self.mail_server_id + + user_admin = self.env.ref("base.user_admin") + # copy-paste from mail.tests.test_mail_template + self._attachments = [ + { + "name": "first.txt", + "datas": base64.b64encode(b"My first attachment"), + "res_model": "res.partner", + "res_id": user_admin.partner_id.id, + }, + { + "name": "second.txt", + "datas": base64.b64encode(b"My second attachment"), + "res_model": "res.partner", + "res_id": user_admin.partner_id.id, + }, + ] + + self.partner_1 = self.env["res.partner"].create({"name": "partner_1"}) + self.partner_2 = self.env["res.partner"].create({"name": "partner_2"}) + self.email_1 = "test1@example.com" + self.email_2 = "test2@example.com" + self.email_3 = self.partner_1.email + self.email_template = self.env["mail.template"].create( + { + "model_id": self.env["ir.model"]._get("mail.test").id, + "name": "Pigs Template", + "subject": "${website.name}", + "body_html": "${object.description}", + "user_signature": False, + "attachment_ids": [ + (0, 0, self._attachments[0]), + (0, 0, self._attachments[1]), + ], + "partner_to": "%s,%s" + % (self.partner_2.id, self.user_employee.partner_id.id), + "email_to": "{}, {}".format(self.email_1, self.email_2), + "email_cc": "%s" % self.email_3, + } + ) + + def switch_user_website(self): + # add website to allowed + self.env.user.write( + dict( + backend_website_ids=[(4, self.website.id)], + backend_website_id=self.website.id, + company_id=self.company.id, + company_ids=[(4, self.company.id)], + ) + ) + + def test_website_in_render_variables(self): + """Mail values are per website""" + + self.env.user.backend_website_id = None + TestModel = self.env["mail.test"].with_context( + {"mail_create_nolog": True, "mail_create_nosubscribe": True} + ) + self.test_pigs = TestModel.create( + { + "name": "Pigs", + "description": "Fans of Pigs, unite !", + "alias_name": "pigs", + "alias_contact": "followers", + } + ) + + # sending without website + mail_id = self.email_template.send_mail(self.test_pigs.id) + mail = self.env["mail.mail"].browse(mail_id) + self.assertEqual(mail.subject, "") + self.assertFalse(mail.mail_server_id) + + # sending from frontend + self.test_pigs.company_id = None + mail_id = self.email_template.with_context( + wdb=True, allowed_website_ids=self.website.ids + ).send_mail(self.test_pigs.id) + mail = self.env["mail.mail"].browse(mail_id) + self.assertEqual(mail.subject, self.website.name) + self.assertEqual(mail.mail_server_id, self.mail_server_id) + + # copy-pasted tests + self.assertEqual(mail.email_to, self.email_template.email_to) + # for some reason self.email_template.email_cc might return u'False' + self.assertEqual( + mail.email_cc or "False", self.email_template.email_cc or "False" + ) + self.assertEqual( + mail.recipient_ids, self.partner_2 | self.user_employee.partner_id + ) + + # sending from frontend + self.switch_user_website() + mail_id = self.email_template.send_mail(self.test_pigs.id) + mail = self.env["mail.mail"].browse(mail_id) + self.assertEqual(mail.subject, self.website.name) + + def _test_message_post_with_template(self): + # It's deactivated, because workaround is based on checking host value in get_current_website() + """Simulate sending email on eCommerce checkout""" + self.switch_user_website() + self.env.user.email = self.email + self.env.user.invalidate_cache() + self.env.user.invalidate_cache() + self.assertEqual(self.env.user.email, self.email) + # switch admin user back + self.env.user.company_id = self.original_company + self.env.user.invalidate_cache() + self.assertEqual(self.env.user.email, self.original_email) + + self.test_pigs.with_context( + allowed_website_ids=self.website.ids + ).message_post_with_template(self.email_template.id) + message = self.env["mail.message"].search([], order="id desc", limit=1) + self.assertIn("<%s>" % self.email, message.email_from) diff --git a/mail_multi_website/tests/test_send.py b/mail_multi_website/tests/test_send.py new file mode 100644 index 0000000..f934cf9 --- /dev/null +++ b/mail_multi_website/tests/test_send.py @@ -0,0 +1,72 @@ +# Copyright 2018 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +from odoo.tests.common import TransactionCase + + +class TestSendMail(TransactionCase): + at_install = True + post_install = True + + def setUp(self): + super(TestSendMail, self).setUp() + self.website = self.env.ref("website.website2") + self.company = self.env["res.company"].create({"name": "New Test Website"}) + self.original_email = self.env.user.email + self.original_company = self.env.user.company_id + self.email = "superadmin@second-website.example" + # Check that current email is set and differs + self.assertTrue(self.email) + self.assertNotEqual(self.original_email, self.email) + self.website.company_id = self.company + + def switch_user_website(self): + # add website to allowed + self.env.user.write( + dict( + backend_website_ids=[(4, self.website.id)], + backend_website_id=self.website.id, + company_id=self.company.id, + company_ids=[(4, self.company.id)], + ) + ) + + def test_multi_email(self): + """User has email addresses per website""" + self.switch_user_website() + # update user's email + self.env.user.email = self.email + # Check that writing works + self.env.user.flush() + self.env.user.invalidate_cache() + self.assertEqual( + self.env.user.email, + self.email, + "Write methods doesn't work (Field is not in registry?)", + ) + + # changing company will automatically update website value to empty value + self.env.user.company_id = self.original_company + self.env.user.invalidate_cache() + self.assertEqual( + self.env.user.email, + self.original_email, + "Multi-email doesn't work on switching websites", + ) + + def test_multi_email_partner(self): + """Partner doesn't have email addresses per website""" + original_email = "original@email1" + new_email = "new@email2" + partner = self.env["res.partner"].create( + {"name": "test", "email": original_email} + ) + self.switch_user_website() + # update partner's email + partner.email = new_email + self.assertEqual(partner.email, new_email) + # changing company will automatically update website value to empty value + self.env.user.company_id = self.original_company + self.env.user.invalidate_cache() + self.assertEqual( + partner.email, new_email, "Partner's email must not be Multi-website" + ) diff --git a/mail_multi_website/views/website_views.xml b/mail_multi_website/views/website_views.xml new file mode 100644 index 0000000..c95bf42 --- /dev/null +++ b/mail_multi_website/views/website_views.xml @@ -0,0 +1,14 @@ + + + + + website + + + + + + + + diff --git a/mail_multi_website/wizard/__init__.py b/mail_multi_website/wizard/__init__.py new file mode 100644 index 0000000..e714a36 --- /dev/null +++ b/mail_multi_website/wizard/__init__.py @@ -0,0 +1,2 @@ +# License MIT (https://opensource.org/licenses/MIT). +from . import mail_compose_message diff --git a/mail_multi_website/wizard/mail_compose_message.py b/mail_multi_website/wizard/mail_compose_message.py new file mode 100644 index 0000000..cb1d42d --- /dev/null +++ b/mail_multi_website/wizard/mail_compose_message.py @@ -0,0 +1,22 @@ +# Copyright 2018 Ivan Yelizariev +# License MIT (https://opensource.org/licenses/MIT). +from odoo import api, models +from odoo.http import request + + +class MailComposer(models.TransientModel): + + _inherit = "mail.compose.message" + + @api.model + def create(self, vals): + """Workaround for https://github.com/odoo/odoo/pull/26589""" + if "website_id" not in self.env.context: + website = ( + request and hasattr(request, "website") and request.website or None + ) + if not website: + website = self.env["website"].get_current_website() + if website: + self = self.with_context(website_id=website.id) + return super(MailComposer, self).create(vals)