diff --git a/mass_mailing_list_dynamic/README.rst b/mass_mailing_list_dynamic/README.rst index 31207bb6..2fd418e9 100644 --- a/mass_mailing_list_dynamic/README.rst +++ b/mass_mailing_list_dynamic/README.rst @@ -18,7 +18,7 @@ Configuration To create a dynamic mailing list, you need to: -#. Go to *Mass Mailing > Mailings > Mailing Lists > Create*. +#. Go to *Mass Mailing > Mailings > Mailing Lists* and create one. #. Check the *Dynamic* box. #. Choose a *Sync method*: - Leave empty to use as a manual mailing list, the normal behavior. @@ -27,10 +27,26 @@ To create a dynamic mailing list, you need to: - *Add and remove records as needed* to make the list be fully synchronized with the *Synchronization critera*, even if that means removing contacts from it. -#. Define a *Synchronization critera* that will be used to match the partners +#. Define a *Synchronization criteria* that will be used to match the partners that should go into the list as contacts. Only partners with emails will be selected. +You can also load an existing filter over contacts: + +#. Click on "Load filter" button below criteria. +#. Select one of the existing filters. +#. Click on "Load filter" and you will have that filter as criteria. + +Usage +===== + +#. Go to *Mass Mailing > Mailings > Mass Mailings*, and create one. +#. Select as recipients a mailing list. +#. On "Select mailing lists:", choose one mailing list with dynamic flag + checked. +#. Before sending the mass mailing, the list will be synced for having latest + changes. + Usage ===== @@ -65,13 +81,15 @@ Credits Images ------ -* Odoo Community Association: `Icon `_. +* Odoo. +* FontAwesome (http://fontawesome.io). Contributors ------------ * `Tecnativa `_: * Jairo Llopis + * Pedro M. Baeza Do not contact contributors directly about support or help with technical issues. diff --git a/mass_mailing_list_dynamic/__init__.py b/mass_mailing_list_dynamic/__init__.py index b44d7659..2966b917 100644 --- a/mass_mailing_list_dynamic/__init__.py +++ b/mass_mailing_list_dynamic/__init__.py @@ -2,3 +2,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import models +from . import wizards diff --git a/mass_mailing_list_dynamic/__manifest__.py b/mass_mailing_list_dynamic/__manifest__.py index 86f405d2..6f4cd76e 100644 --- a/mass_mailing_list_dynamic/__manifest__.py +++ b/mass_mailing_list_dynamic/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Dynamic Mass Mailing Lists", "summary": "Mass mailing lists that get autopopulated", - "version": "10.0.1.0.0", + "version": "10.0.1.1.0", "category": "Marketing", "website": "https://github.com/OCA/social", "author": "Tecnativa, Odoo Community Association (OCA)", @@ -15,6 +15,8 @@ "mass_mailing_partner", ], "data": [ + # This should go first + "wizards/mail_mass_mailing_load_filter_views.xml", "views/mail_mass_mailing_list_view.xml", ], } diff --git a/mass_mailing_list_dynamic/i18n/es.po b/mass_mailing_list_dynamic/i18n/es.po index 900168cb..87182c73 100644 --- a/mass_mailing_list_dynamic/i18n/es.po +++ b/mass_mailing_list_dynamic/i18n/es.po @@ -1,21 +1,23 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * mass_mailing_list_dynamic +# * mass_mailing_list_dynamic # +# Translators: +# OCA Transbot , 2017 +# enjolras , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-11-03 10:58+0000\n" -"PO-Revision-Date: 2017-11-03 12:00+0100\n" -"Last-Translator: Jairo Llopis \n" -"Language-Team: \n" -"Language: es\n" +"POT-Creation-Date: 2018-02-26 01:46+0000\n" +"PO-Revision-Date: 2018-02-26 01:46+0000\n" +"Last-Translator: enjolras , 2018\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: \n" -"X-Generator: Poedit 2.0.3\n" +"Content-Transfer-Encoding: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: mass_mailing_list_dynamic #: model:ir.ui.view,arch_db:mass_mailing_list_dynamic.view_mail_mass_mailing_list_form @@ -23,40 +25,53 @@ msgid "" " If you want to remove contacts from a " "dynamic list, just set them as Opt Out." msgstr "" -" Si quiere eliminar contactos de una lista " -"dinámica, simplemente márquelos como Envío no deseado." #. module: mass_mailing_list_dynamic #: model:ir.ui.view,arch_db:mass_mailing_list_dynamic.view_mail_mass_mailing_list_form msgid "" -" You cannot make manual editions of contacts " -"in fully synchronized lists." +" You cannot make manual editions of contacts" +" in fully synchronized lists." msgstr "" -" No puede editar manualmente los contactos " -"en las listas que están completamente sincronizadas." #. module: mass_mailing_list_dynamic #: selection:mail.mass_mailing.list,sync_method:0 msgid "Add and remove records as needed" -msgstr "Añade y elimina los registros conforme se necesite" +msgstr "" + +#. module: mass_mailing_list_dynamic +#: model:ir.ui.view,arch_db:mass_mailing_list_dynamic.view_mail_mass_mailing_load_filter_form +msgid "Cancel" +msgstr "Cancelar" #. module: mass_mailing_list_dynamic -#: code:addons/mass_mailing_list_dynamic/models/mail_mass_mailing_contact.py:17 +#: code:addons/mass_mailing_list_dynamic/models/mail_mass_mailing_contact.py:18 #, python-format msgid "" "Cannot edit manually contacts in a fully synchronized list. Change its sync " "method or execute a manual sync instead." msgstr "" -"No se pueden editar manualmente los contactos de una lista que está " -"completamente sincronizada. En vez de eso, cambie su método de " -"sincronización o ejecute una sincronización manual." #. module: mass_mailing_list_dynamic #: model:ir.model.fields,help:mass_mailing_list_dynamic.field_mail_mass_mailing_list_sync_method msgid "" -"Choose the syncronization method for this list if you want to make it dynamic" +"Choose the syncronization method for this list if you want to make it " +"dynamic" msgstr "" -"Escoja el método de sincronización para esta lista si quiere hacerla dinámica" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_create_date +msgid "Created on" +msgstr "Creado el" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_display_name +msgid "Display Name" +msgstr "Nombre mostrado" #. module: mass_mailing_list_dynamic #: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_list_dynamic @@ -71,7 +86,39 @@ msgstr "Lista dinámica" #. module: mass_mailing_list_dynamic #: model:ir.model.fields,help:mass_mailing_list_dynamic.field_mail_mass_mailing_list_sync_domain msgid "Filter partners to sync in this list" -msgstr "Filtrar contactos a sincronizar en esta lista" +msgstr "" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_filter_id +msgid "Filter to load" +msgstr "" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_id +msgid "ID" +msgstr "ID" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter___last_update +msgid "Last Modified on" +msgstr "Última modificación e" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_load_filter_write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: mass_mailing_list_dynamic +#: model:ir.actions.act_window,name:mass_mailing_list_dynamic.action_mail_mass_mailing_load_filter +#: model:ir.ui.view,arch_db:mass_mailing_list_dynamic.view_mail_mass_mailing_list_form +#: model:ir.ui.view,arch_db:mass_mailing_list_dynamic.view_mail_mass_mailing_load_filter_form +msgid "Load filter" +msgstr "Cargar filtro" #. module: mass_mailing_list_dynamic #: model:ir.model,name:mass_mailing_list_dynamic.model_mail_mass_mailing_list @@ -81,17 +128,29 @@ msgstr "Lista de correo" #. module: mass_mailing_list_dynamic #: model:ir.model,name:mass_mailing_list_dynamic.model_mail_mass_mailing msgid "Mass Mailing" -msgstr "Correo masivo" +msgstr "Envío masivo" #. module: mass_mailing_list_dynamic #: model:ir.model,name:mass_mailing_list_dynamic.model_mail_mass_mailing_contact msgid "Mass Mailing Contact" -msgstr "Contacto de correo masivo" +msgstr "Contacto de envío masivo" #. module: mass_mailing_list_dynamic #: selection:mail.mass_mailing.list,sync_method:0 msgid "Only add new records" -msgstr "Solamente añadir nuevos registros" +msgstr "Añadir solo registros nuevos" + +#. module: mass_mailing_list_dynamic +#: model:ir.model,name:mass_mailing_list_dynamic.model_res_partner +msgid "Partner" +msgstr "Empresa" + +#. module: mass_mailing_list_dynamic +#: model:ir.model.fields,help:mass_mailing_list_dynamic.field_mail_mass_mailing_list_dynamic +msgid "" +"Set this list as dynamic, to make it autosynchronized with partners from a " +"given criteria." +msgstr "" #. module: mass_mailing_list_dynamic #: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_list_sync_method @@ -106,14 +165,9 @@ msgstr "Sincronizar ahora" #. module: mass_mailing_list_dynamic #: model:ir.model.fields,field_description:mass_mailing_list_dynamic.field_mail_mass_mailing_list_sync_domain msgid "Synchronization critera" -msgstr "Criterios de sincronización" +msgstr "Criterio de sincronización" -#~ msgid "" -#~ " If you manually add contacts to " -#~ "the list, they might get removed later automatically!" -#~ msgstr "" -#~ " Si añade contactos a la lista " -#~ "manualmente, ¡podrían ser eliminados automáticamente más tarde!" - -#~ msgid "Sync domain" -#~ msgstr "Dominio de sincronización" +#. module: mass_mailing_list_dynamic +#: model:ir.model,name:mass_mailing_list_dynamic.model_mail_mass_mailing_load_filter +msgid "mail.mass_mailing.load.filter" +msgstr "mail.mass_mailing.load.filter" diff --git a/mass_mailing_list_dynamic/models/__init__.py b/mass_mailing_list_dynamic/models/__init__.py index 3735786a..2e3ac3d4 100644 --- a/mass_mailing_list_dynamic/models/__init__.py +++ b/mass_mailing_list_dynamic/models/__init__.py @@ -4,3 +4,4 @@ from . import mail_mass_mailing from . import mail_mass_mailing_contact from . import mail_mass_mailing_list +from . import res_partner diff --git a/mass_mailing_list_dynamic/models/mail_mass_mailing_list.py b/mass_mailing_list_dynamic/models/mail_mass_mailing_list.py index 8cd7ea6a..93a4fe67 100644 --- a/mass_mailing_list_dynamic/models/mail_mass_mailing_list.py +++ b/mass_mailing_list_dynamic/models/mail_mass_mailing_list.py @@ -32,10 +32,12 @@ class MassMailingList(models.Model): def action_sync(self): """Sync contacts in dynamic lists.""" - Contact = self.env["mail.mass_mailing.contact"] + Contact = self.env["mail.mass_mailing.contact"].with_context( + syncing=True, + ) Partner = self.env["res.partner"] # Skip non-dynamic lists - dynamic = self.filtered("dynamic").with_context(syncing=True) + dynamic = self.filtered("dynamic") for one in dynamic: sync_domain = safe_eval(one.sync_domain) + [("email", "!=", False)] desired_partners = Partner.search(sync_domain) diff --git a/mass_mailing_list_dynamic/models/res_partner.py b/mass_mailing_list_dynamic/models/res_partner.py new file mode 100644 index 00000000..465f5304 --- /dev/null +++ b/mass_mailing_list_dynamic/models/res_partner.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + def write(self, vals): + """Allow to write values in mass mailing contact.""" + return super(ResPartner, self.with_context(syncing=True)).write(vals) diff --git a/mass_mailing_list_dynamic/static/description/icon.png b/mass_mailing_list_dynamic/static/description/icon.png index 3a0328b5..f6e9e06d 100644 Binary files a/mass_mailing_list_dynamic/static/description/icon.png and b/mass_mailing_list_dynamic/static/description/icon.png differ diff --git a/mass_mailing_list_dynamic/static/description/icon.svg b/mass_mailing_list_dynamic/static/description/icon.svg new file mode 100644 index 00000000..c2ca5951 --- /dev/null +++ b/mass_mailing_list_dynamic/static/description/icon.svg @@ -0,0 +1,94 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/mass_mailing_list_dynamic/tests/test_dynamic_lists.py b/mass_mailing_list_dynamic/tests/test_dynamic_lists.py index 14799de6..1f49c967 100644 --- a/mass_mailing_list_dynamic/tests/test_dynamic_lists.py +++ b/mass_mailing_list_dynamic/tests/test_dynamic_lists.py @@ -4,32 +4,35 @@ from mock import patch from odoo.exceptions import ValidationError -from odoo.tests.common import TransactionCase +from odoo.tests import common -class DynamicListCase(TransactionCase): - def setUp(self): - super(DynamicListCase, self).setUp() - self.tag = self.env["res.partner.category"].create({ +@common.at_install(False) +@common.post_install(True) +class DynamicListCase(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(DynamicListCase, cls).setUpClass() + cls.tag = cls.env["res.partner.category"].create({ "name": "testing tag", }) - self.partners = self.env["res.partner"] + cls.partners = cls.env["res.partner"] for number in range(5): - self.partners |= self.partners.create({ + cls.partners |= cls.partners.create({ "name": "partner %d" % number, - "category_id": [(4, self.tag.id, False)], + "category_id": [(4, cls.tag.id, False)], "email": "%d@example.com" % number, }) - self.list = self.env["mail.mass_mailing.list"].create({ + cls.list = cls.env["mail.mass_mailing.list"].create({ "name": "test list", "dynamic": True, - "sync_domain": repr([("category_id", "in", self.tag.ids)]), + "sync_domain": repr([("category_id", "in", cls.tag.ids)]), }) - self.mail = self.env["mail.mass_mailing"].create({ + cls.mail = cls.env["mail.mass_mailing"].create({ "name": "test mass mailing", - "contact_list_ids": [(4, self.list.id, False)], + "contact_list_ids": [(4, cls.list.id, False)], }) - self.mail._onchange_model_and_list() + cls.mail._onchange_model_and_list() def test_list_sync(self): """List is synced correctly.""" @@ -53,6 +56,10 @@ class DynamicListCase(TransactionCase): self.assertTrue(contact0.exists()) # Set list as full-synced self.list.sync_method = "full" + Contact.search([ + ("list_id", "=", self.list.id), + ("partner_id", "=", self.partners[2].id), + ]).unlink() self.list.action_sync() self.assertEqual(self.list.contact_nbr, 3) self.assertFalse(contact0.exists()) @@ -73,7 +80,7 @@ class DynamicListCase(TransactionCase): contact1.partner_id = self.partners[0] def test_sync_when_sending_mail(self): - """Dynamic list is synced before mailing to it.""" + """Check that list in synced when sending a mass mailing.""" self.list.action_sync() self.assertEqual(self.list.contact_nbr, 5) # Create a new partner @@ -82,9 +89,31 @@ class DynamicListCase(TransactionCase): "category_id": [(4, self.tag.id, False)], "email": "extra@example.com", }) - # Before sending the mail, the list is updated - with patch("odoo.addons.base.ir.ir_mail_server" - ".IrMailServer.send_email") as send_email: + # Mock sending low level method, because an auto-commit happens there + with patch("odoo.addons.mail.models.mail_mail.MailMail.send") as s: self.mail.send_mail() - self.assertEqual(6, send_email.call_count) + self.assertEqual(1, s.call_count) self.assertEqual(6, self.list.contact_nbr) + + def test_load_filter(self): + domain = "[('id', '=', 1)]" + ir_filter = self.env['ir.filters'].create({ + 'name': 'Test filter', + 'model_id': 'res.partner', + 'domain': domain, + }) + wizard = self.env['mail.mass_mailing.load.filter'].with_context( + active_id=self.list.id, + ).create({ + 'filter_id': ir_filter.id, + }) + wizard.load_filter() + self.assertEqual(self.list.sync_domain, domain) + + def test_change_partner(self): + self.list.sync_method = 'full' + self.list.action_sync() + # This shouldn't fail + self.partners[:1].write({ + 'email': 'test_mass_mailing_list_dynamic@example.org', + }) diff --git a/mass_mailing_list_dynamic/views/mail_mass_mailing_list_view.xml b/mass_mailing_list_dynamic/views/mail_mass_mailing_list_view.xml index 9c73eed3..10046ef4 100644 --- a/mass_mailing_list_dynamic/views/mail_mass_mailing_list_view.xml +++ b/mass_mailing_list_dynamic/views/mail_mass_mailing_list_view.xml @@ -52,6 +52,13 @@ options='{"model": "res.partner"}' /> +