diff --git a/mail_tracking/README.rst b/mail_tracking/README.rst new file mode 100644 index 00000000..40ba269c --- /dev/null +++ b/mail_tracking/README.rst @@ -0,0 +1,112 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============= +Mail tracking +============= + +This module shows email notification tracking status for any messages in +mail thread (chatter). Each notified partner will have an intuitive icon just +right to his name. + + +Installation +============ + +If you're using a multi-database installation (with or without dbfilter option) +where /web/databse/selector returns a list of more than one database, then +you need to add ``mail_tracking`` addon to wide load addons list +(by default, only ``web`` addon), setting ``--load`` option. +For example, ``--load=web,mail_tracking`` + + +Usage +===== + +When user sends a message in mail_thread (chatter), for instance in partner +form, then an email tracking is created for each email notification. Then a +status icon will appear just right to name of notified partner. + +These are all available status icons: + +.. |sent| image:: mail_tracking/static/src/img/sent.png + :width: 10px + +.. |delivered| image:: mail_tracking/static/src/img/delivered.png + :width: 15px + +.. |opened| image:: mail_tracking/static/src/img/opened.png + :width: 15px + +.. |error| image:: mail_tracking/static/src/img/error.png + :width: 10px + +.. |waiting| image:: mail_tracking/static/src/img/waiting.png + :width: 10px + +.. |unknown| image:: mail_tracking/static/src/img/unknown.png + :width: 10px + +|unknown| **Unknown**: No email tracking info available. Maybe this notified partner has 'Receive Inbox Notifications by Email' == 'Never' + +|waiting| **Waiting**: Waiting to be sent + +|error| **Error**: Error while sending + +|sent| **Sent**: Sent to SMTP server configured + +|delivered| **Delivered**: Delivered to final MX server + +|opened| **Opened**: Opened by partner + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/205/9.0 + +If you want to see all tracking emails and events you can go to + +* Settings > Technical > Email > Tracking emails +* Settings > Technical > Email > Tracking events + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. +* Thanks to `LlubNek `_ and `Openclipart + `_ for `the icon + `_. + +Contributors +------------ + +* Pedro M. Baeza +* Antonio Espinosa + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/mail_tracking/__init__.py b/mail_tracking/__init__.py new file mode 100644 index 00000000..e32deff2 --- /dev/null +++ b/mail_tracking/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# flake8: noqa + +from . import models +from . import controllers +from .hooks import pre_init_hook diff --git a/mail_tracking/__openerp__.py b/mail_tracking/__openerp__.py new file mode 100644 index 00000000..accf9ce0 --- /dev/null +++ b/mail_tracking/__openerp__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Email tracking", + "summary": "Email tracking system for all mails sent", + "version": "9.0.1.0.0", + "category": "Social Network", + "website": "http://www.tecnativa.com", + "author": "Tecnativa, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "decimal_precision", + "mail", + ], + "data": [ + "data/tracking_data.xml", + "security/ir.model.access.csv", + "views/assets.xml", + "views/mail_tracking_email_view.xml", + "views/mail_tracking_event_view.xml", + "views/res_partner_view.xml", + ], + "qweb": [ + "static/src/xml/mail_tracking.xml", + ], + "pre_init_hook": "pre_init_hook", +} diff --git a/mail_tracking/controllers/__init__.py b/mail_tracking/controllers/__init__.py new file mode 100644 index 00000000..73e11057 --- /dev/null +++ b/mail_tracking/controllers/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# flake8: noqa + +from . import main diff --git a/mail_tracking/controllers/main.py b/mail_tracking/controllers/main.py new file mode 100644 index 00000000..da14949d --- /dev/null +++ b/mail_tracking/controllers/main.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import werkzeug +from psycopg2 import OperationalError +from openerp import api, http, registry, SUPERUSER_ID +import logging +_logger = logging.getLogger(__name__) + +BLANK = 'R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==' + + +def _env_get(db): + reg = False + try: + reg = registry(db) + except OperationalError: + _logger.warning("Selected BD '%s' not found", db) + except: # pragma: no cover + _logger.warning("Selected BD '%s' connection error", db) + if reg: + return api.Environment(reg.cursor(), SUPERUSER_ID, {}) + return False + + +class MailTrackingController(http.Controller): + + def _request_metadata(self): + request = http.request.httprequest + return { + 'ip': request.remote_addr or False, + 'user_agent': request.user_agent or False, + 'os_family': request.user_agent.platform or False, + 'ua_family': request.user_agent.browser or False, + } + + @http.route('/mail/tracking/all/', + type='http', auth='none', csrf=False) + def mail_tracking_all(self, db, **kw): + env = _env_get(db) + if not env: + return 'NOT FOUND' + metadata = self._request_metadata() + response = env['mail.tracking.email'].event_process( + http.request, kw, metadata) + env.cr.commit() + env.cr.close() + return response + + @http.route('/mail/tracking/event//', + type='http', auth='none', csrf=False) + def mail_tracking_event(self, db, event_type, **kw): + env = _env_get(db) + if not env: + return 'NOT FOUND' + metadata = self._request_metadata() + response = env['mail.tracking.email'].event_process( + http.request, kw, metadata, event_type=event_type) + env.cr.commit() + env.cr.close() + return response + + @http.route('/mail/tracking/open/' + '//blank.gif', + type='http', auth='none') + def mail_tracking_open(self, db, tracking_email_id, **kw): + env = _env_get(db) + if env: + tracking_email = env['mail.tracking.email'].search([ + ('id', '=', tracking_email_id), + ]) + if tracking_email: + metadata = self._request_metadata() + tracking_email.event_create('open', metadata) + else: + _logger.warning( + "MailTracking email '%s' not found", tracking_email_id) + env.cr.commit() + env.cr.close() + + # Always return GIF blank image + response = werkzeug.wrappers.Response() + response.mimetype = 'image/gif' + response.data = BLANK.decode('base64') + return response diff --git a/mail_tracking/data/tracking_data.xml b/mail_tracking/data/tracking_data.xml new file mode 100644 index 00000000..4bcb2cb8 --- /dev/null +++ b/mail_tracking/data/tracking_data.xml @@ -0,0 +1,13 @@ + + + + + + + MailTracking Timestamp + 6 + + + + diff --git a/mail_tracking/hooks.py b/mail_tracking/hooks.py new file mode 100644 index 00000000..8d3dc43d --- /dev/null +++ b/mail_tracking/hooks.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +from psycopg2.extensions import AsIs + +_logger = logging.getLogger(__name__) + + +def column_exists(cr, table, column): + cr.execute(""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = %s AND column_name = %s""", (table, column)) + return bool(cr.fetchall()) + + +def column_add_with_value(cr, table, column, field_type, value): + if not column_exists(cr, table, column): + cr.execute(""" + ALTER TABLE %s + ADD COLUMN %s %s""", (AsIs(table), AsIs(column), AsIs(field_type))) + cr.execute(""" + UPDATE %s SET %s = %s""", (AsIs(table), AsIs(column), value)) + + +def pre_init_hook(cr): + _logger.info("Creating res.partner.tracking_emails_count column " + "with value 0") + column_add_with_value( + cr, "res_partner", "tracking_emails_count", "integer", 0) + _logger.info("Creating res.partner.email_score column " + "with value 50.0") + column_add_with_value( + cr, "res_partner", "email_score", "double precision", 50.0) diff --git a/mail_tracking/i18n/de.po b/mail_tracking/i18n/de.po new file mode 100644 index 00000000..a8054c1b --- /dev/null +++ b/mail_tracking/i18n/de.po @@ -0,0 +1,448 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: OCA Transbot , 2016\n" +"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking +#: help:mail.tracking.email,state:0 +msgid "" +" * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Bounce" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_description:0 +msgid "Bounce description" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_type:0 +msgid "Bounce type" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Bounced" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Click" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Clicked" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,url:0 +msgid "Clicked URL" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "Country" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,create_uid:0 +#: field:mail.tracking.event,create_uid:0 +msgid "Created by" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,create_date:0 +#: field:mail.tracking.event,create_date:0 +msgid "Created on" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,date:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,date:0 +msgid "Date" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Deferral" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +msgid "Deferred" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Delivered" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,display_name:0 +msgid "Display Name" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,display_name:0 +msgid "Display name" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,mail_id:0 +msgid "Email" +msgstr "Email" + +#. module: mail_tracking +#: field:res.partner,email_score:0 +msgid "Email score" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "Error" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_smtp_server:0 +msgid "Error SMTP server" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_description:0 +#: field:mail.tracking.event,error_description:0 +msgid "Error description" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,error_details:0 +msgid "Error details" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_type:0 +#: field:mail.tracking.event,error_type:0 +msgid "Error type" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,event_type:0 +msgid "Event type" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Failed" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Group By" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Hard bounce" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,id:0 field:mail.tracking.event,id:0 +msgid "ID" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,mobile:0 +msgid "Is mobile?" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,__last_update:0 +#: field:mail.tracking.event,__last_update:0 +msgid "Last Modified on" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,write_uid:0 field:mail.tracking.event,write_uid:0 +msgid "Last Updated by" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,write_date:0 +#: field:mail.tracking.event,write_date:0 +msgid "Last Updated on" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_email +msgid "MailTracking email" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +msgid "MailTracking email search" +msgstr "" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "MailTracking emails" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "MailTracking event" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "MailTracking event search" +msgstr "" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "MailTracking events" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_message +#: field:mail.tracking.email,mail_message_id:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,tracking_email_id:0 +msgid "Message" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:30 +#, python-format +msgid "Message tracking" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Month" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Open" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,os_family:0 +msgid "Operating system family" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_mail +msgid "Outgoing Mails" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_res_partner +#: field:mail.tracking.email,partner_id:0 +msgid "Partner" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,recipient:0 +msgid "Recipient" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient:0 +msgid "Recipient email" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient_address:0 +msgid "Recipient email address" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Rejected" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,smtp_server:0 +msgid "SMTP server" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "Sender" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,sender:0 +msgid "Sender email" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Sent" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Soft bounce" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Soft bounced" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Spam" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,state:0 +msgid "State" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,name:0 +msgid "Subject" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,time:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,time:0 +msgid "Time" +msgstr "" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email +#: view:res.partner:mail_tracking.view_partner_form +#: field:res.partner,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "" + +#. module: mail_tracking +#: field:res.partner,tracking_emails_count:0 +msgid "Tracking emails count" +msgstr "" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: field:mail.tracking.email,tracking_event_ids:0 +msgid "Tracking events" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Type" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "URL" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,timestamp:0 field:mail.tracking.event,timestamp:0 +msgid "UTC timestamp" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Unsubscribe" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Unsubscribed" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,ip:0 +msgid "User IP" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +#: field:mail.tracking.event,user_agent:0 +msgid "User agent" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,ua_family:0 +msgid "User agent family" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,ua_type:0 +msgid "User agent type" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,user_country_id:0 +msgid "User country" +msgstr "" diff --git a/mail_tracking/i18n/es.po b/mail_tracking/i18n/es.po new file mode 100644 index 00000000..15248457 --- /dev/null +++ b/mail_tracking/i18n/es.po @@ -0,0 +1,440 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-08 00:45+0000\n" +"PO-Revision-Date: 2016-08-08 00:45+0000\n" +"Last-Translator: OCA Transbot , 2016\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: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: mail_tracking +#: help:mail.tracking.email,state:0 +msgid "" +" * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Bounce" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_description:0 +msgid "Bounce description" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_type:0 +msgid "Bounce type" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Bounced" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Click" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Clicked" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,url:0 +msgid "Clicked URL" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "Country" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,create_uid:0 +#: field:mail.tracking.event,create_uid:0 +msgid "Created by" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,create_date:0 +#: field:mail.tracking.event,create_date:0 +msgid "Created on" +msgstr "Creado en" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,date:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,date:0 +msgid "Date" +msgstr "Fecha" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Deferral" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +msgid "Deferred" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Delivered" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,display_name:0 +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: mail_tracking +#: field:mail.tracking.email,display_name:0 +msgid "Display name" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,mail_id:0 +msgid "Email" +msgstr "Correo electrónico" + +#. module: mail_tracking +#: field:res.partner,email_score:0 +msgid "Email score" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Error" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_smtp_server:0 +msgid "Error SMTP server" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_description:0 +msgid "Error description" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_type:0 +msgid "Error type" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,event_type:0 +msgid "Event type" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Failed" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Group By" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Hard bounce" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,id:0 field:mail.tracking.event,id:0 +msgid "ID" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,mobile:0 +msgid "Is mobile?" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,__last_update:0 +#: field:mail.tracking.event,__last_update:0 +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: mail_tracking +#: field:mail.tracking.email,write_uid:0 field:mail.tracking.event,write_uid:0 +msgid "Last Updated by" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,write_date:0 +#: field:mail.tracking.event,write_date:0 +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_email +msgid "MailTracking email" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +msgid "MailTracking email search" +msgstr "" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "MailTracking emails" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "MailTracking event" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "MailTracking event search" +msgstr "" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "MailTracking events" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_message +#: field:mail.tracking.email,mail_message_id:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,tracking_email_id:0 +msgid "Message" +msgstr "" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:30 +#, python-format +msgid "Message tracking" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Month" +msgstr "Mes" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Open" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,os_family:0 +msgid "Operating system family" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_mail +msgid "Outgoing Mails" +msgstr "" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_res_partner +#: field:mail.tracking.email,partner_id:0 +msgid "Partner" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,recipient:0 +msgid "Recipient" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient:0 +msgid "Recipient email" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient_address:0 +msgid "Recipient email address" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Rejected" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,smtp_server:0 +msgid "SMTP server" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "Sender" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,sender:0 +msgid "Sender email" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Sent" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Soft bounce" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Soft bounced" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Spam" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,state:0 +msgid "State" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,name:0 +msgid "Subject" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,time:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,time:0 +msgid "Time" +msgstr "" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email +#: view:res.partner:mail_tracking.view_partner_form +#: field:res.partner,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "" + +#. module: mail_tracking +#: field:res.partner,tracking_emails_count:0 +msgid "Tracking emails count" +msgstr "" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: field:mail.tracking.email,tracking_event_ids:0 +msgid "Tracking events" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Type" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "URL" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,timestamp:0 field:mail.tracking.event,timestamp:0 +msgid "UTC timestamp" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Unsubscribe" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Unsubscribed" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,ip:0 +msgid "User IP" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +#: field:mail.tracking.event,user_agent:0 +msgid "User agent" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,ua_family:0 +msgid "User agent family" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,ua_type:0 +msgid "User agent type" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.event,user_country_id:0 +msgid "User country" +msgstr "" diff --git a/mail_tracking/i18n/fr.po b/mail_tracking/i18n/fr.po new file mode 100644 index 00000000..ceb2230f --- /dev/null +++ b/mail_tracking/i18n/fr.po @@ -0,0 +1,448 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking +# +# Translators: +# OCA Transbot , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: OCA Transbot , 2016\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: mail_tracking +#: help:mail.tracking.email,state:0 +msgid "" +" * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Bounce" +msgstr "Erreurs de remise" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_description:0 +msgid "Bounce description" +msgstr "Descriptif du rebond" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_type:0 +msgid "Bounce type" +msgstr "Type de rebond" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Bounced" +msgstr "Rebonds" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Click" +msgstr "Cliquer" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Clicked" +msgstr "Cliqué" + +#. module: mail_tracking +#: field:mail.tracking.event,url:0 +msgid "Clicked URL" +msgstr "URL cliquée" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "Country" +msgstr "Pays" + +#. module: mail_tracking +#: field:mail.tracking.email,create_uid:0 +#: field:mail.tracking.event,create_uid:0 +msgid "Created by" +msgstr "Créée par" + +#. module: mail_tracking +#: field:mail.tracking.email,create_date:0 +#: field:mail.tracking.event,create_date:0 +msgid "Created on" +msgstr "Créée le" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,date:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,date:0 +msgid "Date" +msgstr "Date" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Deferral" +msgstr "Déférer" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +msgid "Deferred" +msgstr "" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Delivered" +msgstr "Livré" + +#. module: mail_tracking +#: field:mail.tracking.event,display_name:0 +msgid "Display Name" +msgstr "Nom à afficher" + +#. module: mail_tracking +#: field:mail.tracking.email,display_name:0 +msgid "Display name" +msgstr "Nom affiché" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,mail_id:0 +msgid "Email" +msgstr "Courriel" + +#. module: mail_tracking +#: field:res.partner,email_score:0 +msgid "Email score" +msgstr "Score du courriel" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "Error" +msgstr "Erreur" + +#. module: mail_tracking +#: field:mail.tracking.email,error_smtp_server:0 +msgid "Error SMTP server" +msgstr "Erreur du serveur SMTP" + +#. module: mail_tracking +#: field:mail.tracking.email,error_description:0 +#: field:mail.tracking.event,error_description:0 +msgid "Error description" +msgstr "Desciptif de l'erreur" + +#. module: mail_tracking +#: field:mail.tracking.event,error_details:0 +msgid "Error details" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,error_type:0 +#: field:mail.tracking.event,error_type:0 +msgid "Error type" +msgstr "Type d'erreur" + +#. module: mail_tracking +#: field:mail.tracking.event,event_type:0 +msgid "Event type" +msgstr "Type d'évènement" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Failed" +msgstr "Échec" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Group By" +msgstr "Regrouper par" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Hard bounce" +msgstr "" + +#. module: mail_tracking +#: field:mail.tracking.email,id:0 field:mail.tracking.event,id:0 +msgid "ID" +msgstr "ID" + +#. module: mail_tracking +#: field:mail.tracking.event,mobile:0 +msgid "Is mobile?" +msgstr "Est mobile?" + +#. module: mail_tracking +#: field:mail.tracking.email,__last_update:0 +#: field:mail.tracking.event,__last_update:0 +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: mail_tracking +#: field:mail.tracking.email,write_uid:0 field:mail.tracking.event,write_uid:0 +msgid "Last Updated by" +msgstr "Dernière modification par" + +#. module: mail_tracking +#: field:mail.tracking.email,write_date:0 +#: field:mail.tracking.event,write_date:0 +msgid "Last Updated on" +msgstr "Modifié le" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_email +msgid "MailTracking email" +msgstr "Suivi du courriel: courriel" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +msgid "MailTracking email search" +msgstr "Suivi du courriel: recherche du courriel" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "MailTracking emails" +msgstr "Suivi du courriel: courriels" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "MailTracking event" +msgstr "Suivi du courriel: évènement" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "MailTracking event search" +msgstr "Suivi du courriel: recherche d'un évènement" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "MailTracking events" +msgstr "Suivi du courriel: évènements" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_message +#: field:mail.tracking.email,mail_message_id:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,tracking_email_id:0 +msgid "Message" +msgstr "Message" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:30 +#, python-format +msgid "Message tracking" +msgstr "Destinataire du courriel" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Month" +msgstr "Mois" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Open" +msgstr "Ouverte" + +#. module: mail_tracking +#: field:mail.tracking.event,os_family:0 +msgid "Operating system family" +msgstr "Famille du système d'exploitation" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_mail +msgid "Outgoing Mails" +msgstr "Courriels sortants" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_res_partner +#: field:mail.tracking.email,partner_id:0 +msgid "Partner" +msgstr "Partenaire" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,recipient:0 +msgid "Recipient" +msgstr "Destinataire" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient:0 +msgid "Recipient email" +msgstr "Courriel du destinataire " + +#. module: mail_tracking +#: field:mail.tracking.email,recipient_address:0 +msgid "Recipient email address" +msgstr "Adresse de courriel du destinataire" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Rejected" +msgstr "Refusé" + +#. module: mail_tracking +#: field:mail.tracking.event,smtp_server:0 +msgid "SMTP server" +msgstr "Serveur SMTP" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "Sender" +msgstr "Émetteur" + +#. module: mail_tracking +#: field:mail.tracking.email,sender:0 +msgid "Sender email" +msgstr "Courriel de l'expéditeur" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Sent" +msgstr "Envoyé" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Soft bounce" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Soft bounced" +msgstr "" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Spam" +msgstr "Pourriel" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,state:0 +msgid "State" +msgstr "État" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,name:0 +msgid "Subject" +msgstr "Objet" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,time:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,time:0 +msgid "Time" +msgstr "Heure" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email +#: view:res.partner:mail_tracking.view_partner_form +#: field:res.partner,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "Suivi du courriel: courriels" + +#. module: mail_tracking +#: field:res.partner,tracking_emails_count:0 +msgid "Tracking emails count" +msgstr "Suivi du courriel: nombre de courriels" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: field:mail.tracking.email,tracking_event_ids:0 +msgid "Tracking events" +msgstr "Suivi du courriel: évènements" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Type" +msgstr "Type" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "URL" +msgstr "URL" + +#. module: mail_tracking +#: field:mail.tracking.email,timestamp:0 field:mail.tracking.event,timestamp:0 +msgid "UTC timestamp" +msgstr "Horodatage en UTC" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Unsubscribe" +msgstr "Se désabonner" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Unsubscribed" +msgstr "Désincrit" + +#. module: mail_tracking +#: field:mail.tracking.event,ip:0 +msgid "User IP" +msgstr "IP de l'utilisateur" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +#: field:mail.tracking.event,user_agent:0 +msgid "User agent" +msgstr "Navigateur" + +#. module: mail_tracking +#: field:mail.tracking.event,ua_family:0 +msgid "User agent family" +msgstr "Famille du navigateur" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,ua_type:0 +msgid "User agent type" +msgstr "type de navigateur" + +#. module: mail_tracking +#: field:mail.tracking.event,user_country_id:0 +msgid "User country" +msgstr "Pays de l'utilisateur" diff --git a/mail_tracking/i18n/sl.po b/mail_tracking/i18n/sl.po new file mode 100644 index 00000000..93a16958 --- /dev/null +++ b/mail_tracking/i18n/sl.po @@ -0,0 +1,458 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mail_tracking +# +# Translators: +# OCA Transbot , 2016 +# Matjaž Mozetič , 2016 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-02 02:43+0000\n" +"PO-Revision-Date: 2016-09-02 02:43+0000\n" +"Last-Translator: Matjaž Mozetič , 2016\n" +"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: mail_tracking +#: help:mail.tracking.email,state:0 +msgid "" +" * The 'Error' status indicates that there was an error when trying to sent the email, for example, 'No valid recipient'\n" +" * The 'Sent' status indicates that message was succesfully sent via outgoing email server (SMTP).\n" +" * The 'Delivered' status indicates that message was succesfully delivered to recipient Mail Exchange (MX) server.\n" +" * The 'Open' status indicates that message was opened or clicked by recipient.\n" +" * The 'Rejected' status indicates that recipient email address is blacklisted by outgoing email server (SMTP). It is recomended to delete this email address.\n" +" * The 'Spam' status indicates that outgoing email server (SMTP) consider this message as spam.\n" +" * The 'Unsubscribed' status indicates that recipient has requested to be unsubscribed from this message.\n" +" * The 'Bounced' status indicates that message was bounced by recipient Mail Exchange (MX) server.\n" +" * The 'Soft bounced' status indicates that message was soft bounced by recipient Mail Exchange (MX) server.\n" +msgstr "" +" * Stanje 'Napaka' pomeni, da je prišlo do napake pri pošiljanju e-pošte, npr. 'Ni veljavnih prejemnikov'\n" +" * Stanje 'Poslano' pomeni, da je bilo sporočilo uspešno poslano preko izhodnega e-poštnega strežnika (SMTP).\n" +" * Stanje 'Dostavljeno' pomeni, da je bilo sporočilo uspešno dostavljeno na prejemnikov Mail Exchange (MX) strežnik.\n" +" * Stanje 'Odprto' pomeni, da je prejemnik sporočilo odprl ali kliknil.\n" +" * Stanje 'Zavrnjeno' pomeni, da je prejemnikov e-poštni naslov na črni listi odhodnega e-poštnega strežnika (SMTP). Priporočeno je, da se ta e-poštni naslov izbriše.\n" +" * Stanje 'Neželjeno' pomeni, da izhodni strežnik (SMTP) obravnava to sporočilo kot neželjeno pošto.\n" +" * Stanje 'Izpisan' pomeni, da je prejemnik zahteval izpis iz seznama prejemnikov tega sporočila.\n" +" * Stanje 'Odbito' pomeni, da je bilo sporočilo odbito s strani prejemnikovega Mail Exchange (MX) strežnika.\n" +" * Stanje 'Mehko odbito' pomeni, da je bilo sporočilo 'mehko' odbito s strani prejemnikovega Mail Exchange (MX) strežnika.\n" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Bounce" +msgstr "Odboj" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_description:0 +msgid "Bounce description" +msgstr "Opis odboja" + +#. module: mail_tracking +#: field:mail.tracking.email,bounce_type:0 +msgid "Bounce type" +msgstr "Tip odboja" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Bounced" +msgstr "Odbito" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Click" +msgstr "Kilk" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Clicked" +msgstr "Kliknjeno" + +#. module: mail_tracking +#: field:mail.tracking.event,url:0 +msgid "Clicked URL" +msgstr "Kliknjena URL" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "Country" +msgstr "Država" + +#. module: mail_tracking +#: field:mail.tracking.email,create_uid:0 +#: field:mail.tracking.event,create_uid:0 +msgid "Created by" +msgstr "Ustvaril" + +#. module: mail_tracking +#: field:mail.tracking.email,create_date:0 +#: field:mail.tracking.event,create_date:0 +msgid "Created on" +msgstr "Ustvarjeno" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,date:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,date:0 +msgid "Date" +msgstr "Datum" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Deferral" +msgstr "Odložitev" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +msgid "Deferred" +msgstr "Odloženo" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Delivered" +msgstr "Dostavljeno" + +#. module: mail_tracking +#: field:mail.tracking.event,display_name:0 +msgid "Display Name" +msgstr "Prikazni naziv" + +#. module: mail_tracking +#: field:mail.tracking.email,display_name:0 +msgid "Display name" +msgstr "Prikazni naziv" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,mail_id:0 +msgid "Email" +msgstr "E-pošta" + +#. module: mail_tracking +#: field:res.partner,email_score:0 +msgid "Email score" +msgstr "Točkovanje e-pošte" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "Error" +msgstr "Napaka" + +#. module: mail_tracking +#: field:mail.tracking.email,error_smtp_server:0 +msgid "Error SMTP server" +msgstr "Napaka SMTP strežnika" + +#. module: mail_tracking +#: field:mail.tracking.email,error_description:0 +#: field:mail.tracking.event,error_description:0 +msgid "Error description" +msgstr "Opis napake" + +#. module: mail_tracking +#: field:mail.tracking.event,error_details:0 +msgid "Error details" +msgstr "Podrobnosti o napaki" + +#. module: mail_tracking +#: field:mail.tracking.email,error_type:0 +#: field:mail.tracking.event,error_type:0 +msgid "Error type" +msgstr "Tip napake" + +#. module: mail_tracking +#: field:mail.tracking.event,event_type:0 +msgid "Event type" +msgstr "Tip dogodka" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Failed" +msgstr "Neuspelo" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Group By" +msgstr "Združi po" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Hard bounce" +msgstr "Trdo odbito" + +#. module: mail_tracking +#: field:mail.tracking.email,id:0 field:mail.tracking.event,id:0 +msgid "ID" +msgstr "ID" + +#. module: mail_tracking +#: field:mail.tracking.event,mobile:0 +msgid "Is mobile?" +msgstr "Mobilno?" + +#. module: mail_tracking +#: field:mail.tracking.email,__last_update:0 +#: field:mail.tracking.event,__last_update:0 +msgid "Last Modified on" +msgstr "Zadnjič spremenjeno" + +#. module: mail_tracking +#: field:mail.tracking.email,write_uid:0 field:mail.tracking.event,write_uid:0 +msgid "Last Updated by" +msgstr "Zadnji posodobil" + +#. module: mail_tracking +#: field:mail.tracking.email,write_date:0 +#: field:mail.tracking.event,write_date:0 +msgid "Last Updated on" +msgstr "Zadnjič posodobljeno" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_email +msgid "MailTracking email" +msgstr "Sledenje e-pošte" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +msgid "MailTracking email search" +msgstr "Iskalnik sledenja e-pošte" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_email +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "MailTracking emails" +msgstr "Sporočila sledenja" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_form +msgid "MailTracking event" +msgstr "Dogodek sledenja e-pošte" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "MailTracking event search" +msgstr "Iskalnik dogodkov sledenja e-pošte" + +#. module: mail_tracking +#: model:ir.actions.act_window,name:mail_tracking.action_view_mail_tracking_event +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +msgid "MailTracking events" +msgstr "Dogodki sledenja e-pošte" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_message +#: field:mail.tracking.email,mail_message_id:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,tracking_email_id:0 +msgid "Message" +msgstr "Sporočilo" + +#. module: mail_tracking +#. openerp-web +#: code:addons/mail_tracking/static/src/js/mail_tracking.js:30 +#, python-format +msgid "Message tracking" +msgstr "Sledenje sporočila" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Month" +msgstr "Mesec" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Open" +msgstr "Odprto" + +#. module: mail_tracking +#: field:mail.tracking.event,os_family:0 +msgid "Operating system family" +msgstr "Družina operacijskega sistema" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_mail_mail +msgid "Outgoing Mails" +msgstr "Izhodna pošta" + +#. module: mail_tracking +#: model:ir.model,name:mail_tracking.model_res_partner +#: field:mail.tracking.email,partner_id:0 +msgid "Partner" +msgstr "Partner" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,recipient:0 +msgid "Recipient" +msgstr "Prejemnik" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient:0 +msgid "Recipient email" +msgstr "Prejemnikova e-pošta" + +#. module: mail_tracking +#: field:mail.tracking.email,recipient_address:0 +msgid "Recipient email address" +msgstr "Prejemnikova e-pošta" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Rejected" +msgstr "Zavrnjeno" + +#. module: mail_tracking +#: field:mail.tracking.event,smtp_server:0 +msgid "SMTP server" +msgstr "SMTP strežnik" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_tree +msgid "Sender" +msgstr "Pošiljatelj" + +#. module: mail_tracking +#: field:mail.tracking.email,sender:0 +msgid "Sender email" +msgstr "Pošiljateljeva e-pošta" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: selection:mail.tracking.event,event_type:0 +msgid "Sent" +msgstr "Poslano" + +#. module: mail_tracking +#: selection:mail.tracking.event,event_type:0 +msgid "Soft bounce" +msgstr "Mehki odboj" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +msgid "Soft bounced" +msgstr "Mehko odbito" + +#. module: mail_tracking +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Spam" +msgstr "Spam" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,state:0 +msgid "State" +msgstr "Stanje" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,name:0 +msgid "Subject" +msgstr "Zadeva" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: field:mail.tracking.email,time:0 +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,time:0 +msgid "Time" +msgstr "Čas" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_email +#: view:res.partner:mail_tracking.view_partner_form +#: field:res.partner,tracking_email_ids:0 +msgid "Tracking emails" +msgstr "Sledilna e-pošta" + +#. module: mail_tracking +#: field:res.partner,tracking_emails_count:0 +msgid "Tracking emails count" +msgstr "Števec sledenja e-pošte" + +#. module: mail_tracking +#: model:ir.ui.menu,name:mail_tracking.menu_mail_tracking_event +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: field:mail.tracking.email,tracking_event_ids:0 +msgid "Tracking events" +msgstr "Sledilni dogodki" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Type" +msgstr "Tip" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "URL" +msgstr "URL" + +#. module: mail_tracking +#: field:mail.tracking.email,timestamp:0 field:mail.tracking.event,timestamp:0 +msgid "UTC timestamp" +msgstr "UTC časovni žig" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +msgid "Unsubscribe" +msgstr "Odjava" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_search +#: selection:mail.tracking.email,state:0 +#: selection:mail.tracking.event,event_type:0 +msgid "Unsubscribed" +msgstr "Odjavljeno" + +#. module: mail_tracking +#: field:mail.tracking.event,ip:0 +msgid "User IP" +msgstr "Uporabnikov IP" + +#. module: mail_tracking +#: view:mail.tracking.email:mail_tracking.view_mail_tracking_email_form +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_tree +#: field:mail.tracking.event,user_agent:0 +msgid "User agent" +msgstr "Uporabnikov odjemalec" + +#. module: mail_tracking +#: field:mail.tracking.event,ua_family:0 +msgid "User agent family" +msgstr "Družina uporabnikovih odjemalcev" + +#. module: mail_tracking +#: view:mail.tracking.event:mail_tracking.view_mail_tracking_event_search +#: field:mail.tracking.event,ua_type:0 +msgid "User agent type" +msgstr "Tip uporabnikovega odjemalca" + +#. module: mail_tracking +#: field:mail.tracking.event,user_country_id:0 +msgid "User country" +msgstr "Država uporabnika" diff --git a/mail_tracking/models/__init__.py b/mail_tracking/models/__init__.py new file mode 100644 index 00000000..42a28f51 --- /dev/null +++ b/mail_tracking/models/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# flake8: noqa + +from . import ir_mail_server +from . import mail_mail +from . import mail_message +from . import mail_tracking_email +from . import mail_tracking_event +from . import res_partner diff --git a/mail_tracking/models/ir_mail_server.py b/mail_tracking/models/ir_mail_server.py new file mode 100644 index 00000000..e364a570 --- /dev/null +++ b/mail_tracking/models/ir_mail_server.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import re +import threading +from openerp import models, api, tools + + +class IrMailServer(models.Model): + _inherit = "ir.mail_server" + + def _tracking_headers_add(self, tracking_email_id, headers): + """Allow other addons to add its own tracking SMTP headers""" + headers = headers or {} + headers['X-Odoo-Database'] = getattr( + threading.currentThread(), 'dbname', None), + headers['X-Odoo-Tracking-ID'] = tracking_email_id + return headers + + def _tracking_email_id_body_get(self, body): + body = body or '' + tracking_email_id = False + # https://regex101.com/r/lW4cB1/2 + match = re.search( + r']* data-odoo-tracking-email=["\']([0-9]*)["\']', body) + if match: + try: + tracking_email_id = int(match.group(1)) + except: # pragma: no cover + pass + return tracking_email_id + + def build_email(self, email_from, email_to, subject, body, email_cc=None, + email_bcc=None, reply_to=False, attachments=None, + message_id=None, references=None, object_id=False, + subtype='plain', headers=None, body_alternative=None, + subtype_alternative='plain'): + tracking_email_id = self._tracking_email_id_body_get(body) + if tracking_email_id: + headers = self._tracking_headers_add(tracking_email_id, headers) + msg = super(IrMailServer, self).build_email( + email_from, email_to, subject, body, email_cc=email_cc, + email_bcc=email_bcc, reply_to=reply_to, attachments=attachments, + message_id=message_id, references=references, object_id=object_id, + subtype=subtype, headers=headers, + body_alternative=body_alternative, + subtype_alternative=subtype_alternative) + return msg + + def _tracking_email_get(self, message): + tracking_email_id = False + if message.get('X-Odoo-Tracking-ID', '').isdigit(): + tracking_email_id = int(message['X-Odoo-Tracking-ID']) + return self.env['mail.tracking.email'].browse(tracking_email_id) + + def _smtp_server_get(self, mail_server_id, smtp_server): + smtp_server_used = False + mail_server = None + if mail_server_id: + mail_server = self.browse(mail_server_id) + elif not smtp_server: + mail_server_ids = self.search([], order='sequence', limit=1) + mail_server = mail_server_ids[0] if mail_server_ids else None + if mail_server: + smtp_server_used = mail_server.smtp_host + else: # pragma: no cover + smtp_server_used = smtp_server or tools.config.get('smtp_server') + return smtp_server_used + + @api.model + def send_email(self, message, mail_server_id=None, smtp_server=None, + smtp_port=None, smtp_user=None, smtp_password=None, + smtp_encryption=None, smtp_debug=False): + message_id = False + tracking_email = self._tracking_email_get(message) + smtp_server_used = self._smtp_server_get( + mail_server_id, smtp_server) + try: + message_id = super(IrMailServer, self).send_email( + message, mail_server_id=mail_server_id, + smtp_server=smtp_server, smtp_port=smtp_port, + smtp_user=smtp_user, smtp_password=smtp_password, + smtp_encryption=smtp_encryption, smtp_debug=smtp_debug) + except Exception as e: + if tracking_email: + tracking_email.smtp_error(self, smtp_server_used, e) + raise + if message_id and tracking_email: + vals = tracking_email._tracking_sent_prepare( + self, smtp_server_used, message, message_id) + if vals: + self.env['mail.tracking.event'].sudo().create(vals) + return message_id diff --git a/mail_tracking/models/mail_mail.py b/mail_tracking/models/mail_mail.py new file mode 100644 index 00000000..b33a1c5e --- /dev/null +++ b/mail_tracking/models/mail_mail.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import time +from datetime import datetime +from email.utils import COMMASPACE + +from openerp import models, api, fields + + +class MailMail(models.Model): + _inherit = 'mail.mail' + + def _tracking_email_prepare(self, partner, email): + ts = time.time() + dt = datetime.utcfromtimestamp(ts) + email_to_list = email.get('email_to', []) + email_to = COMMASPACE.join(email_to_list) + return { + 'name': email.get('subject', False), + 'timestamp': '%.6f' % ts, + 'time': fields.Datetime.to_string(dt), + 'mail_id': self.id, + 'mail_message_id': self.mail_message_id.id, + 'partner_id': partner.id if partner else False, + 'recipient': email_to, + 'sender': self.email_from, + } + + @api.multi + def send_get_email_dict(self, partner=None): + email = super(MailMail, self).send_get_email_dict(partner=partner) + vals = self._tracking_email_prepare(partner, email) + tracking_email = self.env['mail.tracking.email'].sudo().create(vals) + return tracking_email.tracking_img_add(email) diff --git a/mail_tracking/models/mail_message.py b/mail_tracking/models/mail_message.py new file mode 100644 index 00000000..fc6562bf --- /dev/null +++ b/mail_tracking/models/mail_message.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api + + +class MailMessage(models.Model): + _inherit = "mail.message" + + def _tracking_status_map_get(self): + return { + 'False': 'waiting', + 'error': 'error', + 'deferred': 'sent', + 'sent': 'sent', + 'delivered': 'delivered', + 'opened': 'opened', + 'rejected': 'error', + 'spam': 'error', + 'unsub': 'opened', + 'bounced': 'error', + 'soft-bounced': 'error', + } + + def _partner_tracking_status_get(self, tracking_email): + tracking_status_map = self._tracking_status_map_get() + status = 'unknown' + if tracking_email: + tracking_email_status = str(tracking_email.state) + status = tracking_status_map.get(tracking_email_status, 'unknown') + return status + + @api.multi + def tracking_status(self): + res = {} + for message in self: + partner_trackings = [] + partners_already = self.env['res.partner'] + partners = self.env['res.partner'] + trackings = self.env['mail.tracking.email'].search([ + ('mail_message_id', '=', message.id), + ]) + # Search all trackings for this message + for tracking in trackings: + status = self._partner_tracking_status_get(tracking) + recipient = ( + tracking.partner_id.display_name or tracking.recipient) + partner_trackings.append(( + status, tracking.id, recipient, tracking.partner_id.id)) + if tracking.partner_id: + partners_already |= tracking.partner_id + # Search all recipients for this message + if message.partner_ids: + partners |= message.partner_ids + if message.needaction_partner_ids: + partners |= message.needaction_partner_ids + # Remove recipients already included + partners -= partners_already + for partner in partners: + # If there is partners not included, then status is 'unknown' + partner_trackings.append(( + 'unknown', False, partner.display_name, partner.id)) + res[message.id] = partner_trackings + return res + + @api.model + def _message_read_dict_postprocess(self, messages, message_tree): + res = super(MailMessage, self)._message_read_dict_postprocess( + messages, message_tree) + mail_message_ids = {m.get('id') for m in messages if m.get('id')} + mail_messages = self.browse(mail_message_ids) + partner_trackings = mail_messages.tracking_status() + for message_dict in messages: + mail_message_id = message_dict.get('id', False) + if mail_message_id: + message_dict['partner_trackings'] = \ + partner_trackings[mail_message_id] + return res diff --git a/mail_tracking/models/mail_tracking_email.py b/mail_tracking/models/mail_tracking_email.py new file mode 100644 index 00000000..c72129af --- /dev/null +++ b/mail_tracking/models/mail_tracking_email.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging +import urlparse +import time +import re +from datetime import datetime + +from openerp import models, api, fields, tools +import openerp.addons.decimal_precision as dp + +_logger = logging.getLogger(__name__) + +EVENT_OPEN_DELTA = 10 # seconds +EVENT_CLICK_DELTA = 5 # seconds + + +class MailTrackingEmail(models.Model): + _name = "mail.tracking.email" + _order = 'time desc' + _rec_name = 'display_name' + _description = 'MailTracking email' + + name = fields.Char(string="Subject", readonly=True, index=True) + display_name = fields.Char( + string="Display name", readonly=True, store=True, + compute="_compute_display_name") + timestamp = fields.Float( + string='UTC timestamp', readonly=True, + digits=dp.get_precision('MailTracking Timestamp')) + time = fields.Datetime(string="Time", readonly=True) + date = fields.Date( + string="Date", readonly=True, compute="_compute_date", store=True) + mail_message_id = fields.Many2one( + string="Message", comodel_name='mail.message', readonly=True) + mail_id = fields.Many2one( + string="Email", comodel_name='mail.mail', readonly=True) + partner_id = fields.Many2one( + string="Partner", comodel_name='res.partner', readonly=True) + recipient = fields.Char(string='Recipient email', readonly=True) + recipient_address = fields.Char( + string='Recipient email address', readonly=True, store=True, + compute='_compute_recipient_address') + sender = fields.Char(string='Sender email', readonly=True) + state = fields.Selection([ + ('error', 'Error'), + ('deferred', 'Deferred'), + ('sent', 'Sent'), + ('delivered', 'Delivered'), + ('opened', 'Open'), + ('rejected', 'Rejected'), + ('spam', 'Spam'), + ('unsub', 'Unsubscribed'), + ('bounced', 'Bounced'), + ('soft-bounced', 'Soft bounced'), + ], string='State', index=True, readonly=True, default=False, + help=" * The 'Error' status indicates that there was an error " + "when trying to sent the email, for example, " + "'No valid recipient'\n" + " * The 'Sent' status indicates that message was succesfully " + "sent via outgoing email server (SMTP).\n" + " * The 'Delivered' status indicates that message was " + "succesfully delivered to recipient Mail Exchange (MX) server.\n" + " * The 'Open' status indicates that message was opened or " + "clicked by recipient.\n" + " * The 'Rejected' status indicates that recipient email " + "address is blacklisted by outgoing email server (SMTP). " + "It is recomended to delete this email address.\n" + " * The 'Spam' status indicates that outgoing email " + "server (SMTP) consider this message as spam.\n" + " * The 'Unsubscribed' status indicates that recipient has " + "requested to be unsubscribed from this message.\n" + " * The 'Bounced' status indicates that message was bounced " + "by recipient Mail Exchange (MX) server.\n" + " * The 'Soft bounced' status indicates that message was soft " + "bounced by recipient Mail Exchange (MX) server.\n") + error_smtp_server = fields.Char(string='Error SMTP server', readonly=True) + error_type = fields.Char(string='Error type', readonly=True) + error_description = fields.Char( + string='Error description', readonly=True) + bounce_type = fields.Char(string='Bounce type', readonly=True) + bounce_description = fields.Char( + string='Bounce description', readonly=True) + tracking_event_ids = fields.One2many( + string="Tracking events", comodel_name='mail.tracking.event', + inverse_name='tracking_email_id', readonly=True) + + @api.model + def tracking_ids_recalculate(self, model, email_field, tracking_field, + email, new_tracking=None): + objects = self.env[model].search([ + (email_field, '=ilike', email), + ]) + for obj in objects: + trackings = obj[tracking_field] + if new_tracking: + trackings |= new_tracking + trackings = trackings._email_score_tracking_filter() + if set(obj[tracking_field].ids) != set(trackings.ids): + if trackings: + obj.write({ + tracking_field: [(6, False, trackings.ids)] + }) + else: + obj.write({ + tracking_field: [(5, False, False)] + }) + return objects + + @api.model + def _tracking_ids_to_write(self, email): + trackings = self.env['mail.tracking.email'].search([ + ('recipient_address', '=ilike', email) + ]) + trackings = trackings._email_score_tracking_filter() + if trackings: + return [(6, False, trackings.ids)] + else: + return [(5, False, False)] + + @api.multi + def _email_score_tracking_filter(self): + """Default email score filter for tracking emails""" + # Consider only last 10 tracking emails + return self.sorted(key=lambda r: r.time, reverse=True)[:10] + + @api.model + def email_score_from_email(self, email): + trackings = self.env['mail.tracking.email'].search([ + ('recipient_address', '=ilike', email) + ]) + return trackings.email_score() + + @api.multi + def email_score(self): + """Default email score algorimth""" + score = 50.0 + trackings = self._email_score_tracking_filter() + for tracking in trackings: + if tracking.state in ('error',): + score -= 50.0 + elif tracking.state in ('rejected', 'spam', 'bounced'): + score -= 25.0 + elif tracking.state in ('soft-bounced', 'unsub'): + score -= 10.0 + elif tracking.state in ('delivered',): + score += 5.0 + elif tracking.state in ('opened',): + score += 10.0 + if score > 100.0: + score = 100.0 + return score + + @api.multi + @api.depends('recipient') + def _compute_recipient_address(self): + for email in self: + matches = re.search(r'<(.*@.*)>', email.recipient) + if matches: + email.recipient_address = matches.group(1) + else: + email.recipient_address = email.recipient + + @api.multi + @api.depends('name', 'recipient') + def _compute_display_name(self): + for email in self: + parts = [email.name or ''] + if email.recipient: + parts.append(email.recipient) + email.display_name = ' - '.join(parts) + + @api.multi + @api.depends('time') + def _compute_date(self): + for email in self: + email.date = fields.Date.to_string( + fields.Date.from_string(email.time)) + + @api.model + def create(self, vals): + tracking = super(MailTrackingEmail, self).create(vals) + self.tracking_ids_recalculate( + 'res.partner', 'email', 'tracking_email_ids', + tracking.recipient_address, new_tracking=tracking) + return tracking + + def _get_mail_tracking_img(self): + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + path_url = ( + 'mail/tracking/open/%(db)s/%(tracking_email_id)s/blank.gif' % { + 'db': self.env.cr.dbname, + 'tracking_email_id': self.id, + }) + track_url = urlparse.urljoin(base_url, path_url) + return ( + '' % { + 'url': track_url, + 'tracking_email_id': self.id, + }) + + @api.multi + def smtp_error(self, mail_server, smtp_server, exception): + self.sudo().write({ + 'error_smtp_server': tools.ustr(smtp_server), + 'error_type': exception.__class__.__name__, + 'error_description': tools.ustr(exception), + 'state': 'error', + }) + return True + + @api.multi + def tracking_img_add(self, email): + self.ensure_one() + tracking_url = self._get_mail_tracking_img() + if tracking_url: + body = tools.append_content_to_html( + email.get('body', ''), tracking_url, plaintext=False, + container_tag='div') + email['body'] = body + return email + + def _message_partners_check(self, message, message_id): + mail_message = self.mail_message_id + partners = ( + mail_message.needaction_partner_ids | mail_message.partner_ids) + if (self.partner_id and self.partner_id not in partners): + # If mail_message haven't tracking partner, then + # add it in order to see his tracking status in chatter + if mail_message.subtype_id: + mail_message.sudo().write({ + 'needaction_partner_ids': [(4, self.partner_id.id)], + }) + else: + mail_message.sudo().write({ + 'partner_ids': [(4, self.partner_id.id)], + }) + return True + + @api.multi + def _tracking_sent_prepare(self, mail_server, smtp_server, message, + message_id): + self.ensure_one() + ts = time.time() + dt = datetime.utcfromtimestamp(ts) + self._message_partners_check(message, message_id) + self.sudo().write({'state': 'sent'}) + return { + 'recipient': message['To'], + 'timestamp': '%.6f' % ts, + 'time': fields.Datetime.to_string(dt), + 'tracking_email_id': self.id, + 'event_type': 'sent', + 'smtp_server': smtp_server, + } + + def _event_prepare(self, event_type, metadata): + self.ensure_one() + m_event = self.env['mail.tracking.event'] + method = getattr(m_event, 'process_' + event_type, None) + if method and hasattr(method, '__call__'): + return method(self, metadata) + else: # pragma: no cover + _logger.info('Unknown event type: %s' % event_type) + return False + + def _concurrent_events(self, event_type, metadata): + m_event = self.env['mail.tracking.event'] + self.ensure_one() + concurrent_event_ids = False + if event_type in {'open', 'click'}: + ts = metadata.get('timestamp', time.time()) + delta = EVENT_OPEN_DELTA if event_type == 'open' \ + else EVENT_CLICK_DELTA + domain = [ + ('timestamp', '>=', ts - delta), + ('timestamp', '<=', ts + delta), + ('tracking_email_id', '=', self.id), + ('event_type', '=', event_type), + ] + if event_type == 'click': + domain.append(('url', '=', metadata.get('url', False))) + concurrent_event_ids = m_event.search(domain) + return concurrent_event_ids + + @api.multi + def event_create(self, event_type, metadata): + event_ids = self.env['mail.tracking.event'] + for tracking_email in self: + other_ids = tracking_email._concurrent_events(event_type, metadata) + if not other_ids: + vals = tracking_email._event_prepare(event_type, metadata) + if vals: + event_ids += event_ids.sudo().create(vals) + partners = self.tracking_ids_recalculate( + 'res.partner', 'email', 'tracking_email_ids', + tracking_email.recipient_address) + if partners: + partners.email_score_calculate() + else: + _logger.debug("Concurrent event '%s' discarded", event_type) + return event_ids + + @api.model + def event_process(self, request, post, metadata, event_type=None): + # Generic event process hook, inherit it and + # - return 'OK' if processed + # - return 'NONE' if this request is not for you + # - return 'ERROR' if any error + return 'NONE' # pragma: no cover diff --git a/mail_tracking/models/mail_tracking_event.py b/mail_tracking/models/mail_tracking_event.py new file mode 100644 index 00000000..728f88c0 --- /dev/null +++ b/mail_tracking/models/mail_tracking_event.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import time +from datetime import datetime + +from openerp import models, api, fields +import openerp.addons.decimal_precision as dp + + +class MailTrackingEvent(models.Model): + _name = "mail.tracking.event" + _order = 'timestamp desc' + _rec_name = 'event_type' + _description = 'MailTracking event' + + recipient = fields.Char(string="Recipient", readonly=True) + timestamp = fields.Float( + string='UTC timestamp', readonly=True, + digits=dp.get_precision('MailTracking Timestamp')) + time = fields.Datetime(string="Time", readonly=True) + date = fields.Date( + string="Date", readonly=True, compute="_compute_date", store=True) + tracking_email_id = fields.Many2one( + string='Message', readonly=True, + comodel_name='mail.tracking.email') + event_type = fields.Selection(string='Event type', selection=[ + ('sent', 'Sent'), + ('delivered', 'Delivered'), + ('deferral', 'Deferral'), + ('hard_bounce', 'Hard bounce'), + ('soft_bounce', 'Soft bounce'), + ('open', 'Open'), + ('click', 'Clicked'), + ('spam', 'Spam'), + ('unsub', 'Unsubscribed'), + ('reject', 'Rejected'), + ], readonly=True) + smtp_server = fields.Char(string='SMTP server', readonly=True) + url = fields.Char(string='Clicked URL', readonly=True) + ip = fields.Char(string='User IP', readonly=True) + user_agent = fields.Char(string='User agent', readonly=True) + mobile = fields.Boolean(string='Is mobile?', readonly=True) + os_family = fields.Char(string='Operating system family', readonly=True) + ua_family = fields.Char(string='User agent family', readonly=True) + ua_type = fields.Char(string='User agent type', readonly=True) + user_country_id = fields.Many2one(string='User country', readonly=True, + comodel_name='res.country') + error_type = fields.Char(string='Error type', readonly=True) + error_description = fields.Char(string='Error description', readonly=True) + error_details = fields.Text(string='Error details', readonly=True) + + @api.multi + @api.depends('time') + def _compute_date(self): + for email in self: + email.date = fields.Date.to_string( + fields.Date.from_string(email.time)) + + def _process_data(self, tracking_email, metadata, event_type, state): + ts = time.time() + dt = datetime.utcfromtimestamp(ts) + return { + 'recipient': metadata.get('recipient', tracking_email.recipient), + 'timestamp': metadata.get('timestamp', ts), + 'time': metadata.get('time', fields.Datetime.to_string(dt)), + 'date': metadata.get('date', fields.Date.to_string(dt)), + 'tracking_email_id': tracking_email.id, + 'event_type': event_type, + 'ip': metadata.get('ip', False), + 'url': metadata.get('url', False), + 'user_agent': metadata.get('user_agent', False), + 'mobile': metadata.get('mobile', False), + 'os_family': metadata.get('os_family', False), + 'ua_family': metadata.get('ua_family', False), + 'ua_type': metadata.get('ua_type', False), + 'user_country_id': metadata.get('user_country_id', False), + 'error_type': metadata.get('error_type', False), + 'error_description': metadata.get('error_description', False), + 'error_details': metadata.get('error_details', False), + } + + def _process_status(self, tracking_email, metadata, event_type, state): + tracking_email.sudo().write({'state': state}) + return self._process_data(tracking_email, metadata, event_type, state) + + def _process_bounce(self, tracking_email, metadata, event_type, state): + tracking_email.sudo().write({ + 'state': state, + 'bounce_type': metadata.get('bounce_type', False), + 'bounce_description': metadata.get('bounce_description', False), + }) + return self._process_data(tracking_email, metadata, event_type, state) + + @api.model + def process_delivered(self, tracking_email, metadata): + return self._process_status( + tracking_email, metadata, 'delivered', 'delivered') + + @api.model + def process_deferral(self, tracking_email, metadata): + return self._process_status( + tracking_email, metadata, 'deferral', 'deferred') + + @api.model + def process_hard_bounce(self, tracking_email, metadata): + return self._process_bounce( + tracking_email, metadata, 'hard_bounce', 'bounced') + + @api.model + def process_soft_bounce(self, tracking_email, metadata): + return self._process_bounce( + tracking_email, metadata, 'soft_bounce', 'soft-bounced') + + @api.model + def process_open(self, tracking_email, metadata): + return self._process_status(tracking_email, metadata, 'open', 'opened') + + @api.model + def process_click(self, tracking_email, metadata): + return self._process_status( + tracking_email, metadata, 'click', 'opened') + + @api.model + def process_spam(self, tracking_email, metadata): + return self._process_status(tracking_email, metadata, 'spam', 'spam') + + @api.model + def process_unsub(self, tracking_email, metadata): + return self._process_status(tracking_email, metadata, 'unsub', 'unsub') + + @api.model + def process_reject(self, tracking_email, metadata): + return self._process_status( + tracking_email, metadata, 'reject', 'rejected') diff --git a/mail_tracking/models/res_partner.py b/mail_tracking/models/res_partner.py new file mode 100644 index 00000000..908f1190 --- /dev/null +++ b/mail_tracking/models/res_partner.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, api, fields + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + tracking_email_ids = fields.Many2many( + string="Tracking emails", comodel_name="mail.tracking.email", + readonly=True) + tracking_emails_count = fields.Integer( + string="Tracking emails count", store=True, readonly=True, + compute="_compute_tracking_emails_count") + email_score = fields.Float( + string="Email score", readonly=True, default=50.0) + + @api.multi + def email_score_calculate(self): + # This is not a compute method because is causing a inter-block + # in mail_tracking_email PostgreSQL table + # We suspect that tracking_email write to state field block that + # table and then inside write ORM try to read from DB + # tracking_email_ids because it's not in cache. + # PostgreSQL blocks read because we have not committed yet the write + for partner in self: + partner.email_score = partner.tracking_email_ids.email_score() + + @api.one + @api.depends('tracking_email_ids') + def _compute_tracking_emails_count(self): + self.tracking_emails_count = self.env['mail.tracking.email'].\ + search_count([ + ('recipient_address', '=ilike', self.email) + ]) + + @api.multi + def write(self, vals): + email = vals.get('email') + if email is not None: + m_track = self.env['mail.tracking.email'] + vals['tracking_email_ids'] = m_track._tracking_ids_to_write(email) + vals['email_score'] = m_track.email_score_from_email(email) + return super(ResPartner, self).write(vals) diff --git a/mail_tracking/security/ir.model.access.csv b/mail_tracking/security/ir.model.access.csv new file mode 100644 index 00000000..ab17dc33 --- /dev/null +++ b/mail_tracking/security/ir.model.access.csv @@ -0,0 +1,5 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_mail_tracking_email_group_user","mail_tracking_email group_user","model_mail_tracking_email","base.group_user",1,0,0,0 +"access_mail_tracking_event_group_user","mail_tracking_event group_user","model_mail_tracking_event","base.group_user",1,0,0,0 +"access_mail_tracking_email_group_system","mail_tracking_email group_system","model_mail_tracking_email","base.group_system",1,1,1,1 +"access_mail_tracking_event_group_system","mail_tracking_event group_system","model_mail_tracking_event","base.group_system",1,1,1,1 diff --git a/mail_tracking/static/description/icon.png b/mail_tracking/static/description/icon.png new file mode 100644 index 00000000..c1af4955 Binary files /dev/null and b/mail_tracking/static/description/icon.png differ diff --git a/mail_tracking/static/src/css/mail_tracking.css b/mail_tracking/static/src/css/mail_tracking.css new file mode 100644 index 00000000..bca73d3a --- /dev/null +++ b/mail_tracking/static/src/css/mail_tracking.css @@ -0,0 +1,21 @@ +/* © 2016 Antonio Espinosa - + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ + +.mail_tracking span { + color: #909090; +} +.mail_tracking_pointer { + cursor: pointer; +} + +.mail_tracking span.mail_tracking_opened { + color: #a34a8b; +} + +.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_info { + margin: 0; +} + +.o_mail_thread .o_thread_message .o_thread_message_core .o_mail_tracking { + margin: 0 0 2px 0; +} diff --git a/mail_tracking/static/src/img/delivered.png b/mail_tracking/static/src/img/delivered.png new file mode 100644 index 00000000..25f98034 Binary files /dev/null and b/mail_tracking/static/src/img/delivered.png differ diff --git a/mail_tracking/static/src/img/error.png b/mail_tracking/static/src/img/error.png new file mode 100644 index 00000000..e8e114ae Binary files /dev/null and b/mail_tracking/static/src/img/error.png differ diff --git a/mail_tracking/static/src/img/opened.png b/mail_tracking/static/src/img/opened.png new file mode 100644 index 00000000..a5ce70f1 Binary files /dev/null and b/mail_tracking/static/src/img/opened.png differ diff --git a/mail_tracking/static/src/img/sent.png b/mail_tracking/static/src/img/sent.png new file mode 100644 index 00000000..0abeb1ed Binary files /dev/null and b/mail_tracking/static/src/img/sent.png differ diff --git a/mail_tracking/static/src/img/unknown.png b/mail_tracking/static/src/img/unknown.png new file mode 100644 index 00000000..c17c681e Binary files /dev/null and b/mail_tracking/static/src/img/unknown.png differ diff --git a/mail_tracking/static/src/img/waiting.png b/mail_tracking/static/src/img/waiting.png new file mode 100644 index 00000000..12af3b26 Binary files /dev/null and b/mail_tracking/static/src/img/waiting.png differ diff --git a/mail_tracking/static/src/js/mail_tracking.js b/mail_tracking/static/src/js/mail_tracking.js new file mode 100644 index 00000000..473b9f7a --- /dev/null +++ b/mail_tracking/static/src/js/mail_tracking.js @@ -0,0 +1,103 @@ +/* © 2016 Antonio Espinosa - + License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */ + +odoo.define('mail_tracking.partner_tracking', function(require){ +"use strict"; + +var $ = require('$'); +var core = require('web.core'); +var session = require('web.session'); +var Model = require('web.Model'); +var ActionManager = require('web.ActionManager'); +var chat_manager = require('mail.chat_manager'); +var ChatThread = require('mail.ChatThread'); +var Chatter = require('mail.Chatter'); + +var _t = core._t; +var MessageModel = new Model('mail.message', session.context); + +// chat_manager is a simple dictionary, not an OdooClass +chat_manager._make_message_super = chat_manager.make_message; +chat_manager.make_message = function(data) { + var msg = this._make_message_super(data); + msg.partner_trackings = data.partner_trackings || []; + return msg; +}; + +ChatThread.include({ + on_tracking_partner_click: function (event) { + var partner_id = $(event.currentTarget).data('partner'); + var state = { + 'model': 'res.partner', + 'id': partner_id, + 'title': _t("Tracking partner"), + }; + event.preventDefault(); + this.action_manager.do_push_state(state); + var action = { + type:'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: 'res.partner', + views: [[false, 'form']], + target: 'current', + res_id: partner_id, + }; + this.do_action(action); + }, + on_tracking_status_click: function (event) { + var tracking_email_id = $(event.currentTarget).data('tracking'); + var state = { + 'model': 'mail.tracking.email', + 'id': tracking_email_id, + 'title': _t("Message tracking"), + }; + event.preventDefault(); + this.action_manager.do_push_state(state); + var action = { + type:'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: 'mail.tracking.email', + views: [[false, 'form']], + target: 'new', + res_id: tracking_email_id, + }; + this.do_action(action); + }, + bind_events: function () { + this.$el.on('click', '.o_mail_action_tracking_partner', + this.on_tracking_partner_click); + this.$el.on('click', '.o_mail_action_tracking_status', + this.on_tracking_status_click); + }, + init: function (parent, options) { + this._super.apply(this, arguments); + this.action_manager = this.findAncestor(function(ancestor){ + return ancestor instanceof ActionManager; + }); + }, + start: function () { + this._super(); + this.bind_events(); + }, + render: function(messages, options) { + var self = this, render_super = this._super, + msgs = {}, + msg_ids = []; + // Update trackings (async) each time we re-render thread + _.each(messages, function (message) { + msgs[message.id] = message; + msg_ids.push(message.id); + }); + MessageModel.call('tracking_status', [msg_ids]).then(function (trackings) { + _.each(trackings, function (tracking, id) { + msgs[id].partner_trackings = tracking; + }); + render_super.apply(self, [messages, options]); + }); + }, + +}); + +}); // odoo.define diff --git a/mail_tracking/static/src/xml/mail_tracking.xml b/mail_tracking/static/src/xml/mail_tracking.xml new file mode 100644 index 00000000..68b5d12b --- /dev/null +++ b/mail_tracking/static/src/xml/mail_tracking.xml @@ -0,0 +1,73 @@ + + + diff --git a/mail_tracking/tests/__init__.py b/mail_tracking/tests/__init__.py new file mode 100644 index 00000000..ce3de1c8 --- /dev/null +++ b/mail_tracking/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# flake8: noqa + +from . import test_mail_tracking diff --git a/mail_tracking/tests/test_mail_tracking.py b/mail_tracking/tests/test_mail_tracking.py new file mode 100644 index 00000000..5862db90 --- /dev/null +++ b/mail_tracking/tests/test_mail_tracking.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +# © 2016 Antonio Espinosa - +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import mock +import base64 +import time +from openerp.tests.common import TransactionCase +from ..controllers.main import MailTrackingController, BLANK + +mock_request = 'openerp.http.request' +mock_send_email = ('openerp.addons.base.ir.ir_mail_server.' + 'ir_mail_server.send_email') + + +class FakeUserAgent(object): + browser = 'Test browser' + platform = 'Test platform' + + def __str__(self): + """Return name""" + return 'Test suite' + + +# One test case per method +class TestMailTracking(TransactionCase): + # Use case : Prepare some data for current test case + def setUp(self): + super(TestMailTracking, self).setUp() + self.sender = self.env['res.partner'].create({ + 'name': 'Test sender', + 'email': 'sender@example.com', + 'notify_email': 'always', + }) + self.recipient = self.env['res.partner'].create({ + 'name': 'Test recipient', + 'email': 'recipient@example.com', + 'notify_email': 'always', + }) + self.request = { + 'httprequest': type('obj', (object,), { + 'remote_addr': '123.123.123.123', + 'user_agent': FakeUserAgent(), + }), + } + + def test_message_post(self): + # This message will generate a notification for recipient + message = self.env['mail.message'].create({ + 'subject': 'Message test', + 'author_id': self.sender.id, + 'email_from': self.sender.email, + 'type': 'comment', + 'model': 'res.partner', + 'res_id': self.recipient.id, + 'partner_ids': [(4, self.recipient.id)], + 'body': '

This is a test message

', + }) + # Search tracking created + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_message_id', '=', message.id), + ('partner_id', '=', self.recipient.id), + ]) + # The tracking email must be sent + self.assertTrue(tracking_email) + self.assertEqual(tracking_email.state, 'sent') + # message_dict read by web interface + message_dict = message.message_read() + # First item in threads is message content + message_dict = message_dict['threads'][0][0] + self.assertTrue(len(message_dict['partner_ids']) > 0) + # First partner is recipient + partner_id = message_dict['partner_ids'][0][0] + self.assertEqual(partner_id, self.recipient.id) + status = message_dict['partner_trackings'][0] + # Tracking status must be sent and + # mail tracking must be the one search before + self.assertEqual(status[0], 'sent') + self.assertEqual(status[1], tracking_email.id) + self.assertEqual(status[2], self.recipient.display_name) + self.assertEqual(status[3], self.recipient.id) + # And now open the email + metadata = { + 'ip': '127.0.0.1', + 'user_agent': 'Odoo Test/1.0', + 'os_family': 'linux', + 'ua_family': 'odoo', + } + tracking_email.event_create('open', metadata) + self.assertEqual(tracking_email.state, 'opened') + + def mail_send(self, recipient): + mail = self.env['mail.mail'].create({ + 'subject': 'Test subject', + 'email_from': 'from@domain.com', + 'email_to': recipient, + 'body_html': '

This is a test message

', + }) + mail.send() + # Search tracking created + tracking_email = self.env['mail.tracking.email'].search([ + ('mail_id', '=', mail.id), + ]) + return mail, tracking_email + + def test_mail_send(self): + controller = MailTrackingController() + db = self.env.cr.dbname + image = base64.decodestring(BLANK) + mail, tracking = self.mail_send(self.recipient.email) + self.assertEqual(mail.email_to, tracking.recipient) + self.assertEqual(mail.email_from, tracking.sender) + with mock.patch(mock_request) as mock_func: + mock_func.return_value = type('obj', (object,), self.request) + res = controller.mail_tracking_open(db, tracking.id) + self.assertEqual(image, res.response[0]) + + def test_concurrent_open(self): + mail, tracking = self.mail_send(self.recipient.email) + ts = time.time() + metadata = { + 'ip': '127.0.0.1', + 'user_agent': 'Odoo Test/1.0', + 'os_family': 'linux', + 'ua_family': 'odoo', + 'timestamp': ts, + } + # First open event + tracking.event_create('open', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'open' + ) + self.assertEqual(len(opens), 1) + # Concurrent open event + metadata['timestamp'] = ts + 2 + tracking.event_create('open', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'open' + ) + self.assertEqual(len(opens), 1) + # Second open event + metadata['timestamp'] = ts + 350 + tracking.event_create('open', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'open' + ) + self.assertEqual(len(opens), 2) + + def test_concurrent_click(self): + mail, tracking = self.mail_send(self.recipient.email) + ts = time.time() + metadata = { + 'ip': '127.0.0.1', + 'user_agent': 'Odoo Test/1.0', + 'os_family': 'linux', + 'ua_family': 'odoo', + 'timestamp': ts, + 'url': 'https://www.example.com/route/1', + } + # First click event (URL 1) + tracking.event_create('click', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'click' + ) + self.assertEqual(len(opens), 1) + # Concurrent click event (URL 1) + metadata['timestamp'] = ts + 2 + tracking.event_create('click', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'click' + ) + self.assertEqual(len(opens), 1) + # Second click event (URL 1) + metadata['timestamp'] = ts + 350 + tracking.event_create('click', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'click' + ) + self.assertEqual(len(opens), 2) + # Concurrent click event (URL 2) + metadata['timestamp'] = ts + 2 + metadata['url'] = 'https://www.example.com/route/2' + tracking.event_create('click', metadata) + opens = tracking.tracking_event_ids.filtered( + lambda r: r.event_type == 'click' + ) + self.assertEqual(len(opens), 3) + + def test_smtp_error(self): + with mock.patch(mock_send_email) as mock_func: + mock_func.side_effect = Warning('Test error') + mail, tracking = self.mail_send(self.recipient.email) + self.assertEqual('error', tracking.state) + self.assertEqual('Warning', tracking.error_type) + self.assertEqual('Test error', tracking.error_description) + + def test_partner_email_change(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('open', {}) + orig_score = self.recipient.email_score + orig_email = self.recipient.email + self.recipient.email = orig_email + '2' + self.assertEqual(50.0, self.recipient.email_score) + self.recipient.email = orig_email + self.assertEqual(orig_score, self.recipient.email_score) + + def test_process_hard_bounce(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('hard_bounce', {}) + self.assertEqual('bounced', tracking.state) + + def test_process_soft_bounce(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('soft_bounce', {}) + self.assertEqual('soft-bounced', tracking.state) + + def test_process_delivered(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('delivered', {}) + self.assertEqual('delivered', tracking.state) + + def test_process_deferral(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('deferral', {}) + self.assertEqual('deferred', tracking.state) + + def test_process_spam(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('spam', {}) + self.assertEqual('spam', tracking.state) + + def test_process_unsub(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('unsub', {}) + self.assertEqual('unsub', tracking.state) + + def test_process_reject(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('reject', {}) + self.assertEqual('rejected', tracking.state) + + def test_process_open(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('open', {}) + self.assertEqual('opened', tracking.state) + + def test_process_click(self): + mail, tracking = self.mail_send(self.recipient.email) + tracking.event_create('click', {}) + self.assertEqual('opened', tracking.state) + + def test_db(self): + db = self.env.cr.dbname + controller = MailTrackingController() + with mock.patch(mock_request) as mock_func: + mock_func.return_value = type('obj', (object,), self.request) + not_found = controller.mail_tracking_all('not_found_db') + self.assertEqual('NOT FOUND', not_found.response[0]) + none = controller.mail_tracking_all(db) + self.assertEqual('NONE', none.response[0]) + none = controller.mail_tracking_event(db, 'open') + self.assertEqual('NONE', none.response[0]) diff --git a/mail_tracking/views/assets.xml b/mail_tracking/views/assets.xml new file mode 100644 index 00000000..e4cac0d7 --- /dev/null +++ b/mail_tracking/views/assets.xml @@ -0,0 +1,17 @@ + + + + +